diff --git a/.bowerrc b/.bowerrc
new file mode 100644
index 0000000000..a4238fa0bf
--- /dev/null
+++ b/.bowerrc
@@ -0,0 +1,3 @@
+{
+ "directory": "core/shared/vendor"
+}
\ No newline at end of file
diff --git a/Gruntfile.js b/Gruntfile.js
index d258abe1f3..d3df91718b 100644
--- a/Gruntfile.js
+++ b/Gruntfile.js
@@ -54,6 +54,10 @@ var path = require('path'),
files: ['core/clientold/tpl/**/*.hbs'],
tasks: ['handlebars']
},
+ emberTemplates: {
+ files: 'core/client/templates/**/*.hbs',
+ tasks: ['emberTemplates']
+ },
sass: {
files: ['<%= paths.adminOldAssets %>/sass/**/*'],
tasks: ['sass:admin']
@@ -68,6 +72,12 @@ var path = require('path'),
],
tasks: ['concat']
},
+ 'concat-ember': {
+ files: [
+ 'core/client/**/*.js'
+ ],
+ tasks: ['concat:dev-ember']
+ },
livereload: {
files: [
// Theme CSS
@@ -298,6 +308,19 @@ var path = require('path'),
}
},
+ // ### Config for grunt-ember-templates
+ // Compiles handlebar templates for ember
+ emberTemplates: {
+ compile: {
+ options: {
+ templateBasePath: /core\/client\/templates/
+ },
+ files: {
+ "core/built/scripts/templates-ember.js": "core/client/templates/**/*.hbs"
+ }
+ }
+ },
+
// ### Config for grunt-groc
// Generate documentation from code
groc: {
@@ -415,6 +438,19 @@ var path = require('path'),
]
}
},
+ 'dev-ember': {
+ files: {
+ 'core/built/scripts/vendor-ember.js': [
+ 'core/shared/vendor/jquery/jquery.js',
+ 'core/shared/vendor/handlebars/handlebars.js',
+ 'core/shared/vendor/ember/ember.js'
+ ],
+
+ 'core/built/scripts/ghost-dev-ember.js': [
+ 'core/client/**/*.js'
+ ]
+ }
+ },
prod: {
files: {
'core/built/scripts/ghost.js': [
@@ -840,7 +876,7 @@ var path = require('path'),
grunt.registerTask('prod', 'Build CSS, JS & templates for production', ['sass:compress', 'handlebars', 'concat', 'uglify']);
// When you just say 'grunt'
- grunt.registerTask('default', 'Build CSS, JS & templates for development', ['update_submodules', 'sass:compress', 'handlebars', 'concat']);
+ grunt.registerTask('default', 'Build CSS, JS & templates for development', ['update_submodules', 'sass:compress', 'handlebars', 'emberTemplates:compile', 'concat']);
};
module.exports = configureGrunt;
diff --git a/bower.json b/bower.json
new file mode 100644
index 0000000000..f07fdfc27b
--- /dev/null
+++ b/bower.json
@@ -0,0 +1,7 @@
+{
+ "name": "ghost",
+ "dependencies": {
+ "handlebars": "~1.1.2",
+ "ember": "~1.4.0"
+ }
+}
diff --git a/core/client/README.md b/core/client/README.md
deleted file mode 100644
index d1a4aff4ab..0000000000
--- a/core/client/README.md
+++ /dev/null
@@ -1 +0,0 @@
-#Ember goes here
\ No newline at end of file
diff --git a/core/client/app.js b/core/client/app.js
new file mode 100755
index 0000000000..b4c353615a
--- /dev/null
+++ b/core/client/app.js
@@ -0,0 +1,14 @@
+/*global Ember */
+
+var App = Ember.Application.create({
+ /**
+ * These are debugging flags, they are useful during development
+ */
+ LOG_ACTIVE_GENERATION: true,
+ LOG_MODULE_RESOLVER: true,
+ LOG_TRANSITIONS: true,
+ LOG_TRANSITIONS_INTERNAL: true,
+ LOG_VIEW_LOOKUPS: true,
+ rootElement: '#ember-app' // tells ember to inject this app into element with selector #ember-app
+});
+
diff --git a/core/client/router.js b/core/client/router.js
new file mode 100755
index 0000000000..1133384526
--- /dev/null
+++ b/core/client/router.js
@@ -0,0 +1,9 @@
+/*global App */
+
+App.Router.map(function () {
+ 'use strict';
+ this.resource('posts');
+ this.resource('post', {path: 'post/:id'}, function () {
+ this.route('edit');
+ });
+});
diff --git a/core/client/templates/application.hbs b/core/client/templates/application.hbs
new file mode 100755
index 0000000000..7a1a31e653
--- /dev/null
+++ b/core/client/templates/application.hbs
@@ -0,0 +1,3 @@
+
Welcome to Ghost on Ember.js
+
+{{outlet}}
diff --git a/core/clientold/assets/css/screen.css b/core/clientold/assets/css/screen.css
index 643bb67c45..6b6253e7d6 100644
--- a/core/clientold/assets/css/screen.css
+++ b/core/clientold/assets/css/screen.css
@@ -1 +1,5066 @@
-.editor .entry-title,.box{padding:15px;margin-bottom:15px;background:#fff;position:relative;box-shadow:rgba(0,0,0,0.05) 0 1px 5px}.editor .entry-title header,.box header{height:14px;border-bottom:1px solid #edece4;padding-bottom:15px;margin-bottom:15px;text-transform:uppercase;font-size:0.85em;color:#aaa9a2}.editor .entry-title footer,.box footer{height:14px;border-top:1px solid #edece4;padding-top:10px;margin-top:15px;text-transform:uppercase;font-size:0.85em;color:#aaa9a2}.editor .entry-title header a,.editor .entry-title footer a,.box header a,.box footer a{color:#aaa9a2}.editor .entry-title header a:hover,.editor .entry-title footer a:hover,.box header a:hover,.box footer a:hover{color:#242628;text-decoration:none}@-webkit-keyframes fade-out{from{opacity:1}to{opacity:0}}@-moz-keyframes fade-out{from{opacity:1}to{opacity:0}}@-o-keyframes fade-out{from{opacity:1}to{opacity:0}}@keyframes fade-out{from{opacity:1}to{opacity:0}}/*! normalize.css v2.1.0 | MIT License | git.io/normalize */article,aside,details,figcaption,figure,footer,header,hgroup,main,nav,section,summary{display:block}audio,canvas,video{display:inline-block}audio:not([controls]){display:none;height:0}[hidden]{display:none}html{font-family:sans-serif;-webkit-text-size-adjust:100%;-ms-text-size-adjust:100%}body{margin:0}a:focus{outline:thin dotted}a:active,a:hover{outline:0}h1{font-size:2em;margin:0.67em 0}abbr[title]{border-bottom:1px dotted}b,strong{font-weight:bold}dfn{font-style:italic}hr{-moz-box-sizing:content-box;box-sizing:content-box;height:0}mark{background:#ff0;color:#000}code,kbd,pre,samp{font-family:monospace, serif;font-size:1em}pre{white-space:pre-wrap}q{quotes:"\201C" "\201D" "\2018" "\2019"}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sup{top:-0.5em}sub{bottom:-0.25em}img{border:0}svg:not(:root){overflow:hidden}figure{margin:0}fieldset{border:1px solid #c0c0c0;margin:0 2px;padding:0.35em 0.625em 0.75em}legend{border:0;padding:0}button,input,select,textarea{font-family:inherit;font-size:100%;margin:0}button,input{line-height:normal}button,select{text-transform:none}button,html input[type="button"],input[type="reset"],input[type="submit"]{-webkit-appearance:button;cursor:pointer}button[disabled],html input[disabled]{cursor:default}input[type="checkbox"],input[type="radio"]{box-sizing:border-box;padding:0}input[type="search"]{-webkit-appearance:textfield;-moz-box-sizing:content-box;-webkit-box-sizing:content-box;box-sizing:content-box}input[type="search"]::-webkit-search-cancel-button,input[type="search"]::-webkit-search-decoration{-webkit-appearance:none}button::-moz-focus-inner,input::-moz-focus-inner{border:0;padding:0}textarea{overflow:auto;vertical-align:top}table{border-collapse:collapse;border-spacing:0}@font-face{font-family:'Icons';src:url("../fonts/icons.eot");src:url("../fonts/icons.eot?#iefix") format("embedded-opentype"),url("../fonts/icons.woff") format("woff"),url("../fonts/icons.ttf") format("truetype"),url("../fonts/icons.svg#icons") format("svg");font-weight:normal;font-style:normal}@-webkit-keyframes off-canvas{0%{left:0}100%{left:300px}}@-moz-keyframes off-canvas{0%{opacity:0}100%{opacity:1}}@-o-keyframes off-canvas{0%{opacity:0}100%{opacity:1}}@keyframes off-canvas{0%{opacity:0}100%{opacity:1}}@-webkit-keyframes fadeIn{from{opacity:0}to{opacity:1}}@-moz-keyframes fadeIn{from{opacity:0}to{opacity:1}}@-o-keyframes fadeIn{from{opacity:0}to{opacity:1}}@keyframes fadeIn{from{opacity:0}to{opacity:1}}.hidden{text-indent:-9999px;visibility:hidden;display:none}.invisible{visibility:hidden}.right{float:right}.left{float:left}.markdown,pre,code{font-family:Inconsolata,monospace}.visuallyhidden,.screen-reader-text{border:0;clip:rect(0 0 0 0);height:1px;margin:-1px;overflow:hidden;padding:0;position:absolute;width:1px}.visuallyhidden.focusable:active,.visuallyhidden.focusable:focus{clip:auto;height:auto;margin:0;overflow:visible;position:static;width:auto}.clearfix:before,.clearfix:after{content:" ";display:table}.clearfix:after{clear:both}.clearfix{*zoom:1}html{font:normal 81.2%/1.65 "Open Sans", sans-serif}body{width:100%;color:#242628;font-weight:300;background:#edece4}@media (max-width: 400px){body{background:#fff}}::-moz-selection{color:#242628;background:#b3d5f3;text-shadow:none}::selection{color:#242628;background:#b3d5f3;text-shadow:none}article aside{width:30%;padding:0 2.2em;margin:0 2.2em 1.6em 2.2em;float:right;background:#edece4;border-radius:3px}h1,h2,h3,h4,h5,h6{color:#242628;text-rendering:optimizeLegibility;line-height:1;margin-top:0}h2{padding-top:0.8em;margin-top:0.8em;border-top:#edece4 1px solid}h1 a:hover{text-decoration:none;box-shadow:#5ba4e5 0 -5px 0 inset}h2 a:hover{text-decoration:none;box-shadow:#5ba4e5 0 -4px 0 inset}h3 a:hover{text-decoration:none;box-shadow:#5ba4e5 0 -3px 0 inset}h4 a:hover,h5 a:hover,h6 a:hover{text-decoration:none;box-shadow:#5ba4e5 0 -1px 0 inset}hgroup{margin:1.6em 0}hgroup h1,hgroup h2,hgroup h3,hgroup h4,hgroup h5,hgroup h6{padding:0;margin:0;border:none;margin-bottom:5px}hgroup h1 a,hgroup h2 a,hgroup h3 a,hgroup h4 a,hgroup h5 a,hgroup h6 a{color:#242628}hgroup h1 a:hover,hgroup h2 a:hover,hgroup h3 a:hover,hgroup h4 a:hover,hgroup h5 a:hover,hgroup h6 a:hover{box-shadow:#242628 0 -1px 0 inset}hgroup h1:nth-child(n+2),hgroup h2:nth-child(n+2),hgroup h3:nth-child(n+2),hgroup h4:nth-child(n+2),hgroup h5:nth-child(n+2),hgroup h6:nth-child(n+2){font-size:1.8em;font-weight:300;color:#aaa9a2}p,ul,ol{margin:1.6em 0}ol ol,ul ul,ul ol,ol ul{margin:0.4em 0}a{color:#5ba4e5;text-decoration:none;-webkit-transition:all 0.15s ease-in-out;-moz-transition:all 0.15s ease-in-out;transition:all 0.15s ease-in-out}a:hover{text-decoration:underline}a.highlight{color:#f2a925;font-weight:bold}hr{display:block;height:1px;border:0;border-top:1px solid #edece4;margin:3.2em 0;padding:0}blockquote{margin:1.6em 0;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box;padding:0 1.6em 0 1.6em;border-left:#edece4 0.6em solid}blockquote p{margin:0.8em 0;font-size:1.2em;font-weight:300}blockquote small{display:inline-block;margin:0.8em 0 0.8em 1.5em;font-size:0.9em;color:#aaa9a2}blockquote small:before{content:'\2014 \00A0'}blockquote cite{font-weight:bold}blockquote cite a{font-weight:normal}dl{margin:1.6em 0}dl dt{float:left;width:180px;overflow:hidden;clear:left;text-align:right;text-overflow:ellipsis;white-space:nowrap;font-weight:bold;margin-bottom:1em}dl dd{margin-left:200px;margin-bottom:1em}mark{background-color:#ffc336}code,tt{font-family:Inconsolata,monospace;font-size:0.85em;white-space:pre-wrap;background:#f1f0ea;border:1px solid #dddbcc;border-radius:2px;padding:1px 3px}pre{margin:1.6em 0;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box;background:#f1f0ea;border:1px solid #dddbcc;width:100%;padding:10px;font-family:Inconsolata,monospace;font-size:0.9em;white-space:pre;overflow:auto;border-radius:3px}pre code,pre tt{font-size:inherit;white-space:-moz-pre-wrap;white-space:pre-wrap;background:transparent;border:none;padding:0}kbd{display:inline-block;margin-bottom:0.4em;padding:1px 8px;border:#ccc 1px solid;color:#242628;text-shadow:#fff 0 1px 0;font-size:0.9em;font-weight:bold;background:#f4f4f4;border-radius:4px;box-shadow:0 1px 0 rgba(0,0,0,0.2),0 1px 0 0 #fff inset}table{margin:1.6em 0;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box;width:100%;max-width:100%;background-color:transparent}table th,table td{padding:8px;line-height:20px;text-align:left;vertical-align:middle;border-top:1px solid #edece4}table th{color:#aaa9a2}table caption+thead tr:first-child th,table caption+thead tr:first-child td,table colgroup+thead tr:first-child th,table colgroup+thead tr:first-child td,table thead:first-child tr:first-child th,table thead:first-child tr:first-child td{border-top:0}table tbody+tbody{border-top:2px solid #edece4}table table table{background-color:#fff}table tbody>tr:nth-child(odd)>td,table tbody>tr:nth-child(odd)>th{background-color:#f7f7f3}table.plain tbody>tr:nth-child(odd)>td,table.plain tbody>tr:nth-child(odd)>th{background:transparent}nav ul{list-style:none;margin:0;padding:0;border-top:#edece4 1px solid}nav li a{display:block;padding:10px 15px;color:#aaa9a2;border-bottom:#edece4 1px solid}nav li a:hover{color:#242628;background:#edece4;text-decoration:none}nav li a:before{margin-right:1em}.ghost-logo{display:block;float:left;height:40px;padding:12px 15px;color:#4d5356;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}.ghost-logo:before{font-family:"Icons";font-weight:normal;font-style:normal;vertical-align:-7%;text-transform:none;speak:none;line-height:1;-webkit-font-smoothing:antialiased;content:"\e000";line-height:0}.ghost-logo:hover{text-decoration:none}.ghost-logo:hover{text-decoration:none}.ghost-logo:hover{color:#e2edf2;background:#1f2123}.navbar{height:40px;font-size:0.85em;background:#242628}@media (max-width: 1000px){.navbar{font-weight:normal}}.navbar nav ul{float:left;border-left:#35393b 1px solid;border-top:none}.navbar nav li{float:left;font-size:1em;position:relative;border-right:#35393b 1px solid}.navbar nav li a{display:block;height:40px;padding:11px 15px;border-bottom:none;color:#7d878a;text-transform:uppercase;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}.navbar nav li a:hover,.navbar nav li.active a{color:#e2edf2;text-decoration:none;position:relative;background:#303436;box-shadow:0 -2px 2px rgba(0,0,0,0.2) inset}.navbar nav li.active a:after{content:"";position:absolute;bottom:0;left:50%;margin-left:-5px;border-width:0 5px 5px 5px;border-style:solid;border-color:#edece4 transparent;display:block;width:0}@media (max-width: 400px){.navbar nav li.active a:after{border-color:#fff transparent}}.navbar nav li ul{position:absolute;top:40px;right:0;min-width:200px;background:#242628}.navbar nav li li{width:100%;border-right:none}.navbar nav a:before{margin-right:5px}.navbar nav .dashboard a:before{font-family:"Icons";font-weight:normal;font-style:normal;vertical-align:-7%;text-transform:none;speak:none;line-height:1;-webkit-font-smoothing:antialiased;content:"\e025";vertical-align:-10%}.navbar nav .dashboard a:hover{text-decoration:none}.navbar nav .content a:before{font-family:"Icons";font-weight:normal;font-style:normal;vertical-align:-7%;text-transform:none;speak:none;line-height:1;-webkit-font-smoothing:antialiased;content:"\e02d"}.navbar nav .content a:hover{text-decoration:none}.navbar nav .editor a:before{font-family:"Icons";font-weight:normal;font-style:normal;vertical-align:-7%;text-transform:none;speak:none;line-height:1;-webkit-font-smoothing:antialiased;content:"\e032"}.navbar nav .editor a:hover{text-decoration:none}.navbar nav .settings a:before{font-family:"Icons";font-weight:normal;font-style:normal;vertical-align:-7%;text-transform:none;speak:none;line-height:1;-webkit-font-smoothing:antialiased;content:"\e029"}.navbar nav .settings a:hover{text-decoration:none}.navbar .subnav{position:relative}.navbar .subnav>a:after{font-family:"Icons";font-weight:normal;font-style:normal;vertical-align:-7%;text-transform:none;speak:none;line-height:1;-webkit-font-smoothing:antialiased;content:"\e001";font-size:8px;margin-left:8px}.navbar .subnav>a:hover{text-decoration:none}.navbar .subnav>a.active{color:#e2edf2;background:#2e3133;-webkit-transition:none;-moz-transition:none;transition:none;box-shadow:none}.navbar .subnav ul{display:none;padding:7px 0;border-left:none;position:absolute;top:40px;left:-1px;z-index:800;background:#2e3133;box-shadow:rgba(0,0,0,0.2) 0 4px 6px}.navbar .subnav li a{color:#e2edf2}.navbar .subnav li a:hover{background:#0c0d0d;-webkit-transition:none;-moz-transition:none;transition:none;box-shadow:none}.navbar .subnav li a:before{margin-right:1em}.navbar .subnav .divider{height:1px;margin:7px 0;overflow:hidden;background:#35393b}.usermenu.subnav{position:absolute;top:0;right:0;border-right:none;border-left:#35393b 1px solid}.usermenu.subnav>a{padding-left:43px}.usermenu.subnav .avatar{height:18px;width:18px;border-radius:50px;position:absolute;top:11px;left:15px}.usermenu.subnav>ul{right:0;left:auto}.usermenu.subnav .usermenu-profile a:before{font-family:"Icons";font-weight:normal;font-style:normal;vertical-align:-7%;text-transform:none;speak:none;line-height:1;-webkit-font-smoothing:antialiased;content:"\e02e"}.usermenu.subnav .usermenu-profile a:hover{text-decoration:none}.usermenu.subnav .usermenu-help a:before{font-family:"Icons";font-weight:normal;font-style:normal;vertical-align:-7%;text-transform:none;speak:none;line-height:1;-webkit-font-smoothing:antialiased;content:"\e02f";font-size:1.1em}.usermenu.subnav .usermenu-help a:hover{text-decoration:none}.usermenu.subnav .usermenu-shortcuts a:before{font-family:"Icons";font-weight:normal;font-style:normal;vertical-align:-7%;text-transform:none;speak:none;line-height:1;-webkit-font-smoothing:antialiased;content:"\e00d"}.usermenu.subnav .usermenu-shortcuts a:hover{text-decoration:none}.usermenu.subnav .usermenu-signout a:before{font-family:"Icons";font-weight:normal;font-style:normal;vertical-align:-7%;text-transform:none;speak:none;line-height:1;-webkit-font-smoothing:antialiased;content:"\e02b"}.usermenu.subnav .usermenu-signout a:hover{text-decoration:none}@media (max-width: 650px){#global-header .ghost-logo{height:40px;width:40px;text-align:center;padding:12px 0;-webkit-transition:margin-left 0.3s ease 0s;-moz-transition:margin-left 0.3s ease 0s;transition:margin-left 0.3s ease 0s}#global-header .ghost-logo:before{font-family:"Icons";font-weight:normal;font-style:normal;vertical-align:-7%;text-transform:none;speak:none;line-height:1;-webkit-font-smoothing:antialiased;content:"\e005";font-size:14px}#global-header .ghost-logo:hover{text-decoration:none}.off-canvas #global-header .ghost-logo{margin-left:280px;-webkit-transition:margin-left 0.3s ease 0.1s;-moz-transition:margin-left 0.3s ease 0.1s;transition:margin-left 0.3s ease 0.1s}#global-header ul{position:fixed;overflow:auto;top:0;right:auto;bottom:0;left:-280px;z-index:980;width:280px;padding-top:40px;font-weight:normal;background:#242628;border-left:none;-webkit-transition:left 0.3s ease 0.2s;-moz-transition:left 0.3s ease 0.2s;transition:left 0.3s ease 0.2s}.off-canvas #global-header ul{left:0;-webkit-transition:left 0.3s ease 0s;-moz-transition:left 0.3s ease 0s;transition:left 0.3s ease 0s}#global-header li{float:none;border-right:none;border-bottom:#35393b 1px solid}#global-header li a:hover,#global-header li.active a{box-shadow:none}#global-header li.active a:after{display:none}#global-header li a:before{margin-right:1em}#global-header li ul{position:static;min-width:0;background:#242628}#global-header li li{width:auto}#global-header .usermenu{position:fixed;top:0;right:auto;bottom:auto;left:-280px;height:40px;z-index:990;width:279px;border-left:none;border-right:#242728 1px solid;border-bottom:#292c2e 1px solid;background-color:#1d1e20;background-image:-webkit-linear-gradient(bottom, #111213, #1d1e20);background-image:-moz-linear-gradient(bottom, #111213, #1d1e20);background-image:-ms-linear-gradient(bottom, #111213, #1d1e20);background-image:linear,to top,#111213,#1d1e20;-webkit-transition:left 0.3s ease 0.2s;-moz-transition:left 0.3s ease 0.2s;transition:left 0.3s ease 0.2s}.off-canvas #global-header .usermenu{left:0;-webkit-transition:left 0.3s ease 0s;-moz-transition:left 0.3s ease 0s;transition:left 0.3s ease 0s}#global-header .usermenu>a:hover{background:inherit}#global-header .usermenu>a.active{background:#2e3133}#global-header .usermenu>ul{padding:0;box-shadow:none;width:100%;font-weight:300}#global-header .usermenu .open{box-shadow:rgba(0,0,0,0.4) 0 10px 20px}#global-header .usermenu li{border-bottom:#2e3133 1px solid}#global-header .usermenu li a{background:#2e3133}#global-header .usermenu li a:hover{background:#222426}#global-header .usermenu li a:before{margin-right:1em}#global-header .usermenu .divider{display:none}}.dropdown:after{font-family:"Icons";font-weight:normal;font-style:normal;vertical-align:-7%;text-transform:none;speak:none;line-height:1;-webkit-font-smoothing:antialiased;content:"\e001";font-size:8px;padding-left:6px;vertical-align:0}.dropdown:hover{text-decoration:none}.dropdown.active{color:#242628}.dropdown.active:after{font-family:"Icons";font-weight:normal;font-style:normal;vertical-align:-7%;text-transform:none;speak:none;line-height:1;-webkit-font-smoothing:antialiased;content:"\e001";font-size:8px}.dropdown.active:hover{text-decoration:none}.menu,.menu-left,.menu-right,.menu-drop,.menu-drop-left,.menu-drop-right,#publish-bar .splitbutton-save .editor-options,#publish-bar .splitbutton-delete .editor-options,.suggestions{display:inline-block;position:absolute;z-index:960;padding:6px 0;border:none;list-style:none;color:#e2edf2;background:#242628;border-radius:3px;box-shadow:rgba(0,0,0,0.5) 0 1px 15px}.menu:before,.menu-left:before,.menu-right:before,.menu-drop:before,.menu-drop-left:before,.menu-drop-right:before,#publish-bar .splitbutton-save .editor-options:before,#publish-bar .splitbutton-delete .editor-options:before,.suggestions:before{content:"";position:absolute;bottom:-10px;left:50%;margin-left:-10px;border-width:10px 10px 0 10px;border-style:solid;border-color:#242628 transparent;display:block;width:0}.menu li,.menu-left li,.menu-right li,.menu-drop li,.menu-drop-left li,.menu-drop-right li,#publish-bar .splitbutton-save .editor-options li,#publish-bar .splitbutton-delete .editor-options li,.suggestions li{overflow:hidden}.menu a,.menu-left a,.menu-right a,.menu-drop a,.menu-drop-left a,.menu-drop-right a,#publish-bar .splitbutton-save .editor-options a,#publish-bar .splitbutton-delete .editor-options a,.suggestions a,.menu p,.menu-left p,.menu-right p,.menu-drop p,.menu-drop-left p,.menu-drop-right p,#publish-bar .splitbutton-save .editor-options p,#publish-bar .splitbutton-delete .editor-options p,.suggestions p{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box;display:block;position:relative;padding:10px 25px 10px 35px;border:none;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box;color:#e2edf2 !important;text-transform:none;text-decoration:none}.menu a:hover,.menu-left a:hover,.menu-right a:hover,.menu-drop a:hover,.menu-drop-left a:hover,.menu-drop-right a:hover,#publish-bar .splitbutton-save .editor-options a:hover,#publish-bar .splitbutton-delete .editor-options a:hover,.suggestions a:hover,.menu p:hover,.menu-left p:hover,.menu-right p:hover,.menu-drop p:hover,.menu-drop-left p:hover,.menu-drop-right p:hover,#publish-bar .splitbutton-save .editor-options p:hover,#publish-bar .splitbutton-delete .editor-options p:hover,.suggestions p:hover{background:#5ba4e5;box-shadow:rgba(255,255,255,0.2) 0 1px 0 inset,rgba(0,0,0,0.5) 0 1px 5px}.menu .active a:before,.menu-left .active a:before,.menu-right .active a:before,.menu-drop .active a:before,.menu-drop-left .active a:before,.menu-drop-right .active a:before,#publish-bar .splitbutton-save .editor-options .active a:before,#publish-bar .splitbutton-delete .editor-options .active a:before,.suggestions .active a:before{font-family:"Icons";font-weight:normal;font-style:normal;vertical-align:-7%;text-transform:none;speak:none;line-height:1;-webkit-font-smoothing:antialiased;content:"\e033";position:absolute;top:14px;left:11px}.menu .active a:hover,.menu-left .active a:hover,.menu-right .active a:hover,.menu-drop .active a:hover,.menu-drop-left .active a:hover,.menu-drop-right .active a:hover,#publish-bar .splitbutton-save .editor-options .active a:hover,#publish-bar .splitbutton-delete .editor-options .active a:hover,.suggestions .active a:hover{text-decoration:none}.menu-drop:before,.menu-drop-left:before,.menu-drop-right:before{top:-10px;bottom:auto;border-width:0 10px 10px 10px}.menu-left:before,.menu-drop-left:before{left:10px;margin-left:0}.menu-right:before,.menu-drop-right:before,#publish-bar .splitbutton-save .editor-options:before,#publish-bar .splitbutton-delete .editor-options:before{left:auto;right:10px;margin-left:0}.post-settings{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box;display:inline-block;padding:0 10px;color:#7d878a;-webkit-transition:all 0.15s ease-out 0;-moz-transition:all 0.15s ease-out 0;transition:all 0.15s ease-out 0;position:relative;top:1px}.post-settings:before{font-family:"Icons";font-weight:normal;font-style:normal;vertical-align:-7%;text-transform:none;speak:none;line-height:1;-webkit-font-smoothing:antialiased;content:"\e006";font-size:14px}.post-settings:hover{text-decoration:none}.post-settings:hover,.post-settings.active{color:#242628}.post-settings-menu{padding-top:0;text-transform:none}.post-settings-menu table{margin:0}.post-settings-menu td{padding:0;border-top:none;border-bottom:#414648 1px solid}.post-settings-menu .post-setting-label{padding:8px 10px 8px 15px;border-right:#414648 1px solid;text-align:right}.post-settings-menu form label,form .post-settings-menu label,.post-settings-menu form .label,form .post-settings-menu .label{position:static;width:auto;font-weight:normal;color:#7d878a;white-space:nowrap}.post-settings-menu input{width:200px;margin:0}@media (max-width: 550px){.post-settings-menu input{width:200px}}.post-settings-menu input[type="text"]{border:none;padding:8px 0 8px 10px;color:#e2edf2;border-radius:0;background:transparent}.post-settings-menu input[type="text"]:focus{background:#35393b;border:none}.post-settings-menu .post-setting-item{padding:5px 0 0 10px}.post-settings-menu .checkbox{position:relative;margin-top:0;width:18px;border:#4d5356 1px solid;background:#35393b}.post-settings-menu .delete{display:block;padding:10px 15px}.post-settings-menu .delete:before{font-family:"Icons";font-weight:normal;font-style:normal;vertical-align:-7%;text-transform:none;speak:none;line-height:1;-webkit-font-smoothing:antialiased;content:"\e023";position:relative;top:-1px;margin-right:10px}.post-settings-menu .delete:hover{text-decoration:none}.post-settings-menu .delete:hover{background:#e25440}@media (min-width: 401px), (min-width: 401px){.notifications{position:absolute;bottom:0;left:0;z-index:980;width:300px}}@media (max-width: 400px){.notifications{position:fixed;top:0;left:0;right:0;z-index:9999}}.js-bb-notification{-webkit-transform:translateZ(0);-moz-transform:translateZ(0);-ms-transform:translateZ(0);-o-transform:translateZ(0);transform:translateZ(0)}.notification-success,.notification-error,.notification-warn,.notification-info,.notification{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box;width:100%;min-height:40px;padding:10px 43px 10px 57px;margin:0 0 15px 0;color:rgba(255,255,255,0.9);background:#5ba4e5;position:relative;box-shadow:rgba(0,0,0,0.05) 0 1px 5px;-webkit-transform:translateZ(0);-moz-transform:translateZ(0);-ms-transform:translateZ(0);-o-transform:translateZ(0);transform:translateZ(0)}.notification-success:before,.notification-error:before,.notification-warn:before,.notification-info:before,.notification:before{font-family:"Icons";font-weight:normal;font-style:normal;vertical-align:-7%;text-transform:none;speak:none;line-height:1;-webkit-font-smoothing:antialiased;content:"\e031";position:absolute;top:0;left:0;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box;height:100%;width:44px;padding:14px 15px;text-align:center;color:rgba(255,255,255,0.8);background:rgba(0,0,0,0.1)}.notification-success:hover,.notification-error:hover,.notification-warn:hover,.notification-info:hover,.notification:hover{text-decoration:none}@media (max-width: 400px){.notification-success,.notification-error,.notification-warn,.notification-info,.notification{margin-bottom:1px}}.notification-success .close,.notification-error .close,.notification-warn .close,.notification-info .close,.notification .close{display:inline-block;color:rgba(255,255,255,0.6);cursor:pointer}.notification-success .close:after,.notification-error .close:after,.notification-warn .close:after,.notification-info .close:after,.notification .close:after{font-family:"Icons";font-weight:normal;font-style:normal;vertical-align:-7%;text-transform:none;speak:none;line-height:1;-webkit-font-smoothing:antialiased;content:"\e01c";padding:6px;position:absolute;top:8px;right:9px}.notification-success .close:hover,.notification-error .close:hover,.notification-warn .close:hover,.notification-info .close:hover,.notification .close:hover{text-decoration:none}.notification-success .close:hover,.notification-error .close:hover,.notification-warn .close:hover,.notification-info .close:hover,.notification .close:hover{color:#fff}.notification-success a,.notification-error a,.notification-warn a,.notification-info a,.notification a{color:inherit;text-decoration:underline}.notification-success{background:#9fbb58}.notification-success:before{font-family:"Icons";font-weight:normal;font-style:normal;vertical-align:-7%;text-transform:none;speak:none;line-height:1;-webkit-font-smoothing:antialiased;content:"\e030"}.notification-success:hover{text-decoration:none}.notification-success.notification-passive{-webkit-animation:fade-out 1s linear;-moz-animation:fade-out 1s linear;animation:fade-out 1s linear;-webkit-animation-delay:3s;-moz-animation-delay:3s;animation-delay:3s;-webkit-animation-iteration-count:1;-moz-animation-iteration-count:1;animation-iteration-count:1;-webkit-animation-fill-mode:forwards;-moz-animation-fill-mode:forwards;animation-fill-mode:forwards}.notification-error{background:#e25440}.notification-error:before{font-family:"Icons";font-weight:normal;font-style:normal;vertical-align:-7%;text-transform:none;speak:none;line-height:1;-webkit-font-smoothing:antialiased;content:"\e01a"}.notification-error:hover{text-decoration:none}.notification-warn{background:#f2a925}.notification-warn:before{font-family:"Icons";font-weight:normal;font-style:normal;vertical-align:-7%;text-transform:none;speak:none;line-height:1;-webkit-font-smoothing:antialiased;content:"\e014"}.notification-warn:hover{text-decoration:none}.notification-info{background:#5ba4e5}.notification-info:before{font-family:"Icons";font-weight:normal;font-style:normal;vertical-align:-7%;text-transform:none;speak:none;line-height:1;-webkit-font-smoothing:antialiased;content:"\e014"}.notification-info:hover{text-decoration:none}.update-available main{bottom:56px}#modal-container{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box;display:none;position:fixed;top:0;bottom:0;left:0;right:0;overflow-x:auto;overflow-y:scroll;z-index:1040;pointer-events:none;-webkit-transition:all 0.15s linear 0s;-moz-transition:all 0.15s linear 0s;transition:all 0.15s linear 0s;-webkit-transform:translateZ(0);-moz-transform:translateZ(0);-ms-transform:translateZ(0);-o-transform:translateZ(0);transform:translateZ(0)}.fade{opacity:0;-webkit-transition:opacity 0.2s linear 0s;-moz-transition:opacity 0.2s linear 0s;transition:opacity 0.2s linear 0s;-webkit-transform:translateZ(0);-moz-transform:translateZ(0);-ms-transform:translateZ(0);-o-transform:translateZ(0);transform:translateZ(0)}.fade.in{opacity:1}.modal-background{display:none;position:fixed;top:0;right:0;bottom:0;left:0;background:rgba(0,0,0,0.4);z-index:1030}.modal-info,.modal-action,.modal{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box;left:50%;right:auto;width:450px;margin-left:auto;margin-right:auto;padding-top:30px;padding-bottom:30px;z-index:1050;pointer-events:auto}@media (max-width: 800px){.modal-info,.modal-action,.modal{width:auto;padding:10px}}.modal-info button,.modal-action button,.modal button{min-width:100px}@media (max-width: 800px){.modal-info,.modal-action,.modal{width:100%;margin-left:0}}.modal-info .image-uploader,.modal-action .image-uploader,.modal-info .pre-image-uploader,.modal-action .pre-image-uploader,.modal .image-uploader,.modal .pre-image-uploader{margin:0}.modal-action{padding:60px 0 30px}@media (max-width: 800px){.modal-action{padding:30px 0}}.modal-content{-webkit-box-sizing:padding-box;-moz-box-sizing:padding-box;box-sizing:padding-box;position:relative;padding:20px;background-clip:padding-box;background-color:#FFFFFF;border-radius:2px;box-shadow:rgba(0,0,0,0.2) 0 0 0 6px}.modal-content .close{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box;position:absolute;top:15px;right:15px;width:16px;min-height:16px;padding:0;margin:0;border:none;z-index:9999;-webkit-transition:opacity 0.3s linear;-moz-transition:opacity 0.3s linear;transition:opacity 0.3s linear}.modal-content .close:before{font-family:"Icons";font-weight:normal;font-style:normal;vertical-align:-7%;text-transform:none;speak:none;line-height:1;-webkit-font-smoothing:antialiased;content:"\e034";font-size:1em;color:#7d878a}.modal-content .close:hover{text-decoration:none}.modal-content .close:hover{color:#242628}.modal-header{position:relative;padding:20px;border-bottom:1px solid #edece4}.modal-header h1{display:inline-block;margin:0;font-size:1.5em;font-weight:bold}.modal-body{position:relative;min-height:100px;overflow-y:auto}.modal-footer{margin-top:20px}.modal-style-wide{width:550px}@media (max-width: 800px){.modal-style-wide{width:100%}}.modal-style-centered{text-align:center}main{position:absolute;top:55px;right:15px;bottom:0;left:15px;padding:0}@media (max-width: 400px){main{top:40px;left:0;right:0}}.floatingheader{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box;position:absolute;top:0;left:0;right:0;z-index:400;height:40px;padding:10px 15px;text-transform:uppercase;color:#aaa9a2;background-color:transparent;background-image:-webkit-gradient(linear, left top, left bottom, color-stop(0%, #fff),color-stop(25%, #fff),color-stop(100%, rgba(255,255,255,0.9)));background-image:-webkit-linear-gradient(top, #fff 0%,#fff 25%,rgba(255,255,255,0.9) 100%);background-image:linear-gradient(to bottom,#fff 0%,#fff 25%,rgba(255,255,255,0.9) 100%)}.floatingheader button,.floatingheader .button{display:inline-block;font-size:10px;min-height:20px;height:20px;padding:3px 4px;vertical-align:top}.floatingheader button.button-back,.floatingheader .button.button-back{position:relative;top:-2px;left:3px;display:none;padding:0 6px 0 3px}.floatingheader button.button-back:active,.floatingheader .button.button-back:active{box-shadow:none}.floatingheader button.button-back:before,.floatingheader .button.button-back:before{left:-8px;border-width:10px 8px 10px 0}@media (max-width: 800px){.floatingheader button.button-back,.floatingheader .button.button-back{display:inline-block}}.floatingheader small{font-size:0.85em}.floatingheader a{color:#aaa9a2}.floatingheader a:hover{color:#242628}.scrolling .floatingheader{box-shadow:rgba(0,0,0,0.02) 0 1px 2px,rgba(255,255,255,0.5) 0 -1px 0 inset}.scrolling .floatingheader::before{content:"";height:40px;width:80%;position:absolute;bottom:0;left:50%;margin-left:-40%;box-shadow:rgba(0,0,0,0.02) 0 2px 2px}.scrolling .floatingheader::after{content:"";height:40px;width:30%;position:absolute;bottom:0;left:50%;margin-left:-15%;box-shadow:rgba(0,0,0,0.02) 0 3px 3px}.image-uploader{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box;margin:1.6em 0;position:relative;overflow:hidden;padding:55px 60px;border:#edece4 3px dashed;width:100%;height:auto;text-align:center;color:#aaa9a2;background:#F9F8F5}.image-uploader a{color:#aaa9a2;text-decoration:none}.image-uploader a:hover{color:#242628}.image-uploader .description{margin-top:10px}.image-uploader .media:before{font-family:"Icons";font-weight:normal;font-style:normal;vertical-align:-7%;text-transform:none;speak:none;line-height:1;-webkit-font-smoothing:antialiased;content:"\e011";font-size:60px;color:#e7e6db;display:inline-block;vertical-align:initial;-webkit-transition:transform 1s ease;-moz-transition:transform 1s ease;transition:transform 1s ease}.image-uploader .media:hover{text-decoration:none}.image-uploader .image-url,.image-uploader .image-upload{line-height:12px;padding:10px;display:block;position:absolute;bottom:0;left:0;color:#aaa9a2;text-decoration:none;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.image-uploader .image-url:hover,.image-uploader .image-upload:hover{cursor:pointer}.image-uploader .image-webcam:before{font-family:"Icons";font-weight:normal;font-style:normal;vertical-align:-7%;text-transform:none;speak:none;line-height:1;-webkit-font-smoothing:antialiased;content:"\e036";font-size:12px}.image-uploader .image-webcam:hover{text-decoration:none}.image-uploader .image-url:before{font-family:"Icons";font-weight:normal;font-style:normal;vertical-align:-7%;text-transform:none;speak:none;line-height:1;-webkit-font-smoothing:antialiased;content:"\e035";font-size:12px}.image-uploader .image-url:hover{text-decoration:none}.image-uploader .image-upload:before{font-family:"Icons";font-weight:normal;font-style:normal;vertical-align:-7%;text-transform:none;speak:none;line-height:1;-webkit-font-smoothing:antialiased;content:"\e011";font-size:12px}.image-uploader .image-upload:hover{text-decoration:none}.image-uploader .button-add{display:inline-block;position:relative;z-index:700;color:#fff;padding-left:5px}.image-uploader .button-save{margin:0 0 0 10px}.image-uploader input.main{position:absolute;right:0;margin:0;opacity:0;-webkit-transform-origin:right;-moz-transform-origin:right;-ms-transform-origin:right;-o-transform-origin:right;transform-origin:right;-webkit-transform:scale(14);-moz-transform:scale(14);-ms-transform:scale(14);-o-transform:scale(14);transform:scale(14);font-size:23px;direction:ltr;cursor:pointer}.image-uploader input.main.right{right:9999px;height:0}.image-uploader input.url{font:-webkit-small-control;box-sizing:border-box;width:276px;vertical-align:middle;padding:9px 7px;margin:10px 0;outline:0;font-size:1.1em;background:#fff;border:#e3e1d5 1px solid;border-radius:4px;-webkit-transition:all 0.15s ease-in-out;-moz-transition:all 0.15s ease-in-out}.image-uploader .progress{position:relative;margin:-19px 0 44px 0;display:block;overflow:hidden;background-color:#f5f5f5;background-image:-webkit-gradient(linear, left top, left bottom, color-stop(0%, #f5f5f5),color-stop(100%, #f9f9f9));background-image:-webkit-linear-gradient(top, #f5f5f5,#f9f9f9);background-image:linear-gradient(to bottom,#f5f5f5,#f9f9f9);border-radius:12px;box-shadow:rgba(0,0,0,0.1) 0 1px 2px inset}.image-uploader .fileupload-loading{display:block;top:50%;width:35px;height:28px;margin:-28px auto 0;background-size:contain}.image-uploader .failed{position:relative;top:-40px;font-size:16px}.image-uploader .bar{height:12px;background:#5ba4e5}.image-uploader .bar.fail{background:#e25440}.pre-image-uploader{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box;margin:1.6em 0;position:relative;overflow:hidden;height:auto;color:#aaa9a2;background:rgba(0,0,0,0.1);border-radius:2px;min-height:46px}.pre-image-uploader input{position:absolute;left:9999px;opacity:0}.pre-image-uploader a{z-index:10000;color:#aaa9a2;text-decoration:none}.pre-image-uploader a:hover{color:#242628}.pre-image-uploader img{display:block;max-width:100%;margin:0 auto;line-height:0}.pre-image-uploader .image-cancel{position:absolute;top:10px;right:10px;padding:8px;z-index:300;color:#fff;text-decoration:none;line-height:0;border-radius:2px;background:rgba(0,0,0,0.6);box-shadow:rgba(255,255,255,0.2) 0 0 0 1px}.pre-image-uploader .image-cancel:before{font-family:"Icons";font-weight:normal;font-style:normal;vertical-align:-7%;text-transform:none;speak:none;line-height:1;-webkit-font-smoothing:antialiased;content:"\e023";font-size:11px}.pre-image-uploader .image-cancel:hover{text-decoration:none}.pre-image-uploader .image-cancel:hover{color:#fff;cursor:pointer;background:#e25440}#nprogress{pointer-events:none;-webkit-pointer-events:none}#nprogress .bar{background:#5ba4e5;position:fixed;z-index:100;top:0;left:0;width:100%;height:2px}#nprogress .peg{display:block;position:absolute;right:0px;width:100px;height:100%;box-shadow:0 0 10px #5ba4e5,0 0 5px #5ba4e5;opacity:1.0;-webkit-transform:rotate(3deg) translate(0px, -4px);-moz-transform:rotate(3deg) translate(0px, -4px);-ms-transform:rotate(3deg) translate(0px, -4px);-o-transform:rotate(3deg) translate(0px, -4px);transform:rotate(3deg) translate(0px, -4px)}#nprogress .spinner{display:block;position:fixed;z-index:100;top:15px;right:15px}#nprogress .spinner-icon{width:14px;height:14px;border:solid 2px transparent;border-top-color:#5ba4e5;border-left-color:#5ba4e5;border-radius:10px;-webkit-animation:nprogress-spinner 400ms linear infinite;-moz-animation:nprogress-spinner 400ms linear infinite;-ms-animation:nprogress-spinner 400ms linear infinite;-o-animation:nprogress-spinner 400ms linear infinite;animation:nprogress-spinner 400ms linear infinite}@-webkit-keyframes nprogress-spinner{0%{-webkit-transform:rotate(0deg);transform:rotate(0deg)}100%{-webkit-transform:rotate(360deg);transform:rotate(360deg)}}@-moz-keyframes nprogress-spinner{0%{-moz-transform:rotate(0deg);transform:rotate(0deg)}100%{-moz-transform:rotate(360deg);transform:rotate(360deg)}}@-o-keyframes nprogress-spinner{0%{-o-transform:rotate(0deg);transform:rotate(0deg)}100%{-o-transform:rotate(360deg);transform:rotate(360deg)}}@-ms-keyframes nprogress-spinner{0%{-ms-transform:rotate(0deg);transform:rotate(0deg)}100%{-ms-transform:rotate(360deg);transform:rotate(360deg)}}@keyframes nprogress-spinner{0%{transform:rotate(0deg);transform:rotate(0deg)}100%{transform:rotate(360deg);transform:rotate(360deg)}}.wrapper{position:relative}.palette{margin-bottom:15px}.palette section{padding:5px 10px;width:90px;height:90px;float:left;color:rgba(0,0,0,0.5);position:relative;font-size:12px;font-weight:bold;font-family:Inconsolata,monospace;overflow:hidden;-webkit-transition:all 0.15s ease-in-out;-moz-transition:all 0.15s ease-in-out;transition:all 0.15s ease-in-out}.palette section:hover{box-shadow:rgba(0,0,0,0.05) 5px 0 0 inset,rgba(0,0,0,0.05) -5px 0 0 inset,rgba(0,0,0,0.05) 0 5px 0 inset,rgba(0,0,0,0.05) 0 -5px 0 inset;-webkit-transition:all 0.15s ease-in-out;-moz-transition:all 0.15s ease-in-out;transition:all 0.15s ease-in-out}.palette section small{position:absolute;top:20px;left:10px;font-size:11px;font-weight:normal;font-family:"Open Sans",sans-serif;display:block;width:100px;opacity:0.6;-webkit-transition:all 0.15s ease-in-out;-moz-transition:all 0.15s ease-in-out;transition:all 0.15s ease-in-out}.palette section:hover small{opacity:1;-webkit-transition:all 0.15s ease-in-out;-moz-transition:all 0.15s ease-in-out;transition:all 0.15s ease-in-out}.palette .brown{background:#aaa9a2}.palette .midbrown{background:#c0bfb6}.palette .lightbrown{background:#edece4}.palette .darkgrey{color:rgba(255,255,255,0.5);background:#242628}.palette .grey{color:rgba(255,255,255,0.5);background:#35393b}.palette .midgrey{background:#7d878a}.palette .lightgrey{background:#e2edf2}.palette .blue{color:#fff;background:#5ba4e5}.palette .red{color:#fff;background:#e25440}.palette .orange{color:#fff;background:#f2a925}form label,form .label{display:inline-block;position:absolute;top:0.5em;left:0;width:120px;font-weight:bold;color:#aaa9a2;text-align:right}@media (max-width: 550px){form label,form .label{display:block;position:relative;top:auto;left:auto;width:auto;margin-bottom:5px;text-align:left}}form p{max-width:400px;color:#9e9d95;font-size:1em;margin:0}fieldset{border:none;margin:0 0 3em 0;padding:0}legend{display:block;width:100%;margin:2em 0;border-bottom:#edece4 1px solid;font-size:1.2em;line-height:2.0em;color:#aaa9a2}input,textarea,select{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box;width:276px;padding:5px 7px;margin:0;outline:0;font-size:1.1em;line-height:1.4em;background:#fff;border:#e3e1d5 1px solid;border-radius:2px;-webkit-transition:all 0.15s ease-in-out;-moz-transition:all 0.15s ease-in-out;transition:all 0.15s ease-in-out}@media (max-width: 550px){input,textarea,select{width:100%}}textarea{width:100%;max-width:340px;min-width:250px;height:auto;min-height:6.5em}input,select,textarea{margin-bottom:5px}input[type="text"]:focus,input[type="email"]:focus,input[type="search"]:focus,input[type="tel"]:focus,input[type="url"]:focus,input[type="password"]:focus,input[type="number"]:focus,input[type="date"]:focus,input[type="month"]:focus,input[type="week"]:focus,input[type="time"]:focus,input[type="datetime"]:focus,input[type="datetime-local"]:focus,textarea:focus{border:#aaa9a2 1px solid;background:#fff;outline:none;outline-width:0}select{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box;width:270px;height:30px;line-height:30px}@media (max-width: 550px){select{width:100%}}@-moz-document url-prefix(){select{height:auto}}.form-group{position:relative;margin:1.5em 0;padding-left:140px}@media (max-width: 550px){.form-group{padding-left:0}}input[type="checkbox"]{display:none}.checkbox{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box;position:relative;top:auto;margin-top:0.5em;display:inline-block;width:18px;height:18px;cursor:pointer;border-radius:2px;background:#f7f7f3;border:#e3e1d5 1px solid;-webkit-transition:all 0.2s ease;-moz-transition:all 0.2s ease;transition:all 0.2s ease}.checkbox:after{opacity:0;content:"";position:absolute;width:7px;height:3px;top:5px;left:4px;border:3px solid #fff;border-top:none;border-right:none;-webkit-transform:rotate(-45deg);-moz-transform:rotate(-45deg);-ms-transform:rotate(-45deg);-o-transform:rotate(-45deg);transform:rotate(-45deg);-webkit-transition:all 0.2s ease;-moz-transition:all 0.2s ease;transition:all 0.2s ease}input[type=checkbox]:checked+.checkbox{background:#9fbb58;border:#b4ca7c}input[type=checkbox]:checked+.checkbox:after{opacity:1}.button,button,input[type="button"],.button-save,button[type="submit"],input[type="submit"],.button-add,.button-delete,button[type="reset"],input[type="reset"],.button-alt,.button-link,.button-back{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box;min-height:35px;width:auto;display:inline-block;padding:0.9em 1.37em;cursor:pointer;text-decoration:none;color:#fff;font-size:11px;line-height:13px;font-weight:300;text-align:center;letter-spacing:1px;text-transform:uppercase;text-shadow:none;border-radius:0.2em;border:rgba(0,0,0,0.05) 0.1em solid;-webkit-transition:background 0.3s ease,border-color 0.3s ease;-moz-transition:background 0.3s ease,border-color 0.3s ease;transition:background 0.3s ease,border-color 0.3s ease}.button:hover,button:hover,input[type="button"]:hover,.button-save:hover,input[type="submit"]:hover,.button-add:hover,.button-delete:hover,input[type="reset"]:hover,.button-alt:hover,.button-link:hover,.button-back:hover{border-color:transparent;background:#f8f8f8;text-decoration:none}.button:active,button:active,input[type="button"]:active,.button-save:active,input[type="submit"]:active,.button-add:active,.button-delete:active,input[type="reset"]:active,.button-alt:active,.button-link:active,.button-back:active{box-shadow:rgba(0,0,0,0.3) 0 1px 3px inset}.button:disabled,button:disabled,input[type="button"]:disabled,.button-save:disabled,input[type="submit"]:disabled,.button-add:disabled,.button-delete:disabled,input[type="reset"]:disabled,.button-alt:disabled,.button-link:disabled,.button-back:disabled{opacity:0.5;cursor:not-allowed}.large.button,button.large,input.large[type="button"],.large.button-save,input.large[type="submit"],.large.button-add,.large.button-delete,input.large[type="reset"],.large.button-alt,.large.button-link,.large.button-back{padding:1em 1.8em;font-size:14px;line-height:16px}.button,button,input[type="button"]{color:#777;font-weight:normal;background:#eee;box-shadow:none}.button:hover,button:hover,input[type="button"]:hover{border-color:rgba(0,0,0,0.1)}.button-save,button[type="submit"],input[type="submit"]{background:#5ba4e5;box-shadow:none}.button-save:hover,button[type="submit"]:hover,input[type="submit"]:hover{background:#2f8cde}.button-add{background:#9fbb58}.button-add:hover{background:#8ba644}.button-delete,button[type="reset"],input[type="reset"]{background:#e25440;box-shadow:none}.button-delete:hover,button[type="reset"]:hover,input[type="reset"]:hover{background:#cf3520}.button-alt{background:#3c4043}.button-alt:hover{background:#242628}.button-link{color:#5ba4e5;background:transparent;border:none}.button-link:hover{background:transparent;text-decoration:underline}.button-back{position:absolute;top:20px;left:20px;margin-right:30px;padding:0.5em 1.37em 0.5em 1.10em;display:none;color:#fff;background:#5ba4e5;border:none;border-top-left-radius:0;border-bottom-left-radius:0}.button-back:before{content:' ';position:absolute;top:0;left:-10px;width:0;height:0;border-width:18px 10px 18px 0;border-color:transparent #5ba4e5 transparent transparent;border-style:solid solid solid none;-webkit-transform:scale(0.9999);-moz-transform:scale(0.9999);-ms-transform:scale(0.9999);-o-transform:scale(0.9999);transform:scale(0.9999);-webkit-transition:border-color 0.3s ease;-moz-transition:border-color 0.3s ease;transition:border-color 0.3s ease}.button-back:hover{color:#fff;background:#2f8cde;border-color:#2f8cde}.button-back:hover:before{border-right-color:#2f8cde}@media (max-width: 800px){.button-back{display:inline-block}}.splitbutton,.splitbutton-save,.splitbutton-add,.splitbutton-delete,.splitbutton-alt{display:inline-block;position:relative;font-size:0;white-space:nowrap}.splitbutton button,.splitbutton-save button,.splitbutton-add button,.splitbutton-delete button,.splitbutton-alt button{font-size:11px;border-top-right-radius:0;border-bottom-right-radius:0}.splitbutton .options,.splitbutton-save .options,.splitbutton-add .options,.splitbutton-delete .options,.splitbutton-alt .options{display:inline-block;position:relative;width:35px;height:35px;margin-left:-1px;vertical-align:top;text-align:center;color:#fff;background:#e5e5e5;border-radius:0 2px 2px 0;box-shadow:rgba(0,0,0,0.02) 0 1px 0 inset,rgba(0,0,0,0.02) -1px 0 0 inset,rgba(0,0,0,0.02) 0 -1px 0 inset;-webkit-transition:background-color 0.3s linear;-moz-transition:background-color 0.3s linear;transition:background-color 0.3s linear}.splitbutton .options:before,.splitbutton-save .options:before,.splitbutton-add .options:before,.splitbutton-delete .options:before,.splitbutton-alt .options:before{font-family:"Icons";font-weight:normal;font-style:normal;vertical-align:-7%;text-transform:none;speak:none;line-height:1;-webkit-font-smoothing:antialiased;content:"\e001";font-size:9px;position:absolute;top:50%;right:50%;margin-top:-3px;margin-right:-5px;-webkit-transition:margin-top 0.3s ease;-moz-transition:margin-top 0.3s ease;transition:margin-top 0.3s ease;-webkit-transition-property:-webkit-transform;-moz-transition-property:-moz-transform;transition-property:transform;-webkit-transition-duration:0.3;-moz-transition-duration:0.3;transition-duration:0.3;-webkit-transition-timing-function:ease;-moz-transition-timing-function:ease;transition-timing-function:ease}.splitbutton .options:hover,.splitbutton-save .options:hover,.splitbutton-add .options:hover,.splitbutton-delete .options:hover,.splitbutton-alt .options:hover{text-decoration:none}.splitbutton .options.active:before,.splitbutton-save .options.active:before,.splitbutton-add .options.active:before,.splitbutton-delete .options.active:before,.splitbutton-alt .options.active:before{-webkit-transform:rotate(360deg);-moz-transform:rotate(360deg);-ms-transform:rotate(360deg);-o-transform:rotate(360deg);transform:rotate(360deg)}.splitbutton .options.up.active:before,.splitbutton-save .options.up.active:before,.splitbutton-add .options.up.active:before,.splitbutton-delete .options.up.active:before,.splitbutton-alt .options.up.active:before{margin-top:-4px;-webkit-transform:rotate(540deg);-moz-transform:rotate(540deg);-ms-transform:rotate(540deg);-o-transform:rotate(540deg);transform:rotate(540deg)}.splitbutton .options:hover,.splitbutton-save .options:hover,.splitbutton-add .options:hover,.splitbutton-delete .options:hover,.splitbutton-alt .options:hover{box-shadow:none;background:#f8f8f8}.splitbutton .options:hover:before,.splitbutton-save .options:hover:before,.splitbutton-add .options:hover:before,.splitbutton-delete .options:hover:before,.splitbutton-alt .options:hover:before{font-family:"Icons";font-weight:normal;font-style:normal;vertical-align:-7%;text-transform:none;speak:none;line-height:1;-webkit-font-smoothing:antialiased;content:"\e001";-webkit-transform:rotate(360deg);-moz-transform:rotate(360deg);-ms-transform:rotate(360deg);-o-transform:rotate(360deg);transform:rotate(360deg)}.splitbutton .options:hover:hover,.splitbutton-save .options:hover:hover,.splitbutton-add .options:hover:hover,.splitbutton-delete .options:hover:hover,.splitbutton-alt .options:hover:hover{text-decoration:none}.splitbutton .options.up:hover:before,.splitbutton-save .options.up:hover:before,.splitbutton-add .options.up:hover:before,.splitbutton-delete .options.up:hover:before,.splitbutton-alt .options.up:hover:before{font-family:"Icons";font-weight:normal;font-style:normal;vertical-align:-7%;text-transform:none;speak:none;line-height:1;-webkit-font-smoothing:antialiased;content:"\e001";margin-top:-4px;-webkit-transform:rotate(540deg);-moz-transform:rotate(540deg);-ms-transform:rotate(540deg);-o-transform:rotate(540deg);transform:rotate(540deg);-webkit-transition-property:-webkit-transform;-moz-transition-property:-moz-transform;transition-property:transform;-webkit-transition-duration:0.6;-moz-transition-duration:0.6;transition-duration:0.6;-webkit-transition-timing-function:ease;-moz-transition-timing-function:ease;transition-timing-function:ease}.splitbutton .options.up:hover:hover,.splitbutton-save .options.up:hover:hover,.splitbutton-add .options.up:hover:hover,.splitbutton-delete .options.up:hover:hover,.splitbutton-alt .options.up:hover:hover{text-decoration:none}.splitbutton .options{color:#777}.splitbutton .options:hover{box-shadow:rgba(0,0,0,0.07) 0 1px 0 inset,rgba(0,0,0,0.07) -1px 0 0 inset,rgba(0,0,0,0.07) 0 -1px 0 inset}.splitbutton-save .options{background:#4598e2}.splitbutton-save .options:hover,.splitbutton-save .options.active{background:#2f8cde}.splitbutton-add .options{background:#91ae47}.splitbutton-add .options:hover{background:#8ba644}.splitbutton-delete .options{background:#de3c25}.splitbutton-delete .options:hover{background:#cf3520}.splitbutton-alt .options{background:#2e3033}.splitbutton-alt .options:hover{background:#242628}.manage .content-view-container{position:relative;height:100%;width:100%}@media (max-width: 800px){.manage .content-view-container{overflow-x:hidden}}.manage .content-list{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box;width:35%;padding:15px;position:absolute;bottom:0;top:0;left:0;border-right:#edece4 2px solid;background:#fff;box-shadow:rgba(0,0,0,0.05) 0 1px 5px}@media (max-width: 800px){.manage .content-list{width:auto;right:0;z-index:500;border:none}}.manage .content-list .content-filter{position:relative;z-index:300}.manage .content-list .content-filter>a{padding:5px;margin-left:-5px}.manage .content-list .content-filter .menu-drop{display:block}.manage .content-list .button-add{position:absolute;top:10px;right:15px;z-index:700;color:#fff;padding-left:5px}.manage .content-list .button-add:before{font-family:"Icons";font-weight:normal;font-style:normal;vertical-align:-7%;text-transform:none;speak:none;line-height:1;-webkit-font-smoothing:antialiased;content:"\e032"}.manage .content-list .button-add:hover{text-decoration:none}.manage .content-list .content-list-content{position:absolute;top:0;right:0;bottom:0;left:0;overflow:auto;padding-top:40px}.manage .content-list .entry-title{font-size:1.4em;line-height:1.1em;margin-bottom:0.5em;font-weight:normal}.manage .content-list .views{float:right;text-align:right;margin-left:15px}.manage .content-list .views:before{font-family:"Icons";font-weight:normal;font-style:normal;vertical-align:-7%;text-transform:none;speak:none;line-height:1;-webkit-font-smoothing:antialiased;content:"\e025";font-size:10px;color:#aaa9a2}.manage .content-list .views:hover{text-decoration:none}@media (max-width: 800px){.manage .content-list .views{float:none}}.manage .content-list .featured .status:before{font-family:"Icons";font-weight:normal;font-style:normal;vertical-align:-7%;text-transform:none;speak:none;line-height:1;-webkit-font-smoothing:antialiased;content:"\e026";font-size:11px;margin-right:10px;vertical-align:7%}.manage .content-list .featured .status:hover{text-decoration:none}.manage .content-list .status .draft{color:#e25440}.manage .content-list .status .scheduled{color:#f2a925}.manage .content-list ol{list-style:none;padding:0;margin:0;border-top:#edece4 1px solid}.manage .content-list ol li{margin:0;padding:0;border-bottom:#edece4 1px solid;position:relative}.manage .content-list ol li a{display:block;padding:20px 15px;color:#aaa9a2}@media (max-width: 400px){.manage .content-list ol li a{padding:15px}}@media (max-width: 800px){.manage .content-list ol li a{padding-right:40px}}.manage .content-list ol li a:after{font-family:"Icons";font-weight:normal;font-style:normal;vertical-align:-7%;text-transform:none;speak:none;line-height:1;-webkit-font-smoothing:antialiased;content:"\e01d";position:absolute;top:50%;margin-top:-6px;right:15px}.manage .content-list ol li a:hover{text-decoration:none}@media (min-width: 800px), (min-width: 800px){.manage .content-list ol li a::after{display:none}}.manage .content-list ol li a:hover{text-decoration:none}@media (min-width: 800px), (min-width: 800px){.manage .content-list ol li.active{border-bottom:#e8eaeb 1px solid;background:#f6f6f7;box-shadow:#e8eaeb 0 -1px 0,rgba(0,0,0,0.06) 7px 0 0 inset,#e8eaeb 1px 0 0 inset}.manage .content-list ol li.active a:hover{box-shadow:rgba(0,0,0,0.1) 7px 0 0 inset;-webkit-transition:all 0.4s ease;-moz-transition:all 0.4s ease;transition:all 0.4s ease}.manage .content-list ol li.active .entry-title{font-weight:bold}.manage .content-list ol li.active .entry-meta{color:#242628}.manage .content-list ol li.active .views{color:#242628;font-weight:normal}.manage .content-list ol li.active .views:before{font-family:"Icons";font-weight:normal;font-style:normal;vertical-align:-7%;text-transform:none;speak:none;line-height:1;-webkit-font-smoothing:antialiased;content:"\e025";font-size:10px;color:#242628}.manage .content-list ol li.active .views:hover{text-decoration:none}}.manage .content-preview{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box;width:65%;padding:15px;position:absolute;bottom:0;top:0;right:0;border-left:#edece4 2px solid;background:#fff;box-shadow:rgba(0,0,0,0.05) 0 1px 5px}@media (max-width: 800px){.manage .content-preview{width:auto;left:100%;right:-100%;margin-left:15px;border:none}}.manage .content-preview .unfeatured{vertical-align:-6%;margin:0 7px 0 -5px;padding:5px}.manage .content-preview .unfeatured:before{font-family:"Icons";font-weight:normal;font-style:normal;vertical-align:-7%;text-transform:none;speak:none;line-height:1;-webkit-font-smoothing:antialiased;content:"\e027";font-size:14px}.manage .content-preview .unfeatured:hover{text-decoration:none}.manage .content-preview .featured{vertical-align:-6%;margin:0 7px 0 -5px;padding:5px}.manage .content-preview .featured:before{font-family:"Icons";font-weight:normal;font-style:normal;vertical-align:-7%;text-transform:none;speak:none;line-height:1;-webkit-font-smoothing:antialiased;content:"\e026";font-size:14px}.manage .content-preview .featured:hover{text-decoration:none}.manage .content-preview .normal{text-transform:none;margin:0 3px}.manage .content-preview .content-preview-content{position:absolute;top:0;right:0;bottom:0;left:0;overflow:auto;padding:80px 40px;word-break:break-word;hyphens:auto}.manage .content-preview .content-preview-content .wrapper{max-width:700px;margin:0 auto}.manage .content-preview .post-controls{float:right;position:relative}.manage .content-preview .post-settings-menu{position:absolute;top:35px;right:-3px}.manage .content-preview .post-edit{margin-right:7px;padding:5px}.manage .content-preview .post-edit:before{font-family:"Icons";font-weight:normal;font-style:normal;vertical-align:-7%;text-transform:none;speak:none;line-height:1;-webkit-font-smoothing:antialiased;content:"\e00f";font-size:14px}.manage .content-preview .post-edit:hover{text-decoration:none}.manage .content-preview img{width:100%;height:auto}.manage .no-posts-box{position:relative;height:90%;margin:0px auto;padding:0px;display:table;z-index:600}@media (max-width: 800px){.manage .no-posts-box{position:fixed;top:45%;left:50%}}.manage .no-posts-box .no-posts{vertical-align:middle;display:table-cell;text-align:center}@media (max-width: 800px){.manage .no-posts-box .no-posts{display:block;position:relative;left:-50%}}.manage .no-posts-box .no-posts h3{color:#aaa9a2;font-weight:200;font-size:2em}@media (min-width: 401px), (min-width: 401px){.editor .notifications{bottom:40px}}.editor .entry-title{height:53px;padding:2px 15px;margin-bottom:5px;position:relative}@media (max-width: 400px){.editor .entry-title{box-shadow:none}}.editor .entry-title input{border:0;margin:0;padding:0;font-size:3em;font-weight:bold;letter-spacing:-1px;width:100%;background:transparent}.editor .entry-title input:focus{outline:0}.editor .entry-container{position:relative;height:100%}.editor .entry-markdown{left:0;border-right:#edece4 2px solid}.editor .entry-preview{right:0;border-left:#edece4 2px solid}.editor .entry-markdown,.editor .entry-preview{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box;width:50%;padding:15px;position:absolute;bottom:40px;top:61px;background:#fff;box-shadow:rgba(0,0,0,0.05) 0 1px 5px}@media (max-width: 400px){.editor .entry-markdown,.editor .entry-preview{box-shadow:none}}@media (max-width: 1000px){.editor .entry-markdown,.editor .entry-preview{top:109px;left:0;right:0;width:100%;border:none;z-index:100;min-height:380px}.editor .entry-markdown .markdown,.editor .entry-markdown .entry-preview-content,.editor .entry-preview .markdown,.editor .entry-preview .entry-preview-content{height:50px;overflow:hidden}}@media (max-width: 1000px){.editor .entry-markdown .floatingheader,.editor .entry-preview .floatingheader{cursor:pointer;width:50%;border-right:#edece4 2px solid;color:#fff;font-weight:normal;background:#aaa9a2;position:absolute;top:-40px;left:0;box-shadow:rgba(0,0,0,0.1) 0 -2px 3px inset}.editor .entry-markdown .floatingheader a,.editor .entry-preview .floatingheader a{color:#fff}}.editor .entry-markdown .floatingheader a,.editor .entry-preview .floatingheader a{color:#aaa9a2}.editor .entry-markdown .floatingheader .markdown-help,.editor .entry-preview .floatingheader .markdown-help{position:relative;top:-5px;right:-5px;float:right;padding:5px}.editor .entry-markdown .floatingheader .markdown-help:before,.editor .entry-preview .floatingheader .markdown-help:before{font-family:"Icons";font-weight:normal;font-style:normal;vertical-align:-7%;text-transform:none;speak:none;line-height:1;-webkit-font-smoothing:antialiased;content:"\e018";color:#cfceca}.editor .entry-markdown .floatingheader .markdown-help:hover,.editor .entry-preview .floatingheader .markdown-help:hover{text-decoration:none}.editor .entry-markdown .floatingheader .markdown-help:hover:before,.editor .entry-preview .floatingheader .markdown-help:hover:before{font-family:"Icons";font-weight:normal;font-style:normal;vertical-align:-7%;text-transform:none;speak:none;line-height:1;-webkit-font-smoothing:antialiased;content:"\e018";color:#aaa9a2}.editor .entry-markdown .floatingheader .markdown-help:hover:hover,.editor .entry-preview .floatingheader .markdown-help:hover:hover{text-decoration:none}.editor .entry-markdown .floatingheader .entry-word-count,.editor .entry-preview .floatingheader .entry-word-count{float:right}.editor .entry-markdown.active,.editor .entry-preview.active{z-index:200}.editor .entry-markdown.active .markdown,.editor .entry-markdown.active .entry-preview-content,.editor .entry-preview.active .markdown,.editor .entry-preview.active .entry-preview-content{height:auto;overflow:auto}@media (max-width: 1000px){.editor .entry-markdown.active header,.editor .entry-preview.active header{cursor:auto;color:#aaa9a2;background:#fff;box-shadow:none}.editor .entry-markdown.active header a,.editor .entry-preview.active header a{color:#aaa9a2}}@media (max-width: 400px){.editor .entry-markdown .markdown-help,.editor .entry-markdown .entry-word-count,.editor .entry-preview .markdown-help,.editor .entry-preview .entry-word-count{display:none}}.editor .entry-markdown-content textarea{border:0;width:100%;height:100%;max-width:100%;margin:0;padding:0;position:absolute;top:0;right:0;bottom:0;left:0}.editor .entry-markdown-content textarea:focus{outline:0}.editor .entry-markdown-content .CodeMirror{height:auto;position:absolute;top:0;left:0;right:0;bottom:0;font-family:Inconsolata,monospace;font-size:1.4em;line-height:1.3em;color:#3c4043}.editor .entry-markdown-content .CodeMirror .CodeMirror-focused,.editor .entry-markdown-content .CodeMirror .CodeMirror-selected{color:#242628;background:#b3d5f3;text-shadow:none}.editor .entry-markdown-content .CodeMirror ::selection{color:#242628;background:#b3d5f3;text-shadow:none}.editor .entry-markdown-content .CodeMirror-lines{padding:65px 0 40px 0}@media (max-width: 1000px){.editor .entry-markdown-content .CodeMirror-lines{padding-top:25px}}@media (max-width: 400px){.editor .entry-markdown-content .CodeMirror-lines{padding:15px 0}}.editor .entry-markdown-content .CodeMirror pre{padding:0 40px}@media (max-width: 400px){.editor .entry-markdown-content .CodeMirror pre{padding:0 15px}}.editor .entry-markdown-content .cm-header{color:#000;font-size:1.4em;line-height:1.4em;font-weight:bold}.editor .entry-markdown-content .cm-variable-2,.editor .entry-markdown-content .cm-variable-3,.editor .entry-markdown-content .cm-keyword{color:#3c4043}.editor .entry-markdown-content .cm-string,.editor .entry-markdown-content .cm-strong,.editor .entry-markdown-content .cm-link,.editor .entry-markdown-content .cm-comment,.editor .entry-markdown-content .cm-quote,.editor .entry-markdown-content .cm-number,.editor .entry-markdown-content .cm-atom,.editor .entry-markdown-content .cm-tag{color:#000;font-weight:bold}@media (max-width: 1000px){.editor .entry-preview .floatingheader{right:0;left:auto;border-right:none;border-left:#edece4 2px solid}}.editor .entry-preview .entry-preview-content{position:absolute;top:0;right:0;bottom:0;left:0;padding:60px 40px 40px 40px;overflow:auto;word-break:break-word;hyphens:auto;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;cursor:default}@media (max-width: 1000px){.editor .entry-preview .entry-preview-content{padding-top:20px}}@media (max-width: 400px){.editor .entry-preview .entry-preview-content{padding:15px}}@media (max-width: 1000px){.editor .scrolling .floatingheader{box-shadow:none}}@media (max-width: 1000px){.editor .scrolling .floatingheader::before,.editor .scrolling .floatingheader::after{display:none}}@media (max-width: 1000px){.editor .scrolling .CodeMirror-scroll,.editor .scrolling .entry-preview-content{box-shadow:0 5px 5px rgba(0,0,0,0.05) inset}}.entry-preview-content,.content-preview-content{font-size:1.4em;line-height:1.5em}.entry-preview-content a,.content-preview-content a{color:#5ba4e5;text-decoration:underline}.entry-preview-content p,.content-preview-content p{margin:1.2em 0 1.6em}.entry-preview-content p:first-child,.content-preview-content p:first-child{margin-top:0}.entry-preview-content h1,.content-preview-content h1{font-size:3em}.entry-preview-content h2,.content-preview-content h2{font-size:2.2em}.entry-preview-content h3,.content-preview-content h3{font-size:1.8em}.entry-preview-content .btn,.content-preview-content .btn{text-decoration:none;color:#35393b}.entry-preview-content .img-placeholder,.content-preview-content .img-placeholder{border:5px dashed #35393b;height:100px;position:relative}.entry-preview-content .img-placeholder span,.content-preview-content .img-placeholder span{display:block;height:30px;position:absolute;margin-top:-15px;top:50%;width:100%;text-align:center}.entry-preview-content a.image-edit,.content-preview-content a.image-edit{width:16px;height:16px}.entry-preview-content img,.content-preview-content img{max-width:100%;height:auto;margin:0 auto}body.zen{background:#f3f2ed}body.zen .usermenu{display:none}body.zen #global-header,body.zen #publish-bar{opacity:0;height:0;overflow:hidden;-webkit-transition:all 0.5s ease-out;-moz-transition:all 0.5s ease-out;transition:all 0.5s ease-out}body.zen main{top:15px;-webkit-transition:all 0.5s ease-out;-moz-transition:all 0.5s ease-out;transition:all 0.5s ease-out}body.zen .entry-markdown,body.zen .entry-preview{bottom:0;-webkit-transition:all 0.5s ease-out;-moz-transition:all 0.5s ease-out;transition:all 0.5s ease-out}#publish-bar{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box;height:40px;padding:0;color:#7d878a;background:#1a1c1d;position:fixed;bottom:0;left:0;right:0;z-index:900;box-shadow:0 -2px 8px rgba(0,0,0,0.2);-webkit-transform:translateZ(0);-moz-transform:translateZ(0);-ms-transform:translateZ(0);-o-transform:translateZ(0);transform:translateZ(0)}@media (max-width: 1000px){#publish-bar{font-weight:normal}}#publish-bar .post-settings:hover,#publish-bar .post-settings.active{color:#e2edf2}#publish-bar .post-settings-menu{position:absolute;bottom:44px;right:-3px}#publish-bar button{min-height:30px;height:30px;line-height:12px;padding:0 10px;margin-top:5px;border-top:rgba(255,255,255,0.4) 1px solid}#publish-bar .button-link{border-top:none}#publish-bar .options{width:30px;min-height:30px;height:30px;margin-top:5px;box-shadow:rgba(255,255,255,0.4) 0 1px 0 inset}#publish-bar .splitbutton-save .button-save,#publish-bar .splitbutton-save .button-delete,#publish-bar .splitbutton-delete .button-save,#publish-bar .splitbutton-delete .button-delete{-webkit-transition:width 0.25s ease,background-color 0.3s linear;-moz-transition:width 0.25s ease,background-color 0.3s linear;transition:width 0.25s ease,background-color 0.3s linear}#publish-bar .splitbutton-save .editor-options,#publish-bar .splitbutton-delete .editor-options{bottom:140%;right:-3%}#publish-bar .splitbutton-save .editor-options a,#publish-bar .splitbutton-delete .editor-options a{font-size:14px}.extended-tags{position:static;min-height:100%}.extended-tags #entry-tags:after{right:10px}.extended-tags .tags{width:281px}.extended-tags .tag-label,.extended-tags .tag-label.touch{color:#fff}.extended-tags .tag-input{width:100%;margin-top:5px;padding-top:5px;padding-left:10px;border-top:1px solid #242628}.extended-tags .right{display:none}#entry-tags{position:absolute;top:0;left:0;right:0;bottom:0;text-transform:none;padding:10px 0 0 0}#entry-tags:after{content:"";position:fixed;top:10px;right:270px;width:20px;height:26px;background-color:rgba(26,28,29,0);background-image:-webkit-gradient(linear, left top, left bottom, color-stop(0%, rgba(26,28,29,0)),color-stop(100%, #1a1c1d));background-image:-webkit-linear-gradient(left, rgba(26,28,29,0),#1a1c1d);background-image:linear-gradient(to right,rgba(26,28,29,0),#1a1c1d);z-index:9999;pointer-events:none}@media (max-width: 400px){#entry-tags:after{right:161px}}#entry-tags .tags{position:relative;display:inline-block;vertical-align:middle;width:auto;max-width:80%;max-width:calc(100% - 320px);height:26px;padding-left:5px;padding-bottom:20px;overflow-x:auto;overflow-y:hidden;-webkit-overflow-scrolling:touch;white-space:nowrap;-webkit-transition:width 0.2s linear;-moz-transition:width 0.2s linear;transition:width 0.2s linear}@media (max-width: 400px){#entry-tags .tags{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box;display:block;width:115px;max-width:inherit;padding-bottom:0}}#entry-tags .tag-label{display:block;float:left;padding:1px 8px 0 8px;-webkit-transition:all 0.15s ease-out 0;-moz-transition:all 0.15s ease-out 0;transition:all 0.15s ease-out 0}#entry-tags .tag-label:before{font-family:"Icons";font-weight:normal;font-style:normal;vertical-align:-7%;text-transform:none;speak:none;line-height:1;-webkit-font-smoothing:antialiased;content:"\e003"}#entry-tags .tag-label:hover{text-decoration:none}#entry-tags .tag-label:hover{cursor:pointer;color:#e2edf2}#entry-tags .tag-label.touch{color:inherit}#entry-tags input[type="text"].tag-input{display:inline-block;padding:0;vertical-align:top;color:#e2edf2;font-weight:300;background:transparent;border:none}#entry-tags input[type="text"].tag-input:focus{outline:none}#entry-tags .tag{display:inline;margin-right:5px;padding:0 5px;color:#e2edf2;white-space:nowrap;background:#596063;border-radius:2px;box-shadow:rgba(255,255,255,0.2) 0 1px 0 inset,#000 0 1px 3px;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}#entry-tags .tag:after{font-family:"Icons";font-weight:normal;font-style:normal;vertical-align:-7%;text-transform:none;speak:none;line-height:1;-webkit-font-smoothing:antialiased;content:"\e034";font-size:8px;color:#242628;margin-left:4px;vertical-align:10%;text-shadow:rgba(255,255,255,0.15) 0 1px 0;-webkit-transition:all 0.15s ease-out 0;-moz-transition:all 0.15s ease-out 0;transition:all 0.15s ease-out 0}#entry-tags .tag:hover{text-decoration:none}#entry-tags .tag:hover{cursor:pointer}#entry-tags .tag:hover:after{font-family:"Icons";font-weight:normal;font-style:normal;vertical-align:-7%;text-transform:none;speak:none;line-height:1;-webkit-font-smoothing:antialiased;content:"\e034";font-size:8px;color:#e2edf2;margin-left:4px;vertical-align:10%;text-shadow:none}#entry-tags .tag:hover:hover{text-decoration:none}.suggestions{bottom:100%}.suggestions li.selected{background:#5ba4e5;box-shadow:rgba(255,255,255,0.2) 0 1px 0 inset,rgba(0,0,0,0.5) 0 1px 5px}.suggestions li a{padding-left:25px}.suggestions mark{background:none;color:white;font-weight:bold}#entry-controls{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box;display:inline-block;position:relative;padding:0;z-index:1}#entry-controls.unsaved .post-settings-menu{padding-bottom:0}#entry-controls.unsaved .post-settings-menu .post-setting:nth-child(3) td{border-bottom:none}#entry-controls.unsaved .post-settings-menu .delete{display:none}#entry-actions{margin-right:6px;position:relative}#entry-actions-menu{position:absolute;bottom:50px;right:-5px}.markdown-help-container{padding-bottom:20px}.modal-markdown-help-table{margin-top:0}.CodeMirror{font-family:monospace;height:300px}.CodeMirror-scroll{overflow:auto}.CodeMirror-lines{padding:4px 0}.CodeMirror pre{padding:0 4px}.CodeMirror-scrollbar-filler{background-color:white}.CodeMirror-gutters{border-right:1px solid #ddd;background-color:#f7f7f7}.CodeMirror div.CodeMirror-cursor{border-left:1px solid black;z-index:3}.CodeMirror div.CodeMirror-secondarycursor{border-left:1px solid silver}.cm-tab{display:inline-block}.cm-s-default .cm-keyword{color:#708}.cm-s-default .cm-atom{color:#219}.cm-s-default .cm-number{color:#164}.cm-s-default .cm-def{color:#00f}.cm-s-default .cm-variable{color:black}.cm-s-default .cm-variable-2{color:#05a}.cm-s-default .cm-variable-3{color:#085}.cm-s-default .cm-property{color:black}.cm-s-default .cm-operator{color:black}.cm-s-default .cm-comment{color:#a50}.cm-s-default .cm-string{color:#a11}.cm-s-default .cm-string-2{color:#f50}.cm-s-default .cm-meta{color:#555}.cm-s-default .cm-error{color:#f00}.cm-s-default .cm-qualifier{color:#555}.cm-s-default .cm-builtin{color:#30a}.cm-s-default .cm-bracket{color:#997}.cm-s-default .cm-tag{color:#170}.cm-s-default .cm-attribute{color:#00c}.cm-s-default .cm-header{color:blue}.cm-s-default .cm-quote{color:#090}.cm-s-default .cm-hr{color:#999}.cm-s-default .cm-link{color:#00c}.cm-negative{color:#d44}.cm-positive{color:#292}.cm-header,.cm-strong{font-weight:bold}.cm-em{font-style:italic}.cm-link{text-decoration:underline}.cm-invalidchar{color:#f00}.CodeMirror{line-height:1;position:relative;overflow:hidden;background:white;color:black}.CodeMirror-scroll{margin-bottom:-30px;margin-right:-30px;padding-bottom:30px;padding-right:30px;height:100%;outline:none;position:relative}.CodeMirror-sizer{position:relative}.CodeMirror-vscrollbar,.CodeMirror-hscrollbar,.CodeMirror-scrollbar-filler{position:absolute;z-index:6;display:none}.CodeMirror-vscrollbar{right:0;top:0;overflow-x:hidden;overflow-y:scroll}.CodeMirror-hscrollbar{bottom:0;left:0;overflow-y:hidden;overflow-x:scroll}.CodeMirror-scrollbar-filler{right:0;bottom:0;z-index:6}.CodeMirror-gutters{position:absolute;left:0;top:0;height:100%;padding-bottom:30px;z-index:3}.CodeMirror-lines{cursor:text}.CodeMirror pre{-moz-border-radius:0;-webkit-border-radius:0;border-radius:0;border-width:0;background:transparent;font-family:inherit;font-size:inherit;margin:0;white-space:pre;word-wrap:normal;line-height:inherit;color:inherit;z-index:2;position:relative;overflow:visible}.CodeMirror-wrap pre{word-wrap:break-word;white-space:pre-wrap;word-break:normal}.CodeMirror-wrap .CodeMirror-scroll{overflow-x:hidden}.CodeMirror-measure{position:absolute;width:100%;height:0px;overflow:hidden;visibility:hidden}.CodeMirror-measure pre{position:static}.CodeMirror div.CodeMirror-cursor{position:absolute;visibility:hidden;border-right:none;width:0}.CodeMirror-focused div.CodeMirror-cursor{visibility:visible}.CodeMirror-selected{background:#d9d9d9}.CodeMirror-focused .CodeMirror-selected{background:#d7d4f0}.CodeMirror span{*vertical-align:text-bottom}@media print{.CodeMirror div.CodeMirror-cursor{visibility:hidden}}.ghost-login,.ghost-signup,.ghost-forgotten,.ghost-reset{color:#7d878a;background:#242628}@media (max-width: 400px){.ghost-login,.ghost-signup,.ghost-forgotten,.ghost-reset{background:#242628}}.ghost-login main,.ghost-signup main,.ghost-forgotten main,.ghost-reset main{top:15px}.ghost-login input:-webkit-autofill,.ghost-signup input:-webkit-autofill,.ghost-forgotten input:-webkit-autofill,.ghost-reset input:-webkit-autofill{-webkit-box-shadow:0 0 0px 1000px #e2edf2 inset !important}.login-box,.signup-box,.forgotten-box,.reset-box{max-width:530px;height:90%;margin:0 auto;padding:0;display:table}@media (max-width: 630px){.login-box,.signup-box,.forgotten-box,.reset-box{max-width:264px;text-align:center}}.login-form{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box;max-width:530px;color:#a5acae;display:table-cell;vertical-align:middle}@media (max-width: 630px){.login-form{max-width:264px}}.login-form div{position:relative;margin:0 0 5px 0;background:#3c4043;float:left}@media (max-width: 630px){.login-form div{margin-bottom:1em}}.login-form input{display:inline-block;clear:both;margin:0;padding:8px 0 8px 8px;width:216px;position:relative;border:none;color:#fff;font-size:1.1em;font-weight:200;background:transparent;box-shadow:none;-webkit-transition:background ease 0.25s;-moz-transition:background ease 0.25s;transition:background ease 0.25s}@media (max-width: 630px){.login-form input{width:264px;-webkit-transition:none;-moz-transition:none;transition:none}}.login-form input:focus{border:none;background:#484c50}.login-form .email-wrap{position:relative;margin-right:3px;border-radius:2px 0 0 2px}.login-form .email-wrap:before{font-family:"Icons";font-weight:normal;font-style:normal;vertical-align:-7%;text-transform:none;speak:none;line-height:1;-webkit-font-smoothing:antialiased;content:"\e012";font-size:12px;position:absolute;bottom:11px;left:8px;z-index:100}.login-form .email-wrap:hover{text-decoration:none}@media (max-width: 630px){.login-form .email-wrap{margin-right:0;border-radius:2px}}.login-form .email-wrap .email{padding-left:28px;border-radius:2px 0 0 2px}.login-form .password-wrap{position:relative;border-radius:0 2px 2px 0}.login-form .password-wrap:before{font-family:"Icons";font-weight:normal;font-style:normal;vertical-align:-7%;text-transform:none;speak:none;line-height:1;-webkit-font-smoothing:antialiased;content:"\e02c";font-size:10px;position:absolute;bottom:12px;left:11px;z-index:100}.login-form .password-wrap:hover{text-decoration:none}@media (max-width: 630px){.login-form .password-wrap{border-radius:2px}}.login-form .password-wrap .password{padding-left:28px;border-radius:0 2px 2px 0}.login-form button{width:85px;height:36px;margin:0 0 0 10px;padding:0.5em 1.37em;min-height:30px;min-width:80px;box-shadow:rgba(255,255,255,0.15) 0 1px 0 inset}@media (max-width: 630px){.login-form button{margin:0;width:100%;margin-bottom:1em}}.login-form .meta{clear:both;color:#7d878a}.login-form a{color:#646d70;font-size:0.9em}.login-form a:hover{color:#8a9396;text-decoration:none}.signup-form,.reset-form{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box;max-width:280px;color:#a5acae;display:table-cell;vertical-align:middle}@media (max-width: 630px){.signup-form,.reset-form{width:264px}}.signup-form div,.reset-form div{position:relative;margin:0 0 1em 0;background:#3c4043;float:left;display:table}.signup-form input,.reset-form input{margin:0;width:280px;padding:8px 10px;position:relative;border:none;color:#fff;font-size:1.1em;font-weight:200;background:transparent;box-shadow:none;-webkit-transition:background ease 0.25s;-moz-transition:background ease 0.25s;transition:background ease 0.25s}@media (max-width: 630px){.signup-form input,.reset-form input{-webkit-transition:none;-moz-transition:none;transition:none;width:264px}}.signup-form input:focus,.reset-form input:focus{border:none;background:#484c50}.signup-form .name-wrap,.reset-form .name-wrap{position:relative;border-radius:2px}.signup-form .name-wrap .name,.reset-form .name-wrap .name{border-radius:2px}.signup-form .email-wrap,.reset-form .email-wrap{position:relative;border-radius:2px}.signup-form .email-wrap .email,.reset-form .email-wrap .email{border-radius:2px}.signup-form .password-wrap,.reset-form .password-wrap{position:relative;border-radius:2px}.signup-form .password-wrap .password,.reset-form .password-wrap .password{border-radius:2px}.signup-form button,.reset-form button{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box;width:100%;height:36px;margin:0 0 1em 0;padding:0.5em 1.37em;min-height:30px;min-width:80px;box-shadow:rgba(255,255,255,0.15) 0 1px 0 inset}.forgotten-form{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box;max-width:280px;color:#a5acae;display:table-cell;vertical-align:middle}@media (max-width: 630px){.forgotten-form{max-width:264px}}.forgotten-form div{position:relative;margin:0 0 1em 0;background:#3c4043;float:left}.forgotten-form input{margin:0;padding:8px 10px;position:relative;border:none;color:#fff;font-size:1.1em;font-weight:200;background:transparent;box-shadow:none;-webkit-transition:background ease 0.25s;-moz-transition:background ease 0.25s;transition:background ease 0.25s}@media (max-width: 630px){.forgotten-form input{-webkit-transition:none;-moz-transition:none;transition:none;max-width:244px}}.forgotten-form input:focus{border:none;background:#484c50}.forgotten-form .email-wrap{position:relative;border-radius:2px}.forgotten-form .email-wrap .email{border-radius:2px}.forgotten-form button{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box;width:100%;height:36px;margin:0 0 1em 0;padding:0.5em 1.37em;min-height:30px;min-width:80px;box-shadow:rgba(255,255,255,0.15) 0 1px 0 inset}.error-content{max-width:530px;margin:0 auto;padding:0;display:table;height:100%}@media (max-width: 630px){.error-content{max-width:264px;text-align:center}}.error-details{display:table-cell;vertical-align:middle}.error-image{display:inline-block;vertical-align:middle;width:96px;height:150px}@media (max-width: 630px){.error-image{width:72px;height:112px}}.error-image img{width:100%;height:100%}.error-message{position:relative;top:-5px;display:inline-block;vertical-align:middle;margin-left:10px}.error-code{margin:0;font-size:7.8em;line-height:0.9em;color:#979797}@media (max-width: 630px){.error-code{font-size:5.8em}}.error-description{margin:0;padding:0;font-weight:300;font-size:1.9em;color:#979797;border:none}@media (max-width: 630px){.error-description{font-size:1.4em}}.error-stack{margin:1em auto;padding:2em;max-width:800px;background-color:rgba(255,255,255,0.3)}.error-stack-list{list-style-type:none;padding:0;margin:0}.error-stack-list li{display:block}.error-stack-list li::before{color:#BBB;content:"\21AA";display:inline-block;font-size:1.2em;margin-right:0.5em}.error-stack-function{font-weight:bold}.settings .wrapper{background:#fff;box-shadow:rgba(0,0,0,0.05) 0 1px 5px;position:relative;width:100%;height:100%;margin:0;padding:0}@media (max-width: 800px){.settings .wrapper{overflow-x:hidden}}.settings .title{text-transform:uppercase;font-weight:normal;font-size:1.6em;line-height:0.8em;margin:0 0 18px 0;padding:0;border:none}.settings .settings-sidebar{width:20%;position:absolute;top:0;left:0;bottom:0;z-index:700;background:#fff;box-shadow:#edece4 1px 0 0}@media (max-width: 800px){.settings .settings-sidebar{width:100%;box-shadow:none}}.settings .settings-sidebar>header{position:relative;z-index:400;height:17px;padding:30px 15px 30px 40px;margin-bottom:0;border-bottom:none;box-shadow:#edece4 0 -1px 0 inset, #edece4 1px 0 0;background:-webkit-gradient(linear, left top, left bottom, color-stop(0%, #fff), color-stop(25%, #fff), color-stop(100%, rgba(255,255,255,0.9)));background:-webkit-linear-gradient(top, #fff 0%, #fff 25%, rgba(255,255,255,0.9) 100%);background:linear,to bottom,#fff 0%,#fff 25%,rgba(255,255,255,0.9) 100%}@media (max-width: 1000px){.settings .settings-sidebar>header{padding-left:15px}}.settings .settings-menu{position:absolute;top:0;left:0;bottom:0;right:-1px;overflow:auto}@media (max-width: 800px){.settings .settings-menu{right:0}}.settings .settings-menu:before{display:block;content:"";height:77px}.settings .settings-menu ul{border-top:none}@media (max-width: 800px){.settings .settings-menu ul{border-bottom:#edece4 1px solid}}.settings .settings-menu li{margin-right:1px;border-top:#fff 1px solid}@media (max-width: 800px){.settings .settings-menu li{margin-right:0;border-top:#edece4 1px solid}}.settings .settings-menu li a{padding:15px 15px 15px 40px;border-bottom:none}@media (max-width: 1000px){.settings .settings-menu li a{padding-left:15px}}@media (max-width: 800px){.settings .settings-menu li a:after{font-family:"Icons";font-weight:normal;font-style:normal;vertical-align:-7%;text-transform:none;speak:none;line-height:1;-webkit-font-smoothing:antialiased;content:"\e01d";float:right;margin-top:5px}.settings .settings-menu li a:hover{text-decoration:none}}.settings .settings-menu li:first-child{border-top:none}.settings .settings-menu li:first-child.active{border-top:none}@media (min-width: 800px), (min-width: 800px){.settings .settings-menu li.active{margin-right:0;position:relative;z-index:300;border-top:#edece4 1px solid;box-shadow:#fff 1px 0 0, #edece4 0 1px 0;-webkit-transition:all 0.15s ease-out 0;-moz-transition:all 0.15s ease-out 0;transition:all 0.15s ease-out 0}.settings .settings-menu li.active a{color:#242628;font-weight:bold;background:#fff}}.settings .settings-menu li a:before{margin-right:20px}@media (max-width: 1000px){.settings .settings-menu li a:before{margin-right:15px}}.settings .settings-menu .general a:before{font-family:"Icons";font-weight:normal;font-style:normal;vertical-align:-7%;text-transform:none;speak:none;line-height:1;-webkit-font-smoothing:antialiased;content:"\e006"}.settings .settings-menu .general a:hover{text-decoration:none}.settings .settings-menu .publishing a:before{font-family:"Icons";font-weight:normal;font-style:normal;vertical-align:-7%;text-transform:none;speak:none;line-height:1;-webkit-font-smoothing:antialiased;content:"\e02d"}.settings .settings-menu .publishing a:hover{text-decoration:none}.settings .settings-menu .services a:before{font-family:"Icons";font-weight:normal;font-style:normal;vertical-align:-7%;text-transform:none;speak:none;line-height:1;-webkit-font-smoothing:antialiased;content:"\e020"}.settings .settings-menu .services a:hover{text-decoration:none}.settings .settings-menu .users a:before{font-family:"Icons";font-weight:normal;font-style:normal;vertical-align:-7%;text-transform:none;speak:none;line-height:1;-webkit-font-smoothing:antialiased;content:"\e002"}.settings .settings-menu .users a:hover{text-decoration:none}.settings .settings-menu .appearance a:before{font-family:"Icons";font-weight:normal;font-style:normal;vertical-align:-7%;text-transform:none;speak:none;line-height:1;-webkit-font-smoothing:antialiased;content:"\e021"}.settings .settings-menu .appearance a:hover{text-decoration:none}.settings .settings-menu .plugins a:before{font-family:"Icons";font-weight:normal;font-style:normal;vertical-align:-7%;text-transform:none;speak:none;line-height:1;-webkit-font-smoothing:antialiased;content:"\e00b"}.settings .settings-menu .plugins a:hover{text-decoration:none}.settings .settings-content{padding:0;position:absolute;top:0;right:0;left:20%;bottom:0;background:#fff;display:none}@media (max-width: 800px){.settings .settings-content{display:none;width:100%;left:100%;right:-100%;margin-left:15px}}.settings .settings-content img{max-width:100%}.settings .settings-content.active{display:block}.settings .settings-content>header{position:relative;z-index:200;height:17px;padding:30px 220px 29px 40px;border-bottom:#edece4 1px solid;margin-bottom:40px;text-transform:none;font-weight:normal;line-height:inherit;color:inherit;background:-moz-linear-gradient(top, #fff 0%, #fff 25%, rgba(255,255,255,0.9) 100%);background:-webkit-gradient(linear, left top, left bottom, color-stop(0%, #fff), color-stop(25%, #fff), color-stop(100%, rgba(255,255,255,0.9)));background:-webkit-linear-gradient(top, #fff 0%, #fff 25%, rgba(255,255,255,0.9) 100%);background:-o-linear-gradient(top, #fff 0%, #fff 25%, rgba(255,255,255,0.9) 100%);background:-ms-linear-gradient(top, #fff 0%, #fff 25%, rgba(255,255,255,0.9) 100%);background:linear,to bottom,#fff 0%,#fff 25%,rgba(255,255,255,0.9) 100%}@media (max-width: 1000px){.settings .settings-content>header{padding-left:15px}}@media (max-width: 800px){.settings .settings-content>header{padding-left:115px}}@media (max-height: 600px), (max-height: 600px){.settings .settings-content>header{height:auto;padding:5px;position:absolute;top:0;right:0;border:none;background:transparent}.settings .settings-content>header .title{display:none}}@media (max-width: 650px){.settings .settings-content>header{padding-left:15px}.settings .settings-content>header .button-back{position:fixed;top:5px;left:14px;min-height:0;height:30px}.settings .settings-content>header .button-back:before{left:-9px;border-width:15px 9px 15px 0}}.settings .settings-content .page-actions{position:absolute;top:20px;right:40px;z-index:700;font-size:1em}@media (max-width: 1000px){.settings .settings-content .page-actions{right:15px}}@media (max-width: 650px){.settings .settings-content .page-actions{position:fixed;top:5px;right:4px}.settings .settings-content .page-actions button{min-height:0;height:30px;padding:0.5em 1.37em}}.settings .settings-content .page-actions .button-add{position:relative;padding-left:50px}.settings .settings-content .page-actions .button-add:before{font-family:"Icons";font-weight:normal;font-style:normal;vertical-align:-7%;text-transform:none;speak:none;line-height:1;-webkit-font-smoothing:antialiased;content:"\e032";font-size:1.4em;color:rgba(255,255,255,0.6);position:absolute;top:0;padding:9px 8px 0 0;left:9px;bottom:0;width:20px;border-right:#8ba644 1px solid}.settings .settings-content .page-actions .button-add:hover{text-decoration:none}.settings .settings-content .content{position:absolute;top:0;right:0;left:0;bottom:0;padding:40px;overflow:auto;-webkit-overflow-scrolling:touch}.settings .settings-content .content:before{display:block;content:"";height:77px}@media (max-height: 600px), (max-height: 600px){.settings .settings-content .content:before{display:none}}.settings .settings-content .content.no-padding{padding:0}@media (max-width: 1000px){.settings .settings-content .content{padding-left:15px}}@media (max-width: 550px){.settings .settings-content .content{padding:0 15px 40px}}.settings .settings-content .description-container,.settings .settings-content .bio-container{max-width:370px}.settings .settings-content .word-count{margin-right:30px;float:right;font-weight:bold;color:#9e9d95}.settings .user-group-header{margin-bottom:0px;padding-bottom:20px;border:0 none;border-bottom:1px solid #d9d6c5}.settings .user-group-header h3{display:inline-block;margin:0;color:#c0bfb6;font-weight:normal;font-size:1.1em;line-height:1em}.settings .user-search{display:inline-block;float:right}.settings .user-search label{margin:0}.settings .user-search:hover .user-search-input,.settings .user-search .user-search-input:focus{width:260px;padding:0 10px}.settings .user-search .user-search-input{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box;width:0px;padding:0;border:none;border-bottom:#f1f0ea 1px solid;-webkit-transition:width 0.2s ease-in-out;-moz-transition:width 0.2s ease-in-out;transition:width 0.2s ease-in-out;box-shadow:none}.settings .user-search .search-icon:before{font-family:"Icons";font-weight:normal;font-style:normal;vertical-align:-7%;text-transform:none;speak:none;line-height:1;-webkit-font-smoothing:antialiased;content:"\e007";font-size:1em;color:#c0bfb6}.settings .user-search .search-icon:hover{text-decoration:none}.settings .users{padding:0px;margin-top:0px;list-style:none}.settings .user{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box;display:block;width:100%;padding:20px;border:0 none;border-top:1px solid #e2edf2}.settings .user:first-child{border:none}.settings .user .user-image{display:inline-block;width:40px;height:40px;margin-right:17px;vertical-align:middle;background-color:#edece4;border-radius:20px}.settings .user .user-image.invite{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box;padding-top:8px;text-align:center}.settings .user .user-image.invite:before{font-family:"Icons";font-weight:normal;font-style:normal;vertical-align:-7%;text-transform:none;speak:none;line-height:1;-webkit-font-smoothing:antialiased;content:"\e012";font-size:1em;color:#aaa9a2}.settings .user .user-image.invite:hover{text-decoration:none}.settings .user .user-image img{width:40px;height:40px;border-radius:20px}.settings .user .user-meta{display:inline-block;vertical-align:middle}.settings .user .user-name{margin:0;margin-top:0.4em;font-weight:400;font-size:1.2em;line-height:1em}.settings .user .user-last-seen{line-height:1em}.settings .user-role{padding:2px 8px;float:right;font-size:0.8em;color:#fff;text-transform:uppercase}.settings .user-role.admin{background-color:#DE523A}.settings .user-role.editor{background-color:#4A8CBD}.settings .user-profile-header{position:relative}.settings .user-profile-header:after{content:"";position:absolute;left:0;right:0;bottom:0;height:110px;background-color:rgba(0,0,0,0);background-image:-webkit-gradient(linear, left top, left bottom, color-stop(0%, rgba(0,0,0,0)),color-stop(100%, rgba(0,0,0,0.3)));background-image:-webkit-linear-gradient(rgba(0,0,0,0),rgba(0,0,0,0.3));background-image:linear-gradient(rgba(0,0,0,0),rgba(0,0,0,0.3))}.settings .cover-image{display:block;line-height:0;width:100%;height:auto;min-height:180px}.settings .edit-cover-image{position:absolute;right:40px;bottom:38px;background:rgba(0,0,0,0.3);border-radius:0;color:rgba(255,255,255,0.8);z-index:2;border-radius:2px;-webkit-transition:color 0.3s ease,background 0.3s ease;-moz-transition:color 0.3s ease,background 0.3s ease;transition:color 0.3s ease,background 0.3s ease}@media (max-width: 1000px){.settings .edit-cover-image{right:15px}}.settings .edit-cover-image:hover{color:#fff;background:rgba(0,0,0,0.5)}.settings .user-profile{position:relative;top:-100px;z-index:1}.settings .user-profile fieldset{padding:0 40px}.settings fieldset.user-details-top{margin-bottom:0;padding:10px 0 0 0}.settings fieldset.user-details-top p{color:#fff}.settings .user-image{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box;display:block;position:relative;width:120px;height:120px;float:left;margin-left:40px;margin-right:20px;text-align:center;border-radius:100%;overflow:hidden;border:5px solid #fff;background:#fff;z-index:2}.settings .user-image .img{display:block;width:110px;height:110px;background-size:cover;background-position:center center;border-radius:100%}.settings .user-image:hover .edit-user-image{opacity:1}.settings .edit-user-image{position:absolute;top:0px;right:0px;bottom:0px;left:0px;border-radius:100%;background:rgba(0,0,0,0.5);opacity:0;color:#fff;line-height:105px;text-transform:uppercase;text-decoration:none;-webkit-transition:opacity 0.3s ease;-moz-transition:opacity 0.3s ease;transition:opacity 0.3s ease}.settings #user-name{border-color:#fff}.settings .user-details-bottom{padding:0 40px;margin:-30px 0 0 0}.settings .plugin-section{padding-bottom:20px}.settings .plugin-section-header h3{margin:15px 0;font-size:1.1em;font-weight:normal;color:#aaa9a2}.settings .plugin-section-footer{text-align:right}.settings .button-update-all:before{font-family:"Icons";font-weight:normal;font-style:normal;vertical-align:-7%;text-transform:none;speak:none;line-height:1;-webkit-font-smoothing:antialiased;content:"\e03d";font-size:1em;color:#ffc125;margin-right:5px}.settings .button-update-all:hover{text-decoration:none}.settings .button-cancel:before{font-family:"Icons";font-weight:normal;font-style:normal;vertical-align:-7%;text-transform:none;speak:none;line-height:1;-webkit-font-smoothing:antialiased;content:"\e034";font-size:1em;color:#fff;margin-right:5px}.settings .button-cancel:hover{text-decoration:none}.settings .plugin-section-table{margin-top:5px}.settings .plugin-section-table tbody>tr:nth-child(odd)>td{background:none}.settings .plugin-section-table .plugin-section-item.inactive .plugin-meta{opacity:0.4}.settings .plugin-section-table .plugin-section-item.inactive td:last-child .plugin-meta{opacity:1}.settings .plugin-section-table .plugin-section-item td{padding:20px 0;border-bottom:#edece4 1px solid}.settings .plugin-section-table .plugin-section-item td:first-child{padding-left:0px;border-top:#edece4 1px solid}.settings .plugin-section-table .plugin-section-item td:first-child .plugin-meta{padding:0px;width:75%;border-left:none;text-align:left}.settings .plugin-section-table .plugin-section-item td:last-child .plugin-meta{padding:0px;text-align:right}.settings .plugin-section-table .plugin-icon{display:inline-block;width:40px;height:40px;margin-right:15px;background:#FFC125;border-radius:5px;vertical-align:middle}.settings .plugin-section-table .plugin-icon img{width:100%}.settings .plugin-section-table .plugin-meta{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box;display:inline-block;width:100%;height:100%;padding:0 20px;vertical-align:middle;border-left:#edece4 1px solid;text-align:center}.settings .plugin-section-table .plugin-info{display:block;color:#414648;font-size:1.2em;font-weight:normal;vertical-align:top}.settings .plugin-section-table .plugin-title{color:#35393b}.settings .plugin-section-table .plugin-sub-info{display:block;color:#7d878a}.settings .plugin-section-table .plugin-download-progress{position:relative;display:block;height:6px;margin-top:10px;background:#edece4;border-radius:3px}.settings .plugin-section-table .plugin-download-progress>span{position:absolute;left:0;top:0;content:"";height:100%;background-color:#5ba4e5;border-radius:3px}.settings .plugin-section-table .rating{unicode-bidi:bidi-override;text-align:center}.settings .plugin-section-table .rating>span{display:inline-block;position:relative;width:1.1em;height:1.1em;font-size:0.8em}.settings .plugin-section-table .rating>span:before{content:"\2605";position:absolute;left:0;opacity:0.5}.settings .plugin-section-table .rating>span.active:before{content:"\2605";opacity:1}.settings .plugin-section-table .plugin-settings-icon{display:block;margin-top:9px;font-size:1.4em}.settings .plugin-section-table .plugin-settings-icon:before{font-family:"Icons";font-weight:normal;font-style:normal;vertical-align:-7%;text-transform:none;speak:none;line-height:1;-webkit-font-smoothing:antialiased;content:"\e006";font-size:1em;color:#35393b}.settings .plugin-section-table .plugin-settings-icon:hover{text-decoration:none}
+/*
+ * Welcome to Ghost - all styles for the Ghost platform are located within
+ * this set of Sass files. Use this file like a table of contents.
+ */
+/* ==========================================================================
+ Modules - These styles are re-used in many areas, and are grouped by type.
+ ========================================================================== */
+/*
+ * These are Sass variables used to make our CSS more dynamic by re-using
+ * common property values throughout our styles. Don't overdo it.
+ *
+ * Table of Contents:
+ *
+ * Bourbon
+ * Breakpoint
+ * Typography
+ * Colors
+ * Gradients
+ * Global Styles
+ *
+ */
+/* =============================================================================
+ Bourbon
+ ============================================================================= */
+/* =============================================================================
+ Breakpoint
+ ============================================================================= */
+/*
+ * Breakpoint Sass 2.0.6
+ * Last updated: July 2013
+ * Copyright: Mason Wendell 2012 - MIT Licensed
+ * Source: https://github.com/canarymason/breakpoint
+ */
+/* =============================================================================
+ Typography
+ ============================================================================= */
+/* =============================================================================
+ Colors
+ ============================================================================= */
+/* =============================================================================
+ Gradients
+ ============================================================================= */
+/*
+ * Auto Gradients
+ *
+ * If the gradient mixin is called with 1 value: gradient(#444) - then a second
+ * color which is 10% lighter than the entered value will be auto-generated. If
+ * the gradient mixin is called with 2 values: gradient(#444,#666) - then those
+ * two values will be used instead, as normal.
+ */
+/* =============================================================================
+ Global Elements
+ ============================================================================= */
+.editor .entry-title, .box {
+ padding: 15px;
+ margin-bottom: 15px;
+ background: #fff;
+ position: relative;
+ box-shadow: rgba(0, 0, 0, 0.05) 0 1px 5px; }
+ .editor .entry-title header, .box header {
+ height: 14px;
+ border-bottom: 1px solid #edece4;
+ padding-bottom: 15px;
+ margin-bottom: 15px;
+ text-transform: uppercase;
+ font-size: 0.85em;
+ color: #aaa9a2; }
+ .editor .entry-title footer, .box footer {
+ height: 14px;
+ border-top: 1px solid #edece4;
+ padding-top: 10px;
+ margin-top: 15px;
+ text-transform: uppercase;
+ font-size: 0.85em;
+ color: #aaa9a2; }
+ .editor .entry-title header a,
+ .editor .entry-title footer a, .box header a,
+ .box footer a {
+ color: #aaa9a2; }
+ .editor .entry-title header a:hover,
+ .editor .entry-title footer a:hover, .box header a:hover,
+ .box footer a:hover {
+ color: #242628;
+ text-decoration: none; }
+
+/* =============================================================================
+ Animations
+ ============================================================================= */
+@-webkit-keyframes fade-out {
+ from {
+ opacity: 1; }
+
+ to {
+ opacity: 0; } }
+
+@-moz-keyframes fade-out {
+ from {
+ opacity: 1; }
+
+ to {
+ opacity: 0; } }
+
+@-o-keyframes fade-out {
+ from {
+ opacity: 1; }
+
+ to {
+ opacity: 0; } }
+
+@keyframes fade-out {
+ from {
+ opacity: 1; }
+
+ to {
+ opacity: 0; } }
+
+/* Sass variables like colours, font sizes, basic styles. */
+/*! normalize.css v2.1.0 | MIT License | git.io/normalize */
+/* ==========================================================================
+ HTML5 display definitions
+ ========================================================================== */
+/**
+ * Correct `block` display not defined in IE 8/9.
+ */
+article,
+aside,
+details,
+figcaption,
+figure,
+footer,
+header,
+hgroup,
+main,
+nav,
+section,
+summary {
+ display: block; }
+
+/**
+ * Correct `inline-block` display not defined in IE 8/9.
+ */
+audio,
+canvas,
+video {
+ display: inline-block; }
+
+/**
+ * Prevent modern browsers from displaying `audio` without controls.
+ * Remove excess height in iOS 5 devices.
+ */
+audio:not([controls]) {
+ display: none;
+ height: 0; }
+
+/**
+ * Address styling not present in IE 8/9.
+ */
+[hidden] {
+ display: none; }
+
+/* ==========================================================================
+ Base
+ ========================================================================== */
+/**
+ * 1. Set default font family to sans-serif.
+ * 2. Prevent iOS text size adjust after orientation change, without disabling
+ * user zoom.
+ */
+html {
+ font-family: sans-serif;
+ /* 1 */
+ -webkit-text-size-adjust: 100%;
+ /* 2 */
+ -ms-text-size-adjust: 100%;
+ /* 2 */ }
+
+/**
+ * Remove default margin.
+ */
+body {
+ margin: 0; }
+
+/* ==========================================================================
+ Links
+ ========================================================================== */
+/**
+ * Address `outline` inconsistency between Chrome and other browsers.
+ */
+a:focus {
+ outline: thin dotted; }
+
+/**
+ * Improve readability when focused and also mouse hovered in all browsers.
+ */
+a:active,
+a:hover {
+ outline: 0; }
+
+/* ==========================================================================
+ Typography
+ ========================================================================== */
+/**
+ * Address variable `h1` font-size and margin within `section` and `article`
+ * contexts in Firefox 4+, Safari 5, and Chrome.
+ */
+h1 {
+ font-size: 2em;
+ margin: 0.67em 0; }
+
+/**
+ * Address styling not present in IE 8/9, Safari 5, and Chrome.
+ */
+abbr[title] {
+ border-bottom: 1px dotted; }
+
+/**
+ * Address style set to `bolder` in Firefox 4+, Safari 5, and Chrome.
+ */
+b,
+strong {
+ font-weight: bold; }
+
+/**
+ * Address styling not present in Safari 5 and Chrome.
+ */
+dfn {
+ font-style: italic; }
+
+/**
+ * Address differences between Firefox and other browsers.
+ */
+hr {
+ -moz-box-sizing: content-box;
+ box-sizing: content-box;
+ height: 0; }
+
+/**
+ * Address styling not present in IE 8/9.
+ */
+mark {
+ background: #ff0;
+ color: #000; }
+
+/**
+ * Correct font family set oddly in Safari 5 and Chrome.
+ */
+code,
+kbd,
+pre,
+samp {
+ font-family: monospace, serif;
+ font-size: 1em; }
+
+/**
+ * Improve readability of pre-formatted text in all browsers.
+ */
+pre {
+ white-space: pre-wrap; }
+
+/**
+ * Set consistent quote types.
+ */
+q {
+ quotes: "\201C" "\201D" "\2018" "\2019"; }
+
+/**
+ * Address inconsistent and variable font size in all browsers.
+ */
+small {
+ font-size: 80%; }
+
+/**
+ * Prevent `sub` and `sup` affecting `line-height` in all browsers.
+ */
+sub,
+sup {
+ font-size: 75%;
+ line-height: 0;
+ position: relative;
+ vertical-align: baseline; }
+
+sup {
+ top: -0.5em; }
+
+sub {
+ bottom: -0.25em; }
+
+/* ==========================================================================
+ Embedded content
+ ========================================================================== */
+/**
+ * Remove border when inside `a` element in IE 8/9.
+ */
+img {
+ border: 0; }
+
+/**
+ * Correct overflow displayed oddly in IE 9.
+ */
+svg:not(:root) {
+ overflow: hidden; }
+
+/* ==========================================================================
+ Figures
+ ========================================================================== */
+/**
+ * Address margin not present in IE 8/9 and Safari 5.
+ */
+figure {
+ margin: 0; }
+
+/* ==========================================================================
+ Forms
+ ========================================================================== */
+/**
+ * Define consistent border, margin, and padding.
+ */
+fieldset {
+ border: 1px solid #c0c0c0;
+ margin: 0 2px;
+ padding: 0.35em 0.625em 0.75em; }
+
+/**
+ * 1. Correct `color` not being inherited in IE 8/9.
+ * 2. Remove padding so people aren't caught out if they zero out fieldsets.
+ */
+legend {
+ border: 0;
+ /* 1 */
+ padding: 0;
+ /* 2 */ }
+
+/**
+ * 1. Correct font family not being inherited in all browsers.
+ * 2. Correct font size not being inherited in all browsers.
+ * 3. Address margins set differently in Firefox 4+, Safari 5, and Chrome.
+ */
+button,
+input,
+select,
+textarea {
+ font-family: inherit;
+ /* 1 */
+ font-size: 100%;
+ /* 2 */
+ margin: 0;
+ /* 3 */ }
+
+/**
+ * Address Firefox 4+ setting `line-height` on `input` using `!important` in
+ * the UA stylesheet.
+ */
+button,
+input {
+ line-height: normal; }
+
+/**
+ * Address inconsistent `text-transform` inheritance for `button` and `select`.
+ * All other form control elements do not inherit `text-transform` values.
+ * Correct `button` style inheritance in Chrome, Safari 5+, and IE 8+.
+ * Correct `select` style inheritance in Firefox 4+ and Opera.
+ */
+button,
+select {
+ text-transform: none; }
+
+/**
+ * 1. Avoid the WebKit bug in Android 4.0.* where (2) destroys native `audio`
+ * and `video` controls.
+ * 2. Correct inability to style clickable `input` types in iOS.
+ * 3. Improve usability and consistency of cursor style between image-type
+ * `input` and others.
+ */
+button,
+html input[type="button"],
+input[type="reset"],
+input[type="submit"] {
+ -webkit-appearance: button;
+ /* 2 */
+ cursor: pointer;
+ /* 3 */ }
+
+/**
+ * Re-set default cursor for disabled elements.
+ */
+button[disabled],
+html input[disabled] {
+ cursor: default; }
+
+/**
+ * 1. Address box sizing set to `content-box` in IE 8/9.
+ * 2. Remove excess padding in IE 8/9.
+ */
+input[type="checkbox"],
+input[type="radio"] {
+ box-sizing: border-box;
+ /* 1 */
+ padding: 0;
+ /* 2 */ }
+
+/**
+ * 1. Address `appearance` set to `searchfield` in Safari 5 and Chrome.
+ * 2. Address `box-sizing` set to `border-box` in Safari 5 and Chrome
+ * (include `-moz` to future-proof).
+ */
+input[type="search"] {
+ -webkit-appearance: textfield;
+ /* 1 */
+ -moz-box-sizing: content-box;
+ -webkit-box-sizing: content-box;
+ /* 2 */
+ box-sizing: content-box; }
+
+/**
+ * Remove inner padding and search cancel button in Safari 5 and Chrome
+ * on OS X.
+ */
+input[type="search"]::-webkit-search-cancel-button,
+input[type="search"]::-webkit-search-decoration {
+ -webkit-appearance: none; }
+
+/**
+ * Remove inner padding and border in Firefox 4+.
+ */
+button::-moz-focus-inner,
+input::-moz-focus-inner {
+ border: 0;
+ padding: 0; }
+
+/**
+ * 1. Remove default vertical scrollbar in IE 8/9.
+ * 2. Improve readability and alignment in all browsers.
+ */
+textarea {
+ overflow: auto;
+ /* 1 */
+ vertical-align: top;
+ /* 2 */ }
+
+/* ==========================================================================
+ Tables
+ ========================================================================== */
+/**
+ * Remove most spacing between table cells.
+ */
+table {
+ border-collapse: collapse;
+ border-spacing: 0; }
+
+/* Browser cross compatibility normalisation*/
+/*
+ * The icons used in Ghost are the Pictos set by Drew Wilson - http://pictos.cc
+ * They are embedded via a custom icon font built with http://icomoon.io
+ *
+ * Table of Contents:
+ *
+ * Font Face
+ * Icon Element
+ * Icon Variables / Short Names
+ * Usage Docs
+ */
+/* =============================================================================
+ The Font Face
+ ============================================================================= */
+@font-face {
+ font-family: 'Icons';
+ src: url("../fonts/icons.eot");
+ src: url("../fonts/icons.eot?#iefix") format("embedded-opentype"), url("../fonts/icons.woff") format("woff"), url("../fonts/icons.ttf") format("truetype"), url("../fonts/icons.svg#icons") format("svg");
+ font-weight: normal;
+ font-style: normal; }
+
+/* =============================================================================
+ The Icon Element
+ ============================================================================= */
+/*
+ * Special use case for when we want to add an icon after an element rather
+ * than before it. For things like dropdowns.
+ */
+/* =============================================================================
+ Icon Variables / Short Names
+ ============================================================================= */
+/*
+ * For accessibility, icon characters in the icon font are stored in Unicode's
+ * Private Use Area characters. This means that screen readers won't attempt to
+ * read them out loud. For code maintainability, we then store these Unicode
+ * references inside Sass variables.
+ */
+/* =============================================================================
+ Usage
+ =============================================================================
+
+To create a button with a label that is prefixed with a camera icon, we might
+write our Sass something like this:
+
+#button {
+ display: block;
+ width: 200px;
+ height: 40px;
+ @include icon($i-camera, 16px, #fff) {vertical-align:-10%;};
+}
+
+This would then output full CSS something like this:
+
+#button {
+ display: block;
+ width: 200px;
+ height: 40px;
+}
+
+#button:before {
+ content: "\e02a";
+ size: 16px;
+ color: #fff;
+ font-family: "Icons";
+ font-weight: normal;
+ font-style: normal;
+ vertical-align: -10%;
+ text-transform:none;
+ speak: none;
+ line-height: 1;
+ -webkit-font-smoothing: antialiased;
+}
+
+*/
+/* All the styles controlling icons. */
+/*
+ * Specific styles for re-usable animations in Ghost admin.
+ *
+ * Table of Contents:
+ *
+ *
+ */
+/* =============================================================================
+ General
+ ============================================================================= */
+@-webkit-keyframes off-canvas {
+ 0% {
+ left: 0; }
+
+ 100% {
+ left: 300px; } }
+
+@-moz-keyframes off-canvas {
+ 0% {
+ opacity: 0; }
+
+ 100% {
+ opacity: 1; } }
+
+@-o-keyframes off-canvas {
+ 0% {
+ opacity: 0; }
+
+ 100% {
+ opacity: 1; } }
+
+@keyframes off-canvas {
+ 0% {
+ opacity: 0; }
+
+ 100% {
+ opacity: 1; } }
+
+@-webkit-keyframes fadeIn {
+ from {
+ opacity: 0; }
+
+ to {
+ opacity: 1; } }
+
+@-moz-keyframes fadeIn {
+ from {
+ opacity: 0; }
+
+ to {
+ opacity: 1; } }
+
+@-o-keyframes fadeIn {
+ from {
+ opacity: 0; }
+
+ to {
+ opacity: 1; } }
+
+@keyframes fadeIn {
+ from {
+ opacity: 0; }
+
+ to {
+ opacity: 1; } }
+
+/* Keyframe animations. */
+/*
+ * Global styles for Ghost which are used throughout the admin interface
+ * Utility classes defined here to keep other libraries (Normalize) from
+ * being modified, preventing upgrade later.
+ *
+ * Table of Contents
+ *
+ * Utility Classes
+ * Global Styles
+ * Global Navigation
+ * Mobile Navigation
+ * Drop-down / Pop-up Menu
+ * Notifications
+ * Modals
+ * Main Elements
+ * Floating Headers
+ * Image Uploader
+ * Misc
+ */
+/* ==========================================================================
+ Utility Classes
+ ========================================================================== */
+.hidden {
+ text-indent: -9999px;
+ visibility: hidden;
+ display: none; }
+
+.invisible {
+ visibility: hidden; }
+
+.right {
+ float: right; }
+
+.left {
+ float: left; }
+
+.markdown, pre, code {
+ font-family: Inconsolata, monospace; }
+
+.visuallyhidden,
+.screen-reader-text {
+ border: 0;
+ clip: rect(0 0 0 0);
+ height: 1px;
+ margin: -1px;
+ overflow: hidden;
+ padding: 0;
+ position: absolute;
+ width: 1px; }
+
+.visuallyhidden.focusable:active,
+.visuallyhidden.focusable:focus {
+ clip: auto;
+ height: auto;
+ margin: 0;
+ overflow: visible;
+ position: static;
+ width: auto; }
+
+.clearfix:before,
+.clearfix:after {
+ content: " ";
+ display: table; }
+
+.clearfix:after {
+ clear: both; }
+
+.clearfix {
+ *zoom: 1; }
+
+/* ==========================================================================
+ Global Styles
+ ========================================================================== */
+html {
+ font: normal 81.2%/1.65 "Open Sans", sans-serif; }
+
+body {
+ width: 100%;
+ color: #242628;
+ font-weight: 300;
+ background: #edece4; }
+ @media (max-width: 400px) {
+ body {
+ background: #fff; } }
+
+::-moz-selection {
+ color: #242628;
+ background: #b3d5f3;
+ text-shadow: none; }
+
+::selection {
+ color: #242628;
+ background: #b3d5f3;
+ text-shadow: none; }
+
+article aside {
+ width: 30%;
+ padding: 0 2.2em;
+ margin: 0 2.2em 1.6em 2.2em;
+ float: right;
+ background: #edece4;
+ border-radius: 3px; }
+
+h1, h2, h3,
+h4, h5, h6 {
+ color: #242628;
+ text-rendering: optimizeLegibility;
+ line-height: 1;
+ margin-top: 0; }
+
+h2 {
+ padding-top: 0.8em;
+ margin-top: 0.8em;
+ border-top: #edece4 1px solid; }
+
+h1 a:hover {
+ text-decoration: none;
+ box-shadow: #5ba4e5 0 -5px 0 inset; }
+
+h2 a:hover {
+ text-decoration: none;
+ box-shadow: #5ba4e5 0 -4px 0 inset; }
+
+h3 a:hover {
+ text-decoration: none;
+ box-shadow: #5ba4e5 0 -3px 0 inset; }
+
+h4 a:hover,
+h5 a:hover,
+h6 a:hover {
+ text-decoration: none;
+ box-shadow: #5ba4e5 0 -1px 0 inset; }
+
+hgroup {
+ margin: 1.6em 0; }
+ hgroup h1, hgroup h2, hgroup h3,
+ hgroup h4, hgroup h5, hgroup h6 {
+ padding: 0;
+ margin: 0;
+ border: none;
+ margin-bottom: 5px;
+ /*
+ * Make everything except the first
+ * heading appear smaller/thinner.
+ */ }
+ hgroup h1 a, hgroup h2 a, hgroup h3 a,
+ hgroup h4 a, hgroup h5 a, hgroup h6 a {
+ color: #242628; }
+ hgroup h1 a:hover, hgroup h2 a:hover, hgroup h3 a:hover,
+ hgroup h4 a:hover, hgroup h5 a:hover, hgroup h6 a:hover {
+ box-shadow: #242628 0 -1px 0 inset; }
+ hgroup h1:nth-child(n+2), hgroup h2:nth-child(n+2), hgroup h3:nth-child(n+2),
+ hgroup h4:nth-child(n+2), hgroup h5:nth-child(n+2), hgroup h6:nth-child(n+2) {
+ font-size: 1.8em;
+ font-weight: 300;
+ color: #aaa9a2; }
+
+p, ul, ol {
+ margin: 1.6em 0; }
+
+ol ol, ul ul,
+ul ol, ol ul {
+ margin: 0.4em 0; }
+
+a {
+ color: #5ba4e5;
+ text-decoration: none;
+ -webkit-transition: all 0.15s ease-in-out;
+ -moz-transition: all 0.15s ease-in-out;
+ transition: all 0.15s ease-in-out; }
+ a:hover {
+ text-decoration: underline; }
+ a.highlight {
+ color: #f2a925;
+ font-weight: bold; }
+
+hr {
+ display: block;
+ height: 1px;
+ border: 0;
+ border-top: 1px solid #edece4;
+ margin: 3.2em 0;
+ padding: 0; }
+
+blockquote {
+ margin: 1.6em 0;
+ -webkit-box-sizing: border-box;
+ -moz-box-sizing: border-box;
+ box-sizing: border-box;
+ padding: 0 1.6em 0 1.6em;
+ border-left: #edece4 0.6em solid; }
+ blockquote p {
+ margin: 0.8em 0;
+ font-size: 1.2em;
+ font-weight: 300; }
+ blockquote small {
+ display: inline-block;
+ margin: 0.8em 0 0.8em 1.5em;
+ font-size: 0.9em;
+ color: #aaa9a2; }
+ blockquote small:before {
+ content: '\2014 \00A0'; }
+ blockquote cite {
+ font-weight: bold; }
+ blockquote cite a {
+ font-weight: normal; }
+
+dl {
+ margin: 1.6em 0; }
+ dl dt {
+ float: left;
+ width: 180px;
+ overflow: hidden;
+ clear: left;
+ text-align: right;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+ font-weight: bold;
+ margin-bottom: 1em; }
+ dl dd {
+ margin-left: 200px;
+ margin-bottom: 1em; }
+
+mark {
+ background-color: #ffc336; }
+
+code, tt {
+ font-family: Inconsolata, monospace;
+ font-size: 0.85em;
+ white-space: pre-wrap;
+ background: #f1f0ea;
+ border: 1px solid #dddbcc;
+ border-radius: 2px;
+ padding: 1px 3px; }
+
+pre {
+ margin: 1.6em 0;
+ -webkit-box-sizing: border-box;
+ -moz-box-sizing: border-box;
+ box-sizing: border-box;
+ background: #f1f0ea;
+ border: 1px solid #dddbcc;
+ width: 100%;
+ padding: 10px;
+ font-family: Inconsolata, monospace;
+ font-size: 0.9em;
+ white-space: pre;
+ overflow: auto;
+ border-radius: 3px; }
+ pre code, pre tt {
+ font-size: inherit;
+ white-space: -moz-pre-wrap;
+ white-space: pre-wrap;
+ background: transparent;
+ border: none;
+ padding: 0; }
+
+kbd {
+ display: inline-block;
+ margin-bottom: 0.4em;
+ padding: 1px 8px;
+ border: #ccc 1px solid;
+ color: #242628;
+ text-shadow: #fff 0 1px 0;
+ font-size: 0.9em;
+ font-weight: bold;
+ background: #f4f4f4;
+ border-radius: 4px;
+ box-shadow: 0 1px 0 rgba(0, 0, 0, 0.2), 0 1px 0 0 white inset; }
+
+table {
+ margin: 1.6em 0;
+ -webkit-box-sizing: border-box;
+ -moz-box-sizing: border-box;
+ box-sizing: border-box;
+ width: 100%;
+ max-width: 100%;
+ background-color: transparent; }
+ table th,
+ table td {
+ padding: 8px;
+ line-height: 20px;
+ text-align: left;
+ vertical-align: middle;
+ border-top: 1px solid #edece4; }
+ table th {
+ color: #aaa9a2; }
+ table caption + thead tr:first-child th,
+ table caption + thead tr:first-child td,
+ table colgroup + thead tr:first-child th,
+ table colgroup + thead tr:first-child td,
+ table thead:first-child tr:first-child th,
+ table thead:first-child tr:first-child td {
+ border-top: 0; }
+ table tbody + tbody {
+ border-top: 2px solid #edece4; }
+ table table table {
+ background-color: #fff; }
+ table tbody > tr:nth-child(odd) > td,
+ table tbody > tr:nth-child(odd) > th {
+ background-color: #f7f7f3; }
+ table.plain tbody > tr:nth-child(odd) > td,
+ table.plain tbody > tr:nth-child(odd) > th {
+ background: transparent; }
+
+nav ul {
+ list-style: none;
+ margin: 0;
+ padding: 0;
+ border-top: #edece4 1px solid; }
+nav li a {
+ display: block;
+ padding: 10px 15px;
+ color: #aaa9a2;
+ border-bottom: #edece4 1px solid; }
+ nav li a:hover {
+ color: #242628;
+ background: #edece4;
+ text-decoration: none; }
+ nav li a:before {
+ margin-right: 1em; }
+
+/* ==========================================================================
+ Main Navigation
+ ========================================================================== */
+.ghost-logo {
+ display: block;
+ float: left;
+ height: 40px;
+ padding: 12px 15px;
+ color: #4d5356;
+ -webkit-box-sizing: border-box;
+ -moz-box-sizing: border-box;
+ box-sizing: border-box; }
+ .ghost-logo:before {
+ font-family: "Icons";
+ font-weight: normal;
+ font-style: normal;
+ vertical-align: -7%;
+ text-transform: none;
+ speak: none;
+ line-height: 1;
+ -webkit-font-smoothing: antialiased;
+ content: "\e000";
+ line-height: 0; }
+ .ghost-logo:hover {
+ text-decoration: none; }
+ .ghost-logo:hover {
+ text-decoration: none; }
+
+.ghost-logo:hover {
+ color: #e2edf2;
+ background: #1f2123; }
+
+.navbar {
+ height: 40px;
+ font-size: 0.85em;
+ background: #242628; }
+ @media (max-width: 1000px) {
+ .navbar {
+ font-weight: normal; } }
+ .navbar nav ul {
+ float: left;
+ border-left: #35393b 1px solid;
+ border-top: none; }
+ .navbar nav li {
+ float: left;
+ font-size: 1em;
+ position: relative;
+ border-right: #35393b 1px solid; }
+ .navbar nav li a {
+ display: block;
+ height: 40px;
+ padding: 11px 15px;
+ border-bottom: none;
+ color: #7d878a;
+ text-transform: uppercase;
+ -webkit-box-sizing: border-box;
+ -moz-box-sizing: border-box;
+ box-sizing: border-box; }
+ .navbar nav li a:hover, .navbar nav li.active a {
+ color: #e2edf2;
+ text-decoration: none;
+ position: relative;
+ background: #303436;
+ box-shadow: 0 -2px 2px rgba(0, 0, 0, 0.2) inset; }
+ .navbar nav li.active a:after {
+ content: "";
+ position: absolute;
+ bottom: 0;
+ left: 50%;
+ margin-left: -5px;
+ border-width: 0 5px 5px 5px;
+ border-style: solid;
+ border-color: #edece4 transparent;
+ display: block;
+ width: 0; }
+ @media (max-width: 400px) {
+ .navbar nav li.active a:after {
+ border-color: #fff transparent; } }
+ .navbar nav li ul {
+ position: absolute;
+ top: 40px;
+ right: 0;
+ min-width: 200px;
+ background: #242628; }
+ .navbar nav li li {
+ width: 100%;
+ border-right: none; }
+ .navbar nav a:before {
+ margin-right: 5px; }
+ .navbar nav .dashboard a:before {
+ font-family: "Icons";
+ font-weight: normal;
+ font-style: normal;
+ vertical-align: -7%;
+ text-transform: none;
+ speak: none;
+ line-height: 1;
+ -webkit-font-smoothing: antialiased;
+ content: "\e025";
+ vertical-align: -10%; }
+ .navbar nav .dashboard a:hover {
+ text-decoration: none; }
+ .navbar nav .content a:before {
+ font-family: "Icons";
+ font-weight: normal;
+ font-style: normal;
+ vertical-align: -7%;
+ text-transform: none;
+ speak: none;
+ line-height: 1;
+ -webkit-font-smoothing: antialiased;
+ content: "\e02d"; }
+ .navbar nav .content a:hover {
+ text-decoration: none; }
+ .navbar nav .editor a:before {
+ font-family: "Icons";
+ font-weight: normal;
+ font-style: normal;
+ vertical-align: -7%;
+ text-transform: none;
+ speak: none;
+ line-height: 1;
+ -webkit-font-smoothing: antialiased;
+ content: "\e032"; }
+ .navbar nav .editor a:hover {
+ text-decoration: none; }
+ .navbar nav .settings a:before {
+ font-family: "Icons";
+ font-weight: normal;
+ font-style: normal;
+ vertical-align: -7%;
+ text-transform: none;
+ speak: none;
+ line-height: 1;
+ -webkit-font-smoothing: antialiased;
+ content: "\e029"; }
+ .navbar nav .settings a:hover {
+ text-decoration: none; }
+ .navbar .subnav {
+ position: relative; }
+ .navbar .subnav > a:after {
+ font-family: "Icons";
+ font-weight: normal;
+ font-style: normal;
+ vertical-align: -7%;
+ text-transform: none;
+ speak: none;
+ line-height: 1;
+ -webkit-font-smoothing: antialiased;
+ content: "\e001";
+ font-size: 8px;
+ margin-left: 8px; }
+ .navbar .subnav > a:hover {
+ text-decoration: none; }
+ .navbar .subnav > a.active {
+ color: #e2edf2;
+ background: #2e3133;
+ -webkit-transition: none;
+ -moz-transition: none;
+ transition: none;
+ box-shadow: none; }
+ .navbar .subnav ul {
+ display: none;
+ padding: 7px 0;
+ border-left: none;
+ position: absolute;
+ top: 40px;
+ left: -1px;
+ z-index: 800;
+ background: #2e3133;
+ box-shadow: rgba(0, 0, 0, 0.2) 0 4px 6px; }
+ .navbar .subnav li a {
+ color: #e2edf2; }
+ .navbar .subnav li a:hover {
+ background: #0c0d0d;
+ -webkit-transition: none;
+ -moz-transition: none;
+ transition: none;
+ box-shadow: none; }
+ .navbar .subnav li a:before {
+ margin-right: 1em; }
+ .navbar .subnav .divider {
+ height: 1px;
+ margin: 7px 0;
+ overflow: hidden;
+ background: #35393b; }
+
+.usermenu.subnav {
+ position: absolute;
+ top: 0;
+ right: 0;
+ border-right: none;
+ border-left: #35393b 1px solid; }
+ .usermenu.subnav > a {
+ padding-left: 43px; }
+ .usermenu.subnav .avatar {
+ height: 18px;
+ width: 18px;
+ border-radius: 50px;
+ position: absolute;
+ top: 11px;
+ left: 15px; }
+ .usermenu.subnav > ul {
+ right: 0;
+ left: auto; }
+ .usermenu.subnav .usermenu-profile a:before {
+ font-family: "Icons";
+ font-weight: normal;
+ font-style: normal;
+ vertical-align: -7%;
+ text-transform: none;
+ speak: none;
+ line-height: 1;
+ -webkit-font-smoothing: antialiased;
+ content: "\e02e"; }
+ .usermenu.subnav .usermenu-profile a:hover {
+ text-decoration: none; }
+ .usermenu.subnav .usermenu-help a:before {
+ font-family: "Icons";
+ font-weight: normal;
+ font-style: normal;
+ vertical-align: -7%;
+ text-transform: none;
+ speak: none;
+ line-height: 1;
+ -webkit-font-smoothing: antialiased;
+ content: "\e02f";
+ font-size: 1.1em; }
+ .usermenu.subnav .usermenu-help a:hover {
+ text-decoration: none; }
+ .usermenu.subnav .usermenu-shortcuts a:before {
+ font-family: "Icons";
+ font-weight: normal;
+ font-style: normal;
+ vertical-align: -7%;
+ text-transform: none;
+ speak: none;
+ line-height: 1;
+ -webkit-font-smoothing: antialiased;
+ content: "\e00d"; }
+ .usermenu.subnav .usermenu-shortcuts a:hover {
+ text-decoration: none; }
+ .usermenu.subnav .usermenu-signout a:before {
+ font-family: "Icons";
+ font-weight: normal;
+ font-style: normal;
+ vertical-align: -7%;
+ text-transform: none;
+ speak: none;
+ line-height: 1;
+ -webkit-font-smoothing: antialiased;
+ content: "\e02b"; }
+ .usermenu.subnav .usermenu-signout a:hover {
+ text-decoration: none; }
+
+/* ==========================================================================
+ Mobile Navigation
+ ========================================================================== */
+@media (max-width: 650px) {
+ #global-header .ghost-logo {
+ height: 40px;
+ width: 40px;
+ text-align: center;
+ padding: 12px 0;
+ -webkit-transition: margin-left 0.3s ease 0s;
+ -moz-transition: margin-left 0.3s ease 0s;
+ transition: margin-left 0.3s ease 0s; }
+ #global-header .ghost-logo:before {
+ font-family: "Icons";
+ font-weight: normal;
+ font-style: normal;
+ vertical-align: -7%;
+ text-transform: none;
+ speak: none;
+ line-height: 1;
+ -webkit-font-smoothing: antialiased;
+ content: "\e005";
+ font-size: 14px; }
+ #global-header .ghost-logo:hover {
+ text-decoration: none; }
+ .off-canvas #global-header .ghost-logo {
+ margin-left: 280px;
+ -webkit-transition: margin-left 0.3s ease 0.1s;
+ -moz-transition: margin-left 0.3s ease 0.1s;
+ transition: margin-left 0.3s ease 0.1s; }
+ #global-header ul {
+ position: fixed;
+ overflow: auto;
+ top: 0;
+ right: auto;
+ bottom: 0;
+ left: -280px;
+ z-index: 980;
+ width: 280px;
+ padding-top: 40px;
+ font-weight: normal;
+ background: #242628;
+ border-left: none;
+ -webkit-transition: left 0.3s ease 0.2s;
+ -moz-transition: left 0.3s ease 0.2s;
+ transition: left 0.3s ease 0.2s; }
+ .off-canvas #global-header ul {
+ left: 0;
+ -webkit-transition: left 0.3s ease 0s;
+ -moz-transition: left 0.3s ease 0s;
+ transition: left 0.3s ease 0s; }
+ #global-header li {
+ float: none;
+ border-right: none;
+ border-bottom: #35393b 1px solid; }
+ #global-header li a:hover, #global-header li.active a {
+ box-shadow: none; }
+ #global-header li.active a:after {
+ display: none; }
+ #global-header li a:before {
+ margin-right: 1em; }
+ #global-header li ul {
+ position: static;
+ min-width: 0;
+ background: #242628; }
+ #global-header li li {
+ width: auto; }
+ #global-header .usermenu {
+ position: fixed;
+ top: 0;
+ right: auto;
+ bottom: auto;
+ left: -280px;
+ height: 40px;
+ z-index: 990;
+ width: 279px;
+ border-left: none;
+ border-right: #242728 1px solid;
+ border-bottom: #292c2e 1px solid;
+ background-color: #1d1e20;
+ background-image: -webkit-linear-gradient(bottom, #111213, #1d1e20);
+ background-image: -moz-linear-gradient(bottom, #111213, #1d1e20);
+ background-image: -ms-linear-gradient(bottom, #111213, #1d1e20);
+ background-image: linear, to top, #111213, #1d1e20;
+ -webkit-transition: left 0.3s ease 0.2s;
+ -moz-transition: left 0.3s ease 0.2s;
+ transition: left 0.3s ease 0.2s; }
+ .off-canvas #global-header .usermenu {
+ left: 0;
+ -webkit-transition: left 0.3s ease 0s;
+ -moz-transition: left 0.3s ease 0s;
+ transition: left 0.3s ease 0s; }
+ #global-header .usermenu > a:hover {
+ background: inherit; }
+ #global-header .usermenu > a.active {
+ background: #2e3133; }
+ #global-header .usermenu > ul {
+ padding: 0;
+ box-shadow: none;
+ width: 100%;
+ font-weight: 300; }
+ #global-header .usermenu .open {
+ box-shadow: rgba(0, 0, 0, 0.4) 0 10px 20px; }
+ #global-header .usermenu li {
+ border-bottom: #2e3133 1px solid; }
+ #global-header .usermenu li a {
+ background: #2e3133; }
+ #global-header .usermenu li a:hover {
+ background: #222426; }
+ #global-header .usermenu li a:before {
+ margin-right: 1em; }
+ #global-header .usermenu .divider {
+ display: none; } }
+
+/* ==========================================================================
+ Drop-down / Pop-up Menu
+ ========================================================================== */
+.dropdown:after {
+ font-family: "Icons";
+ font-weight: normal;
+ font-style: normal;
+ vertical-align: -7%;
+ text-transform: none;
+ speak: none;
+ line-height: 1;
+ -webkit-font-smoothing: antialiased;
+ content: "\e001";
+ font-size: 8px;
+ padding-left: 6px;
+ vertical-align: 0; }
+.dropdown:hover {
+ text-decoration: none; }
+.dropdown.active {
+ color: #242628; }
+ .dropdown.active:after {
+ font-family: "Icons";
+ font-weight: normal;
+ font-style: normal;
+ vertical-align: -7%;
+ text-transform: none;
+ speak: none;
+ line-height: 1;
+ -webkit-font-smoothing: antialiased;
+ content: "\e001";
+ font-size: 8px; }
+ .dropdown.active:hover {
+ text-decoration: none; }
+
+.menu, .menu-left, .menu-right, .menu-drop, .menu-drop-left, .menu-drop-right, #publish-bar .splitbutton-save .editor-options,
+#publish-bar .splitbutton-delete .editor-options, .suggestions {
+ display: inline-block;
+ position: absolute;
+ z-index: 960;
+ padding: 6px 0;
+ border: none;
+ list-style: none;
+ color: #e2edf2;
+ background: #242628;
+ border-radius: 3px;
+ box-shadow: rgba(0, 0, 0, 0.5) 0 1px 15px; }
+ .menu:before, .menu-left:before, .menu-right:before, .menu-drop:before, .menu-drop-left:before, .menu-drop-right:before, #publish-bar .splitbutton-save .editor-options:before,
+ #publish-bar .splitbutton-delete .editor-options:before, .suggestions:before {
+ content: "";
+ position: absolute;
+ bottom: -10px;
+ left: 50%;
+ margin-left: -10px;
+ border-width: 10px 10px 0 10px;
+ border-style: solid;
+ border-color: #242628 transparent;
+ display: block;
+ width: 0; }
+ .menu li, .menu-left li, .menu-right li, .menu-drop li, .menu-drop-left li, .menu-drop-right li, #publish-bar .splitbutton-save .editor-options li,
+ #publish-bar .splitbutton-delete .editor-options li, .suggestions li {
+ overflow: hidden; }
+ .menu a, .menu-left a, .menu-right a, .menu-drop a, .menu-drop-left a, .menu-drop-right a, #publish-bar .splitbutton-save .editor-options a,
+ #publish-bar .splitbutton-delete .editor-options a, .suggestions a, .menu p, .menu-left p, .menu-right p, .menu-drop p, .menu-drop-left p, .menu-drop-right p, #publish-bar .splitbutton-save .editor-options p,
+ #publish-bar .splitbutton-delete .editor-options p, .suggestions p {
+ -webkit-box-sizing: border-box;
+ -moz-box-sizing: border-box;
+ box-sizing: border-box;
+ display: block;
+ position: relative;
+ padding: 10px 25px 10px 35px;
+ border: none;
+ -webkit-box-sizing: border-box;
+ -moz-box-sizing: border-box;
+ box-sizing: border-box;
+ color: #e2edf2 !important;
+ text-transform: none;
+ text-decoration: none; }
+ .menu a:hover, .menu-left a:hover, .menu-right a:hover, .menu-drop a:hover, .menu-drop-left a:hover, .menu-drop-right a:hover, #publish-bar .splitbutton-save .editor-options a:hover,
+ #publish-bar .splitbutton-delete .editor-options a:hover, .suggestions a:hover, .menu p:hover, .menu-left p:hover, .menu-right p:hover, .menu-drop p:hover, .menu-drop-left p:hover, .menu-drop-right p:hover, #publish-bar .splitbutton-save .editor-options p:hover,
+ #publish-bar .splitbutton-delete .editor-options p:hover, .suggestions p:hover {
+ background: #5ba4e5;
+ box-shadow: rgba(255, 255, 255, 0.2) 0 1px 0 inset, rgba(0, 0, 0, 0.5) 0 1px 5px; }
+ .menu .active a:before, .menu-left .active a:before, .menu-right .active a:before, .menu-drop .active a:before, .menu-drop-left .active a:before, .menu-drop-right .active a:before, #publish-bar .splitbutton-save .editor-options .active a:before,
+ #publish-bar .splitbutton-delete .editor-options .active a:before, .suggestions .active a:before {
+ font-family: "Icons";
+ font-weight: normal;
+ font-style: normal;
+ vertical-align: -7%;
+ text-transform: none;
+ speak: none;
+ line-height: 1;
+ -webkit-font-smoothing: antialiased;
+ content: "\e033";
+ position: absolute;
+ top: 14px;
+ left: 11px; }
+ .menu .active a:hover, .menu-left .active a:hover, .menu-right .active a:hover, .menu-drop .active a:hover, .menu-drop-left .active a:hover, .menu-drop-right .active a:hover, #publish-bar .splitbutton-save .editor-options .active a:hover,
+ #publish-bar .splitbutton-delete .editor-options .active a:hover, .suggestions .active a:hover {
+ text-decoration: none; }
+
+.menu-drop:before, .menu-drop-left:before, .menu-drop-right:before {
+ top: -10px;
+ bottom: auto;
+ border-width: 0 10px 10px 10px; }
+
+.menu-left:before, .menu-drop-left:before {
+ left: 10px;
+ margin-left: 0; }
+
+.menu-right:before, .menu-drop-right:before, #publish-bar .splitbutton-save .editor-options:before,
+#publish-bar .splitbutton-delete .editor-options:before {
+ left: auto;
+ right: 10px;
+ margin-left: 0; }
+
+/* ==========================================================================
+ Post Settings
+ ========================================================================== */
+.post-settings {
+ -webkit-box-sizing: border-box;
+ -moz-box-sizing: border-box;
+ box-sizing: border-box;
+ display: inline-block;
+ padding: 0 10px;
+ color: #7d878a;
+ -webkit-transition: all 0.15s ease-out 0;
+ -moz-transition: all 0.15s ease-out 0;
+ transition: all 0.15s ease-out 0;
+ position: relative;
+ top: 1px; }
+ .post-settings:before {
+ font-family: "Icons";
+ font-weight: normal;
+ font-style: normal;
+ vertical-align: -7%;
+ text-transform: none;
+ speak: none;
+ line-height: 1;
+ -webkit-font-smoothing: antialiased;
+ content: "\e006";
+ font-size: 14px; }
+ .post-settings:hover {
+ text-decoration: none; }
+ .post-settings:hover, .post-settings.active {
+ color: #242628; }
+
+.post-settings-menu {
+ padding-top: 0;
+ text-transform: none; }
+ .post-settings-menu table {
+ margin: 0; }
+ .post-settings-menu td {
+ padding: 0;
+ border-top: none;
+ border-bottom: #414648 1px solid; }
+ .post-settings-menu .post-setting-label {
+ padding: 8px 10px 8px 15px;
+ border-right: #414648 1px solid;
+ text-align: right; }
+ .post-settings-menu form label, form .post-settings-menu label,
+ .post-settings-menu form .label,
+ form .post-settings-menu .label {
+ position: static;
+ width: auto;
+ font-weight: normal;
+ color: #7d878a;
+ white-space: nowrap; }
+ .post-settings-menu input {
+ width: 200px;
+ margin: 0; }
+ @media (max-width: 550px) {
+ .post-settings-menu input {
+ width: 200px; } }
+ .post-settings-menu input[type="text"] {
+ border: none;
+ padding: 8px 0 8px 10px;
+ color: #e2edf2;
+ border-radius: 0;
+ background: transparent; }
+ .post-settings-menu input[type="text"]:focus {
+ background: #35393b;
+ border: none; }
+ .post-settings-menu .post-setting-item {
+ padding: 5px 0 0 10px; }
+ .post-settings-menu .checkbox {
+ position: relative;
+ margin-top: 0;
+ width: 18px;
+ border: #4d5356 1px solid;
+ background: #35393b; }
+ .post-settings-menu .delete {
+ display: block;
+ padding: 10px 15px; }
+ .post-settings-menu .delete:before {
+ font-family: "Icons";
+ font-weight: normal;
+ font-style: normal;
+ vertical-align: -7%;
+ text-transform: none;
+ speak: none;
+ line-height: 1;
+ -webkit-font-smoothing: antialiased;
+ content: "\e023";
+ position: relative;
+ top: -1px;
+ margin-right: 10px; }
+ .post-settings-menu .delete:hover {
+ text-decoration: none; }
+ .post-settings-menu .delete:hover {
+ background: #e25440; }
+
+/* ==========================================================================
+ Notifications
+ ========================================================================== */
+@media (min-width: 401px), (min-width: 401px) {
+ .notifications {
+ position: absolute;
+ bottom: 0;
+ left: 0;
+ z-index: 980;
+ width: 300px; } }
+@media (max-width: 400px) {
+ .notifications {
+ position: fixed;
+ top: 0;
+ left: 0;
+ right: 0;
+ z-index: 9999; } }
+
+.js-bb-notification {
+ -webkit-transform: translateZ(0);
+ -moz-transform: translateZ(0);
+ -ms-transform: translateZ(0);
+ -o-transform: translateZ(0);
+ transform: translateZ(0); }
+
+.notification-success, .notification-error, .notification-warn, .notification-info, .notification {
+ -webkit-box-sizing: border-box;
+ -moz-box-sizing: border-box;
+ box-sizing: border-box;
+ width: 100%;
+ min-height: 40px;
+ padding: 10px 43px 10px 57px;
+ margin: 0 0 15px 0;
+ color: rgba(255, 255, 255, 0.9);
+ background: #5ba4e5;
+ position: relative;
+ box-shadow: rgba(0, 0, 0, 0.05) 0 1px 5px;
+ -webkit-transform: translateZ(0);
+ -moz-transform: translateZ(0);
+ -ms-transform: translateZ(0);
+ -o-transform: translateZ(0);
+ transform: translateZ(0); }
+ .notification-success:before, .notification-error:before, .notification-warn:before, .notification-info:before, .notification:before {
+ font-family: "Icons";
+ font-weight: normal;
+ font-style: normal;
+ vertical-align: -7%;
+ text-transform: none;
+ speak: none;
+ line-height: 1;
+ -webkit-font-smoothing: antialiased;
+ content: "\e031";
+ position: absolute;
+ top: 0;
+ left: 0;
+ -webkit-box-sizing: border-box;
+ -moz-box-sizing: border-box;
+ box-sizing: border-box;
+ height: 100%;
+ width: 44px;
+ padding: 14px 15px;
+ text-align: center;
+ color: rgba(255, 255, 255, 0.8);
+ background: rgba(0, 0, 0, 0.1); }
+ .notification-success:hover, .notification-error:hover, .notification-warn:hover, .notification-info:hover, .notification:hover {
+ text-decoration: none; }
+ @media (max-width: 400px) {
+ .notification-success, .notification-error, .notification-warn, .notification-info, .notification {
+ margin-bottom: 1px; } }
+ .notification-success .close, .notification-error .close, .notification-warn .close, .notification-info .close, .notification .close {
+ display: inline-block;
+ color: rgba(255, 255, 255, 0.6);
+ cursor: pointer; }
+ .notification-success .close:after, .notification-error .close:after, .notification-warn .close:after, .notification-info .close:after, .notification .close:after {
+ font-family: "Icons";
+ font-weight: normal;
+ font-style: normal;
+ vertical-align: -7%;
+ text-transform: none;
+ speak: none;
+ line-height: 1;
+ -webkit-font-smoothing: antialiased;
+ content: "\e01c";
+ padding: 6px;
+ position: absolute;
+ top: 8px;
+ right: 9px; }
+ .notification-success .close:hover, .notification-error .close:hover, .notification-warn .close:hover, .notification-info .close:hover, .notification .close:hover {
+ text-decoration: none; }
+ .notification-success .close:hover, .notification-error .close:hover, .notification-warn .close:hover, .notification-info .close:hover, .notification .close:hover {
+ color: #fff; }
+ .notification-success a, .notification-error a, .notification-warn a, .notification-info a, .notification a {
+ color: inherit;
+ text-decoration: underline; }
+
+.notification-success {
+ background: #9fbb58; }
+ .notification-success:before {
+ font-family: "Icons";
+ font-weight: normal;
+ font-style: normal;
+ vertical-align: -7%;
+ text-transform: none;
+ speak: none;
+ line-height: 1;
+ -webkit-font-smoothing: antialiased;
+ content: "\e030"; }
+ .notification-success:hover {
+ text-decoration: none; }
+ .notification-success.notification-passive {
+ -webkit-animation: fade-out 1s linear;
+ -moz-animation: fade-out 1s linear;
+ animation: fade-out 1s linear;
+ -webkit-animation-delay: 3s;
+ -moz-animation-delay: 3s;
+ animation-delay: 3s;
+ -webkit-animation-iteration-count: 1;
+ -moz-animation-iteration-count: 1;
+ animation-iteration-count: 1;
+ -webkit-animation-fill-mode: forwards;
+ -moz-animation-fill-mode: forwards;
+ animation-fill-mode: forwards; }
+
+.notification-error {
+ background: #e25440; }
+ .notification-error:before {
+ font-family: "Icons";
+ font-weight: normal;
+ font-style: normal;
+ vertical-align: -7%;
+ text-transform: none;
+ speak: none;
+ line-height: 1;
+ -webkit-font-smoothing: antialiased;
+ content: "\e01a"; }
+ .notification-error:hover {
+ text-decoration: none; }
+
+.notification-warn {
+ background: #f2a925; }
+ .notification-warn:before {
+ font-family: "Icons";
+ font-weight: normal;
+ font-style: normal;
+ vertical-align: -7%;
+ text-transform: none;
+ speak: none;
+ line-height: 1;
+ -webkit-font-smoothing: antialiased;
+ content: "\e014"; }
+ .notification-warn:hover {
+ text-decoration: none; }
+
+.notification-info {
+ background: #5ba4e5; }
+ .notification-info:before {
+ font-family: "Icons";
+ font-weight: normal;
+ font-style: normal;
+ vertical-align: -7%;
+ text-transform: none;
+ speak: none;
+ line-height: 1;
+ -webkit-font-smoothing: antialiased;
+ content: "\e014"; }
+ .notification-info:hover {
+ text-decoration: none; }
+
+.update-available main {
+ bottom: 56px; }
+
+/* ==========================================================================
+ Modals
+ ========================================================================== */
+#modal-container {
+ -webkit-box-sizing: border-box;
+ -moz-box-sizing: border-box;
+ box-sizing: border-box;
+ display: none;
+ position: fixed;
+ top: 0;
+ bottom: 0;
+ left: 0;
+ right: 0;
+ overflow-x: auto;
+ overflow-y: scroll;
+ z-index: 1040;
+ pointer-events: none;
+ -webkit-transition: all 0.15s linear 0s;
+ -moz-transition: all 0.15s linear 0s;
+ transition: all 0.15s linear 0s;
+ -webkit-transform: translateZ(0);
+ -moz-transform: translateZ(0);
+ -ms-transform: translateZ(0);
+ -o-transform: translateZ(0);
+ transform: translateZ(0); }
+
+.fade {
+ opacity: 0;
+ -webkit-transition: opacity 0.2s linear 0s;
+ -moz-transition: opacity 0.2s linear 0s;
+ transition: opacity 0.2s linear 0s;
+ -webkit-transform: translateZ(0);
+ -moz-transform: translateZ(0);
+ -ms-transform: translateZ(0);
+ -o-transform: translateZ(0);
+ transform: translateZ(0); }
+ .fade.in {
+ opacity: 1; }
+
+.modal-background {
+ display: none;
+ position: fixed;
+ top: 0;
+ right: 0;
+ bottom: 0;
+ left: 0;
+ background: rgba(0, 0, 0, 0.4);
+ z-index: 1030; }
+
+.modal-info, .modal-action, .modal {
+ -webkit-box-sizing: border-box;
+ -moz-box-sizing: border-box;
+ box-sizing: border-box;
+ left: 50%;
+ right: auto;
+ width: 450px;
+ margin-left: auto;
+ margin-right: auto;
+ padding-top: 30px;
+ padding-bottom: 30px;
+ z-index: 1050;
+ pointer-events: auto; }
+ @media (max-width: 800px) {
+ .modal-info, .modal-action, .modal {
+ width: auto;
+ padding: 10px; } }
+ .modal-info button, .modal-action button, .modal button {
+ min-width: 100px; }
+ @media (max-width: 800px) {
+ .modal-info, .modal-action, .modal {
+ width: 100%;
+ margin-left: 0; } }
+ .modal-info .image-uploader, .modal-action .image-uploader,
+ .modal-info .pre-image-uploader,
+ .modal-action .pre-image-uploader, .modal .image-uploader,
+ .modal .pre-image-uploader {
+ margin: 0; }
+
+.modal-action {
+ padding: 60px 0 30px; }
+ @media (max-width: 800px) {
+ .modal-action {
+ padding: 30px 0; } }
+
+.modal-content {
+ -webkit-box-sizing: padding-box;
+ -moz-box-sizing: padding-box;
+ box-sizing: padding-box;
+ position: relative;
+ padding: 20px;
+ background-clip: padding-box;
+ background-color: #FFFFFF;
+ border-radius: 2px;
+ box-shadow: rgba(0, 0, 0, 0.2) 0 0 0 6px; }
+ .modal-content .close {
+ -webkit-box-sizing: border-box;
+ -moz-box-sizing: border-box;
+ box-sizing: border-box;
+ position: absolute;
+ top: 15px;
+ right: 15px;
+ width: 16px;
+ min-height: 16px;
+ padding: 0;
+ margin: 0;
+ border: none;
+ z-index: 9999;
+ -webkit-transition: opacity 0.3s linear;
+ -moz-transition: opacity 0.3s linear;
+ transition: opacity 0.3s linear; }
+ .modal-content .close:before {
+ font-family: "Icons";
+ font-weight: normal;
+ font-style: normal;
+ vertical-align: -7%;
+ text-transform: none;
+ speak: none;
+ line-height: 1;
+ -webkit-font-smoothing: antialiased;
+ content: "\e034";
+ font-size: 1em;
+ color: #7d878a; }
+ .modal-content .close:hover {
+ text-decoration: none; }
+ .modal-content .close:hover {
+ color: #242628; }
+
+.modal-header {
+ position: relative;
+ padding: 20px;
+ border-bottom: 1px solid #edece4; }
+ .modal-header h1 {
+ display: inline-block;
+ margin: 0;
+ font-size: 1.5em;
+ font-weight: bold; }
+
+.modal-body {
+ position: relative;
+ min-height: 100px;
+ overflow-y: auto; }
+
+.modal-footer {
+ margin-top: 20px; }
+
+.modal-style-wide {
+ width: 550px; }
+ @media (max-width: 800px) {
+ .modal-style-wide {
+ width: 100%; } }
+
+.modal-style-centered {
+ text-align: center; }
+
+/* ==========================================================================
+ Main Elements
+ ========================================================================== */
+main {
+ position: absolute;
+ top: 55px;
+ right: 15px;
+ bottom: 0;
+ left: 15px;
+ padding: 0; }
+ @media (max-width: 400px) {
+ main {
+ top: 40px;
+ left: 0;
+ right: 0; } }
+
+/* ==========================================================================
+ Floating Headers
+ ========================================================================== */
+.floatingheader {
+ -webkit-box-sizing: border-box;
+ -moz-box-sizing: border-box;
+ box-sizing: border-box;
+ position: absolute;
+ top: 0;
+ left: 0;
+ right: 0;
+ z-index: 400;
+ height: 40px;
+ padding: 10px 15px;
+ text-transform: uppercase;
+ color: #aaa9a2;
+ background-color: transparent;
+ background-image: -webkit-gradient(linear, left top, left bottom, color-stop(0%, white), color-stop(25%, white), color-stop(100%, rgba(255, 255, 255, 0.9)));
+ background-image: -webkit-linear-gradient(top, white 0%, white 25%, rgba(255, 255, 255, 0.9) 100%);
+ background-image: linear-gradient(to bottom,white 0%, white 25%, rgba(255, 255, 255, 0.9) 100%); }
+ .floatingheader button, .floatingheader .button {
+ display: inline-block;
+ font-size: 10px;
+ min-height: 20px;
+ height: 20px;
+ padding: 3px 4px;
+ vertical-align: top; }
+ .floatingheader button.button-back, .floatingheader .button.button-back {
+ position: relative;
+ top: -2px;
+ left: 3px;
+ display: none;
+ padding: 0 6px 0 3px; }
+ .floatingheader button.button-back:active, .floatingheader .button.button-back:active {
+ box-shadow: none; }
+ .floatingheader button.button-back:before, .floatingheader .button.button-back:before {
+ left: -8px;
+ border-width: 10px 8px 10px 0; }
+ @media (max-width: 800px) {
+ .floatingheader button.button-back, .floatingheader .button.button-back {
+ display: inline-block; } }
+ .floatingheader small {
+ font-size: 0.85em; }
+ .floatingheader a {
+ color: #aaa9a2; }
+ .floatingheader a:hover {
+ color: #242628; }
+
+.scrolling .floatingheader {
+ box-shadow: rgba(0, 0, 0, 0.02) 0 1px 2px, rgba(255, 255, 255, 0.5) 0 -1px 0 inset; }
+ .scrolling .floatingheader::before {
+ content: "";
+ height: 40px;
+ width: 80%;
+ position: absolute;
+ bottom: 0;
+ left: 50%;
+ margin-left: -40%;
+ box-shadow: rgba(0, 0, 0, 0.02) 0 2px 2px; }
+ .scrolling .floatingheader::after {
+ content: "";
+ height: 40px;
+ width: 30%;
+ position: absolute;
+ bottom: 0;
+ left: 50%;
+ margin-left: -15%;
+ box-shadow: rgba(0, 0, 0, 0.02) 0 3px 3px; }
+
+/* ==========================================================================
+ Image Uploader
+ ========================================================================== */
+.image-uploader {
+ -webkit-box-sizing: border-box;
+ -moz-box-sizing: border-box;
+ box-sizing: border-box;
+ margin: 1.6em 0;
+ position: relative;
+ overflow: hidden;
+ padding: 55px 60px;
+ border: #edece4 3px dashed;
+ width: 100%;
+ height: auto;
+ text-align: center;
+ color: #aaa9a2;
+ background: #F9F8F5; }
+ .image-uploader a {
+ color: #aaa9a2;
+ text-decoration: none; }
+ .image-uploader a:hover {
+ color: #242628; }
+ .image-uploader .description {
+ margin-top: 10px; }
+ .image-uploader .media:before {
+ font-family: "Icons";
+ font-weight: normal;
+ font-style: normal;
+ vertical-align: -7%;
+ text-transform: none;
+ speak: none;
+ line-height: 1;
+ -webkit-font-smoothing: antialiased;
+ content: "\e011";
+ font-size: 60px;
+ color: #e7e6db;
+ display: inline-block;
+ vertical-align: initial;
+ -webkit-transition: transform 1s ease;
+ -moz-transition: transform 1s ease;
+ transition: transform 1s ease; }
+ .image-uploader .media:hover {
+ text-decoration: none; }
+ .image-uploader .image-url,
+ .image-uploader .image-upload {
+ line-height: 12px;
+ padding: 10px;
+ display: block;
+ position: absolute;
+ bottom: 0;
+ left: 0;
+ color: #aaa9a2;
+ text-decoration: none;
+ -webkit-user-select: none;
+ -moz-user-select: none;
+ -ms-user-select: none;
+ user-select: none; }
+ .image-uploader .image-url:hover,
+ .image-uploader .image-upload:hover {
+ cursor: pointer; }
+ .image-uploader .image-webcam:before {
+ font-family: "Icons";
+ font-weight: normal;
+ font-style: normal;
+ vertical-align: -7%;
+ text-transform: none;
+ speak: none;
+ line-height: 1;
+ -webkit-font-smoothing: antialiased;
+ content: "\e036";
+ font-size: 12px; }
+ .image-uploader .image-webcam:hover {
+ text-decoration: none; }
+ .image-uploader .image-url:before {
+ font-family: "Icons";
+ font-weight: normal;
+ font-style: normal;
+ vertical-align: -7%;
+ text-transform: none;
+ speak: none;
+ line-height: 1;
+ -webkit-font-smoothing: antialiased;
+ content: "\e035";
+ font-size: 12px; }
+ .image-uploader .image-url:hover {
+ text-decoration: none; }
+ .image-uploader .image-upload:before {
+ font-family: "Icons";
+ font-weight: normal;
+ font-style: normal;
+ vertical-align: -7%;
+ text-transform: none;
+ speak: none;
+ line-height: 1;
+ -webkit-font-smoothing: antialiased;
+ content: "\e011";
+ font-size: 12px; }
+ .image-uploader .image-upload:hover {
+ text-decoration: none; }
+ .image-uploader .button-add {
+ display: inline-block;
+ position: relative;
+ z-index: 700;
+ color: #fff;
+ padding-left: 5px; }
+ .image-uploader .button-save {
+ margin: 0 0 0 10px; }
+ .image-uploader input.main {
+ position: absolute;
+ right: 0;
+ margin: 0;
+ opacity: 0;
+ -webkit-transform-origin: right;
+ -moz-transform-origin: right;
+ -ms-transform-origin: right;
+ -o-transform-origin: right;
+ transform-origin: right;
+ -webkit-transform: scale(14);
+ -moz-transform: scale(14);
+ -ms-transform: scale(14);
+ -o-transform: scale(14);
+ transform: scale(14);
+ font-size: 23px;
+ direction: ltr;
+ cursor: pointer; }
+ .image-uploader input.main.right {
+ right: 9999px;
+ height: 0; }
+ .image-uploader input.url {
+ font: -webkit-small-control;
+ box-sizing: border-box;
+ width: 276px;
+ vertical-align: middle;
+ padding: 9px 7px;
+ margin: 10px 0;
+ outline: 0;
+ font-size: 1.1em;
+ background: #fff;
+ border: #e3e1d5 1px solid;
+ border-radius: 4px;
+ -webkit-transition: all 0.15s ease-in-out;
+ -moz-transition: all 0.15s ease-in-out; }
+ .image-uploader .progress {
+ position: relative;
+ margin: -19px 0 44px 0;
+ display: block;
+ overflow: hidden;
+ background-color: whitesmoke;
+ background-image: -webkit-gradient(linear, left top, left bottom, color-stop(0%, whitesmoke), color-stop(100%, #f9f9f9));
+ background-image: -webkit-linear-gradient(top, whitesmoke, #f9f9f9);
+ background-image: linear-gradient(to bottom,whitesmoke, #f9f9f9);
+ border-radius: 12px;
+ box-shadow: rgba(0, 0, 0, 0.1) 0 1px 2px inset; }
+ .image-uploader .fileupload-loading {
+ display: block;
+ top: 50%;
+ width: 35px;
+ height: 28px;
+ margin: -28px auto 0;
+ background-size: contain; }
+ .image-uploader .failed {
+ position: relative;
+ top: -40px;
+ font-size: 16px; }
+ .image-uploader .bar {
+ height: 12px;
+ background: #5ba4e5; }
+ .image-uploader .bar.fail {
+ background: #e25440; }
+
+.pre-image-uploader {
+ -webkit-box-sizing: border-box;
+ -moz-box-sizing: border-box;
+ box-sizing: border-box;
+ margin: 1.6em 0;
+ position: relative;
+ overflow: hidden;
+ height: auto;
+ color: #aaa9a2;
+ background: rgba(0, 0, 0, 0.1);
+ border-radius: 2px;
+ min-height: 46px; }
+ .pre-image-uploader input {
+ position: absolute;
+ left: 9999px;
+ opacity: 0; }
+ .pre-image-uploader a {
+ z-index: 10000;
+ color: #aaa9a2;
+ text-decoration: none; }
+ .pre-image-uploader a:hover {
+ color: #242628; }
+ .pre-image-uploader img {
+ display: block;
+ max-width: 100%;
+ margin: 0 auto;
+ line-height: 0; }
+ .pre-image-uploader .image-cancel {
+ position: absolute;
+ top: 10px;
+ right: 10px;
+ padding: 8px;
+ z-index: 300;
+ color: #fff;
+ text-decoration: none;
+ line-height: 0;
+ border-radius: 2px;
+ background: rgba(0, 0, 0, 0.6);
+ box-shadow: rgba(255, 255, 255, 0.2) 0 0 0 1px; }
+ .pre-image-uploader .image-cancel:before {
+ font-family: "Icons";
+ font-weight: normal;
+ font-style: normal;
+ vertical-align: -7%;
+ text-transform: none;
+ speak: none;
+ line-height: 1;
+ -webkit-font-smoothing: antialiased;
+ content: "\e023";
+ font-size: 11px; }
+ .pre-image-uploader .image-cancel:hover {
+ text-decoration: none; }
+ .pre-image-uploader .image-cancel:hover {
+ color: #fff;
+ cursor: pointer;
+ background: #e25440; }
+
+/* ==========================================================================
+ NProgress
+ ========================================================================== */
+/* Make clicks pass-through */
+#nprogress {
+ pointer-events: none;
+ -webkit-pointer-events: none; }
+
+#nprogress .bar {
+ background: #5ba4e5;
+ position: fixed;
+ z-index: 100;
+ top: 0;
+ left: 0;
+ width: 100%;
+ height: 2px; }
+
+/* Fancy blur effect */
+#nprogress .peg {
+ display: block;
+ position: absolute;
+ right: 0px;
+ width: 100px;
+ height: 100%;
+ box-shadow: 0 0 10px #5ba4e5, 0 0 5px #5ba4e5;
+ opacity: 1.0;
+ -webkit-transform: rotate(3deg) translate(0px, -4px);
+ -moz-transform: rotate(3deg) translate(0px, -4px);
+ -ms-transform: rotate(3deg) translate(0px, -4px);
+ -o-transform: rotate(3deg) translate(0px, -4px);
+ transform: rotate(3deg) translate(0px, -4px); }
+
+/* Remove these to get rid of the spinner */
+#nprogress .spinner {
+ display: block;
+ position: fixed;
+ z-index: 100;
+ top: 15px;
+ right: 15px; }
+
+#nprogress .spinner-icon {
+ width: 14px;
+ height: 14px;
+ border: solid 2px transparent;
+ border-top-color: #5ba4e5;
+ border-left-color: #5ba4e5;
+ border-radius: 10px;
+ -webkit-animation: nprogress-spinner 400ms linear infinite;
+ -moz-animation: nprogress-spinner 400ms linear infinite;
+ -ms-animation: nprogress-spinner 400ms linear infinite;
+ -o-animation: nprogress-spinner 400ms linear infinite;
+ animation: nprogress-spinner 400ms linear infinite; }
+
+@-webkit-keyframes nprogress-spinner {
+ 0% {
+ -webkit-transform: rotate(0deg);
+ transform: rotate(0deg); }
+
+ 100% {
+ -webkit-transform: rotate(360deg);
+ transform: rotate(360deg); } }
+
+@-moz-keyframes nprogress-spinner {
+ 0% {
+ -moz-transform: rotate(0deg);
+ transform: rotate(0deg); }
+
+ 100% {
+ -moz-transform: rotate(360deg);
+ transform: rotate(360deg); } }
+
+@-o-keyframes nprogress-spinner {
+ 0% {
+ -o-transform: rotate(0deg);
+ transform: rotate(0deg); }
+
+ 100% {
+ -o-transform: rotate(360deg);
+ transform: rotate(360deg); } }
+
+@-ms-keyframes nprogress-spinner {
+ 0% {
+ -ms-transform: rotate(0deg);
+ transform: rotate(0deg); }
+
+ 100% {
+ -ms-transform: rotate(360deg);
+ transform: rotate(360deg); } }
+
+@keyframes nprogress-spinner {
+ 0% {
+ transform: rotate(0deg);
+ transform: rotate(0deg); }
+
+ 100% {
+ transform: rotate(360deg);
+ transform: rotate(360deg); } }
+
+/* ==========================================================================
+ Misc
+ ========================================================================== */
+.wrapper {
+ position: relative; }
+
+.palette {
+ margin-bottom: 15px; }
+ .palette section {
+ padding: 5px 10px;
+ width: 90px;
+ height: 90px;
+ float: left;
+ color: rgba(0, 0, 0, 0.5);
+ position: relative;
+ font-size: 12px;
+ font-weight: bold;
+ font-family: Inconsolata, monospace;
+ overflow: hidden;
+ -webkit-transition: all 0.15s ease-in-out;
+ -moz-transition: all 0.15s ease-in-out;
+ transition: all 0.15s ease-in-out; }
+ .palette section:hover {
+ box-shadow: rgba(0, 0, 0, 0.05) 5px 0 0 inset, rgba(0, 0, 0, 0.05) -5px 0 0 inset, rgba(0, 0, 0, 0.05) 0 5px 0 inset, rgba(0, 0, 0, 0.05) 0 -5px 0 inset;
+ -webkit-transition: all 0.15s ease-in-out;
+ -moz-transition: all 0.15s ease-in-out;
+ transition: all 0.15s ease-in-out; }
+ .palette section small {
+ position: absolute;
+ top: 20px;
+ left: 10px;
+ font-size: 11px;
+ font-weight: normal;
+ font-family: "Open Sans", sans-serif;
+ display: block;
+ width: 100px;
+ opacity: 0.6;
+ -webkit-transition: all 0.15s ease-in-out;
+ -moz-transition: all 0.15s ease-in-out;
+ transition: all 0.15s ease-in-out; }
+ .palette section:hover small {
+ opacity: 1;
+ -webkit-transition: all 0.15s ease-in-out;
+ -moz-transition: all 0.15s ease-in-out;
+ transition: all 0.15s ease-in-out; }
+ .palette .brown {
+ background: #aaa9a2; }
+ .palette .midbrown {
+ background: #c0bfb6; }
+ .palette .lightbrown {
+ background: #edece4; }
+ .palette .darkgrey {
+ color: rgba(255, 255, 255, 0.5);
+ background: #242628; }
+ .palette .grey {
+ color: rgba(255, 255, 255, 0.5);
+ background: #35393b; }
+ .palette .midgrey {
+ background: #7d878a; }
+ .palette .lightgrey {
+ background: #e2edf2; }
+ .palette .blue {
+ color: #fff;
+ background: #5ba4e5; }
+ .palette .red {
+ color: #fff;
+ background: #e25440; }
+ .palette .orange {
+ color: #fff;
+ background: #f2a925; }
+
+/* Global elements for the UI, like the header and footer. */
+/*
+ * These are the global generic form styles used throughout the Ghost admin,
+ * but mainly in the settings pages. Don't fuck with them.
+ *
+ * Table of Contents:
+ *
+ * General
+ * Checkboxes
+ * Buttons
+ * Split Buttons
+ *
+ */
+/* =============================================================================
+ General
+ ============================================================================= */
+form label,
+form .label {
+ display: inline-block;
+ position: absolute;
+ top: 0.5em;
+ left: 0;
+ width: 120px;
+ font-weight: bold;
+ color: #aaa9a2;
+ text-align: right; }
+ @media (max-width: 550px) {
+ form label,
+ form .label {
+ display: block;
+ position: relative;
+ top: auto;
+ left: auto;
+ width: auto;
+ margin-bottom: 5px;
+ text-align: left; } }
+
+form p {
+ max-width: 400px;
+ color: #9e9d95;
+ font-size: 1em;
+ margin: 0; }
+
+fieldset {
+ border: none;
+ margin: 0 0 3em 0;
+ padding: 0; }
+
+legend {
+ display: block;
+ width: 100%;
+ margin: 2em 0;
+ border-bottom: #edece4 1px solid;
+ font-size: 1.2em;
+ line-height: 2.0em;
+ color: #aaa9a2; }
+
+input, textarea, select {
+ -webkit-box-sizing: border-box;
+ -moz-box-sizing: border-box;
+ box-sizing: border-box;
+ width: 276px;
+ padding: 5px 7px;
+ margin: 0;
+ outline: 0;
+ font-size: 1.1em;
+ line-height: 1.4em;
+ background: #fff;
+ border: #e3e1d5 1px solid;
+ border-radius: 2px;
+ -webkit-transition: all 0.15s ease-in-out;
+ -moz-transition: all 0.15s ease-in-out;
+ transition: all 0.15s ease-in-out; }
+ @media (max-width: 550px) {
+ input, textarea, select {
+ width: 100%; } }
+
+textarea {
+ width: 100%;
+ max-width: 340px;
+ min-width: 250px;
+ height: auto;
+ min-height: 6.5em; }
+
+input, select, textarea {
+ margin-bottom: 5px; }
+
+input[type="text"]:focus,
+input[type="email"]:focus,
+input[type="search"]:focus,
+input[type="tel"]:focus,
+input[type="url"]:focus,
+input[type="password"]:focus,
+input[type="number"]:focus,
+input[type="date"]:focus,
+input[type="month"]:focus,
+input[type="week"]:focus,
+input[type="time"]:focus,
+input[type="datetime"]:focus,
+input[type="datetime-local"]:focus,
+textarea:focus {
+ border: #aaa9a2 1px solid;
+ background: #fff;
+ outline: none;
+ outline-width: 0; }
+
+select {
+ -webkit-box-sizing: border-box;
+ -moz-box-sizing: border-box;
+ box-sizing: border-box;
+ width: 270px;
+ height: 30px;
+ line-height: 30px; }
+ @media (max-width: 550px) {
+ select {
+ width: 100%; } }
+
+@-moz-document url-prefix() {
+ select {
+ height: auto; } }
+
+.form-group {
+ position: relative;
+ margin: 1.5em 0;
+ padding-left: 140px; }
+ @media (max-width: 550px) {
+ .form-group {
+ padding-left: 0; } }
+
+/* =============================================================================
+ Checkboxes
+ ============================================================================= */
+input[type="checkbox"] {
+ display: none; }
+
+.checkbox {
+ -webkit-box-sizing: border-box;
+ -moz-box-sizing: border-box;
+ box-sizing: border-box;
+ position: relative;
+ top: auto;
+ margin-top: 0.5em;
+ display: inline-block;
+ width: 18px;
+ height: 18px;
+ cursor: pointer;
+ border-radius: 2px;
+ background: #f7f7f3;
+ border: #e3e1d5 1px solid;
+ -webkit-transition: all 0.2s ease;
+ -moz-transition: all 0.2s ease;
+ transition: all 0.2s ease; }
+ .checkbox:after {
+ opacity: 0;
+ content: "";
+ position: absolute;
+ width: 7px;
+ height: 3px;
+ top: 5px;
+ left: 4px;
+ border: 3px solid #fff;
+ border-top: none;
+ border-right: none;
+ -webkit-transform: rotate(-45deg);
+ -moz-transform: rotate(-45deg);
+ -ms-transform: rotate(-45deg);
+ -o-transform: rotate(-45deg);
+ transform: rotate(-45deg);
+ -webkit-transition: all 0.2s ease;
+ -moz-transition: all 0.2s ease;
+ transition: all 0.2s ease; }
+
+input[type=checkbox]:checked + .checkbox {
+ background: #9fbb58;
+ border: #b4ca7c; }
+
+input[type=checkbox]:checked + .checkbox:after {
+ opacity: 1; }
+
+/* =============================================================================
+ Buttons
+ ============================================================================= */
+/*
+ * Buttons are used for primary calls to action on a page.
+ *
+ * Usage:
+ *
+ */
+.button,
+button,
+input[type="button"], .button-save,
+button[type="submit"],
+input[type="submit"], .button-add, .button-delete,
+button[type="reset"],
+input[type="reset"], .button-alt, .button-link, .button-back {
+ -webkit-box-sizing: border-box;
+ -moz-box-sizing: border-box;
+ box-sizing: border-box;
+ min-height: 35px;
+ width: auto;
+ display: inline-block;
+ padding: 0.9em 1.37em;
+ cursor: pointer;
+ text-decoration: none;
+ color: #fff;
+ font-size: 11px;
+ line-height: 13px;
+ font-weight: 300;
+ text-align: center;
+ letter-spacing: 1px;
+ text-transform: uppercase;
+ text-shadow: none;
+ border-radius: 0.2em;
+ border: rgba(0, 0, 0, 0.05) 0.1em solid;
+ -webkit-transition: background 0.3s ease, border-color 0.3s ease;
+ -moz-transition: background 0.3s ease, border-color 0.3s ease;
+ transition: background 0.3s ease, border-color 0.3s ease; }
+ .button:hover,
+ button:hover,
+ input[type="button"]:hover, .button-save:hover,
+ input[type="submit"]:hover, .button-add:hover, .button-delete:hover,
+ input[type="reset"]:hover, .button-alt:hover, .button-link:hover, .button-back:hover {
+ border-color: transparent;
+ background: #f8f8f8;
+ text-decoration: none; }
+ .button:active,
+ button:active,
+ input[type="button"]:active, .button-save:active,
+ input[type="submit"]:active, .button-add:active, .button-delete:active,
+ input[type="reset"]:active, .button-alt:active, .button-link:active, .button-back:active {
+ box-shadow: rgba(0, 0, 0, 0.3) 0 1px 3px inset; }
+ .button:disabled,
+ button:disabled,
+ input[type="button"]:disabled, .button-save:disabled,
+ input[type="submit"]:disabled, .button-add:disabled, .button-delete:disabled,
+ input[type="reset"]:disabled, .button-alt:disabled, .button-link:disabled, .button-back:disabled {
+ opacity: 0.5;
+ cursor: not-allowed; }
+ .large.button,
+ button.large,
+ input.large[type="button"], .large.button-save,
+ input.large[type="submit"], .large.button-add, .large.button-delete,
+ input.large[type="reset"], .large.button-alt, .large.button-link, .large.button-back {
+ padding: 1em 1.8em;
+ font-size: 14px;
+ line-height: 16px; }
+
+.button,
+button,
+input[type="button"] {
+ color: #777;
+ font-weight: normal;
+ background: #eee;
+ box-shadow: none; }
+ .button:hover,
+ button:hover,
+ input[type="button"]:hover {
+ border-color: rgba(0, 0, 0, 0.1); }
+
+.button-save,
+button[type="submit"],
+input[type="submit"] {
+ background: #5ba4e5;
+ box-shadow: none; }
+ .button-save:hover,
+ button[type="submit"]:hover,
+ input[type="submit"]:hover {
+ background: #2f8cde; }
+
+.button-add {
+ background: #9fbb58; }
+ .button-add:hover {
+ background: #8ba644; }
+
+.button-delete,
+button[type="reset"],
+input[type="reset"] {
+ background: #e25440;
+ box-shadow: none; }
+ .button-delete:hover,
+ button[type="reset"]:hover,
+ input[type="reset"]:hover {
+ background: #cf3520; }
+
+.button-alt {
+ background: #3c4043; }
+ .button-alt:hover {
+ background: #242628; }
+
+.button-link {
+ color: #5ba4e5;
+ background: transparent;
+ border: none; }
+ .button-link:hover {
+ background: transparent;
+ text-decoration: underline; }
+
+.button-back {
+ position: absolute;
+ top: 20px;
+ left: 20px;
+ margin-right: 30px;
+ padding: 0.5em 1.37em 0.5em 1.10em;
+ display: none;
+ color: #fff;
+ background: #5ba4e5;
+ border: none;
+ border-top-left-radius: 0;
+ border-bottom-left-radius: 0; }
+ .button-back:before {
+ content: ' ';
+ position: absolute;
+ top: 0;
+ left: -10px;
+ width: 0;
+ height: 0;
+ border-width: 18px 10px 18px 0;
+ border-color: transparent #5ba4e5 transparent transparent;
+ border-style: solid solid solid none;
+ -webkit-transform: scale(0.9999);
+ -moz-transform: scale(0.9999);
+ -ms-transform: scale(0.9999);
+ -o-transform: scale(0.9999);
+ transform: scale(0.9999);
+ -webkit-transition: border-color 0.3s ease;
+ -moz-transition: border-color 0.3s ease;
+ transition: border-color 0.3s ease; }
+ .button-back:hover {
+ color: #fff;
+ background: #2f8cde;
+ border-color: #2f8cde; }
+ .button-back:hover:before {
+ border-right-color: #2f8cde; }
+ @media (max-width: 800px) {
+ .button-back {
+ display: inline-block; } }
+
+/* =============================================================================
+ Split Buttons
+ ============================================================================= */
+/*
+ * The splitbutton adds addition values to a button, via a dropdown (or drop-up).
+ *
+ * Usage:
+ *
+ *
+ * Options
+ *
+ */
+.splitbutton, .splitbutton-save, .splitbutton-add, .splitbutton-delete, .splitbutton-alt {
+ display: inline-block;
+ position: relative;
+ font-size: 0;
+ white-space: nowrap; }
+ .splitbutton button, .splitbutton-save button, .splitbutton-add button, .splitbutton-delete button, .splitbutton-alt button {
+ font-size: 11px;
+ border-top-right-radius: 0;
+ border-bottom-right-radius: 0; }
+ .splitbutton .options, .splitbutton-save .options, .splitbutton-add .options, .splitbutton-delete .options, .splitbutton-alt .options {
+ display: inline-block;
+ position: relative;
+ width: 35px;
+ height: 35px;
+ margin-left: -1px;
+ vertical-align: top;
+ text-align: center;
+ color: #fff;
+ background: #e5e5e5;
+ border-radius: 0 2px 2px 0;
+ box-shadow: rgba(0, 0, 0, 0.02) 0 1px 0 inset, rgba(0, 0, 0, 0.02) -1px 0 0 inset, rgba(0, 0, 0, 0.02) 0 -1px 0 inset;
+ -webkit-transition: background-color 0.3s linear;
+ -moz-transition: background-color 0.3s linear;
+ transition: background-color 0.3s linear; }
+ .splitbutton .options:before, .splitbutton-save .options:before, .splitbutton-add .options:before, .splitbutton-delete .options:before, .splitbutton-alt .options:before {
+ font-family: "Icons";
+ font-weight: normal;
+ font-style: normal;
+ vertical-align: -7%;
+ text-transform: none;
+ speak: none;
+ line-height: 1;
+ -webkit-font-smoothing: antialiased;
+ content: "\e001";
+ font-size: 9px;
+ position: absolute;
+ top: 50%;
+ right: 50%;
+ margin-top: -3px;
+ margin-right: -5px;
+ -webkit-transition: margin-top 0.3s ease;
+ -moz-transition: margin-top 0.3s ease;
+ transition: margin-top 0.3s ease;
+ /* Transition of transform properties are split out due to a
+ defect in the vendor prefixing of transform transitions.
+ See: http://github.com/thoughtbot/bourbon/pull/86 */
+ -webkit-transition-property: -webkit-transform;
+ -moz-transition-property: -moz-transform;
+ transition-property: transform;
+ -webkit-transition-duration: 0.3;
+ -moz-transition-duration: 0.3;
+ transition-duration: 0.3;
+ -webkit-transition-timing-function: ease;
+ -moz-transition-timing-function: ease;
+ transition-timing-function: ease; }
+ .splitbutton .options:hover, .splitbutton-save .options:hover, .splitbutton-add .options:hover, .splitbutton-delete .options:hover, .splitbutton-alt .options:hover {
+ text-decoration: none; }
+ .splitbutton .options.active:before, .splitbutton-save .options.active:before, .splitbutton-add .options.active:before, .splitbutton-delete .options.active:before, .splitbutton-alt .options.active:before {
+ -webkit-transform: rotate(360deg);
+ -moz-transform: rotate(360deg);
+ -ms-transform: rotate(360deg);
+ -o-transform: rotate(360deg);
+ transform: rotate(360deg); }
+ .splitbutton .options.up.active:before, .splitbutton-save .options.up.active:before, .splitbutton-add .options.up.active:before, .splitbutton-delete .options.up.active:before, .splitbutton-alt .options.up.active:before {
+ margin-top: -4px;
+ -webkit-transform: rotate(540deg);
+ -moz-transform: rotate(540deg);
+ -ms-transform: rotate(540deg);
+ -o-transform: rotate(540deg);
+ transform: rotate(540deg); }
+ .splitbutton .options:hover, .splitbutton-save .options:hover, .splitbutton-add .options:hover, .splitbutton-delete .options:hover, .splitbutton-alt .options:hover {
+ box-shadow: none;
+ background: #f8f8f8; }
+ .splitbutton .options:hover:before, .splitbutton-save .options:hover:before, .splitbutton-add .options:hover:before, .splitbutton-delete .options:hover:before, .splitbutton-alt .options:hover:before {
+ font-family: "Icons";
+ font-weight: normal;
+ font-style: normal;
+ vertical-align: -7%;
+ text-transform: none;
+ speak: none;
+ line-height: 1;
+ -webkit-font-smoothing: antialiased;
+ content: "\e001";
+ -webkit-transform: rotate(360deg);
+ -moz-transform: rotate(360deg);
+ -ms-transform: rotate(360deg);
+ -o-transform: rotate(360deg);
+ transform: rotate(360deg); }
+ .splitbutton .options:hover:hover, .splitbutton-save .options:hover:hover, .splitbutton-add .options:hover:hover, .splitbutton-delete .options:hover:hover, .splitbutton-alt .options:hover:hover {
+ text-decoration: none; }
+ .splitbutton .options.up:hover:before, .splitbutton-save .options.up:hover:before, .splitbutton-add .options.up:hover:before, .splitbutton-delete .options.up:hover:before, .splitbutton-alt .options.up:hover:before {
+ font-family: "Icons";
+ font-weight: normal;
+ font-style: normal;
+ vertical-align: -7%;
+ text-transform: none;
+ speak: none;
+ line-height: 1;
+ -webkit-font-smoothing: antialiased;
+ content: "\e001";
+ margin-top: -4px;
+ -webkit-transform: rotate(540deg);
+ -moz-transform: rotate(540deg);
+ -ms-transform: rotate(540deg);
+ -o-transform: rotate(540deg);
+ transform: rotate(540deg);
+ -webkit-transition-property: -webkit-transform;
+ -moz-transition-property: -moz-transform;
+ transition-property: transform;
+ -webkit-transition-duration: 0.6;
+ -moz-transition-duration: 0.6;
+ transition-duration: 0.6;
+ -webkit-transition-timing-function: ease;
+ -moz-transition-timing-function: ease;
+ transition-timing-function: ease; }
+ .splitbutton .options.up:hover:hover, .splitbutton-save .options.up:hover:hover, .splitbutton-add .options.up:hover:hover, .splitbutton-delete .options.up:hover:hover, .splitbutton-alt .options.up:hover:hover {
+ text-decoration: none; }
+
+.splitbutton .options {
+ color: #777; }
+ .splitbutton .options:hover {
+ box-shadow: rgba(0, 0, 0, 0.07) 0 1px 0 inset, rgba(0, 0, 0, 0.07) -1px 0 0 inset, rgba(0, 0, 0, 0.07) 0 -1px 0 inset; }
+
+.splitbutton-save .options {
+ background: #4598e2; }
+ .splitbutton-save .options:hover, .splitbutton-save .options.active {
+ background: #2f8cde; }
+
+.splitbutton-add .options {
+ background: #91ae47; }
+ .splitbutton-add .options:hover {
+ background: #8ba644; }
+
+.splitbutton-delete .options {
+ background: #de3c25; }
+ .splitbutton-delete .options:hover {
+ background: #cf3520; }
+
+.splitbutton-alt .options {
+ background: #2e3033; }
+ .splitbutton-alt .options:hover {
+ background: #242628; }
+
+/* All the styles controlling forms and form fields. */
+/* ==========================================================================
+ Layouts - Styles for specific admin screen layouts, grouped by screen.
+ ========================================================================== */
+/*
+ * These styles control elements specific to the manage posts screen
+ * used for previewing and reading existing content in Ghost.
+ *
+ * Table of Contents:
+ *
+ * Manage
+ * Preview
+ *
+ */
+/* =============================================================================
+ Manage
+ ============================================================================= */
+.manage {
+ /* =============================================================================
+ Preview
+ ============================================================================= */ }
+ .manage .content-view-container {
+ position: relative;
+ height: 100%;
+ width: 100%; }
+ @media (max-width: 800px) {
+ .manage .content-view-container {
+ overflow-x: hidden; } }
+ .manage .content-list {
+ -webkit-box-sizing: border-box;
+ -moz-box-sizing: border-box;
+ box-sizing: border-box;
+ width: 35%;
+ padding: 15px;
+ position: absolute;
+ bottom: 0;
+ top: 0;
+ left: 0;
+ border-right: #edece4 2px solid;
+ background: #fff;
+ box-shadow: rgba(0, 0, 0, 0.05) 0 1px 5px; }
+ @media (max-width: 800px) {
+ .manage .content-list {
+ width: auto;
+ right: 0;
+ z-index: 500;
+ border: none; } }
+ .manage .content-list .content-filter {
+ position: relative;
+ z-index: 300; }
+ .manage .content-list .content-filter > a {
+ padding: 5px;
+ margin-left: -5px; }
+ .manage .content-list .content-filter .menu-drop {
+ display: block; }
+ .manage .content-list .button-add {
+ position: absolute;
+ top: 10px;
+ right: 15px;
+ z-index: 700;
+ color: #fff;
+ padding-left: 5px; }
+ .manage .content-list .button-add:before {
+ font-family: "Icons";
+ font-weight: normal;
+ font-style: normal;
+ vertical-align: -7%;
+ text-transform: none;
+ speak: none;
+ line-height: 1;
+ -webkit-font-smoothing: antialiased;
+ content: "\e032"; }
+ .manage .content-list .button-add:hover {
+ text-decoration: none; }
+ .manage .content-list .content-list-content {
+ position: absolute;
+ top: 0;
+ right: 0;
+ bottom: 0;
+ left: 0;
+ overflow: auto;
+ padding-top: 40px; }
+ .manage .content-list .entry-title {
+ font-size: 1.4em;
+ line-height: 1.1em;
+ margin-bottom: 0.5em;
+ font-weight: normal; }
+ .manage .content-list .views {
+ float: right;
+ text-align: right;
+ margin-left: 15px; }
+ .manage .content-list .views:before {
+ font-family: "Icons";
+ font-weight: normal;
+ font-style: normal;
+ vertical-align: -7%;
+ text-transform: none;
+ speak: none;
+ line-height: 1;
+ -webkit-font-smoothing: antialiased;
+ content: "\e025";
+ font-size: 10px;
+ color: #aaa9a2; }
+ .manage .content-list .views:hover {
+ text-decoration: none; }
+ @media (max-width: 800px) {
+ .manage .content-list .views {
+ float: none; } }
+ .manage .content-list .featured .status:before {
+ font-family: "Icons";
+ font-weight: normal;
+ font-style: normal;
+ vertical-align: -7%;
+ text-transform: none;
+ speak: none;
+ line-height: 1;
+ -webkit-font-smoothing: antialiased;
+ content: "\e026";
+ font-size: 11px;
+ margin-right: 10px;
+ vertical-align: 7%; }
+ .manage .content-list .featured .status:hover {
+ text-decoration: none; }
+ .manage .content-list .status .draft {
+ color: #e25440; }
+ .manage .content-list .status .scheduled {
+ color: #f2a925; }
+ .manage .content-list ol {
+ list-style: none;
+ padding: 0;
+ margin: 0;
+ border-top: #edece4 1px solid; }
+ .manage .content-list ol li {
+ margin: 0;
+ padding: 0;
+ border-bottom: #edece4 1px solid;
+ position: relative; }
+ .manage .content-list ol li a {
+ display: block;
+ padding: 20px 15px;
+ color: #aaa9a2; }
+ @media (max-width: 400px) {
+ .manage .content-list ol li a {
+ padding: 15px; } }
+ @media (max-width: 800px) {
+ .manage .content-list ol li a {
+ padding-right: 40px; } }
+ .manage .content-list ol li a:after {
+ font-family: "Icons";
+ font-weight: normal;
+ font-style: normal;
+ vertical-align: -7%;
+ text-transform: none;
+ speak: none;
+ line-height: 1;
+ -webkit-font-smoothing: antialiased;
+ content: "\e01d";
+ position: absolute;
+ top: 50%;
+ margin-top: -6px;
+ right: 15px; }
+ .manage .content-list ol li a:hover {
+ text-decoration: none; }
+ @media (min-width: 800px), (min-width: 800px) {
+ .manage .content-list ol li a::after {
+ display: none; } }
+ .manage .content-list ol li a:hover {
+ text-decoration: none; }
+ @media (min-width: 800px), (min-width: 800px) {
+ .manage .content-list ol li.active {
+ border-bottom: #e8eaeb 1px solid;
+ background: #f6f6f7;
+ box-shadow: #e8eaeb 0 -1px 0, rgba(0, 0, 0, 0.06) 7px 0 0 inset, #e8eaeb 1px 0 0 inset; }
+ .manage .content-list ol li.active a:hover {
+ box-shadow: rgba(0, 0, 0, 0.1) 7px 0 0 inset;
+ -webkit-transition: all 0.4s ease;
+ -moz-transition: all 0.4s ease;
+ transition: all 0.4s ease; }
+ .manage .content-list ol li.active .entry-title {
+ font-weight: bold; }
+ .manage .content-list ol li.active .entry-meta {
+ color: #242628; }
+ .manage .content-list ol li.active .views {
+ color: #242628;
+ font-weight: normal; }
+ .manage .content-list ol li.active .views:before {
+ font-family: "Icons";
+ font-weight: normal;
+ font-style: normal;
+ vertical-align: -7%;
+ text-transform: none;
+ speak: none;
+ line-height: 1;
+ -webkit-font-smoothing: antialiased;
+ content: "\e025";
+ font-size: 10px;
+ color: #242628; }
+ .manage .content-list ol li.active .views:hover {
+ text-decoration: none; } }
+ .manage .content-preview {
+ -webkit-box-sizing: border-box;
+ -moz-box-sizing: border-box;
+ box-sizing: border-box;
+ width: 65%;
+ padding: 15px;
+ position: absolute;
+ bottom: 0;
+ top: 0;
+ right: 0;
+ border-left: #edece4 2px solid;
+ background: #fff;
+ box-shadow: rgba(0, 0, 0, 0.05) 0 1px 5px; }
+ @media (max-width: 800px) {
+ .manage .content-preview {
+ width: auto;
+ left: 100%;
+ right: -100%;
+ margin-left: 15px;
+ border: none; } }
+ .manage .content-preview .unfeatured {
+ vertical-align: -6%;
+ margin: 0 7px 0 -5px;
+ padding: 5px; }
+ .manage .content-preview .unfeatured:before {
+ font-family: "Icons";
+ font-weight: normal;
+ font-style: normal;
+ vertical-align: -7%;
+ text-transform: none;
+ speak: none;
+ line-height: 1;
+ -webkit-font-smoothing: antialiased;
+ content: "\e027";
+ font-size: 14px; }
+ .manage .content-preview .unfeatured:hover {
+ text-decoration: none; }
+ .manage .content-preview .featured {
+ vertical-align: -6%;
+ margin: 0 7px 0 -5px;
+ padding: 5px; }
+ .manage .content-preview .featured:before {
+ font-family: "Icons";
+ font-weight: normal;
+ font-style: normal;
+ vertical-align: -7%;
+ text-transform: none;
+ speak: none;
+ line-height: 1;
+ -webkit-font-smoothing: antialiased;
+ content: "\e026";
+ font-size: 14px; }
+ .manage .content-preview .featured:hover {
+ text-decoration: none; }
+ .manage .content-preview .normal {
+ text-transform: none;
+ margin: 0 3px; }
+ .manage .content-preview .content-preview-content {
+ position: absolute;
+ top: 0;
+ right: 0;
+ bottom: 0;
+ left: 0;
+ overflow: auto;
+ padding: 80px 40px;
+ word-break: break-word;
+ hyphens: auto; }
+ .manage .content-preview .content-preview-content .wrapper {
+ max-width: 700px;
+ margin: 0 auto; }
+ .manage .content-preview .post-controls {
+ float: right;
+ position: relative; }
+ .manage .content-preview .post-settings-menu {
+ position: absolute;
+ top: 35px;
+ right: -3px; }
+ .manage .content-preview .post-edit {
+ margin-right: 7px;
+ padding: 5px; }
+ .manage .content-preview .post-edit:before {
+ font-family: "Icons";
+ font-weight: normal;
+ font-style: normal;
+ vertical-align: -7%;
+ text-transform: none;
+ speak: none;
+ line-height: 1;
+ -webkit-font-smoothing: antialiased;
+ content: "\e00f";
+ font-size: 14px; }
+ .manage .content-preview .post-edit:hover {
+ text-decoration: none; }
+ .manage .content-preview img {
+ width: 100%;
+ height: auto; }
+ .manage .no-posts-box {
+ position: relative;
+ height: 90%;
+ margin: 0px auto;
+ padding: 0px;
+ display: table;
+ z-index: 600; }
+ @media (max-width: 800px) {
+ .manage .no-posts-box {
+ position: fixed;
+ top: 45%;
+ left: 50%; } }
+ .manage .no-posts-box .no-posts {
+ vertical-align: middle;
+ display: table-cell;
+ text-align: center; }
+ @media (max-width: 800px) {
+ .manage .no-posts-box .no-posts {
+ display: block;
+ position: relative;
+ left: -50%; } }
+ .manage .no-posts-box .no-posts h3 {
+ color: #aaa9a2;
+ font-weight: 200;
+ font-size: 2em; }
+
+/* The manage posts screen. */
+/*
+ * These styles control elements specific to the post editor screen
+ * used for publishing content with Ghost.
+ *
+ * Table of Contents:
+ *
+ * Editor / Preview
+ * Post Preview Content
+ * Full Screen Mode
+ * Publish Bar
+ * CodeMirror
+ */
+/* =============================================================================
+ Editor / Preview
+ ============================================================================= */
+@media (min-width: 401px), (min-width: 401px) {
+ .editor .notifications {
+ bottom: 40px; } }
+.editor .entry-title {
+ height: 53px;
+ padding: 2px 15px;
+ margin-bottom: 5px;
+ position: relative; }
+ @media (max-width: 400px) {
+ .editor .entry-title {
+ box-shadow: none; } }
+ .editor .entry-title input {
+ border: 0;
+ margin: 0;
+ padding: 0;
+ font-size: 3em;
+ font-weight: bold;
+ letter-spacing: -1px;
+ width: 100%;
+ background: transparent; }
+ .editor .entry-title input:focus {
+ outline: 0; }
+.editor .entry-container {
+ position: relative;
+ height: 100%; }
+.editor .entry-markdown {
+ left: 0;
+ border-right: #edece4 2px solid; }
+.editor .entry-preview {
+ right: 0;
+ border-left: #edece4 2px solid; }
+.editor .entry-markdown, .editor .entry-preview {
+ -webkit-box-sizing: border-box;
+ -moz-box-sizing: border-box;
+ box-sizing: border-box;
+ width: 50%;
+ padding: 15px;
+ position: absolute;
+ bottom: 40px;
+ top: 61px;
+ background: #fff;
+ box-shadow: rgba(0, 0, 0, 0.05) 0 1px 5px; }
+ @media (max-width: 400px) {
+ .editor .entry-markdown, .editor .entry-preview {
+ box-shadow: none; } }
+ @media (max-width: 1000px) {
+ .editor .entry-markdown, .editor .entry-preview {
+ top: 109px;
+ left: 0;
+ right: 0;
+ width: 100%;
+ border: none;
+ z-index: 100;
+ min-height: 380px; }
+ .editor .entry-markdown .markdown, .editor .entry-markdown .entry-preview-content, .editor .entry-preview .markdown, .editor .entry-preview .entry-preview-content {
+ height: 50px;
+ overflow: hidden; } }
+ @media (max-width: 1000px) {
+ .editor .entry-markdown .floatingheader, .editor .entry-preview .floatingheader {
+ cursor: pointer;
+ width: 50%;
+ border-right: #edece4 2px solid;
+ color: #fff;
+ font-weight: normal;
+ background: #aaa9a2;
+ position: absolute;
+ top: -40px;
+ left: 0;
+ box-shadow: rgba(0, 0, 0, 0.1) 0 -2px 3px inset; }
+ .editor .entry-markdown .floatingheader a, .editor .entry-preview .floatingheader a {
+ color: #fff; } }
+ .editor .entry-markdown .floatingheader a, .editor .entry-preview .floatingheader a {
+ color: #aaa9a2; }
+ .editor .entry-markdown .floatingheader .markdown-help, .editor .entry-preview .floatingheader .markdown-help {
+ position: relative;
+ top: -5px;
+ right: -5px;
+ float: right;
+ padding: 5px; }
+ .editor .entry-markdown .floatingheader .markdown-help:before, .editor .entry-preview .floatingheader .markdown-help:before {
+ font-family: "Icons";
+ font-weight: normal;
+ font-style: normal;
+ vertical-align: -7%;
+ text-transform: none;
+ speak: none;
+ line-height: 1;
+ -webkit-font-smoothing: antialiased;
+ content: "\e018";
+ color: #cfceca; }
+ .editor .entry-markdown .floatingheader .markdown-help:hover, .editor .entry-preview .floatingheader .markdown-help:hover {
+ text-decoration: none; }
+ .editor .entry-markdown .floatingheader .markdown-help:hover:before, .editor .entry-preview .floatingheader .markdown-help:hover:before {
+ font-family: "Icons";
+ font-weight: normal;
+ font-style: normal;
+ vertical-align: -7%;
+ text-transform: none;
+ speak: none;
+ line-height: 1;
+ -webkit-font-smoothing: antialiased;
+ content: "\e018";
+ color: #aaa9a2; }
+ .editor .entry-markdown .floatingheader .markdown-help:hover:hover, .editor .entry-preview .floatingheader .markdown-help:hover:hover {
+ text-decoration: none; }
+ .editor .entry-markdown .floatingheader .entry-word-count, .editor .entry-preview .floatingheader .entry-word-count {
+ float: right; }
+ .editor .entry-markdown.active, .editor .entry-preview.active {
+ z-index: 200; }
+ .editor .entry-markdown.active .markdown, .editor .entry-markdown.active .entry-preview-content, .editor .entry-preview.active .markdown, .editor .entry-preview.active .entry-preview-content {
+ height: auto;
+ overflow: auto; }
+ @media (max-width: 1000px) {
+ .editor .entry-markdown.active header, .editor .entry-preview.active header {
+ cursor: auto;
+ color: #aaa9a2;
+ background: #fff;
+ box-shadow: none; }
+ .editor .entry-markdown.active header a, .editor .entry-preview.active header a {
+ color: #aaa9a2; } }
+ @media (max-width: 400px) {
+ .editor .entry-markdown .markdown-help,
+ .editor .entry-markdown .entry-word-count, .editor .entry-preview .markdown-help,
+ .editor .entry-preview .entry-word-count {
+ display: none; } }
+.editor .entry-markdown-content textarea {
+ border: 0;
+ width: 100%;
+ height: 100%;
+ max-width: 100%;
+ margin: 0;
+ padding: 0;
+ position: absolute;
+ top: 0;
+ right: 0;
+ bottom: 0;
+ left: 0; }
+ .editor .entry-markdown-content textarea:focus {
+ outline: 0; }
+.editor .entry-markdown-content .CodeMirror {
+ height: auto;
+ position: absolute;
+ top: 0;
+ left: 0;
+ right: 0;
+ bottom: 0;
+ font-family: Inconsolata, monospace;
+ font-size: 1.4em;
+ line-height: 1.3em;
+ color: #3c4043; }
+ .editor .entry-markdown-content .CodeMirror .CodeMirror-focused,
+ .editor .entry-markdown-content .CodeMirror .CodeMirror-selected {
+ color: #242628;
+ background: #b3d5f3;
+ text-shadow: none; }
+ .editor .entry-markdown-content .CodeMirror ::selection {
+ color: #242628;
+ background: #b3d5f3;
+ text-shadow: none; }
+.editor .entry-markdown-content .CodeMirror-lines {
+ padding: 65px 0 40px 0;
+ /* Vertical padding around content */ }
+ @media (max-width: 1000px) {
+ .editor .entry-markdown-content .CodeMirror-lines {
+ padding-top: 25px; } }
+ @media (max-width: 400px) {
+ .editor .entry-markdown-content .CodeMirror-lines {
+ padding: 15px 0; } }
+.editor .entry-markdown-content .CodeMirror pre {
+ padding: 0 40px;
+ /* Horizontal padding of content */ }
+ @media (max-width: 400px) {
+ .editor .entry-markdown-content .CodeMirror pre {
+ padding: 0 15px; } }
+.editor .entry-markdown-content .cm-header {
+ color: #000;
+ font-size: 1.4em;
+ line-height: 1.4em;
+ font-weight: bold; }
+.editor .entry-markdown-content .cm-variable-2,
+.editor .entry-markdown-content .cm-variable-3,
+.editor .entry-markdown-content .cm-keyword {
+ color: #3c4043; }
+.editor .entry-markdown-content .cm-string,
+.editor .entry-markdown-content .cm-strong,
+.editor .entry-markdown-content .cm-link,
+.editor .entry-markdown-content .cm-comment,
+.editor .entry-markdown-content .cm-quote,
+.editor .entry-markdown-content .cm-number,
+.editor .entry-markdown-content .cm-atom,
+.editor .entry-markdown-content .cm-tag {
+ color: #000;
+ font-weight: bold; }
+@media (max-width: 1000px) {
+ .editor .entry-preview .floatingheader {
+ right: 0;
+ left: auto;
+ border-right: none;
+ border-left: #edece4 2px solid; } }
+.editor .entry-preview .entry-preview-content {
+ position: absolute;
+ top: 0;
+ right: 0;
+ bottom: 0;
+ left: 0;
+ padding: 60px 40px 40px 40px;
+ overflow: auto;
+ word-break: break-word;
+ hyphens: auto;
+ -webkit-user-select: none;
+ -moz-user-select: none;
+ -ms-user-select: none;
+ user-select: none;
+ cursor: default; }
+ @media (max-width: 1000px) {
+ .editor .entry-preview .entry-preview-content {
+ padding-top: 20px; } }
+ @media (max-width: 400px) {
+ .editor .entry-preview .entry-preview-content {
+ padding: 15px; } }
+@media (max-width: 1000px) {
+ .editor .scrolling .floatingheader {
+ box-shadow: none; } }
+@media (max-width: 1000px) {
+ .editor .scrolling .floatingheader::before, .editor .scrolling .floatingheader::after {
+ display: none; } }
+@media (max-width: 1000px) {
+ .editor .scrolling .CodeMirror-scroll,
+ .editor .scrolling .entry-preview-content {
+ box-shadow: 0 5px 5px rgba(0, 0, 0, 0.05) inset; } }
+
+/* =============================================================================
+ Post Preview Content
+ ============================================================================= */
+.entry-preview-content,
+.content-preview-content {
+ font-size: 1.4em;
+ line-height: 1.5em; }
+ .entry-preview-content a,
+ .content-preview-content a {
+ color: #5ba4e5;
+ text-decoration: underline; }
+ .entry-preview-content p,
+ .content-preview-content p {
+ margin: 1.2em 0 1.6em; }
+ .entry-preview-content p:first-child,
+ .content-preview-content p:first-child {
+ margin-top: 0; }
+ .entry-preview-content h1,
+ .content-preview-content h1 {
+ font-size: 3em; }
+ .entry-preview-content h2,
+ .content-preview-content h2 {
+ font-size: 2.2em; }
+ .entry-preview-content h3,
+ .content-preview-content h3 {
+ font-size: 1.8em; }
+ .entry-preview-content .btn,
+ .content-preview-content .btn {
+ text-decoration: none;
+ color: #35393b; }
+ .entry-preview-content .img-placeholder,
+ .content-preview-content .img-placeholder {
+ border: 5px dashed #35393b;
+ height: 100px;
+ position: relative; }
+ .entry-preview-content .img-placeholder span,
+ .content-preview-content .img-placeholder span {
+ display: block;
+ height: 30px;
+ position: absolute;
+ margin-top: -15px;
+ top: 50%;
+ width: 100%;
+ text-align: center; }
+ .entry-preview-content a.image-edit,
+ .content-preview-content a.image-edit {
+ width: 16px;
+ height: 16px; }
+ .entry-preview-content img,
+ .content-preview-content img {
+ max-width: 100%;
+ height: auto;
+ margin: 0 auto; }
+
+/* =============================================================================
+ Full Screen Mode
+ ============================================================================= */
+body.zen {
+ background: #f3f2ed; }
+ body.zen .usermenu {
+ display: none; }
+ body.zen #global-header, body.zen #publish-bar {
+ opacity: 0;
+ height: 0;
+ overflow: hidden;
+ -webkit-transition: all 0.5s ease-out;
+ -moz-transition: all 0.5s ease-out;
+ transition: all 0.5s ease-out; }
+ body.zen main {
+ top: 15px;
+ -webkit-transition: all 0.5s ease-out;
+ -moz-transition: all 0.5s ease-out;
+ transition: all 0.5s ease-out; }
+ body.zen .entry-markdown, body.zen .entry-preview {
+ bottom: 0;
+ -webkit-transition: all 0.5s ease-out;
+ -moz-transition: all 0.5s ease-out;
+ transition: all 0.5s ease-out; }
+
+/* =============================================================================
+ Publish Bar
+ ============================================================================= */
+#publish-bar {
+ -webkit-box-sizing: border-box;
+ -moz-box-sizing: border-box;
+ box-sizing: border-box;
+ height: 40px;
+ padding: 0;
+ color: #7d878a;
+ background: #1a1c1d;
+ position: fixed;
+ bottom: 0;
+ left: 0;
+ right: 0;
+ z-index: 900;
+ box-shadow: 0 -2px 8px rgba(0, 0, 0, 0.2);
+ -webkit-transform: translateZ(0);
+ -moz-transform: translateZ(0);
+ -ms-transform: translateZ(0);
+ -o-transform: translateZ(0);
+ transform: translateZ(0); }
+ @media (max-width: 1000px) {
+ #publish-bar {
+ font-weight: normal; } }
+ #publish-bar .post-settings:hover, #publish-bar .post-settings.active {
+ color: #e2edf2; }
+ #publish-bar .post-settings-menu {
+ position: absolute;
+ bottom: 44px;
+ right: -3px; }
+ #publish-bar button {
+ min-height: 30px;
+ height: 30px;
+ line-height: 12px;
+ padding: 0 10px;
+ margin-top: 5px;
+ border-top: rgba(255, 255, 255, 0.4) 1px solid; }
+ #publish-bar .button-link {
+ border-top: none; }
+ #publish-bar .options {
+ width: 30px;
+ min-height: 30px;
+ height: 30px;
+ margin-top: 5px;
+ box-shadow: rgba(255, 255, 255, 0.4) 0 1px 0 inset; }
+ #publish-bar .splitbutton-save .button-save,
+ #publish-bar .splitbutton-save .button-delete,
+ #publish-bar .splitbutton-delete .button-save,
+ #publish-bar .splitbutton-delete .button-delete {
+ -webkit-transition: width 0.25s ease, background-color 0.3s linear;
+ -moz-transition: width 0.25s ease, background-color 0.3s linear;
+ transition: width 0.25s ease, background-color 0.3s linear; }
+ #publish-bar .splitbutton-save .editor-options,
+ #publish-bar .splitbutton-delete .editor-options {
+ bottom: 140%;
+ right: -3%; }
+ #publish-bar .splitbutton-save .editor-options a,
+ #publish-bar .splitbutton-delete .editor-options a {
+ font-size: 14px; }
+
+.extended-tags {
+ position: static;
+ min-height: 100%; }
+ .extended-tags #entry-tags:after {
+ right: 10px; }
+ .extended-tags .tags {
+ width: 281px; }
+ .extended-tags .tag-label, .extended-tags .tag-label.touch {
+ color: #fff; }
+ .extended-tags .tag-input {
+ width: 100%;
+ margin-top: 5px;
+ padding-top: 5px;
+ padding-left: 10px;
+ border-top: 1px solid #242628; }
+ .extended-tags .right {
+ display: none; }
+
+#entry-tags {
+ position: absolute;
+ top: 0;
+ left: 0;
+ right: 0;
+ bottom: 0;
+ text-transform: none;
+ padding: 10px 0 0 0; }
+ #entry-tags:after {
+ content: "";
+ position: fixed;
+ top: 10px;
+ right: 270px;
+ width: 20px;
+ height: 26px;
+ background-color: rgba(26, 28, 29, 0);
+ background-image: -webkit-gradient(linear, left top, left bottom, color-stop(0%, rgba(26, 28, 29, 0)), color-stop(100%, #1a1c1d));
+ background-image: -webkit-linear-gradient(left, rgba(26, 28, 29, 0), #1a1c1d);
+ background-image: linear-gradient(to right,rgba(26, 28, 29, 0), #1a1c1d);
+ z-index: 9999;
+ pointer-events: none; }
+ @media (max-width: 400px) {
+ #entry-tags:after {
+ right: 161px; } }
+ #entry-tags .tags {
+ position: relative;
+ display: inline-block;
+ vertical-align: middle;
+ width: auto;
+ max-width: 80%;
+ max-width: calc(100% - 320px);
+ height: 26px;
+ padding-left: 5px;
+ padding-bottom: 20px;
+ overflow-x: auto;
+ overflow-y: hidden;
+ -webkit-overflow-scrolling: touch;
+ white-space: nowrap;
+ -webkit-transition: width 0.2s linear;
+ -moz-transition: width 0.2s linear;
+ transition: width 0.2s linear; }
+ @media (max-width: 400px) {
+ #entry-tags .tags {
+ -webkit-box-sizing: border-box;
+ -moz-box-sizing: border-box;
+ box-sizing: border-box;
+ display: block;
+ width: 115px;
+ max-width: inherit;
+ padding-bottom: 0; } }
+ #entry-tags .tag-label {
+ display: block;
+ float: left;
+ padding: 1px 8px 0 8px;
+ -webkit-transition: all 0.15s ease-out 0;
+ -moz-transition: all 0.15s ease-out 0;
+ transition: all 0.15s ease-out 0; }
+ #entry-tags .tag-label:before {
+ font-family: "Icons";
+ font-weight: normal;
+ font-style: normal;
+ vertical-align: -7%;
+ text-transform: none;
+ speak: none;
+ line-height: 1;
+ -webkit-font-smoothing: antialiased;
+ content: "\e003"; }
+ #entry-tags .tag-label:hover {
+ text-decoration: none; }
+ #entry-tags .tag-label:hover {
+ cursor: pointer;
+ color: #e2edf2; }
+ #entry-tags .tag-label.touch {
+ color: inherit; }
+ #entry-tags input[type="text"].tag-input {
+ display: inline-block;
+ padding: 0;
+ vertical-align: top;
+ color: #e2edf2;
+ font-weight: 300;
+ background: transparent;
+ border: none; }
+ #entry-tags input[type="text"].tag-input:focus {
+ outline: none; }
+ #entry-tags .tag {
+ display: inline;
+ margin-right: 5px;
+ padding: 0 5px;
+ color: #e2edf2;
+ white-space: nowrap;
+ background: #596063;
+ border-radius: 2px;
+ box-shadow: rgba(255, 255, 255, 0.2) 0 1px 0 inset, black 0 1px 3px;
+ -webkit-user-select: none;
+ -moz-user-select: none;
+ -ms-user-select: none;
+ user-select: none; }
+ #entry-tags .tag:after {
+ font-family: "Icons";
+ font-weight: normal;
+ font-style: normal;
+ vertical-align: -7%;
+ text-transform: none;
+ speak: none;
+ line-height: 1;
+ -webkit-font-smoothing: antialiased;
+ content: "\e034";
+ font-size: 8px;
+ color: #242628;
+ margin-left: 4px;
+ vertical-align: 10%;
+ text-shadow: rgba(255, 255, 255, 0.15) 0 1px 0;
+ -webkit-transition: all 0.15s ease-out 0;
+ -moz-transition: all 0.15s ease-out 0;
+ transition: all 0.15s ease-out 0; }
+ #entry-tags .tag:hover {
+ text-decoration: none; }
+ #entry-tags .tag:hover {
+ cursor: pointer; }
+ #entry-tags .tag:hover:after {
+ font-family: "Icons";
+ font-weight: normal;
+ font-style: normal;
+ vertical-align: -7%;
+ text-transform: none;
+ speak: none;
+ line-height: 1;
+ -webkit-font-smoothing: antialiased;
+ content: "\e034";
+ font-size: 8px;
+ color: #e2edf2;
+ margin-left: 4px;
+ vertical-align: 10%;
+ text-shadow: none; }
+ #entry-tags .tag:hover:hover {
+ text-decoration: none; }
+
+.suggestions {
+ bottom: 100%; }
+ .suggestions li.selected {
+ background: #5ba4e5;
+ box-shadow: rgba(255, 255, 255, 0.2) 0 1px 0 inset, rgba(0, 0, 0, 0.5) 0 1px 5px; }
+ .suggestions li a {
+ padding-left: 25px; }
+ .suggestions mark {
+ background: none;
+ color: white;
+ font-weight: bold; }
+
+#entry-controls {
+ -webkit-box-sizing: border-box;
+ -moz-box-sizing: border-box;
+ box-sizing: border-box;
+ display: inline-block;
+ position: relative;
+ padding: 0;
+ z-index: 1; }
+ #entry-controls.unsaved .post-settings-menu {
+ padding-bottom: 0; }
+ #entry-controls.unsaved .post-settings-menu .post-setting:nth-child(3) td {
+ border-bottom: none; }
+ #entry-controls.unsaved .post-settings-menu .delete {
+ display: none; }
+
+#entry-actions {
+ margin-right: 6px;
+ position: relative; }
+
+#entry-actions-menu {
+ position: absolute;
+ bottom: 50px;
+ right: -5px; }
+
+/* =============================================================================
+ Markdown Help Modal
+ ============================================================================= */
+.markdown-help-container {
+ padding-bottom: 20px; }
+
+.modal-markdown-help-table {
+ margin-top: 0; }
+
+/* =============================================================================
+ CodeMirror
+ ============================================================================= */
+.CodeMirror {
+ /* Set height, width, borders, and global font properties here */
+ font-family: monospace;
+ height: 300px; }
+
+.CodeMirror-scroll {
+ /* Set scrolling behaviour here */
+ overflow: auto; }
+
+/* PADDING */
+.CodeMirror-lines {
+ padding: 4px 0;
+ /* Vertical padding around content */ }
+
+.CodeMirror pre {
+ padding: 0 4px;
+ /* Horizontal padding of content */ }
+
+.CodeMirror-scrollbar-filler {
+ background-color: white;
+ /* The little square between H and V scrollbars */ }
+
+/* GUTTER */
+.CodeMirror-gutters {
+ border-right: 1px solid #ddd;
+ background-color: #f7f7f7; }
+
+/* CURSOR */
+.CodeMirror div.CodeMirror-cursor {
+ border-left: 1px solid black;
+ z-index: 3; }
+
+/* Shown when moving in bi-directional text */
+.CodeMirror div.CodeMirror-secondarycursor {
+ border-left: 1px solid silver; }
+
+.cm-tab {
+ display: inline-block; }
+
+/* DEFAULT THEME */
+.cm-s-default .cm-keyword {
+ color: #708; }
+
+.cm-s-default .cm-atom {
+ color: #219; }
+
+.cm-s-default .cm-number {
+ color: #164; }
+
+.cm-s-default .cm-def {
+ color: #00f; }
+
+.cm-s-default .cm-variable {
+ color: black; }
+
+.cm-s-default .cm-variable-2 {
+ color: #05a; }
+
+.cm-s-default .cm-variable-3 {
+ color: #085; }
+
+.cm-s-default .cm-property {
+ color: black; }
+
+.cm-s-default .cm-operator {
+ color: black; }
+
+.cm-s-default .cm-comment {
+ color: #a50; }
+
+.cm-s-default .cm-string {
+ color: #a11; }
+
+.cm-s-default .cm-string-2 {
+ color: #f50; }
+
+.cm-s-default .cm-meta {
+ color: #555; }
+
+.cm-s-default .cm-error {
+ color: #f00; }
+
+.cm-s-default .cm-qualifier {
+ color: #555; }
+
+.cm-s-default .cm-builtin {
+ color: #30a; }
+
+.cm-s-default .cm-bracket {
+ color: #997; }
+
+.cm-s-default .cm-tag {
+ color: #170; }
+
+.cm-s-default .cm-attribute {
+ color: #00c; }
+
+.cm-s-default .cm-header {
+ color: blue; }
+
+.cm-s-default .cm-quote {
+ color: #090; }
+
+.cm-s-default .cm-hr {
+ color: #999; }
+
+.cm-s-default .cm-link {
+ color: #00c; }
+
+.cm-negative {
+ color: #d44; }
+
+.cm-positive {
+ color: #292; }
+
+.cm-header, .cm-strong {
+ font-weight: bold; }
+
+.cm-em {
+ font-style: italic; }
+
+.cm-link {
+ text-decoration: underline; }
+
+.cm-invalidchar {
+ color: #f00; }
+
+/* STOP */
+/* The rest of this file contains styles related to the mechanics of
+ the editor. You probably shouldn't touch them. */
+.CodeMirror {
+ line-height: 1;
+ position: relative;
+ overflow: hidden;
+ background: white;
+ color: black; }
+
+.CodeMirror-scroll {
+ /* 30px is the magic margin used to hide the element's real scrollbars */
+ /* See overflow: hidden in .CodeMirror */
+ margin-bottom: -30px;
+ margin-right: -30px;
+ padding-bottom: 30px;
+ padding-right: 30px;
+ height: 100%;
+ outline: none;
+ /* Prevent dragging from highlighting the element */
+ position: relative; }
+
+.CodeMirror-sizer {
+ position: relative; }
+
+/* The fake, visible scrollbars. Used to force redraw during scrolling
+ before actuall scrolling happens, thus preventing shaking and
+ flickering artifacts. */
+.CodeMirror-vscrollbar, .CodeMirror-hscrollbar, .CodeMirror-scrollbar-filler {
+ position: absolute;
+ z-index: 6;
+ display: none; }
+
+.CodeMirror-vscrollbar {
+ right: 0;
+ top: 0;
+ overflow-x: hidden;
+ overflow-y: scroll; }
+
+.CodeMirror-hscrollbar {
+ bottom: 0;
+ left: 0;
+ overflow-y: hidden;
+ overflow-x: scroll; }
+
+.CodeMirror-scrollbar-filler {
+ right: 0;
+ bottom: 0;
+ z-index: 6; }
+
+.CodeMirror-gutters {
+ position: absolute;
+ left: 0;
+ top: 0;
+ height: 100%;
+ padding-bottom: 30px;
+ z-index: 3; }
+
+.CodeMirror-lines {
+ cursor: text; }
+
+.CodeMirror pre {
+ /* Reset some styles that the rest of the page might have set */
+ -moz-border-radius: 0;
+ -webkit-border-radius: 0;
+ border-radius: 0;
+ border-width: 0;
+ background: transparent;
+ font-family: inherit;
+ font-size: inherit;
+ margin: 0;
+ white-space: pre;
+ word-wrap: normal;
+ line-height: inherit;
+ color: inherit;
+ z-index: 2;
+ position: relative;
+ overflow: visible; }
+
+.CodeMirror-wrap pre {
+ word-wrap: break-word;
+ white-space: pre-wrap;
+ word-break: normal; }
+
+.CodeMirror-wrap .CodeMirror-scroll {
+ overflow-x: hidden; }
+
+.CodeMirror-measure {
+ position: absolute;
+ width: 100%;
+ height: 0px;
+ overflow: hidden;
+ visibility: hidden; }
+
+.CodeMirror-measure pre {
+ position: static; }
+
+.CodeMirror div.CodeMirror-cursor {
+ position: absolute;
+ visibility: hidden;
+ border-right: none;
+ width: 0; }
+
+.CodeMirror-focused div.CodeMirror-cursor {
+ visibility: visible; }
+
+.CodeMirror-selected {
+ background: #d9d9d9; }
+
+.CodeMirror-focused .CodeMirror-selected {
+ background: #d7d4f0; }
+
+/* IE7 hack to prevent it from returning funny offsetTops on the spans */
+.CodeMirror span {
+ *vertical-align: text-bottom; }
+
+@media print {
+ /* Hide the cursor when printing */
+ .CodeMirror div.CodeMirror-cursor {
+ visibility: hidden; } }
+/* The write/edit post screen. */
+/*
+ * These styles control elements specific to the Ghost admin login / signup screens.
+ *
+ * Table of Contents:
+ *
+ * 0. General
+ * 1. Login
+ * 2. Signup
+ *
+ */
+/* =============================================================================
+ 0. General
+ ============================================================================= */
+.ghost-login,
+.ghost-signup,
+.ghost-forgotten,
+.ghost-reset {
+ color: #7d878a;
+ background: #242628; }
+ @media (max-width: 400px) {
+ .ghost-login,
+ .ghost-signup,
+ .ghost-forgotten,
+ .ghost-reset {
+ background: #242628; } }
+ .ghost-login main,
+ .ghost-signup main,
+ .ghost-forgotten main,
+ .ghost-reset main {
+ top: 15px; }
+ .ghost-login input:-webkit-autofill,
+ .ghost-signup input:-webkit-autofill,
+ .ghost-forgotten input:-webkit-autofill,
+ .ghost-reset input:-webkit-autofill {
+ -webkit-box-shadow: 0 0 0px 1000px #e2edf2 inset !important; }
+
+.login-box,
+.signup-box,
+.forgotten-box,
+.reset-box {
+ max-width: 530px;
+ height: 90%;
+ margin: 0 auto;
+ padding: 0;
+ display: table; }
+ @media (max-width: 630px) {
+ .login-box,
+ .signup-box,
+ .forgotten-box,
+ .reset-box {
+ max-width: 264px;
+ text-align: center; } }
+
+/* =============================================================================
+ 1. Login
+ ============================================================================= */
+.login-form {
+ -webkit-box-sizing: border-box;
+ -moz-box-sizing: border-box;
+ box-sizing: border-box;
+ max-width: 530px;
+ color: #a5acae;
+ display: table-cell;
+ vertical-align: middle; }
+ @media (max-width: 630px) {
+ .login-form {
+ max-width: 264px; } }
+ .login-form div {
+ position: relative;
+ margin: 0 0 5px 0;
+ background: #3c4043;
+ float: left; }
+ @media (max-width: 630px) {
+ .login-form div {
+ margin-bottom: 1em; } }
+ .login-form input {
+ display: inline-block;
+ clear: both;
+ margin: 0;
+ padding: 8px 0 8px 8px;
+ width: 216px;
+ position: relative;
+ border: none;
+ color: #fff;
+ font-size: 1.1em;
+ font-weight: 200;
+ background: transparent;
+ box-shadow: none;
+ -webkit-transition: background ease 0.25s;
+ -moz-transition: background ease 0.25s;
+ transition: background ease 0.25s; }
+ @media (max-width: 630px) {
+ .login-form input {
+ width: 264px;
+ -webkit-transition: none;
+ -moz-transition: none;
+ transition: none; } }
+ .login-form input:focus {
+ border: none;
+ background: #484c50; }
+ .login-form .email-wrap {
+ position: relative;
+ margin-right: 3px;
+ border-radius: 2px 0 0 2px; }
+ .login-form .email-wrap:before {
+ font-family: "Icons";
+ font-weight: normal;
+ font-style: normal;
+ vertical-align: -7%;
+ text-transform: none;
+ speak: none;
+ line-height: 1;
+ -webkit-font-smoothing: antialiased;
+ content: "\e012";
+ font-size: 12px;
+ position: absolute;
+ bottom: 11px;
+ left: 8px;
+ z-index: 100; }
+ .login-form .email-wrap:hover {
+ text-decoration: none; }
+ @media (max-width: 630px) {
+ .login-form .email-wrap {
+ margin-right: 0;
+ border-radius: 2px; } }
+ .login-form .email-wrap .email {
+ padding-left: 28px;
+ border-radius: 2px 0 0 2px; }
+ .login-form .password-wrap {
+ position: relative;
+ border-radius: 0 2px 2px 0; }
+ .login-form .password-wrap:before {
+ font-family: "Icons";
+ font-weight: normal;
+ font-style: normal;
+ vertical-align: -7%;
+ text-transform: none;
+ speak: none;
+ line-height: 1;
+ -webkit-font-smoothing: antialiased;
+ content: "\e02c";
+ font-size: 10px;
+ position: absolute;
+ bottom: 12px;
+ left: 11px;
+ z-index: 100; }
+ .login-form .password-wrap:hover {
+ text-decoration: none; }
+ @media (max-width: 630px) {
+ .login-form .password-wrap {
+ border-radius: 2px; } }
+ .login-form .password-wrap .password {
+ padding-left: 28px;
+ border-radius: 0 2px 2px 0; }
+ .login-form button {
+ width: 85px;
+ height: 36px;
+ margin: 0 0 0 10px;
+ padding: 0.5em 1.37em;
+ min-height: 30px;
+ min-width: 80px;
+ box-shadow: rgba(255, 255, 255, 0.15) 0 1px 0 inset; }
+ @media (max-width: 630px) {
+ .login-form button {
+ margin: 0;
+ width: 100%;
+ margin-bottom: 1em; } }
+ .login-form .meta {
+ clear: both;
+ color: #7d878a; }
+ .login-form a {
+ color: #646d70;
+ font-size: 0.9em; }
+ .login-form a:hover {
+ color: #8a9396;
+ text-decoration: none; }
+
+/* =============================================================================
+ 2. Signup and Reset
+ ============================================================================= */
+.signup-form, .reset-form {
+ -webkit-box-sizing: border-box;
+ -moz-box-sizing: border-box;
+ box-sizing: border-box;
+ max-width: 280px;
+ color: #a5acae;
+ display: table-cell;
+ vertical-align: middle; }
+ @media (max-width: 630px) {
+ .signup-form, .reset-form {
+ width: 264px; } }
+ .signup-form div, .reset-form div {
+ position: relative;
+ margin: 0 0 1em 0;
+ background: #3c4043;
+ float: left;
+ display: table; }
+ .signup-form input, .reset-form input {
+ margin: 0;
+ width: 280px;
+ padding: 8px 10px;
+ position: relative;
+ border: none;
+ color: #fff;
+ font-size: 1.1em;
+ font-weight: 200;
+ background: transparent;
+ box-shadow: none;
+ -webkit-transition: background ease 0.25s;
+ -moz-transition: background ease 0.25s;
+ transition: background ease 0.25s; }
+ @media (max-width: 630px) {
+ .signup-form input, .reset-form input {
+ -webkit-transition: none;
+ -moz-transition: none;
+ transition: none;
+ width: 264px; } }
+ .signup-form input:focus, .reset-form input:focus {
+ border: none;
+ background: #484c50; }
+ .signup-form .name-wrap, .reset-form .name-wrap {
+ position: relative;
+ border-radius: 2px; }
+ .signup-form .name-wrap .name, .reset-form .name-wrap .name {
+ border-radius: 2px; }
+ .signup-form .email-wrap, .reset-form .email-wrap {
+ position: relative;
+ border-radius: 2px; }
+ .signup-form .email-wrap .email, .reset-form .email-wrap .email {
+ border-radius: 2px; }
+ .signup-form .password-wrap, .reset-form .password-wrap {
+ position: relative;
+ border-radius: 2px; }
+ .signup-form .password-wrap .password, .reset-form .password-wrap .password {
+ border-radius: 2px; }
+ .signup-form button, .reset-form button {
+ -webkit-box-sizing: border-box;
+ -moz-box-sizing: border-box;
+ box-sizing: border-box;
+ width: 100%;
+ height: 36px;
+ margin: 0 0 1em 0;
+ padding: 0.5em 1.37em;
+ min-height: 30px;
+ min-width: 80px;
+ box-shadow: rgba(255, 255, 255, 0.15) 0 1px 0 inset; }
+
+/* =============================================================================
+ 3. Forgotten
+ ============================================================================= */
+.forgotten-form {
+ -webkit-box-sizing: border-box;
+ -moz-box-sizing: border-box;
+ box-sizing: border-box;
+ max-width: 280px;
+ color: #a5acae;
+ display: table-cell;
+ vertical-align: middle; }
+ @media (max-width: 630px) {
+ .forgotten-form {
+ max-width: 264px; } }
+ .forgotten-form div {
+ position: relative;
+ margin: 0 0 1em 0;
+ background: #3c4043;
+ float: left; }
+ .forgotten-form input {
+ margin: 0;
+ padding: 8px 10px;
+ position: relative;
+ border: none;
+ color: #fff;
+ font-size: 1.1em;
+ font-weight: 200;
+ background: transparent;
+ box-shadow: none;
+ -webkit-transition: background ease 0.25s;
+ -moz-transition: background ease 0.25s;
+ transition: background ease 0.25s; }
+ @media (max-width: 630px) {
+ .forgotten-form input {
+ -webkit-transition: none;
+ -moz-transition: none;
+ transition: none;
+ max-width: 244px; } }
+ .forgotten-form input:focus {
+ border: none;
+ background: #484c50; }
+ .forgotten-form .email-wrap {
+ position: relative;
+ border-radius: 2px; }
+ .forgotten-form .email-wrap .email {
+ border-radius: 2px; }
+ .forgotten-form button {
+ -webkit-box-sizing: border-box;
+ -moz-box-sizing: border-box;
+ box-sizing: border-box;
+ width: 100%;
+ height: 36px;
+ margin: 0 0 1em 0;
+ padding: 0.5em 1.37em;
+ min-height: 30px;
+ min-width: 80px;
+ box-shadow: rgba(255, 255, 255, 0.15) 0 1px 0 inset; }
+
+/* The login screen. */
+/*
+ * These styles control elements specific to the error screens
+ *
+ * Table of Contents:
+ *
+ * General
+ * 404
+ */
+/* =============================================================================
+ General
+ ============================================================================= */
+.error-content {
+ max-width: 530px;
+ margin: 0 auto;
+ padding: 0;
+ display: table;
+ height: 100%; }
+ @media (max-width: 630px) {
+ .error-content {
+ max-width: 264px;
+ text-align: center; } }
+
+.error-details {
+ display: table-cell;
+ vertical-align: middle; }
+
+.error-image {
+ display: inline-block;
+ vertical-align: middle;
+ width: 96px;
+ height: 150px; }
+ @media (max-width: 630px) {
+ .error-image {
+ width: 72px;
+ height: 112px; } }
+ .error-image img {
+ width: 100%;
+ height: 100%; }
+
+.error-message {
+ position: relative;
+ top: -5px;
+ display: inline-block;
+ vertical-align: middle;
+ margin-left: 10px; }
+
+.error-code {
+ margin: 0;
+ font-size: 7.8em;
+ line-height: 0.9em;
+ color: #979797; }
+ @media (max-width: 630px) {
+ .error-code {
+ font-size: 5.8em; } }
+
+.error-description {
+ margin: 0;
+ padding: 0;
+ font-weight: 300;
+ font-size: 1.9em;
+ color: #979797;
+ border: none; }
+ @media (max-width: 630px) {
+ .error-description {
+ font-size: 1.4em; } }
+
+.error-stack {
+ margin: 1em auto;
+ padding: 2em;
+ max-width: 800px;
+ background-color: rgba(255, 255, 255, 0.3); }
+
+.error-stack-list {
+ list-style-type: none;
+ padding: 0;
+ margin: 0; }
+
+.error-stack-list li {
+ display: block; }
+ .error-stack-list li::before {
+ color: #BBB;
+ content: "\21AA";
+ display: inline-block;
+ font-size: 1.2em;
+ margin-right: 0.5em; }
+
+.error-stack-function {
+ font-weight: bold; }
+
+/* The error screens. */
+/* ==========================================================================
+ Settings Layouts - Styles for the individual settings panes, grouped by pane.
+ ========================================================================== */
+/*
+ * These styles control elements specific to the settings screen
+ * used for configuring your Ghost install.
+ *
+ * Table of Contents:
+ *
+ * General
+ * Sidebar
+ * Content
+ *
+ */
+/* =============================================================================
+ Settings
+ ============================================================================= */
+.settings {
+ /* =============================================================================
+ Sidebar
+ ============================================================================= */
+ /* =============================================================================
+ Content
+ ============================================================================= */ }
+ .settings .wrapper {
+ background: #fff;
+ box-shadow: rgba(0, 0, 0, 0.05) 0 1px 5px;
+ position: relative;
+ width: 100%;
+ height: 100%;
+ margin: 0;
+ padding: 0; }
+ @media (max-width: 800px) {
+ .settings .wrapper {
+ overflow-x: hidden; } }
+ .settings .title {
+ text-transform: uppercase;
+ font-weight: normal;
+ font-size: 1.6em;
+ line-height: 0.8em;
+ margin: 0 0 18px 0;
+ padding: 0;
+ border: none; }
+ .settings .settings-sidebar {
+ width: 20%;
+ position: absolute;
+ top: 0;
+ left: 0;
+ bottom: 0;
+ z-index: 700;
+ background: #fff;
+ box-shadow: #edece4 1px 0 0; }
+ @media (max-width: 800px) {
+ .settings .settings-sidebar {
+ width: 100%;
+ box-shadow: none; } }
+ .settings .settings-sidebar > header {
+ position: relative;
+ z-index: 400;
+ height: 17px;
+ padding: 30px 15px 30px 40px;
+ margin-bottom: 0;
+ border-bottom: none;
+ box-shadow: #edece4 0 -1px 0 inset, #edece4 1px 0 0;
+ background: -webkit-gradient(linear, left top, left bottom, color-stop(0%, white), color-stop(25%, white), color-stop(100%, rgba(255, 255, 255, 0.9)));
+ background: -webkit-linear-gradient(top, white 0%, white 25%, rgba(255, 255, 255, 0.9) 100%);
+ background: linear, to bottom, white 0%, white 25%, rgba(255, 255, 255, 0.9) 100%; }
+ @media (max-width: 1000px) {
+ .settings .settings-sidebar > header {
+ padding-left: 15px; } }
+ .settings .settings-menu {
+ position: absolute;
+ top: 0;
+ left: 0;
+ bottom: 0;
+ right: -1px;
+ overflow: auto; }
+ @media (max-width: 800px) {
+ .settings .settings-menu {
+ right: 0; } }
+ .settings .settings-menu:before {
+ display: block;
+ content: "";
+ height: 77px; }
+ .settings .settings-menu ul {
+ border-top: none; }
+ @media (max-width: 800px) {
+ .settings .settings-menu ul {
+ border-bottom: #edece4 1px solid; } }
+ .settings .settings-menu li {
+ margin-right: 1px;
+ border-top: #fff 1px solid; }
+ @media (max-width: 800px) {
+ .settings .settings-menu li {
+ margin-right: 0;
+ border-top: #edece4 1px solid; } }
+ .settings .settings-menu li a {
+ padding: 15px 15px 15px 40px;
+ border-bottom: none; }
+ @media (max-width: 1000px) {
+ .settings .settings-menu li a {
+ padding-left: 15px; } }
+ @media (max-width: 800px) {
+ .settings .settings-menu li a:after {
+ font-family: "Icons";
+ font-weight: normal;
+ font-style: normal;
+ vertical-align: -7%;
+ text-transform: none;
+ speak: none;
+ line-height: 1;
+ -webkit-font-smoothing: antialiased;
+ content: "\e01d";
+ float: right;
+ margin-top: 5px; }
+ .settings .settings-menu li a:hover {
+ text-decoration: none; } }
+ .settings .settings-menu li:first-child {
+ border-top: none; }
+ .settings .settings-menu li:first-child.active {
+ border-top: none; }
+ @media (min-width: 800px), (min-width: 800px) {
+ .settings .settings-menu li.active {
+ margin-right: 0;
+ position: relative;
+ z-index: 300;
+ border-top: #edece4 1px solid;
+ box-shadow: #fff 1px 0 0, #edece4 0 1px 0;
+ -webkit-transition: all 0.15s ease-out 0;
+ -moz-transition: all 0.15s ease-out 0;
+ transition: all 0.15s ease-out 0; }
+ .settings .settings-menu li.active a {
+ color: #242628;
+ font-weight: bold;
+ background: #fff; } }
+ .settings .settings-menu li a:before {
+ margin-right: 20px; }
+ @media (max-width: 1000px) {
+ .settings .settings-menu li a:before {
+ margin-right: 15px; } }
+ .settings .settings-menu .general a:before {
+ font-family: "Icons";
+ font-weight: normal;
+ font-style: normal;
+ vertical-align: -7%;
+ text-transform: none;
+ speak: none;
+ line-height: 1;
+ -webkit-font-smoothing: antialiased;
+ content: "\e006"; }
+ .settings .settings-menu .general a:hover {
+ text-decoration: none; }
+ .settings .settings-menu .publishing a:before {
+ font-family: "Icons";
+ font-weight: normal;
+ font-style: normal;
+ vertical-align: -7%;
+ text-transform: none;
+ speak: none;
+ line-height: 1;
+ -webkit-font-smoothing: antialiased;
+ content: "\e02d"; }
+ .settings .settings-menu .publishing a:hover {
+ text-decoration: none; }
+ .settings .settings-menu .services a:before {
+ font-family: "Icons";
+ font-weight: normal;
+ font-style: normal;
+ vertical-align: -7%;
+ text-transform: none;
+ speak: none;
+ line-height: 1;
+ -webkit-font-smoothing: antialiased;
+ content: "\e020"; }
+ .settings .settings-menu .services a:hover {
+ text-decoration: none; }
+ .settings .settings-menu .users a:before {
+ font-family: "Icons";
+ font-weight: normal;
+ font-style: normal;
+ vertical-align: -7%;
+ text-transform: none;
+ speak: none;
+ line-height: 1;
+ -webkit-font-smoothing: antialiased;
+ content: "\e002"; }
+ .settings .settings-menu .users a:hover {
+ text-decoration: none; }
+ .settings .settings-menu .appearance a:before {
+ font-family: "Icons";
+ font-weight: normal;
+ font-style: normal;
+ vertical-align: -7%;
+ text-transform: none;
+ speak: none;
+ line-height: 1;
+ -webkit-font-smoothing: antialiased;
+ content: "\e021"; }
+ .settings .settings-menu .appearance a:hover {
+ text-decoration: none; }
+ .settings .settings-menu .plugins a:before {
+ font-family: "Icons";
+ font-weight: normal;
+ font-style: normal;
+ vertical-align: -7%;
+ text-transform: none;
+ speak: none;
+ line-height: 1;
+ -webkit-font-smoothing: antialiased;
+ content: "\e00b"; }
+ .settings .settings-menu .plugins a:hover {
+ text-decoration: none; }
+ .settings .settings-content {
+ padding: 0;
+ position: absolute;
+ top: 0;
+ right: 0;
+ left: 20%;
+ bottom: 0;
+ background: #fff;
+ display: none; }
+ @media (max-width: 800px) {
+ .settings .settings-content {
+ display: none;
+ width: 100%;
+ left: 100%;
+ right: -100%;
+ margin-left: 15px; } }
+ .settings .settings-content img {
+ max-width: 100%; }
+ .settings .settings-content.active {
+ display: block; }
+ .settings .settings-content > header {
+ position: relative;
+ z-index: 200;
+ height: 17px;
+ padding: 30px 220px 29px 40px;
+ border-bottom: #edece4 1px solid;
+ margin-bottom: 40px;
+ text-transform: none;
+ font-weight: normal;
+ line-height: inherit;
+ color: inherit;
+ background: -moz-linear-gradient(top, white 0%, white 25%, rgba(255, 255, 255, 0.9) 100%);
+ background: -webkit-gradient(linear, left top, left bottom, color-stop(0%, white), color-stop(25%, white), color-stop(100%, rgba(255, 255, 255, 0.9)));
+ background: -webkit-linear-gradient(top, white 0%, white 25%, rgba(255, 255, 255, 0.9) 100%);
+ background: -o-linear-gradient(top, white 0%, white 25%, rgba(255, 255, 255, 0.9) 100%);
+ background: -ms-linear-gradient(top, white 0%, white 25%, rgba(255, 255, 255, 0.9) 100%);
+ background: linear, to bottom, white 0%, white 25%, rgba(255, 255, 255, 0.9) 100%; }
+ @media (max-width: 1000px) {
+ .settings .settings-content > header {
+ padding-left: 15px; } }
+ @media (max-width: 800px) {
+ .settings .settings-content > header {
+ padding-left: 115px; } }
+ @media (max-height: 600px), (max-height: 600px) {
+ .settings .settings-content > header {
+ height: auto;
+ padding: 5px;
+ position: absolute;
+ top: 0;
+ right: 0;
+ border: none;
+ background: transparent; }
+ .settings .settings-content > header .title {
+ display: none; } }
+ @media (max-width: 650px) {
+ .settings .settings-content > header {
+ padding-left: 15px; }
+ .settings .settings-content > header .button-back {
+ position: fixed;
+ top: 5px;
+ left: 14px;
+ min-height: 0;
+ height: 30px; }
+ .settings .settings-content > header .button-back:before {
+ left: -9px;
+ border-width: 15px 9px 15px 0; } }
+ .settings .settings-content .page-actions {
+ position: absolute;
+ top: 20px;
+ right: 40px;
+ z-index: 700;
+ font-size: 1em; }
+ @media (max-width: 1000px) {
+ .settings .settings-content .page-actions {
+ right: 15px; } }
+ @media (max-width: 650px) {
+ .settings .settings-content .page-actions {
+ position: fixed;
+ top: 5px;
+ right: 4px; }
+ .settings .settings-content .page-actions button {
+ min-height: 0;
+ height: 30px;
+ padding: 0.5em 1.37em; } }
+ .settings .settings-content .page-actions .button-add {
+ position: relative;
+ padding-left: 50px; }
+ .settings .settings-content .page-actions .button-add:before {
+ font-family: "Icons";
+ font-weight: normal;
+ font-style: normal;
+ vertical-align: -7%;
+ text-transform: none;
+ speak: none;
+ line-height: 1;
+ -webkit-font-smoothing: antialiased;
+ content: "\e032";
+ font-size: 1.4em;
+ color: rgba(255, 255, 255, 0.6);
+ position: absolute;
+ top: 0;
+ padding: 9px 8px 0 0;
+ left: 9px;
+ bottom: 0;
+ width: 20px;
+ border-right: #8ba644 1px solid; }
+ .settings .settings-content .page-actions .button-add:hover {
+ text-decoration: none; }
+ .settings .settings-content .content {
+ position: absolute;
+ top: 0;
+ right: 0;
+ left: 0;
+ bottom: 0;
+ padding: 40px;
+ overflow: auto;
+ -webkit-overflow-scrolling: touch; }
+ .settings .settings-content .content:before {
+ display: block;
+ content: "";
+ height: 77px; }
+ @media (max-height: 600px), (max-height: 600px) {
+ .settings .settings-content .content:before {
+ display: none; } }
+ .settings .settings-content .content.no-padding {
+ padding: 0; }
+ @media (max-width: 1000px) {
+ .settings .settings-content .content {
+ padding-left: 15px; } }
+ @media (max-width: 550px) {
+ .settings .settings-content .content {
+ padding: 0 15px 40px; } }
+ .settings .settings-content .description-container, .settings .settings-content .bio-container {
+ max-width: 370px; }
+ .settings .settings-content .word-count {
+ margin-right: 30px;
+ float: right;
+ font-weight: bold;
+ color: #9e9d95; }
+
+/* The settings screen. */
+/* =============================================================================
+ Users List
+ ============================================================================= */
+.settings {
+ /* =============================================================================
+ User Profile
+ ============================================================================= */ }
+ .settings .user-group-header {
+ margin-bottom: 0px;
+ padding-bottom: 20px;
+ border: 0 none;
+ border-bottom: 1px solid #d9d6c5; }
+ .settings .user-group-header h3 {
+ display: inline-block;
+ margin: 0;
+ color: #c0bfb6;
+ font-weight: normal;
+ font-size: 1.1em;
+ line-height: 1em; }
+ .settings .user-search {
+ display: inline-block;
+ float: right; }
+ .settings .user-search label {
+ margin: 0; }
+ .settings .user-search:hover .user-search-input, .settings .user-search .user-search-input:focus {
+ width: 260px;
+ padding: 0 10px; }
+ .settings .user-search .user-search-input {
+ -webkit-box-sizing: border-box;
+ -moz-box-sizing: border-box;
+ box-sizing: border-box;
+ width: 0px;
+ padding: 0;
+ border: none;
+ border-bottom: #f1f0ea 1px solid;
+ -webkit-transition: width 0.2s ease-in-out;
+ -moz-transition: width 0.2s ease-in-out;
+ transition: width 0.2s ease-in-out;
+ box-shadow: none; }
+ .settings .user-search .search-icon:before {
+ font-family: "Icons";
+ font-weight: normal;
+ font-style: normal;
+ vertical-align: -7%;
+ text-transform: none;
+ speak: none;
+ line-height: 1;
+ -webkit-font-smoothing: antialiased;
+ content: "\e007";
+ font-size: 1em;
+ color: #c0bfb6; }
+ .settings .user-search .search-icon:hover {
+ text-decoration: none; }
+ .settings .users {
+ padding: 0px;
+ margin-top: 0px;
+ list-style: none; }
+ .settings .user {
+ -webkit-box-sizing: border-box;
+ -moz-box-sizing: border-box;
+ box-sizing: border-box;
+ display: block;
+ width: 100%;
+ padding: 20px;
+ border: 0 none;
+ border-top: 1px solid #e2edf2; }
+ .settings .user:first-child {
+ border: none; }
+ .settings .user .user-image {
+ display: inline-block;
+ width: 40px;
+ height: 40px;
+ margin-right: 17px;
+ vertical-align: middle;
+ background-color: #edece4;
+ border-radius: 20px; }
+ .settings .user .user-image.invite {
+ -webkit-box-sizing: border-box;
+ -moz-box-sizing: border-box;
+ box-sizing: border-box;
+ padding-top: 8px;
+ text-align: center; }
+ .settings .user .user-image.invite:before {
+ font-family: "Icons";
+ font-weight: normal;
+ font-style: normal;
+ vertical-align: -7%;
+ text-transform: none;
+ speak: none;
+ line-height: 1;
+ -webkit-font-smoothing: antialiased;
+ content: "\e012";
+ font-size: 1em;
+ color: #aaa9a2; }
+ .settings .user .user-image.invite:hover {
+ text-decoration: none; }
+ .settings .user .user-image img {
+ width: 40px;
+ height: 40px;
+ border-radius: 20px; }
+ .settings .user .user-meta {
+ display: inline-block;
+ vertical-align: middle; }
+ .settings .user .user-name {
+ margin: 0;
+ margin-top: 0.4em;
+ font-weight: 400;
+ font-size: 1.2em;
+ line-height: 1em; }
+ .settings .user .user-last-seen {
+ line-height: 1em; }
+ .settings .user-role {
+ padding: 2px 8px;
+ float: right;
+ font-size: 0.8em;
+ color: #fff;
+ text-transform: uppercase; }
+ .settings .user-role.admin {
+ background-color: #DE523A; }
+ .settings .user-role.editor {
+ background-color: #4A8CBD; }
+ .settings .user-profile-header {
+ position: relative; }
+ .settings .user-profile-header:after {
+ content: "";
+ position: absolute;
+ left: 0;
+ right: 0;
+ bottom: 0;
+ height: 110px;
+ background-color: rgba(0, 0, 0, 0);
+ background-image: -webkit-gradient(linear, left top, left bottom, color-stop(0%, rgba(0, 0, 0, 0)), color-stop(100%, rgba(0, 0, 0, 0.3)));
+ background-image: -webkit-linear-gradient(rgba(0, 0, 0, 0), rgba(0, 0, 0, 0.3));
+ background-image: linear-gradient(rgba(0, 0, 0, 0), rgba(0, 0, 0, 0.3)); }
+ .settings .cover-image {
+ display: block;
+ line-height: 0;
+ width: 100%;
+ height: auto;
+ min-height: 180px; }
+ .settings .edit-cover-image {
+ position: absolute;
+ right: 40px;
+ bottom: 38px;
+ background: rgba(0, 0, 0, 0.3);
+ border-radius: 0;
+ color: rgba(255, 255, 255, 0.8);
+ z-index: 2;
+ border-radius: 2px;
+ -webkit-transition: color 0.3s ease, background 0.3s ease;
+ -moz-transition: color 0.3s ease, background 0.3s ease;
+ transition: color 0.3s ease, background 0.3s ease; }
+ @media (max-width: 1000px) {
+ .settings .edit-cover-image {
+ right: 15px; } }
+ .settings .edit-cover-image:hover {
+ color: #fff;
+ background: rgba(0, 0, 0, 0.5); }
+ .settings .user-profile {
+ position: relative;
+ top: -100px;
+ z-index: 1; }
+ .settings .user-profile fieldset {
+ padding: 0 40px; }
+ .settings fieldset.user-details-top {
+ margin-bottom: 0;
+ padding: 10px 0 0 0; }
+ .settings fieldset.user-details-top p {
+ color: #fff; }
+ .settings .user-image {
+ -webkit-box-sizing: border-box;
+ -moz-box-sizing: border-box;
+ box-sizing: border-box;
+ display: block;
+ position: relative;
+ width: 120px;
+ height: 120px;
+ float: left;
+ margin-left: 40px;
+ margin-right: 20px;
+ text-align: center;
+ border-radius: 100%;
+ overflow: hidden;
+ border: 5px solid #fff;
+ background: #fff;
+ z-index: 2; }
+ .settings .user-image .img {
+ display: block;
+ width: 110px;
+ height: 110px;
+ background-size: cover;
+ background-position: center center;
+ border-radius: 100%; }
+ .settings .user-image:hover .edit-user-image {
+ opacity: 1; }
+ .settings .edit-user-image {
+ position: absolute;
+ top: 0px;
+ right: 0px;
+ bottom: 0px;
+ left: 0px;
+ border-radius: 100%;
+ background: rgba(0, 0, 0, 0.5);
+ opacity: 0;
+ color: #fff;
+ line-height: 105px;
+ text-transform: uppercase;
+ text-decoration: none;
+ -webkit-transition: opacity 0.3s ease;
+ -moz-transition: opacity 0.3s ease;
+ transition: opacity 0.3s ease; }
+ .settings #user-name {
+ border-color: #fff; }
+ .settings .user-details-bottom {
+ padding: 0 40px;
+ margin: -30px 0 0 0; }
+
+/* The users pane. */
+/* =============================================================================
+ Plugins
+ ============================================================================= */
+.settings .plugin-section {
+ padding-bottom: 20px; }
+.settings .plugin-section-header h3 {
+ margin: 15px 0;
+ font-size: 1.1em;
+ font-weight: normal;
+ color: #aaa9a2; }
+.settings .plugin-section-footer {
+ text-align: right; }
+.settings .button-update-all:before {
+ font-family: "Icons";
+ font-weight: normal;
+ font-style: normal;
+ vertical-align: -7%;
+ text-transform: none;
+ speak: none;
+ line-height: 1;
+ -webkit-font-smoothing: antialiased;
+ content: "\e03d";
+ font-size: 1em;
+ color: #ffc125;
+ margin-right: 5px; }
+.settings .button-update-all:hover {
+ text-decoration: none; }
+.settings .button-cancel:before {
+ font-family: "Icons";
+ font-weight: normal;
+ font-style: normal;
+ vertical-align: -7%;
+ text-transform: none;
+ speak: none;
+ line-height: 1;
+ -webkit-font-smoothing: antialiased;
+ content: "\e034";
+ font-size: 1em;
+ color: white;
+ margin-right: 5px; }
+.settings .button-cancel:hover {
+ text-decoration: none; }
+.settings .plugin-section-table {
+ margin-top: 5px; }
+ .settings .plugin-section-table tbody > tr:nth-child(odd) > td {
+ background: none; }
+ .settings .plugin-section-table .plugin-section-item.inactive .plugin-meta {
+ opacity: 0.4; }
+ .settings .plugin-section-table .plugin-section-item.inactive td:last-child .plugin-meta {
+ opacity: 1; }
+ .settings .plugin-section-table .plugin-section-item td {
+ padding: 20px 0;
+ border-bottom: #edece4 1px solid; }
+ .settings .plugin-section-table .plugin-section-item td:first-child {
+ padding-left: 0px;
+ border-top: #edece4 1px solid; }
+ .settings .plugin-section-table .plugin-section-item td:first-child .plugin-meta {
+ padding: 0px;
+ width: 75%;
+ border-left: none;
+ text-align: left; }
+ .settings .plugin-section-table .plugin-section-item td:last-child .plugin-meta {
+ padding: 0px;
+ text-align: right; }
+ .settings .plugin-section-table .plugin-icon {
+ display: inline-block;
+ width: 40px;
+ height: 40px;
+ margin-right: 15px;
+ background: #FFC125;
+ border-radius: 5px;
+ vertical-align: middle; }
+ .settings .plugin-section-table .plugin-icon img {
+ width: 100%; }
+ .settings .plugin-section-table .plugin-meta {
+ -webkit-box-sizing: border-box;
+ -moz-box-sizing: border-box;
+ box-sizing: border-box;
+ display: inline-block;
+ width: 100%;
+ height: 100%;
+ padding: 0 20px;
+ vertical-align: middle;
+ border-left: #edece4 1px solid;
+ text-align: center; }
+ .settings .plugin-section-table .plugin-info {
+ display: block;
+ color: #414648;
+ font-size: 1.2em;
+ font-weight: normal;
+ vertical-align: top; }
+ .settings .plugin-section-table .plugin-title {
+ color: #35393b; }
+ .settings .plugin-section-table .plugin-sub-info {
+ display: block;
+ color: #7d878a; }
+ .settings .plugin-section-table .plugin-download-progress {
+ position: relative;
+ display: block;
+ height: 6px;
+ margin-top: 10px;
+ background: #edece4;
+ border-radius: 3px; }
+ .settings .plugin-section-table .plugin-download-progress > span {
+ position: absolute;
+ left: 0;
+ top: 0;
+ content: "";
+ height: 100%;
+ background-color: #5ba4e5;
+ border-radius: 3px; }
+ .settings .plugin-section-table .rating {
+ unicode-bidi: bidi-override;
+ text-align: center; }
+ .settings .plugin-section-table .rating > span {
+ display: inline-block;
+ position: relative;
+ width: 1.1em;
+ height: 1.1em;
+ font-size: 0.8em; }
+ .settings .plugin-section-table .rating > span:before {
+ content: "\2605";
+ position: absolute;
+ left: 0;
+ opacity: 0.5; }
+ .settings .plugin-section-table .rating > span.active:before {
+ content: "\2605";
+ opacity: 1; }
+ .settings .plugin-section-table .plugin-settings-icon {
+ display: block;
+ margin-top: 9px;
+ font-size: 1.4em; }
+ .settings .plugin-section-table .plugin-settings-icon:before {
+ font-family: "Icons";
+ font-weight: normal;
+ font-style: normal;
+ vertical-align: -7%;
+ text-transform: none;
+ speak: none;
+ line-height: 1;
+ -webkit-font-smoothing: antialiased;
+ content: "\e006";
+ font-size: 1em;
+ color: #35393b; }
+ .settings .plugin-section-table .plugin-settings-icon:hover {
+ text-decoration: none; }
+
+/* The plugins pane. */
diff --git a/core/server/controllers/admin.js b/core/server/controllers/admin.js
index e343a40bdf..b651e7acd9 100644
--- a/core/server/controllers/admin.js
+++ b/core/server/controllers/admin.js
@@ -45,8 +45,7 @@ function setSelected(list, name) {
adminControllers = {
'index': function (req, res) {
/*jslint unparam:true*/
- // Ember goes here.
- res.send(200);
+ res.render('default-ember');
},
// Route: index
// Path: /ghost/
diff --git a/core/server/routes/admin.js b/core/server/routes/admin.js
index 91c41fcdcd..8152dcf32e 100644
--- a/core/server/routes/admin.js
+++ b/core/server/routes/admin.js
@@ -55,4 +55,6 @@ module.exports = function (server) {
res.redirect(subdir + '/ghost/');
});
server.get('/ghost/', admin.indexold);
+
+ server.get('/ghost/ember/', admin.index);
};
\ No newline at end of file
diff --git a/core/server/views/default-ember.hbs b/core/server/views/default-ember.hbs
new file mode 100644
index 0000000000..1fc08b3dc3
--- /dev/null
+++ b/core/server/views/default-ember.hbs
@@ -0,0 +1,41 @@
+
+
+
+
+
+
+
+
+ Ghost Admin
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/core/shared/vendor/ember/ember.js b/core/shared/vendor/ember/ember.js
new file mode 100644
index 0000000000..740a41b9af
--- /dev/null
+++ b/core/shared/vendor/ember/ember.js
@@ -0,0 +1,41803 @@
+/*!
+ * @overview Ember - JavaScript Application Framework
+ * @copyright Copyright 2011-2014 Tilde Inc. and contributors
+ * Portions Copyright 2006-2011 Strobe Inc.
+ * Portions Copyright 2008-2011 Apple Inc. All rights reserved.
+ * @license Licensed under MIT license
+ * See https://raw.github.com/emberjs/ember.js/master/LICENSE
+ * @version 1.4.0
+ */
+
+
+(function() {
+/*global __fail__*/
+
+/**
+Ember Debug
+
+@module ember
+@submodule ember-debug
+*/
+
+/**
+@class Ember
+*/
+
+if ('undefined' === typeof Ember) {
+ Ember = {};
+
+ if ('undefined' !== typeof window) {
+ window.Em = window.Ember = Em = Ember;
+ }
+}
+
+// This needs to be kept in sync with the logic in
+// `packages/ember-metal/lib/core.js`.
+//
+// This is duplicated here to ensure that `Ember.ENV`
+// is setup even if `Ember` is not loaded yet.
+if (Ember.ENV) {
+ // do nothing if Ember.ENV is already setup
+} else if ('undefined' !== typeof EmberENV) {
+ Ember.ENV = EmberENV;
+} else if('undefined' !== typeof ENV) {
+ Ember.ENV = ENV;
+} else {
+ Ember.ENV = {};
+}
+
+if (!('MANDATORY_SETTER' in Ember.ENV)) {
+ Ember.ENV.MANDATORY_SETTER = true; // default to true for debug dist
+}
+
+/**
+ Define an assertion that will throw an exception if the condition is not
+ met. Ember build tools will remove any calls to `Ember.assert()` when
+ doing a production build. Example:
+
+ ```javascript
+ // Test for truthiness
+ Ember.assert('Must pass a valid object', obj);
+ // Fail unconditionally
+ Ember.assert('This code path should never be run')
+ ```
+
+ @method assert
+ @param {String} desc A description of the assertion. This will become
+ the text of the Error thrown if the assertion fails.
+ @param {Boolean} test Must be truthy for the assertion to pass. If
+ falsy, an exception will be thrown.
+*/
+Ember.assert = function(desc, test) {
+ if (!test) {
+ throw new Ember.Error("Assertion Failed: " + desc);
+ }
+};
+
+
+/**
+ Display a warning with the provided message. Ember build tools will
+ remove any calls to `Ember.warn()` when doing a production build.
+
+ @method warn
+ @param {String} message A warning to display.
+ @param {Boolean} test An optional boolean. If falsy, the warning
+ will be displayed.
+*/
+Ember.warn = function(message, test) {
+ if (!test) {
+ Ember.Logger.warn("WARNING: "+message);
+ if ('trace' in Ember.Logger) Ember.Logger.trace();
+ }
+};
+
+/**
+ Display a debug notice. Ember build tools will remove any calls to
+ `Ember.debug()` when doing a production build.
+
+ ```javascript
+ Ember.debug("I'm a debug notice!");
+ ```
+
+ @method debug
+ @param {String} message A debug message to display.
+*/
+Ember.debug = function(message) {
+ Ember.Logger.debug("DEBUG: "+message);
+};
+
+/**
+ Display a deprecation warning with the provided message and a stack trace
+ (Chrome and Firefox only). Ember build tools will remove any calls to
+ `Ember.deprecate()` when doing a production build.
+
+ @method deprecate
+ @param {String} message A description of the deprecation.
+ @param {Boolean} test An optional boolean. If falsy, the deprecation
+ will be displayed.
+*/
+Ember.deprecate = function(message, test) {
+ if (Ember.TESTING_DEPRECATION) { return; }
+
+ if (arguments.length === 1) { test = false; }
+ if (test) { return; }
+
+ if (Ember.ENV.RAISE_ON_DEPRECATION) { throw new Ember.Error(message); }
+
+ var error;
+
+ // When using new Error, we can't do the arguments check for Chrome. Alternatives are welcome
+ try { __fail__.fail(); } catch (e) { error = e; }
+
+ if (Ember.LOG_STACKTRACE_ON_DEPRECATION && error.stack) {
+ var stack, stackStr = '';
+ if (error['arguments']) {
+ // Chrome
+ stack = error.stack.replace(/^\s+at\s+/gm, '').
+ replace(/^([^\(]+?)([\n$])/gm, '{anonymous}($1)$2').
+ replace(/^Object.\s*\(([^\)]+)\)/gm, '{anonymous}($1)').split('\n');
+ stack.shift();
+ } else {
+ // Firefox
+ stack = error.stack.replace(/(?:\n@:0)?\s+$/m, '').
+ replace(/^\(/gm, '{anonymous}(').split('\n');
+ }
+
+ stackStr = "\n " + stack.slice(2).join("\n ");
+ message = message + stackStr;
+ }
+
+ Ember.Logger.warn("DEPRECATION: "+message);
+};
+
+
+
+/**
+ Alias an old, deprecated method with its new counterpart.
+
+ Display a deprecation warning with the provided message and a stack trace
+ (Chrome and Firefox only) when the assigned method is called.
+
+ Ember build tools will not remove calls to `Ember.deprecateFunc()`, though
+ no warnings will be shown in production.
+
+ ```javascript
+ Ember.oldMethod = Ember.deprecateFunc("Please use the new, updated method", Ember.newMethod);
+ ```
+
+ @method deprecateFunc
+ @param {String} message A description of the deprecation.
+ @param {Function} func The new function called to replace its deprecated counterpart.
+ @return {Function} a new function that wrapped the original function with a deprecation warning
+*/
+Ember.deprecateFunc = function(message, func) {
+ return function() {
+ Ember.deprecate(message);
+ return func.apply(this, arguments);
+ };
+};
+
+
+// Inform the developer about the Ember Inspector if not installed.
+if (!Ember.testing) {
+ var isFirefox = typeof InstallTrigger !== 'undefined';
+ var isChrome = !!window.chrome && !window.opera;
+
+ if (typeof window !== 'undefined' && (isFirefox || isChrome) && window.addEventListener) {
+ window.addEventListener("load", function() {
+ if (document.body && document.body.dataset && !document.body.dataset.emberExtension) {
+ var downloadURL;
+
+ if(isChrome) {
+ downloadURL = 'https://chrome.google.com/webstore/detail/ember-inspector/bmdblncegkenkacieihfhpjfppoconhi';
+ } else if(isFirefox) {
+ downloadURL = 'https://addons.mozilla.org/en-US/firefox/addon/ember-inspector/'
+ }
+
+ Ember.debug('For more advanced debugging, install the Ember Inspector from ' + downloadURL);
+ }
+ }, false);
+ }
+}
+
+})();
+
+/*!
+ * @overview Ember - JavaScript Application Framework
+ * @copyright Copyright 2011-2014 Tilde Inc. and contributors
+ * Portions Copyright 2006-2011 Strobe Inc.
+ * Portions Copyright 2008-2011 Apple Inc. All rights reserved.
+ * @license Licensed under MIT license
+ * See https://raw.github.com/emberjs/ember.js/master/LICENSE
+ * @version 1.4.0
+ */
+
+
+(function() {
+var define, requireModule, require, requirejs;
+
+(function() {
+ var registry = {}, seen = {};
+
+ define = function(name, deps, callback) {
+ registry[name] = { deps: deps, callback: callback };
+ };
+
+ requirejs = require = requireModule = function(name) {
+ requirejs._eak_seen = registry;
+
+ if (seen[name]) { return seen[name]; }
+ seen[name] = {};
+
+ if (!registry[name]) {
+ throw new Error("Could not find module " + name);
+ }
+
+ var mod = registry[name],
+ deps = mod.deps,
+ callback = mod.callback,
+ reified = [],
+ exports;
+
+ for (var i=0, l=deps.length; i -1;
+};
+
+// From: https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/array/map
+var arrayMap = isNativeFunc(Array.prototype.map) ? Array.prototype.map : function(fun /*, thisp */) {
+ //"use strict";
+
+ if (this === void 0 || this === null) {
+ throw new TypeError();
+ }
+
+ var t = Object(this);
+ var len = t.length >>> 0;
+ if (typeof fun !== "function") {
+ throw new TypeError();
+ }
+
+ var res = new Array(len);
+ var thisp = arguments[1];
+ for (var i = 0; i < len; i++) {
+ if (i in t) {
+ res[i] = fun.call(thisp, t[i], i, t);
+ }
+ }
+
+ return res;
+};
+
+// From: https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/array/foreach
+var arrayForEach = isNativeFunc(Array.prototype.forEach) ? Array.prototype.forEach : function(fun /*, thisp */) {
+ //"use strict";
+
+ if (this === void 0 || this === null) {
+ throw new TypeError();
+ }
+
+ var t = Object(this);
+ var len = t.length >>> 0;
+ if (typeof fun !== "function") {
+ throw new TypeError();
+ }
+
+ var thisp = arguments[1];
+ for (var i = 0; i < len; i++) {
+ if (i in t) {
+ fun.call(thisp, t[i], i, t);
+ }
+ }
+};
+
+var arrayIndexOf = isNativeFunc(Array.prototype.indexOf) ? Array.prototype.indexOf : function (obj, fromIndex) {
+ if (fromIndex === null || fromIndex === undefined) { fromIndex = 0; }
+ else if (fromIndex < 0) { fromIndex = Math.max(0, this.length + fromIndex); }
+ for (var i = fromIndex, j = this.length; i < j; i++) {
+ if (this[i] === obj) { return i; }
+ }
+ return -1;
+};
+
+
+ /**
+ Array polyfills to support ES5 features in older browsers.
+
+ @namespace Ember
+ @property ArrayPolyfills
+ */
+ Ember.ArrayPolyfills = {
+ map: arrayMap,
+ forEach: arrayForEach,
+ indexOf: arrayIndexOf
+ };
+
+
+if (Ember.SHIM_ES5) {
+ if (!Array.prototype.map) {
+ Array.prototype.map = arrayMap;
+ }
+
+ if (!Array.prototype.forEach) {
+ Array.prototype.forEach = arrayForEach;
+ }
+
+ if (!Array.prototype.indexOf) {
+ Array.prototype.indexOf = arrayIndexOf;
+ }
+}
+
+})();
+
+
+
+(function() {
+var errorProps = ['description', 'fileName', 'lineNumber', 'message', 'name', 'number', 'stack'];
+
+/**
+ A subclass of the JavaScript Error object for use in Ember.
+
+ @class Error
+ @namespace Ember
+ @extends Error
+ @constructor
+*/
+Ember.Error = function() {
+ var tmp = Error.apply(this, arguments);
+
+ // Adds a `stack` property to the given error object that will yield the
+ // stack trace at the time captureStackTrace was called.
+ // When collecting the stack trace all frames above the topmost call
+ // to this function, including that call, will be left out of the
+ // stack trace.
+ // This is useful because we can hide Ember implementation details
+ // that are not very helpful for the user.
+ if (Error.captureStackTrace) {
+ Error.captureStackTrace(this, Ember.Error);
+ }
+ // Unfortunately errors are not enumerable in Chrome (at least), so `for prop in tmp` doesn't work.
+ for (var idx = 0; idx < errorProps.length; idx++) {
+ this[errorProps[idx]] = tmp[errorProps[idx]];
+ }
+};
+
+Ember.Error.prototype = Ember.create(Error.prototype);
+
+// ..........................................................
+// ERROR HANDLING
+//
+
+/**
+ A function may be assigned to `Ember.onerror` to be called when Ember
+ internals encounter an error. This is useful for specialized error handling
+ and reporting code.
+
+ ```javascript
+ Ember.onerror = function(error) {
+ Em.$.ajax('/report-error', 'POST', {
+ stack: error.stack,
+ otherInformation: 'whatever app state you want to provide'
+ });
+ };
+ ```
+
+ @event onerror
+ @for Ember
+ @param {Exception} error the error object
+*/
+Ember.onerror = null;
+
+/**
+ Wrap code block in a try/catch if `Ember.onerror` is set.
+
+ @private
+ @method handleErrors
+ @for Ember
+ @param {Function} func
+ @param [context]
+*/
+Ember.handleErrors = function(func, context) {
+ // Unfortunately in some browsers we lose the backtrace if we rethrow the existing error,
+ // so in the event that we don't have an `onerror` handler we don't wrap in a try/catch
+ if ('function' === typeof Ember.onerror) {
+ try {
+ return func.call(context || this);
+ } catch (error) {
+ Ember.onerror(error);
+ }
+ } else {
+ return func.call(context || this);
+ }
+};
+
+})();
+
+
+
+(function() {
+/**
+@module ember-metal
+*/
+
+/**
+ Prefix used for guids through out Ember.
+ @private
+*/
+Ember.GUID_PREFIX = 'ember';
+
+
+var o_defineProperty = Ember.platform.defineProperty,
+ o_create = Ember.create,
+ // Used for guid generation...
+ GUID_KEY = '__ember'+ (+ new Date()),
+ uuid = 0,
+ numberCache = [],
+ stringCache = {};
+
+var MANDATORY_SETTER = Ember.ENV.MANDATORY_SETTER;
+
+/**
+ A unique key used to assign guids and other private metadata to objects.
+ If you inspect an object in your browser debugger you will often see these.
+ They can be safely ignored.
+
+ On browsers that support it, these properties are added with enumeration
+ disabled so they won't show up when you iterate over your properties.
+
+ @private
+ @property GUID_KEY
+ @for Ember
+ @type String
+ @final
+*/
+Ember.GUID_KEY = GUID_KEY;
+
+var GUID_DESC = {
+ writable: false,
+ configurable: false,
+ enumerable: false,
+ value: null
+};
+
+/**
+ Generates a new guid, optionally saving the guid to the object that you
+ pass in. You will rarely need to use this method. Instead you should
+ call `Ember.guidFor(obj)`, which return an existing guid if available.
+
+ @private
+ @method generateGuid
+ @for Ember
+ @param {Object} [obj] Object the guid will be used for. If passed in, the guid will
+ be saved on the object and reused whenever you pass the same object
+ again.
+
+ If no object is passed, just generate a new guid.
+ @param {String} [prefix] Prefix to place in front of the guid. Useful when you want to
+ separate the guid into separate namespaces.
+ @return {String} the guid
+*/
+Ember.generateGuid = function generateGuid(obj, prefix) {
+ if (!prefix) prefix = Ember.GUID_PREFIX;
+ var ret = (prefix + (uuid++));
+ if (obj) {
+ GUID_DESC.value = ret;
+ o_defineProperty(obj, GUID_KEY, GUID_DESC);
+ }
+ return ret;
+};
+
+/**
+ Returns a unique id for the object. If the object does not yet have a guid,
+ one will be assigned to it. You can call this on any object,
+ `Ember.Object`-based or not, but be aware that it will add a `_guid`
+ property.
+
+ You can also use this method on DOM Element objects.
+
+ @private
+ @method guidFor
+ @for Ember
+ @param {Object} obj any object, string, number, Element, or primitive
+ @return {String} the unique guid for this instance.
+*/
+Ember.guidFor = function guidFor(obj) {
+
+ // special cases where we don't want to add a key to object
+ if (obj === undefined) return "(undefined)";
+ if (obj === null) return "(null)";
+
+ var ret;
+ var type = typeof obj;
+
+ // Don't allow prototype changes to String etc. to change the guidFor
+ switch(type) {
+ case 'number':
+ ret = numberCache[obj];
+ if (!ret) ret = numberCache[obj] = 'nu'+obj;
+ return ret;
+
+ case 'string':
+ ret = stringCache[obj];
+ if (!ret) ret = stringCache[obj] = 'st'+(uuid++);
+ return ret;
+
+ case 'boolean':
+ return obj ? '(true)' : '(false)';
+
+ default:
+ if (obj[GUID_KEY]) return obj[GUID_KEY];
+ if (obj === Object) return '(Object)';
+ if (obj === Array) return '(Array)';
+ ret = 'ember'+(uuid++);
+ GUID_DESC.value = ret;
+ o_defineProperty(obj, GUID_KEY, GUID_DESC);
+ return ret;
+ }
+};
+
+// ..........................................................
+// META
+//
+
+var META_DESC = Ember.META_DESC = {
+ writable: true,
+ configurable: false,
+ enumerable: false,
+ value: null
+};
+
+var META_KEY = Ember.GUID_KEY+'_meta';
+
+/**
+ The key used to store meta information on object for property observing.
+
+ @property META_KEY
+ @for Ember
+ @private
+ @final
+ @type String
+*/
+Ember.META_KEY = META_KEY;
+
+var isDefinePropertySimulated = Ember.platform.defineProperty.isSimulated;
+
+function Meta(obj) {
+ this.descs = {};
+ this.watching = {};
+ this.cache = {};
+ this.source = obj;
+}
+
+Meta.prototype = {
+ descs: null,
+ deps: null,
+ watching: null,
+ listeners: null,
+ cache: null,
+ source: null,
+ mixins: null,
+ bindings: null,
+ chains: null,
+ chainWatchers: null,
+ values: null,
+ proto: null
+};
+
+if (isDefinePropertySimulated) {
+ // on platforms that don't support enumerable false
+ // make meta fail jQuery.isPlainObject() to hide from
+ // jQuery.extend() by having a property that fails
+ // hasOwnProperty check.
+ Meta.prototype.__preventPlainObject__ = true;
+
+ // Without non-enumerable properties, meta objects will be output in JSON
+ // unless explicitly suppressed
+ Meta.prototype.toJSON = function () { };
+}
+
+// Placeholder for non-writable metas.
+var EMPTY_META = new Meta(null);
+
+if (MANDATORY_SETTER) { EMPTY_META.values = {}; }
+
+Ember.EMPTY_META = EMPTY_META;
+
+/**
+ Retrieves the meta hash for an object. If `writable` is true ensures the
+ hash is writable for this object as well.
+
+ The meta object contains information about computed property descriptors as
+ well as any watched properties and other information. You generally will
+ not access this information directly but instead work with higher level
+ methods that manipulate this hash indirectly.
+
+ @method meta
+ @for Ember
+ @private
+
+ @param {Object} obj The object to retrieve meta for
+ @param {Boolean} [writable=true] Pass `false` if you do not intend to modify
+ the meta hash, allowing the method to avoid making an unnecessary copy.
+ @return {Object} the meta hash for an object
+*/
+Ember.meta = function meta(obj, writable) {
+
+ var ret = obj[META_KEY];
+ if (writable===false) return ret || EMPTY_META;
+
+ if (!ret) {
+ if (!isDefinePropertySimulated) o_defineProperty(obj, META_KEY, META_DESC);
+
+ ret = new Meta(obj);
+
+ if (MANDATORY_SETTER) { ret.values = {}; }
+
+ obj[META_KEY] = ret;
+
+ // make sure we don't accidentally try to create constructor like desc
+ ret.descs.constructor = null;
+
+ } else if (ret.source !== obj) {
+ if (!isDefinePropertySimulated) o_defineProperty(obj, META_KEY, META_DESC);
+
+ ret = o_create(ret);
+ ret.descs = o_create(ret.descs);
+ ret.watching = o_create(ret.watching);
+ ret.cache = {};
+ ret.source = obj;
+
+ if (MANDATORY_SETTER) { ret.values = o_create(ret.values); }
+
+ obj[META_KEY] = ret;
+ }
+ return ret;
+};
+
+Ember.getMeta = function getMeta(obj, property) {
+ var meta = Ember.meta(obj, false);
+ return meta[property];
+};
+
+Ember.setMeta = function setMeta(obj, property, value) {
+ var meta = Ember.meta(obj, true);
+ meta[property] = value;
+ return value;
+};
+
+/**
+ @deprecated
+ @private
+
+ In order to store defaults for a class, a prototype may need to create
+ a default meta object, which will be inherited by any objects instantiated
+ from the class's constructor.
+
+ However, the properties of that meta object are only shallow-cloned,
+ so if a property is a hash (like the event system's `listeners` hash),
+ it will by default be shared across all instances of that class.
+
+ This method allows extensions to deeply clone a series of nested hashes or
+ other complex objects. For instance, the event system might pass
+ `['listeners', 'foo:change', 'ember157']` to `prepareMetaPath`, which will
+ walk down the keys provided.
+
+ For each key, if the key does not exist, it is created. If it already
+ exists and it was inherited from its constructor, the constructor's
+ key is cloned.
+
+ You can also pass false for `writable`, which will simply return
+ undefined if `prepareMetaPath` discovers any part of the path that
+ shared or undefined.
+
+ @method metaPath
+ @for Ember
+ @param {Object} obj The object whose meta we are examining
+ @param {Array} path An array of keys to walk down
+ @param {Boolean} writable whether or not to create a new meta
+ (or meta property) if one does not already exist or if it's
+ shared with its constructor
+*/
+Ember.metaPath = function metaPath(obj, path, writable) {
+ Ember.deprecate("Ember.metaPath is deprecated and will be removed from future releases.");
+ var meta = Ember.meta(obj, writable), keyName, value;
+
+ for (var i=0, l=path.length; i size ? size : ends;
+ if (count <= 0) { count = 0; }
+
+ chunk = args.splice(0, size);
+ chunk = [start, count].concat(chunk);
+
+ start += size;
+ ends -= count;
+
+ ret = ret.concat(splice.apply(array, chunk));
+ }
+ return ret;
+ },
+
+ /**
+ * Replaces objects in an array with the passed objects.
+ *
+ * ```javascript
+ * var array = [1,2,3];
+ * Ember.EnumerableUtils.replace(array, 1, 2, [4, 5]); // [1, 4, 5]
+ *
+ * var array = [1,2,3];
+ * Ember.EnumerableUtils.replace(array, 1, 1, [4, 5]); // [1, 4, 5, 3]
+ *
+ * var array = [1,2,3];
+ * Ember.EnumerableUtils.replace(array, 10, 1, [4, 5]); // [1, 2, 3, 4, 5]
+ * ```
+ *
+ * @method replace
+ * @param {Array} array The array the objects should be inserted into.
+ * @param {Number} idx Starting index in the array to replace. If *idx* >=
+ * length, then append to the end of the array.
+ * @param {Number} amt Number of elements that should be remove from the array,
+ * starting at *idx*
+ * @param {Array} objects An array of zero or more objects that should be
+ * inserted into the array at *idx*
+ *
+ * @return {Array} The changed array.
+ */
+ replace: function(array, idx, amt, objects) {
+ if (array.replace) {
+ return array.replace(idx, amt, objects);
+ } else {
+ return utils._replace(array, idx, amt, objects);
+ }
+ },
+
+ /**
+ * Calculates the intersection of two arrays. This method returns a new array
+ * filled with the records that the two passed arrays share with each other.
+ * If there is no intersection, an empty array will be returned.
+ *
+ * ```javascript
+ * var array1 = [1, 2, 3, 4, 5];
+ * var array2 = [1, 3, 5, 6, 7];
+ *
+ * Ember.EnumerableUtils.intersection(array1, array2); // [1, 3, 5]
+ *
+ * var array1 = [1, 2, 3];
+ * var array2 = [4, 5, 6];
+ *
+ * Ember.EnumerableUtils.intersection(array1, array2); // []
+ * ```
+ *
+ * @method intersection
+ * @param {Array} array1 The first array
+ * @param {Array} array2 The second array
+ *
+ * @return {Array} The intersection of the two passed arrays.
+ */
+ intersection: function(array1, array2) {
+ var intersection = [];
+
+ utils.forEach(array1, function(element) {
+ if (utils.indexOf(array2, element) >= 0) {
+ intersection.push(element);
+ }
+ });
+
+ return intersection;
+ }
+};
+
+})();
+
+
+
+(function() {
+/**
+@module ember-metal
+*/
+
+var META_KEY = Ember.META_KEY, get;
+
+var MANDATORY_SETTER = Ember.ENV.MANDATORY_SETTER;
+
+var IS_GLOBAL_PATH = /^([A-Z$]|([0-9][A-Z$])).*[\.\*]/;
+var HAS_THIS = /^this[\.\*]/;
+var FIRST_KEY = /^([^\.\*]+)/;
+
+// ..........................................................
+// GET AND SET
+//
+// If we are on a platform that supports accessors we can use those.
+// Otherwise simulate accessors by looking up the property directly on the
+// object.
+
+/**
+ Gets the value of a property on an object. If the property is computed,
+ the function will be invoked. If the property is not defined but the
+ object implements the `unknownProperty` method then that will be invoked.
+
+ If you plan to run on IE8 and older browsers then you should use this
+ method anytime you want to retrieve a property on an object that you don't
+ know for sure is private. (Properties beginning with an underscore '_'
+ are considered private.)
+
+ On all newer browsers, you only need to use this method to retrieve
+ properties if the property might not be defined on the object and you want
+ to respect the `unknownProperty` handler. Otherwise you can ignore this
+ method.
+
+ Note that if the object itself is `undefined`, this method will throw
+ an error.
+
+ @method get
+ @for Ember
+ @param {Object} obj The object to retrieve from.
+ @param {String} keyName The property key to retrieve
+ @return {Object} the property value or `null`.
+*/
+get = function get(obj, keyName) {
+ // Helpers that operate with 'this' within an #each
+ if (keyName === '') {
+ return obj;
+ }
+
+ if (!keyName && 'string'===typeof obj) {
+ keyName = obj;
+ obj = null;
+ }
+
+ Ember.assert("Cannot call get with "+ keyName +" key.", !!keyName);
+ Ember.assert("Cannot call get with '"+ keyName +"' on an undefined object.", obj !== undefined);
+
+ if (obj === null || keyName.indexOf('.') !== -1) {
+ return getPath(obj, keyName);
+ }
+
+ var meta = obj[META_KEY], desc = meta && meta.descs[keyName], ret;
+ if (desc) {
+ return desc.get(obj, keyName);
+ } else {
+ if (MANDATORY_SETTER && meta && meta.watching[keyName] > 0) {
+ ret = meta.values[keyName];
+ } else {
+ ret = obj[keyName];
+ }
+
+ if (ret === undefined &&
+ 'object' === typeof obj && !(keyName in obj) && 'function' === typeof obj.unknownProperty) {
+ return obj.unknownProperty(keyName);
+ }
+
+ return ret;
+ }
+};
+
+// Currently used only by Ember Data tests
+if (Ember.config.overrideAccessors) {
+ Ember.get = get;
+ Ember.config.overrideAccessors();
+ get = Ember.get;
+}
+
+/**
+ Normalizes a target/path pair to reflect that actual target/path that should
+ be observed, etc. This takes into account passing in global property
+ paths (i.e. a path beginning with a captial letter not defined on the
+ target) and * separators.
+
+ @private
+ @method normalizeTuple
+ @for Ember
+ @param {Object} target The current target. May be `null`.
+ @param {String} path A path on the target or a global property path.
+ @return {Array} a temporary array with the normalized target/path pair.
+*/
+var normalizeTuple = Ember.normalizeTuple = function(target, path) {
+ var hasThis = HAS_THIS.test(path),
+ isGlobal = !hasThis && IS_GLOBAL_PATH.test(path),
+ key;
+
+ if (!target || isGlobal) target = Ember.lookup;
+ if (hasThis) path = path.slice(5);
+
+ if (target === Ember.lookup) {
+ key = path.match(FIRST_KEY)[0];
+ target = get(target, key);
+ path = path.slice(key.length+1);
+ }
+
+ // must return some kind of path to be valid else other things will break.
+ if (!path || path.length===0) throw new Ember.Error('Path cannot be empty');
+
+ return [ target, path ];
+};
+
+var getPath = Ember._getPath = function(root, path) {
+ var hasThis, parts, tuple, idx, len;
+
+ // If there is no root and path is a key name, return that
+ // property from the global object.
+ // E.g. get('Ember') -> Ember
+ if (root === null && path.indexOf('.') === -1) { return get(Ember.lookup, path); }
+
+ // detect complicated paths and normalize them
+ hasThis = HAS_THIS.test(path);
+
+ if (!root || hasThis) {
+ tuple = normalizeTuple(root, path);
+ root = tuple[0];
+ path = tuple[1];
+ tuple.length = 0;
+ }
+
+ parts = path.split(".");
+ len = parts.length;
+ for (idx = 0; root != null && idx < len; idx++) {
+ root = get(root, parts[idx], true);
+ if (root && root.isDestroyed) { return undefined; }
+ }
+ return root;
+};
+
+Ember.getWithDefault = function(root, key, defaultValue) {
+ var value = get(root, key);
+
+ if (value === undefined) { return defaultValue; }
+ return value;
+};
+
+
+Ember.get = get;
+
+})();
+
+
+
+(function() {
+/**
+@module ember-metal
+*/
+
+var o_create = Ember.create,
+ metaFor = Ember.meta,
+ META_KEY = Ember.META_KEY,
+ a_slice = [].slice,
+ /* listener flags */
+ ONCE = 1, SUSPENDED = 2;
+
+/*
+ The event system uses a series of nested hashes to store listeners on an
+ object. When a listener is registered, or when an event arrives, these
+ hashes are consulted to determine which target and action pair to invoke.
+
+ The hashes are stored in the object's meta hash, and look like this:
+
+ // Object's meta hash
+ {
+ listeners: { // variable name: `listenerSet`
+ "foo:changed": [ // variable name: `actions`
+ target, method, flags
+ ]
+ }
+ }
+
+*/
+
+function indexOf(array, target, method) {
+ var index = -1;
+ for (var i = 0, l = array.length; i < l; i += 3) {
+ if (target === array[i] && method === array[i+1]) { index = i; break; }
+ }
+ return index;
+}
+
+function actionsFor(obj, eventName) {
+ var meta = metaFor(obj, true),
+ actions;
+
+ if (!meta.listeners) { meta.listeners = {}; }
+
+ if (!meta.hasOwnProperty('listeners')) {
+ // setup inherited copy of the listeners object
+ meta.listeners = o_create(meta.listeners);
+ }
+
+ actions = meta.listeners[eventName];
+
+ // if there are actions, but the eventName doesn't exist in our listeners, then copy them from the prototype
+ if (actions && !meta.listeners.hasOwnProperty(eventName)) {
+ actions = meta.listeners[eventName] = meta.listeners[eventName].slice();
+ } else if (!actions) {
+ actions = meta.listeners[eventName] = [];
+ }
+
+ return actions;
+}
+
+function actionsUnion(obj, eventName, otherActions) {
+ var meta = obj[META_KEY],
+ actions = meta && meta.listeners && meta.listeners[eventName];
+
+ if (!actions) { return; }
+ for (var i = actions.length - 3; i >= 0; i -= 3) {
+ var target = actions[i],
+ method = actions[i+1],
+ flags = actions[i+2],
+ actionIndex = indexOf(otherActions, target, method);
+
+ if (actionIndex === -1) {
+ otherActions.push(target, method, flags);
+ }
+ }
+}
+
+function actionsDiff(obj, eventName, otherActions) {
+ var meta = obj[META_KEY],
+ actions = meta && meta.listeners && meta.listeners[eventName],
+ diffActions = [];
+
+ if (!actions) { return; }
+ for (var i = actions.length - 3; i >= 0; i -= 3) {
+ var target = actions[i],
+ method = actions[i+1],
+ flags = actions[i+2],
+ actionIndex = indexOf(otherActions, target, method);
+
+ if (actionIndex !== -1) { continue; }
+
+ otherActions.push(target, method, flags);
+ diffActions.push(target, method, flags);
+ }
+
+ return diffActions;
+}
+
+/**
+ Add an event listener
+
+ @method addListener
+ @for Ember
+ @param obj
+ @param {String} eventName
+ @param {Object|Function} targetOrMethod A target object or a function
+ @param {Function|String} method A function or the name of a function to be called on `target`
+ @param {Boolean} once A flag whether a function should only be called once
+*/
+function addListener(obj, eventName, target, method, once) {
+ Ember.assert("You must pass at least an object and event name to Ember.addListener", !!obj && !!eventName);
+
+ if (!method && 'function' === typeof target) {
+ method = target;
+ target = null;
+ }
+
+ var actions = actionsFor(obj, eventName),
+ actionIndex = indexOf(actions, target, method),
+ flags = 0;
+
+ if (once) flags |= ONCE;
+
+ if (actionIndex !== -1) { return; }
+
+ actions.push(target, method, flags);
+
+ if ('function' === typeof obj.didAddListener) {
+ obj.didAddListener(eventName, target, method);
+ }
+}
+
+/**
+ Remove an event listener
+
+ Arguments should match those passed to `Ember.addListener`.
+
+ @method removeListener
+ @for Ember
+ @param obj
+ @param {String} eventName
+ @param {Object|Function} targetOrMethod A target object or a function
+ @param {Function|String} method A function or the name of a function to be called on `target`
+*/
+function removeListener(obj, eventName, target, method) {
+ Ember.assert("You must pass at least an object and event name to Ember.removeListener", !!obj && !!eventName);
+
+ if (!method && 'function' === typeof target) {
+ method = target;
+ target = null;
+ }
+
+ function _removeListener(target, method) {
+ var actions = actionsFor(obj, eventName),
+ actionIndex = indexOf(actions, target, method);
+
+ // action doesn't exist, give up silently
+ if (actionIndex === -1) { return; }
+
+ actions.splice(actionIndex, 3);
+
+ if ('function' === typeof obj.didRemoveListener) {
+ obj.didRemoveListener(eventName, target, method);
+ }
+ }
+
+ if (method) {
+ _removeListener(target, method);
+ } else {
+ var meta = obj[META_KEY],
+ actions = meta && meta.listeners && meta.listeners[eventName];
+
+ if (!actions) { return; }
+ for (var i = actions.length - 3; i >= 0; i -= 3) {
+ _removeListener(actions[i], actions[i+1]);
+ }
+ }
+}
+
+/**
+ Suspend listener during callback.
+
+ This should only be used by the target of the event listener
+ when it is taking an action that would cause the event, e.g.
+ an object might suspend its property change listener while it is
+ setting that property.
+
+ @private
+ @method suspendListener
+ @for Ember
+ @param obj
+ @param {String} eventName
+ @param {Object|Function} targetOrMethod A target object or a function
+ @param {Function|String} method A function or the name of a function to be called on `target`
+ @param {Function} callback
+*/
+function suspendListener(obj, eventName, target, method, callback) {
+ if (!method && 'function' === typeof target) {
+ method = target;
+ target = null;
+ }
+
+ var actions = actionsFor(obj, eventName),
+ actionIndex = indexOf(actions, target, method);
+
+ if (actionIndex !== -1) {
+ actions[actionIndex+2] |= SUSPENDED; // mark the action as suspended
+ }
+
+ function tryable() { return callback.call(target); }
+ function finalizer() { if (actionIndex !== -1) { actions[actionIndex+2] &= ~SUSPENDED; } }
+
+ return Ember.tryFinally(tryable, finalizer);
+}
+
+/**
+ Suspends multiple listeners during a callback.
+
+ @private
+ @method suspendListeners
+ @for Ember
+ @param obj
+ @param {Array} eventName Array of event names
+ @param {Object|Function} targetOrMethod A target object or a function
+ @param {Function|String} method A function or the name of a function to be called on `target`
+ @param {Function} callback
+*/
+function suspendListeners(obj, eventNames, target, method, callback) {
+ if (!method && 'function' === typeof target) {
+ method = target;
+ target = null;
+ }
+
+ var suspendedActions = [],
+ actionsList = [],
+ eventName, actions, i, l;
+
+ for (i=0, l=eventNames.length; i= 0; i -= 3) { // looping in reverse for once listeners
+ var target = actions[i], method = actions[i+1], flags = actions[i+2];
+ if (!method) { continue; }
+ if (flags & SUSPENDED) { continue; }
+ if (flags & ONCE) { removeListener(obj, eventName, target, method); }
+ if (!target) { target = obj; }
+ if ('string' === typeof method) { method = target[method]; }
+ if (params) {
+ method.apply(target, params);
+ } else {
+ method.call(target);
+ }
+ }
+ return true;
+}
+
+/**
+ @private
+ @method hasListeners
+ @for Ember
+ @param obj
+ @param {String} eventName
+*/
+function hasListeners(obj, eventName) {
+ var meta = obj[META_KEY],
+ actions = meta && meta.listeners && meta.listeners[eventName];
+
+ return !!(actions && actions.length);
+}
+
+/**
+ @private
+ @method listenersFor
+ @for Ember
+ @param obj
+ @param {String} eventName
+*/
+function listenersFor(obj, eventName) {
+ var ret = [];
+ var meta = obj[META_KEY],
+ actions = meta && meta.listeners && meta.listeners[eventName];
+
+ if (!actions) { return ret; }
+
+ for (var i = 0, l = actions.length; i < l; i += 3) {
+ var target = actions[i],
+ method = actions[i+1];
+ ret.push([target, method]);
+ }
+
+ return ret;
+}
+
+/**
+ Define a property as a function that should be executed when
+ a specified event or events are triggered.
+
+
+ ``` javascript
+ var Job = Ember.Object.extend({
+ logCompleted: Ember.on('completed', function(){
+ console.log('Job completed!');
+ })
+ });
+ var job = Job.create();
+ Ember.sendEvent(job, 'completed'); // Logs "Job completed!"
+ ```
+
+ @method on
+ @for Ember
+ @param {String} eventNames*
+ @param {Function} func
+ @return func
+*/
+Ember.on = function(){
+ var func = a_slice.call(arguments, -1)[0],
+ events = a_slice.call(arguments, 0, -1);
+ func.__ember_listens__ = events;
+ return func;
+};
+
+Ember.addListener = addListener;
+Ember.removeListener = removeListener;
+Ember._suspendListener = suspendListener;
+Ember._suspendListeners = suspendListeners;
+Ember.sendEvent = sendEvent;
+Ember.hasListeners = hasListeners;
+Ember.watchedEvents = watchedEvents;
+Ember.listenersFor = listenersFor;
+Ember.listenersDiff = actionsDiff;
+Ember.listenersUnion = actionsUnion;
+
+})();
+
+
+
+(function() {
+var guidFor = Ember.guidFor,
+ sendEvent = Ember.sendEvent;
+
+/*
+ this.observerSet = {
+ [senderGuid]: { // variable name: `keySet`
+ [keyName]: listIndex
+ }
+ },
+ this.observers = [
+ {
+ sender: obj,
+ keyName: keyName,
+ eventName: eventName,
+ listeners: [
+ [target, method, flags]
+ ]
+ },
+ ...
+ ]
+*/
+var ObserverSet = Ember._ObserverSet = function() {
+ this.clear();
+};
+
+ObserverSet.prototype.add = function(sender, keyName, eventName) {
+ var observerSet = this.observerSet,
+ observers = this.observers,
+ senderGuid = guidFor(sender),
+ keySet = observerSet[senderGuid],
+ index;
+
+ if (!keySet) {
+ observerSet[senderGuid] = keySet = {};
+ }
+ index = keySet[keyName];
+ if (index === undefined) {
+ index = observers.push({
+ sender: sender,
+ keyName: keyName,
+ eventName: eventName,
+ listeners: []
+ }) - 1;
+ keySet[keyName] = index;
+ }
+ return observers[index].listeners;
+};
+
+ObserverSet.prototype.flush = function() {
+ var observers = this.observers, i, len, observer, sender;
+ this.clear();
+ for (i=0, len=observers.length; i < len; ++i) {
+ observer = observers[i];
+ sender = observer.sender;
+ if (sender.isDestroying || sender.isDestroyed) { continue; }
+ sendEvent(sender, observer.eventName, [sender, observer.keyName], observer.listeners);
+ }
+};
+
+ObserverSet.prototype.clear = function() {
+ this.observerSet = {};
+ this.observers = [];
+};
+})();
+
+
+
+(function() {
+var META_KEY = Ember.META_KEY,
+ guidFor = Ember.guidFor,
+ tryFinally = Ember.tryFinally,
+ sendEvent = Ember.sendEvent,
+ listenersUnion = Ember.listenersUnion,
+ listenersDiff = Ember.listenersDiff,
+ ObserverSet = Ember._ObserverSet,
+ beforeObserverSet = new ObserverSet(),
+ observerSet = new ObserverSet(),
+ deferred = 0;
+
+// ..........................................................
+// PROPERTY CHANGES
+//
+
+/**
+ This function is called just before an object property is about to change.
+ It will notify any before observers and prepare caches among other things.
+
+ Normally you will not need to call this method directly but if for some
+ reason you can't directly watch a property you can invoke this method
+ manually along with `Ember.propertyDidChange()` which you should call just
+ after the property value changes.
+
+ @method propertyWillChange
+ @for Ember
+ @param {Object} obj The object with the property that will change
+ @param {String} keyName The property key (or path) that will change.
+ @return {void}
+*/
+function propertyWillChange(obj, keyName) {
+ var m = obj[META_KEY],
+ watching = (m && m.watching[keyName] > 0) || keyName === 'length',
+ proto = m && m.proto,
+ desc = m && m.descs[keyName];
+
+ if (!watching) { return; }
+ if (proto === obj) { return; }
+ if (desc && desc.willChange) { desc.willChange(obj, keyName); }
+ dependentKeysWillChange(obj, keyName, m);
+ chainsWillChange(obj, keyName, m);
+ notifyBeforeObservers(obj, keyName);
+}
+Ember.propertyWillChange = propertyWillChange;
+
+/**
+ This function is called just after an object property has changed.
+ It will notify any observers and clear caches among other things.
+
+ Normally you will not need to call this method directly but if for some
+ reason you can't directly watch a property you can invoke this method
+ manually along with `Ember.propertyWillChange()` which you should call just
+ before the property value changes.
+
+ @method propertyDidChange
+ @for Ember
+ @param {Object} obj The object with the property that will change
+ @param {String} keyName The property key (or path) that will change.
+ @return {void}
+*/
+function propertyDidChange(obj, keyName) {
+ var m = obj[META_KEY],
+ watching = (m && m.watching[keyName] > 0) || keyName === 'length',
+ proto = m && m.proto,
+ desc = m && m.descs[keyName];
+
+ if (proto === obj) { return; }
+
+ // shouldn't this mean that we're watching this key?
+ if (desc && desc.didChange) { desc.didChange(obj, keyName); }
+ if (!watching && keyName !== 'length') { return; }
+
+ dependentKeysDidChange(obj, keyName, m);
+ chainsDidChange(obj, keyName, m, false);
+ notifyObservers(obj, keyName);
+}
+Ember.propertyDidChange = propertyDidChange;
+
+var WILL_SEEN, DID_SEEN;
+
+// called whenever a property is about to change to clear the cache of any dependent keys (and notify those properties of changes, etc...)
+function dependentKeysWillChange(obj, depKey, meta) {
+ if (obj.isDestroying) { return; }
+
+ var seen = WILL_SEEN, top = !seen;
+ if (top) { seen = WILL_SEEN = {}; }
+ iterDeps(propertyWillChange, obj, depKey, seen, meta);
+ if (top) { WILL_SEEN = null; }
+}
+
+// called whenever a property has just changed to update dependent keys
+function dependentKeysDidChange(obj, depKey, meta) {
+ if (obj.isDestroying) { return; }
+
+ var seen = DID_SEEN, top = !seen;
+ if (top) { seen = DID_SEEN = {}; }
+ iterDeps(propertyDidChange, obj, depKey, seen, meta);
+ if (top) { DID_SEEN = null; }
+}
+
+function iterDeps(method, obj, depKey, seen, meta) {
+ var guid = guidFor(obj);
+ if (!seen[guid]) seen[guid] = {};
+ if (seen[guid][depKey]) return;
+ seen[guid][depKey] = true;
+
+ var deps = meta.deps;
+ deps = deps && deps[depKey];
+ if (deps) {
+ for(var key in deps) {
+ var desc = meta.descs[key];
+ if (desc && desc._suspended === obj) continue;
+ method(obj, key);
+ }
+ }
+}
+
+function chainsWillChange(obj, keyName, m) {
+ if (!(m.hasOwnProperty('chainWatchers') &&
+ m.chainWatchers[keyName])) {
+ return;
+ }
+
+ var nodes = m.chainWatchers[keyName],
+ events = [],
+ i, l;
+
+ for(i = 0, l = nodes.length; i < l; i++) {
+ nodes[i].willChange(events);
+ }
+
+ for (i = 0, l = events.length; i < l; i += 2) {
+ propertyWillChange(events[i], events[i+1]);
+ }
+}
+
+function chainsDidChange(obj, keyName, m, suppressEvents) {
+ if (!(m && m.hasOwnProperty('chainWatchers') &&
+ m.chainWatchers[keyName])) {
+ return;
+ }
+
+ var nodes = m.chainWatchers[keyName],
+ events = suppressEvents ? null : [],
+ i, l;
+
+ for(i = 0, l = nodes.length; i < l; i++) {
+ nodes[i].didChange(events);
+ }
+
+ if (suppressEvents) {
+ return;
+ }
+
+ for (i = 0, l = events.length; i < l; i += 2) {
+ propertyDidChange(events[i], events[i+1]);
+ }
+}
+
+Ember.overrideChains = function(obj, keyName, m) {
+ chainsDidChange(obj, keyName, m, true);
+};
+
+/**
+ @method beginPropertyChanges
+ @chainable
+ @private
+*/
+function beginPropertyChanges() {
+ deferred++;
+}
+
+Ember.beginPropertyChanges = beginPropertyChanges;
+
+/**
+ @method endPropertyChanges
+ @private
+*/
+function endPropertyChanges() {
+ deferred--;
+ if (deferred<=0) {
+ beforeObserverSet.clear();
+ observerSet.flush();
+ }
+}
+
+Ember.endPropertyChanges = endPropertyChanges;
+
+/**
+ Make a series of property changes together in an
+ exception-safe way.
+
+ ```javascript
+ Ember.changeProperties(function() {
+ obj1.set('foo', mayBlowUpWhenSet);
+ obj2.set('bar', baz);
+ });
+ ```
+
+ @method changeProperties
+ @param {Function} callback
+ @param [binding]
+*/
+Ember.changeProperties = function(cb, binding) {
+ beginPropertyChanges();
+ tryFinally(cb, endPropertyChanges, binding);
+};
+
+function notifyBeforeObservers(obj, keyName) {
+ if (obj.isDestroying) { return; }
+
+ var eventName = keyName + ':before', listeners, diff;
+ if (deferred) {
+ listeners = beforeObserverSet.add(obj, keyName, eventName);
+ diff = listenersDiff(obj, eventName, listeners);
+ sendEvent(obj, eventName, [obj, keyName], diff);
+ } else {
+ sendEvent(obj, eventName, [obj, keyName]);
+ }
+}
+
+function notifyObservers(obj, keyName) {
+ if (obj.isDestroying) { return; }
+
+ var eventName = keyName + ':change', listeners;
+ if (deferred) {
+ listeners = observerSet.add(obj, keyName, eventName);
+ listenersUnion(obj, eventName, listeners);
+ } else {
+ sendEvent(obj, eventName, [obj, keyName]);
+ }
+}
+
+})();
+
+
+
+(function() {
+// META_KEY
+// _getPath
+// propertyWillChange, propertyDidChange
+
+var META_KEY = Ember.META_KEY,
+ MANDATORY_SETTER = Ember.ENV.MANDATORY_SETTER,
+ IS_GLOBAL = /^([A-Z$]|([0-9][A-Z$]))/,
+ getPath = Ember._getPath;
+
+/**
+ Sets the value of a property on an object, respecting computed properties
+ and notifying observers and other listeners of the change. If the
+ property is not defined but the object implements the `setUnknownProperty`
+ method then that will be invoked as well.
+
+ @method set
+ @for Ember
+ @param {Object} obj The object to modify.
+ @param {String} keyName The property key to set
+ @param {Object} value The value to set
+ @return {Object} the passed value.
+*/
+var set = function set(obj, keyName, value, tolerant) {
+ if (typeof obj === 'string') {
+ Ember.assert("Path '" + obj + "' must be global if no obj is given.", IS_GLOBAL.test(obj));
+ value = keyName;
+ keyName = obj;
+ obj = null;
+ }
+
+ Ember.assert("Cannot call set with "+ keyName +" key.", !!keyName);
+
+ if (!obj || keyName.indexOf('.') !== -1) {
+ return setPath(obj, keyName, value, tolerant);
+ }
+
+ Ember.assert("You need to provide an object and key to `set`.", !!obj && keyName !== undefined);
+ Ember.assert('calling set on destroyed object', !obj.isDestroyed);
+
+ var meta = obj[META_KEY], desc = meta && meta.descs[keyName],
+ isUnknown, currentValue;
+ if (desc) {
+ desc.set(obj, keyName, value);
+ } else {
+ isUnknown = 'object' === typeof obj && !(keyName in obj);
+
+ // setUnknownProperty is called if `obj` is an object,
+ // the property does not already exist, and the
+ // `setUnknownProperty` method exists on the object
+ if (isUnknown && 'function' === typeof obj.setUnknownProperty) {
+ obj.setUnknownProperty(keyName, value);
+ } else if (meta && meta.watching[keyName] > 0) {
+ if (MANDATORY_SETTER) {
+ currentValue = meta.values[keyName];
+ } else {
+ currentValue = obj[keyName];
+ }
+ // only trigger a change if the value has changed
+ if (value !== currentValue) {
+ Ember.propertyWillChange(obj, keyName);
+ if (MANDATORY_SETTER) {
+ if ((currentValue === undefined && !(keyName in obj)) || !obj.propertyIsEnumerable(keyName)) {
+ Ember.defineProperty(obj, keyName, null, value); // setup mandatory setter
+ } else {
+ meta.values[keyName] = value;
+ }
+ } else {
+ obj[keyName] = value;
+ }
+ Ember.propertyDidChange(obj, keyName);
+ }
+ } else {
+ obj[keyName] = value;
+ }
+ }
+ return value;
+};
+
+// Currently used only by Ember Data tests
+if (Ember.config.overrideAccessors) {
+ Ember.set = set;
+ Ember.config.overrideAccessors();
+ set = Ember.set;
+}
+
+function setPath(root, path, value, tolerant) {
+ var keyName;
+
+ // get the last part of the path
+ keyName = path.slice(path.lastIndexOf('.') + 1);
+
+ // get the first part of the part
+ path = (path === keyName) ? keyName : path.slice(0, path.length-(keyName.length+1));
+
+ // unless the path is this, look up the first part to
+ // get the root
+ if (path !== 'this') {
+ root = getPath(root, path);
+ }
+
+ if (!keyName || keyName.length === 0) {
+ throw new Ember.Error('Property set failed: You passed an empty path');
+ }
+
+ if (!root) {
+ if (tolerant) { return; }
+ else { throw new Ember.Error('Property set failed: object in path "'+path+'" could not be found or was destroyed.'); }
+ }
+
+ return set(root, keyName, value);
+}
+
+Ember.set = set;
+
+/**
+ Error-tolerant form of `Ember.set`. Will not blow up if any part of the
+ chain is `undefined`, `null`, or destroyed.
+
+ This is primarily used when syncing bindings, which may try to update after
+ an object has been destroyed.
+
+ @method trySet
+ @for Ember
+ @param {Object} obj The object to modify.
+ @param {String} path The property path to set
+ @param {Object} value The value to set
+*/
+Ember.trySet = function(root, path, value) {
+ return set(root, path, value, true);
+};
+
+})();
+
+
+
+(function() {
+/**
+@module ember-metal
+*/
+
+/*
+ JavaScript (before ES6) does not have a Map implementation. Objects,
+ which are often used as dictionaries, may only have Strings as keys.
+
+ Because Ember has a way to get a unique identifier for every object
+ via `Ember.guidFor`, we can implement a performant Map with arbitrary
+ keys. Because it is commonly used in low-level bookkeeping, Map is
+ implemented as a pure JavaScript object for performance.
+
+ This implementation follows the current iteration of the ES6 proposal for
+ maps (http://wiki.ecmascript.org/doku.php?id=harmony:simple_maps_and_sets),
+ with two exceptions. First, because we need our implementation to be pleasant
+ on older browsers, we do not use the `delete` name (using `remove` instead).
+ Second, as we do not have the luxury of in-VM iteration, we implement a
+ forEach method for iteration.
+
+ Map is mocked out to look like an Ember object, so you can do
+ `Ember.Map.create()` for symmetry with other Ember classes.
+*/
+var set = Ember.set,
+ guidFor = Ember.guidFor,
+ indexOf = Ember.ArrayPolyfills.indexOf;
+
+var copy = function(obj) {
+ var output = {};
+
+ for (var prop in obj) {
+ if (obj.hasOwnProperty(prop)) { output[prop] = obj[prop]; }
+ }
+
+ return output;
+};
+
+var copyMap = function(original, newObject) {
+ var keys = original.keys.copy(),
+ values = copy(original.values);
+
+ newObject.keys = keys;
+ newObject.values = values;
+ newObject.length = original.length;
+
+ return newObject;
+};
+
+/**
+ This class is used internally by Ember and Ember Data.
+ Please do not use it at this time. We plan to clean it up
+ and add many tests soon.
+
+ @class OrderedSet
+ @namespace Ember
+ @constructor
+ @private
+*/
+var OrderedSet = Ember.OrderedSet = function() {
+ this.clear();
+};
+
+/**
+ @method create
+ @static
+ @return {Ember.OrderedSet}
+*/
+OrderedSet.create = function() {
+ return new OrderedSet();
+};
+
+
+OrderedSet.prototype = {
+ /**
+ @method clear
+ */
+ clear: function() {
+ this.presenceSet = {};
+ this.list = [];
+ },
+
+ /**
+ @method add
+ @param obj
+ */
+ add: function(obj) {
+ var guid = guidFor(obj),
+ presenceSet = this.presenceSet,
+ list = this.list;
+
+ if (guid in presenceSet) { return; }
+
+ presenceSet[guid] = true;
+ list.push(obj);
+ },
+
+ /**
+ @method remove
+ @param obj
+ */
+ remove: function(obj) {
+ var guid = guidFor(obj),
+ presenceSet = this.presenceSet,
+ list = this.list;
+
+ delete presenceSet[guid];
+
+ var index = indexOf.call(list, obj);
+ if (index > -1) {
+ list.splice(index, 1);
+ }
+ },
+
+ /**
+ @method isEmpty
+ @return {Boolean}
+ */
+ isEmpty: function() {
+ return this.list.length === 0;
+ },
+
+ /**
+ @method has
+ @param obj
+ @return {Boolean}
+ */
+ has: function(obj) {
+ var guid = guidFor(obj),
+ presenceSet = this.presenceSet;
+
+ return guid in presenceSet;
+ },
+
+ /**
+ @method forEach
+ @param {Function} fn
+ @param self
+ */
+ forEach: function(fn, self) {
+ // allow mutation during iteration
+ var list = this.toArray();
+
+ for (var i = 0, j = list.length; i < j; i++) {
+ fn.call(self, list[i]);
+ }
+ },
+
+ /**
+ @method toArray
+ @return {Array}
+ */
+ toArray: function() {
+ return this.list.slice();
+ },
+
+ /**
+ @method copy
+ @return {Ember.OrderedSet}
+ */
+ copy: function() {
+ var set = new OrderedSet();
+
+ set.presenceSet = copy(this.presenceSet);
+ set.list = this.toArray();
+
+ return set;
+ }
+};
+
+/**
+ A Map stores values indexed by keys. Unlike JavaScript's
+ default Objects, the keys of a Map can be any JavaScript
+ object.
+
+ Internally, a Map has two data structures:
+
+ 1. `keys`: an OrderedSet of all of the existing keys
+ 2. `values`: a JavaScript Object indexed by the `Ember.guidFor(key)`
+
+ When a key/value pair is added for the first time, we
+ add the key to the `keys` OrderedSet, and create or
+ replace an entry in `values`. When an entry is deleted,
+ we delete its entry in `keys` and `values`.
+
+ @class Map
+ @namespace Ember
+ @private
+ @constructor
+*/
+var Map = Ember.Map = function() {
+ this.keys = Ember.OrderedSet.create();
+ this.values = {};
+};
+
+/**
+ @method create
+ @static
+*/
+Map.create = function() {
+ return new Map();
+};
+
+Map.prototype = {
+ /**
+ This property will change as the number of objects in the map changes.
+
+ @property length
+ @type number
+ @default 0
+ */
+ length: 0,
+
+
+ /**
+ Retrieve the value associated with a given key.
+
+ @method get
+ @param {*} key
+ @return {*} the value associated with the key, or `undefined`
+ */
+ get: function(key) {
+ var values = this.values,
+ guid = guidFor(key);
+
+ return values[guid];
+ },
+
+ /**
+ Adds a value to the map. If a value for the given key has already been
+ provided, the new value will replace the old value.
+
+ @method set
+ @param {*} key
+ @param {*} value
+ */
+ set: function(key, value) {
+ var keys = this.keys,
+ values = this.values,
+ guid = guidFor(key);
+
+ keys.add(key);
+ values[guid] = value;
+ set(this, 'length', keys.list.length);
+ },
+
+ /**
+ Removes a value from the map for an associated key.
+
+ @method remove
+ @param {*} key
+ @return {Boolean} true if an item was removed, false otherwise
+ */
+ remove: function(key) {
+ // don't use ES6 "delete" because it will be annoying
+ // to use in browsers that are not ES6 friendly;
+ var keys = this.keys,
+ values = this.values,
+ guid = guidFor(key);
+
+ if (values.hasOwnProperty(guid)) {
+ keys.remove(key);
+ delete values[guid];
+ set(this, 'length', keys.list.length);
+ return true;
+ } else {
+ return false;
+ }
+ },
+
+ /**
+ Check whether a key is present.
+
+ @method has
+ @param {*} key
+ @return {Boolean} true if the item was present, false otherwise
+ */
+ has: function(key) {
+ var values = this.values,
+ guid = guidFor(key);
+
+ return values.hasOwnProperty(guid);
+ },
+
+ /**
+ Iterate over all the keys and values. Calls the function once
+ for each key, passing in the key and value, in that order.
+
+ The keys are guaranteed to be iterated over in insertion order.
+
+ @method forEach
+ @param {Function} callback
+ @param {*} self if passed, the `this` value inside the
+ callback. By default, `this` is the map.
+ */
+ forEach: function(callback, self) {
+ var keys = this.keys,
+ values = this.values;
+
+ keys.forEach(function(key) {
+ var guid = guidFor(key);
+ callback.call(self, key, values[guid]);
+ });
+ },
+
+ /**
+ @method copy
+ @return {Ember.Map}
+ */
+ copy: function() {
+ return copyMap(this, new Map());
+ }
+};
+
+/**
+ @class MapWithDefault
+ @namespace Ember
+ @extends Ember.Map
+ @private
+ @constructor
+ @param [options]
+ @param {*} [options.defaultValue]
+*/
+var MapWithDefault = Ember.MapWithDefault = function(options) {
+ Map.call(this);
+ this.defaultValue = options.defaultValue;
+};
+
+/**
+ @method create
+ @static
+ @param [options]
+ @param {*} [options.defaultValue]
+ @return {Ember.MapWithDefault|Ember.Map} If options are passed, returns
+ `Ember.MapWithDefault` otherwise returns `Ember.Map`
+*/
+MapWithDefault.create = function(options) {
+ if (options) {
+ return new MapWithDefault(options);
+ } else {
+ return new Map();
+ }
+};
+
+MapWithDefault.prototype = Ember.create(Map.prototype);
+
+/**
+ Retrieve the value associated with a given key.
+
+ @method get
+ @param {*} key
+ @return {*} the value associated with the key, or the default value
+*/
+MapWithDefault.prototype.get = function(key) {
+ var hasValue = this.has(key);
+
+ if (hasValue) {
+ return Map.prototype.get.call(this, key);
+ } else {
+ var defaultValue = this.defaultValue(key);
+ this.set(key, defaultValue);
+ return defaultValue;
+ }
+};
+
+/**
+ @method copy
+ @return {Ember.MapWithDefault}
+*/
+MapWithDefault.prototype.copy = function() {
+ return copyMap(this, new MapWithDefault({
+ defaultValue: this.defaultValue
+ }));
+};
+
+})();
+
+
+
+(function() {
+function consoleMethod(name) {
+ var consoleObj, logToConsole;
+ if (Ember.imports.console) {
+ consoleObj = Ember.imports.console;
+ } else if (typeof console !== 'undefined') {
+ consoleObj = console;
+ }
+
+ var method = typeof consoleObj === 'object' ? consoleObj[name] : null;
+
+ if (method) {
+ // Older IE doesn't support apply, but Chrome needs it
+ if (method.apply) {
+ logToConsole = function() {
+ method.apply(consoleObj, arguments);
+ };
+ logToConsole.displayName = 'console.' + name;
+ return logToConsole;
+ } else {
+ return function() {
+ var message = Array.prototype.join.call(arguments, ', ');
+ method(message);
+ };
+ }
+ }
+}
+
+function assertPolyfill(test, message) {
+ if (!test) {
+ try {
+ // attempt to preserve the stack
+ throw new Ember.Error("assertion failed: " + message);
+ } catch(error) {
+ setTimeout(function() {
+ throw error;
+ }, 0);
+ }
+ }
+}
+
+/**
+ Inside Ember-Metal, simply uses the methods from `imports.console`.
+ Override this to provide more robust logging functionality.
+
+ @class Logger
+ @namespace Ember
+*/
+Ember.Logger = {
+ /**
+ Logs the arguments to the console.
+ You can pass as many arguments as you want and they will be joined together with a space.
+
+ ```javascript
+ var foo = 1;
+ Ember.Logger.log('log value of foo:', foo); // "log value of foo: 1" will be printed to the console
+ ```
+
+ @method log
+ @for Ember.Logger
+ @param {*} arguments
+ */
+ log: consoleMethod('log') || Ember.K,
+
+ /**
+ Prints the arguments to the console with a warning icon.
+ You can pass as many arguments as you want and they will be joined together with a space.
+
+ ```javascript
+ Ember.Logger.warn('Something happened!'); // "Something happened!" will be printed to the console with a warning icon.
+ ```
+
+ @method warn
+ @for Ember.Logger
+ @param {*} arguments
+ */
+ warn: consoleMethod('warn') || Ember.K,
+
+ /**
+ Prints the arguments to the console with an error icon, red text and a stack trace.
+ You can pass as many arguments as you want and they will be joined together with a space.
+
+ ```javascript
+ Ember.Logger.error('Danger! Danger!'); // "Danger! Danger!" will be printed to the console in red text.
+ ```
+
+ @method error
+ @for Ember.Logger
+ @param {*} arguments
+ */
+ error: consoleMethod('error') || Ember.K,
+
+ /**
+ Logs the arguments to the console.
+ You can pass as many arguments as you want and they will be joined together with a space.
+
+ ```javascript
+ var foo = 1;
+ Ember.Logger.info('log value of foo:', foo); // "log value of foo: 1" will be printed to the console
+ ```
+
+ @method info
+ @for Ember.Logger
+ @param {*} arguments
+ */
+ info: consoleMethod('info') || Ember.K,
+
+ /**
+ Logs the arguments to the console in blue text.
+ You can pass as many arguments as you want and they will be joined together with a space.
+
+ ```javascript
+ var foo = 1;
+ Ember.Logger.debug('log value of foo:', foo); // "log value of foo: 1" will be printed to the console
+ ```
+
+ @method debug
+ @for Ember.Logger
+ @param {*} arguments
+ */
+ debug: consoleMethod('debug') || consoleMethod('info') || Ember.K,
+
+ /**
+ If the value passed into `Ember.Logger.assert` is not truthy it will throw an error with a stack trace.
+
+ ```javascript
+ Ember.Logger.assert(true); // undefined
+ Ember.Logger.assert(true === false); // Throws an Assertion failed error.
+ ```
+
+ @method assert
+ @for Ember.Logger
+ @param {Boolean} bool Value to test
+ */
+ assert: consoleMethod('assert') || assertPolyfill
+};
+
+
+})();
+
+
+
+(function() {
+/**
+@module ember-metal
+*/
+
+var META_KEY = Ember.META_KEY,
+ metaFor = Ember.meta,
+ objectDefineProperty = Ember.platform.defineProperty;
+
+var MANDATORY_SETTER = Ember.ENV.MANDATORY_SETTER;
+
+// ..........................................................
+// DESCRIPTOR
+//
+
+/**
+ Objects of this type can implement an interface to respond to requests to
+ get and set. The default implementation handles simple properties.
+
+ You generally won't need to create or subclass this directly.
+
+ @class Descriptor
+ @namespace Ember
+ @private
+ @constructor
+*/
+Ember.Descriptor = function() {};
+
+// ..........................................................
+// DEFINING PROPERTIES API
+//
+
+var MANDATORY_SETTER_FUNCTION = Ember.MANDATORY_SETTER_FUNCTION = function(value) {
+ Ember.assert("You must use Ember.set() to access this property (of " + this + ")", false);
+};
+
+var DEFAULT_GETTER_FUNCTION = Ember.DEFAULT_GETTER_FUNCTION = function(name) {
+ return function() {
+ var meta = this[META_KEY];
+ return meta && meta.values[name];
+ };
+};
+
+/**
+ NOTE: This is a low-level method used by other parts of the API. You almost
+ never want to call this method directly. Instead you should use
+ `Ember.mixin()` to define new properties.
+
+ Defines a property on an object. This method works much like the ES5
+ `Object.defineProperty()` method except that it can also accept computed
+ properties and other special descriptors.
+
+ Normally this method takes only three parameters. However if you pass an
+ instance of `Ember.Descriptor` as the third param then you can pass an
+ optional value as the fourth parameter. This is often more efficient than
+ creating new descriptor hashes for each property.
+
+ ## Examples
+
+ ```javascript
+ // ES5 compatible mode
+ Ember.defineProperty(contact, 'firstName', {
+ writable: true,
+ configurable: false,
+ enumerable: true,
+ value: 'Charles'
+ });
+
+ // define a simple property
+ Ember.defineProperty(contact, 'lastName', undefined, 'Jolley');
+
+ // define a computed property
+ Ember.defineProperty(contact, 'fullName', Ember.computed(function() {
+ return this.firstName+' '+this.lastName;
+ }).property('firstName', 'lastName'));
+ ```
+
+ @private
+ @method defineProperty
+ @for Ember
+ @param {Object} obj the object to define this property on. This may be a prototype.
+ @param {String} keyName the name of the property
+ @param {Ember.Descriptor} [desc] an instance of `Ember.Descriptor` (typically a
+ computed property) or an ES5 descriptor.
+ You must provide this or `data` but not both.
+ @param {*} [data] something other than a descriptor, that will
+ become the explicit value of this property.
+*/
+Ember.defineProperty = function(obj, keyName, desc, data, meta) {
+ var descs, existingDesc, watching, value;
+
+ if (!meta) meta = metaFor(obj);
+ descs = meta.descs;
+ existingDesc = meta.descs[keyName];
+ watching = meta.watching[keyName] > 0;
+
+ if (existingDesc instanceof Ember.Descriptor) {
+ existingDesc.teardown(obj, keyName);
+ }
+
+ if (desc instanceof Ember.Descriptor) {
+ value = desc;
+
+ descs[keyName] = desc;
+ if (MANDATORY_SETTER && watching) {
+ objectDefineProperty(obj, keyName, {
+ configurable: true,
+ enumerable: true,
+ writable: true,
+ value: undefined // make enumerable
+ });
+ } else {
+ obj[keyName] = undefined; // make enumerable
+ }
+
+ } else {
+ descs[keyName] = undefined; // shadow descriptor in proto
+ if (desc == null) {
+ value = data;
+
+ if (MANDATORY_SETTER && watching) {
+ meta.values[keyName] = data;
+ objectDefineProperty(obj, keyName, {
+ configurable: true,
+ enumerable: true,
+ set: MANDATORY_SETTER_FUNCTION,
+ get: DEFAULT_GETTER_FUNCTION(keyName)
+ });
+ } else {
+ obj[keyName] = data;
+ }
+ } else {
+ value = desc;
+
+ // compatibility with ES5
+ objectDefineProperty(obj, keyName, desc);
+ }
+ }
+
+ // if key is being watched, override chains that
+ // were initialized with the prototype
+ if (watching) { Ember.overrideChains(obj, keyName, meta); }
+
+ // The `value` passed to the `didDefineProperty` hook is
+ // either the descriptor or data, whichever was passed.
+ if (obj.didDefineProperty) { obj.didDefineProperty(obj, keyName, value); }
+
+ return this;
+};
+
+
+})();
+
+
+
+(function() {
+var get = Ember.get;
+
+/**
+ To get multiple properties at once, call `Ember.getProperties`
+ with an object followed by a list of strings or an array:
+
+ ```javascript
+ Ember.getProperties(record, 'firstName', 'lastName', 'zipCode'); // { firstName: 'John', lastName: 'Doe', zipCode: '10011' }
+ ```
+
+ is equivalent to:
+
+ ```javascript
+ Ember.getProperties(record, ['firstName', 'lastName', 'zipCode']); // { firstName: 'John', lastName: 'Doe', zipCode: '10011' }
+ ```
+
+ @method getProperties
+ @param obj
+ @param {String...|Array} list of keys to get
+ @return {Hash}
+*/
+Ember.getProperties = function(obj) {
+ var ret = {},
+ propertyNames = arguments,
+ i = 1;
+
+ if (arguments.length === 2 && Ember.typeOf(arguments[1]) === 'array') {
+ i = 0;
+ propertyNames = arguments[1];
+ }
+ for(var len = propertyNames.length; i < len; i++) {
+ ret[propertyNames[i]] = get(obj, propertyNames[i]);
+ }
+ return ret;
+};
+
+})();
+
+
+
+(function() {
+var changeProperties = Ember.changeProperties,
+ set = Ember.set;
+
+/**
+ Set a list of properties on an object. These properties are set inside
+ a single `beginPropertyChanges` and `endPropertyChanges` batch, so
+ observers will be buffered.
+
+ ```javascript
+ anObject.setProperties({
+ firstName: "Stanley",
+ lastName: "Stuart",
+ age: "21"
+ })
+ ```
+
+ @method setProperties
+ @param self
+ @param {Object} hash
+ @return self
+*/
+Ember.setProperties = function(self, hash) {
+ changeProperties(function() {
+ for(var prop in hash) {
+ if (hash.hasOwnProperty(prop)) { set(self, prop, hash[prop]); }
+ }
+ });
+ return self;
+};
+
+})();
+
+
+
+(function() {
+var metaFor = Ember.meta, // utils.js
+ typeOf = Ember.typeOf, // utils.js
+ MANDATORY_SETTER = Ember.ENV.MANDATORY_SETTER,
+ o_defineProperty = Ember.platform.defineProperty;
+
+Ember.watchKey = function(obj, keyName, meta) {
+ // can't watch length on Array - it is special...
+ if (keyName === 'length' && typeOf(obj) === 'array') { return; }
+
+ var m = meta || metaFor(obj), watching = m.watching;
+
+ // activate watching first time
+ if (!watching[keyName]) {
+ watching[keyName] = 1;
+
+ if ('function' === typeof obj.willWatchProperty) {
+ obj.willWatchProperty(keyName);
+ }
+
+ if (MANDATORY_SETTER && keyName in obj) {
+ m.values[keyName] = obj[keyName];
+ o_defineProperty(obj, keyName, {
+ configurable: true,
+ enumerable: obj.propertyIsEnumerable(keyName),
+ set: Ember.MANDATORY_SETTER_FUNCTION,
+ get: Ember.DEFAULT_GETTER_FUNCTION(keyName)
+ });
+ }
+ } else {
+ watching[keyName] = (watching[keyName] || 0) + 1;
+ }
+};
+
+
+Ember.unwatchKey = function(obj, keyName, meta) {
+ var m = meta || metaFor(obj), watching = m.watching;
+
+ if (watching[keyName] === 1) {
+ watching[keyName] = 0;
+
+ if ('function' === typeof obj.didUnwatchProperty) {
+ obj.didUnwatchProperty(keyName);
+ }
+
+ if (MANDATORY_SETTER && keyName in obj) {
+ o_defineProperty(obj, keyName, {
+ configurable: true,
+ enumerable: obj.propertyIsEnumerable(keyName),
+ set: function(val) {
+ // redefine to set as enumerable
+ o_defineProperty(obj, keyName, {
+ configurable: true,
+ writable: true,
+ enumerable: true,
+ value: val
+ });
+ delete m.values[keyName];
+ },
+ get: Ember.DEFAULT_GETTER_FUNCTION(keyName)
+ });
+ }
+ } else if (watching[keyName] > 1) {
+ watching[keyName]--;
+ }
+};
+
+})();
+
+
+
+(function() {
+var metaFor = Ember.meta, // utils.js
+ get = Ember.get, // property_get.js
+ normalizeTuple = Ember.normalizeTuple, // property_get.js
+ forEach = Ember.ArrayPolyfills.forEach, // array.js
+ warn = Ember.warn,
+ watchKey = Ember.watchKey,
+ unwatchKey = Ember.unwatchKey,
+ FIRST_KEY = /^([^\.\*]+)/,
+ META_KEY = Ember.META_KEY;
+
+function firstKey(path) {
+ return path.match(FIRST_KEY)[0];
+}
+
+var pendingQueue = [];
+
+// attempts to add the pendingQueue chains again. If some of them end up
+// back in the queue and reschedule is true, schedules a timeout to try
+// again.
+Ember.flushPendingChains = function() {
+ if (pendingQueue.length === 0) { return; } // nothing to do
+
+ var queue = pendingQueue;
+ pendingQueue = [];
+
+ forEach.call(queue, function(q) { q[0].add(q[1]); });
+
+ warn('Watching an undefined global, Ember expects watched globals to be setup by the time the run loop is flushed, check for typos', pendingQueue.length === 0);
+};
+
+
+function addChainWatcher(obj, keyName, node) {
+ if (!obj || ('object' !== typeof obj)) { return; } // nothing to do
+
+ var m = metaFor(obj), nodes = m.chainWatchers;
+
+ if (!m.hasOwnProperty('chainWatchers')) {
+ nodes = m.chainWatchers = {};
+ }
+
+ if (!nodes[keyName]) { nodes[keyName] = []; }
+ nodes[keyName].push(node);
+ watchKey(obj, keyName, m);
+}
+
+var removeChainWatcher = Ember.removeChainWatcher = function(obj, keyName, node) {
+ if (!obj || 'object' !== typeof obj) { return; } // nothing to do
+
+ var m = obj[META_KEY];
+ if (m && !m.hasOwnProperty('chainWatchers')) { return; } // nothing to do
+
+ var nodes = m && m.chainWatchers;
+
+ if (nodes && nodes[keyName]) {
+ nodes = nodes[keyName];
+ for (var i = 0, l = nodes.length; i < l; i++) {
+ if (nodes[i] === node) { nodes.splice(i, 1); }
+ }
+ }
+ unwatchKey(obj, keyName, m);
+};
+
+// A ChainNode watches a single key on an object. If you provide a starting
+// value for the key then the node won't actually watch it. For a root node
+// pass null for parent and key and object for value.
+var ChainNode = Ember._ChainNode = function(parent, key, value) {
+ this._parent = parent;
+ this._key = key;
+
+ // _watching is true when calling get(this._parent, this._key) will
+ // return the value of this node.
+ //
+ // It is false for the root of a chain (because we have no parent)
+ // and for global paths (because the parent node is the object with
+ // the observer on it)
+ this._watching = value===undefined;
+
+ this._value = value;
+ this._paths = {};
+ if (this._watching) {
+ this._object = parent.value();
+ if (this._object) { addChainWatcher(this._object, this._key, this); }
+ }
+
+ // Special-case: the EachProxy relies on immediate evaluation to
+ // establish its observers.
+ //
+ // TODO: Replace this with an efficient callback that the EachProxy
+ // can implement.
+ if (this._parent && this._parent._key === '@each') {
+ this.value();
+ }
+};
+
+var ChainNodePrototype = ChainNode.prototype;
+
+function lazyGet(obj, key) {
+ if (!obj) return undefined;
+
+ var meta = obj[META_KEY];
+ // check if object meant only to be a prototype
+ if (meta && meta.proto === obj) return undefined;
+
+ if (key === "@each") return get(obj, key);
+
+ // if a CP only return cached value
+ var desc = meta && meta.descs[key];
+ if (desc && desc._cacheable) {
+ if (key in meta.cache) {
+ return meta.cache[key];
+ } else {
+ return undefined;
+ }
+ }
+
+ return get(obj, key);
+}
+
+ChainNodePrototype.value = function() {
+ if (this._value === undefined && this._watching) {
+ var obj = this._parent.value();
+ this._value = lazyGet(obj, this._key);
+ }
+ return this._value;
+};
+
+ChainNodePrototype.destroy = function() {
+ if (this._watching) {
+ var obj = this._object;
+ if (obj) { removeChainWatcher(obj, this._key, this); }
+ this._watching = false; // so future calls do nothing
+ }
+};
+
+// copies a top level object only
+ChainNodePrototype.copy = function(obj) {
+ var ret = new ChainNode(null, null, obj),
+ paths = this._paths, path;
+ for (path in paths) {
+ if (paths[path] <= 0) { continue; } // this check will also catch non-number vals.
+ ret.add(path);
+ }
+ return ret;
+};
+
+// called on the root node of a chain to setup watchers on the specified
+// path.
+ChainNodePrototype.add = function(path) {
+ var obj, tuple, key, src, paths;
+
+ paths = this._paths;
+ paths[path] = (paths[path] || 0) + 1;
+
+ obj = this.value();
+ tuple = normalizeTuple(obj, path);
+
+ // the path was a local path
+ if (tuple[0] && tuple[0] === obj) {
+ path = tuple[1];
+ key = firstKey(path);
+ path = path.slice(key.length+1);
+
+ // global path, but object does not exist yet.
+ // put into a queue and try to connect later.
+ } else if (!tuple[0]) {
+ pendingQueue.push([this, path]);
+ tuple.length = 0;
+ return;
+
+ // global path, and object already exists
+ } else {
+ src = tuple[0];
+ key = path.slice(0, 0-(tuple[1].length+1));
+ path = tuple[1];
+ }
+
+ tuple.length = 0;
+ this.chain(key, path, src);
+};
+
+// called on the root node of a chain to teardown watcher on the specified
+// path
+ChainNodePrototype.remove = function(path) {
+ var obj, tuple, key, src, paths;
+
+ paths = this._paths;
+ if (paths[path] > 0) { paths[path]--; }
+
+ obj = this.value();
+ tuple = normalizeTuple(obj, path);
+ if (tuple[0] === obj) {
+ path = tuple[1];
+ key = firstKey(path);
+ path = path.slice(key.length+1);
+ } else {
+ src = tuple[0];
+ key = path.slice(0, 0-(tuple[1].length+1));
+ path = tuple[1];
+ }
+
+ tuple.length = 0;
+ this.unchain(key, path);
+};
+
+ChainNodePrototype.count = 0;
+
+ChainNodePrototype.chain = function(key, path, src) {
+ var chains = this._chains, node;
+ if (!chains) { chains = this._chains = {}; }
+
+ node = chains[key];
+ if (!node) { node = chains[key] = new ChainNode(this, key, src); }
+ node.count++; // count chains...
+
+ // chain rest of path if there is one
+ if (path && path.length>0) {
+ key = firstKey(path);
+ path = path.slice(key.length+1);
+ node.chain(key, path); // NOTE: no src means it will observe changes...
+ }
+};
+
+ChainNodePrototype.unchain = function(key, path) {
+ var chains = this._chains, node = chains[key];
+
+ // unchain rest of path first...
+ if (path && path.length>1) {
+ key = firstKey(path);
+ path = path.slice(key.length+1);
+ node.unchain(key, path);
+ }
+
+ // delete node if needed.
+ node.count--;
+ if (node.count<=0) {
+ delete chains[node._key];
+ node.destroy();
+ }
+
+};
+
+ChainNodePrototype.willChange = function(events) {
+ var chains = this._chains;
+ if (chains) {
+ for(var key in chains) {
+ if (!chains.hasOwnProperty(key)) { continue; }
+ chains[key].willChange(events);
+ }
+ }
+
+ if (this._parent) { this._parent.chainWillChange(this, this._key, 1, events); }
+};
+
+ChainNodePrototype.chainWillChange = function(chain, path, depth, events) {
+ if (this._key) { path = this._key + '.' + path; }
+
+ if (this._parent) {
+ this._parent.chainWillChange(this, path, depth+1, events);
+ } else {
+ if (depth > 1) {
+ events.push(this.value(), path);
+ }
+ path = 'this.' + path;
+ if (this._paths[path] > 0) {
+ events.push(this.value(), path);
+ }
+ }
+};
+
+ChainNodePrototype.chainDidChange = function(chain, path, depth, events) {
+ if (this._key) { path = this._key + '.' + path; }
+ if (this._parent) {
+ this._parent.chainDidChange(this, path, depth+1, events);
+ } else {
+ if (depth > 1) {
+ events.push(this.value(), path);
+ }
+ path = 'this.' + path;
+ if (this._paths[path] > 0) {
+ events.push(this.value(), path);
+ }
+ }
+};
+
+ChainNodePrototype.didChange = function(events) {
+ // invalidate my own value first.
+ if (this._watching) {
+ var obj = this._parent.value();
+ if (obj !== this._object) {
+ removeChainWatcher(this._object, this._key, this);
+ this._object = obj;
+ addChainWatcher(obj, this._key, this);
+ }
+ this._value = undefined;
+
+ // Special-case: the EachProxy relies on immediate evaluation to
+ // establish its observers.
+ if (this._parent && this._parent._key === '@each')
+ this.value();
+ }
+
+ // then notify chains...
+ var chains = this._chains;
+ if (chains) {
+ for(var key in chains) {
+ if (!chains.hasOwnProperty(key)) { continue; }
+ chains[key].didChange(events);
+ }
+ }
+
+ // if no events are passed in then we only care about the above wiring update
+ if (events === null) { return; }
+
+ // and finally tell parent about my path changing...
+ if (this._parent) { this._parent.chainDidChange(this, this._key, 1, events); }
+};
+
+Ember.finishChains = function(obj) {
+ // We only create meta if we really have to
+ var m = obj[META_KEY], chains = m && m.chains;
+ if (chains) {
+ if (chains.value() !== obj) {
+ metaFor(obj).chains = chains = chains.copy(obj);
+ } else {
+ chains.didChange(null);
+ }
+ }
+};
+
+})();
+
+
+
+(function() {
+/**
+ @module ember-metal
+ */
+
+var forEach = Ember.EnumerableUtils.forEach,
+BRACE_EXPANSION = /^((?:[^\.]*\.)*)\{(.*)\}$/;
+
+/**
+ Expands `pattern`, invoking `callback` for each expansion.
+
+ The only pattern supported is brace-expansion, anything else will be passed
+ once to `callback` directly. Brace expansion can only appear at the end of a
+ pattern, for example as the last item in a chain.
+
+ Example
+ ```js
+ function echo(arg){ console.log(arg); }
+
+ Ember.expandProperties('foo.bar', echo); //=> 'foo.bar'
+ Ember.expandProperties('{foo,bar}', echo); //=> 'foo', 'bar'
+ Ember.expandProperties('foo.{bar,baz}', echo); //=> 'foo.bar', 'foo.baz'
+ Ember.expandProperties('{foo,bar}.baz', echo); //=> '{foo,bar}.baz'
+ ```
+
+ @method
+ @private
+ @param {string} pattern The property pattern to expand.
+ @param {function} callback The callback to invoke. It is invoked once per
+ expansion, and is passed the expansion.
+ */
+Ember.expandProperties = function (pattern, callback) {
+ var match, prefix, list;
+
+ if (match = BRACE_EXPANSION.exec(pattern)) {
+ prefix = match[1];
+ list = match[2];
+
+ forEach(list.split(','), function (suffix) {
+ callback(prefix + suffix);
+ });
+ } else {
+ callback(pattern);
+ }
+};
+
+})();
+
+
+
+(function() {
+var metaFor = Ember.meta, // utils.js
+ typeOf = Ember.typeOf, // utils.js
+ ChainNode = Ember._ChainNode; // chains.js
+
+// get the chains for the current object. If the current object has
+// chains inherited from the proto they will be cloned and reconfigured for
+// the current object.
+function chainsFor(obj, meta) {
+ var m = meta || metaFor(obj), ret = m.chains;
+ if (!ret) {
+ ret = m.chains = new ChainNode(null, null, obj);
+ } else if (ret.value() !== obj) {
+ ret = m.chains = ret.copy(obj);
+ }
+ return ret;
+}
+
+Ember.watchPath = function(obj, keyPath, meta) {
+ // can't watch length on Array - it is special...
+ if (keyPath === 'length' && typeOf(obj) === 'array') { return; }
+
+ var m = meta || metaFor(obj), watching = m.watching;
+
+ if (!watching[keyPath]) { // activate watching first time
+ watching[keyPath] = 1;
+ chainsFor(obj, m).add(keyPath);
+ } else {
+ watching[keyPath] = (watching[keyPath] || 0) + 1;
+ }
+};
+
+Ember.unwatchPath = function(obj, keyPath, meta) {
+ var m = meta || metaFor(obj), watching = m.watching;
+
+ if (watching[keyPath] === 1) {
+ watching[keyPath] = 0;
+ chainsFor(obj, m).remove(keyPath);
+ } else if (watching[keyPath] > 1) {
+ watching[keyPath]--;
+ }
+};
+})();
+
+
+
+(function() {
+/**
+@module ember-metal
+*/
+
+var metaFor = Ember.meta, // utils.js
+ GUID_KEY = Ember.GUID_KEY, // utils.js
+ META_KEY = Ember.META_KEY, // utils.js
+ removeChainWatcher = Ember.removeChainWatcher,
+ watchKey = Ember.watchKey, // watch_key.js
+ unwatchKey = Ember.unwatchKey,
+ watchPath = Ember.watchPath, // watch_path.js
+ unwatchPath = Ember.unwatchPath,
+ typeOf = Ember.typeOf, // utils.js
+ generateGuid = Ember.generateGuid,
+ IS_PATH = /[\.\*]/;
+
+// returns true if the passed path is just a keyName
+function isKeyName(path) {
+ return path==='*' || !IS_PATH.test(path);
+}
+
+/**
+ Starts watching a property on an object. Whenever the property changes,
+ invokes `Ember.propertyWillChange` and `Ember.propertyDidChange`. This is the
+ primitive used by observers and dependent keys; usually you will never call
+ this method directly but instead use higher level methods like
+ `Ember.addObserver()`
+
+ @private
+ @method watch
+ @for Ember
+ @param obj
+ @param {String} keyName
+*/
+Ember.watch = function(obj, _keyPath, m) {
+ // can't watch length on Array - it is special...
+ if (_keyPath === 'length' && typeOf(obj) === 'array') { return; }
+
+ if (isKeyName(_keyPath)) {
+ watchKey(obj, _keyPath, m);
+ } else {
+ watchPath(obj, _keyPath, m);
+ }
+};
+
+Ember.isWatching = function isWatching(obj, key) {
+ var meta = obj[META_KEY];
+ return (meta && meta.watching[key]) > 0;
+};
+
+Ember.watch.flushPending = Ember.flushPendingChains;
+
+Ember.unwatch = function(obj, _keyPath, m) {
+ // can't watch length on Array - it is special...
+ if (_keyPath === 'length' && typeOf(obj) === 'array') { return; }
+
+ if (isKeyName(_keyPath)) {
+ unwatchKey(obj, _keyPath, m);
+ } else {
+ unwatchPath(obj, _keyPath, m);
+ }
+};
+
+/**
+ Call on an object when you first beget it from another object. This will
+ setup any chained watchers on the object instance as needed. This method is
+ safe to call multiple times.
+
+ @private
+ @method rewatch
+ @for Ember
+ @param obj
+*/
+Ember.rewatch = function(obj) {
+ var m = obj[META_KEY], chains = m && m.chains;
+
+ // make sure the object has its own guid.
+ if (GUID_KEY in obj && !obj.hasOwnProperty(GUID_KEY)) {
+ generateGuid(obj);
+ }
+
+ // make sure any chained watchers update.
+ if (chains && chains.value() !== obj) {
+ m.chains = chains.copy(obj);
+ }
+};
+
+var NODE_STACK = [];
+
+/**
+ Tears down the meta on an object so that it can be garbage collected.
+ Multiple calls will have no effect.
+
+ @method destroy
+ @for Ember
+ @param {Object} obj the object to destroy
+ @return {void}
+*/
+Ember.destroy = function (obj) {
+ var meta = obj[META_KEY], node, nodes, key, nodeObject;
+ if (meta) {
+ obj[META_KEY] = null;
+ // remove chainWatchers to remove circular references that would prevent GC
+ node = meta.chains;
+ if (node) {
+ NODE_STACK.push(node);
+ // process tree
+ while (NODE_STACK.length > 0) {
+ node = NODE_STACK.pop();
+ // push children
+ nodes = node._chains;
+ if (nodes) {
+ for (key in nodes) {
+ if (nodes.hasOwnProperty(key)) {
+ NODE_STACK.push(nodes[key]);
+ }
+ }
+ }
+ // remove chainWatcher in node object
+ if (node._watching) {
+ nodeObject = node._object;
+ if (nodeObject) {
+ removeChainWatcher(nodeObject, node._key, node);
+ }
+ }
+ }
+ }
+ }
+};
+
+})();
+
+
+
+(function() {
+/**
+@module ember-metal
+*/
+
+Ember.warn("The CP_DEFAULT_CACHEABLE flag has been removed and computed properties are always cached by default. Use `volatile` if you don't want caching.", Ember.ENV.CP_DEFAULT_CACHEABLE !== false);
+
+
+var get = Ember.get,
+ set = Ember.set,
+ metaFor = Ember.meta,
+ a_slice = [].slice,
+ o_create = Ember.create,
+ META_KEY = Ember.META_KEY,
+ watch = Ember.watch,
+ unwatch = Ember.unwatch;
+
+var expandProperties = Ember.expandProperties;
+
+// ..........................................................
+// DEPENDENT KEYS
+//
+
+// data structure:
+// meta.deps = {
+// 'depKey': {
+// 'keyName': count,
+// }
+// }
+
+/*
+ This function returns a map of unique dependencies for a
+ given object and key.
+*/
+function keysForDep(depsMeta, depKey) {
+ var keys = depsMeta[depKey];
+ if (!keys) {
+ // if there are no dependencies yet for a the given key
+ // create a new empty list of dependencies for the key
+ keys = depsMeta[depKey] = {};
+ } else if (!depsMeta.hasOwnProperty(depKey)) {
+ // otherwise if the dependency list is inherited from
+ // a superclass, clone the hash
+ keys = depsMeta[depKey] = o_create(keys);
+ }
+ return keys;
+}
+
+function metaForDeps(meta) {
+ return keysForDep(meta, 'deps');
+}
+
+function addDependentKeys(desc, obj, keyName, meta) {
+ // the descriptor has a list of dependent keys, so
+ // add all of its dependent keys.
+ var depKeys = desc._dependentKeys, depsMeta, idx, len, depKey, keys;
+ if (!depKeys) return;
+
+ depsMeta = metaForDeps(meta);
+
+ for(idx = 0, len = depKeys.length; idx < len; idx++) {
+ depKey = depKeys[idx];
+ // Lookup keys meta for depKey
+ keys = keysForDep(depsMeta, depKey);
+ // Increment the number of times depKey depends on keyName.
+ keys[keyName] = (keys[keyName] || 0) + 1;
+ // Watch the depKey
+ watch(obj, depKey, meta);
+ }
+}
+
+function removeDependentKeys(desc, obj, keyName, meta) {
+ // the descriptor has a list of dependent keys, so
+ // add all of its dependent keys.
+ var depKeys = desc._dependentKeys, depsMeta, idx, len, depKey, keys;
+ if (!depKeys) return;
+
+ depsMeta = metaForDeps(meta);
+
+ for(idx = 0, len = depKeys.length; idx < len; idx++) {
+ depKey = depKeys[idx];
+ // Lookup keys meta for depKey
+ keys = keysForDep(depsMeta, depKey);
+ // Increment the number of times depKey depends on keyName.
+ keys[keyName] = (keys[keyName] || 0) - 1;
+ // Watch the depKey
+ unwatch(obj, depKey, meta);
+ }
+}
+
+// ..........................................................
+// COMPUTED PROPERTY
+//
+
+/**
+ A computed property transforms an objects function into a property.
+
+ By default the function backing the computed property will only be called
+ once and the result will be cached. You can specify various properties
+ that your computed property is dependent on. This will force the cached
+ result to be recomputed if the dependencies are modified.
+
+ In the following example we declare a computed property (by calling
+ `.property()` on the fullName function) and setup the properties
+ dependencies (depending on firstName and lastName). The fullName function
+ will be called once (regardless of how many times it is accessed) as long
+ as it's dependencies have not been changed. Once firstName or lastName are updated
+ any future calls (or anything bound) to fullName will incorporate the new
+ values.
+
+ ```javascript
+ Person = Ember.Object.extend({
+ // these will be supplied by `create`
+ firstName: null,
+ lastName: null,
+
+ fullName: function() {
+ var firstName = this.get('firstName');
+ var lastName = this.get('lastName');
+
+ return firstName + ' ' + lastName;
+ }.property('firstName', 'lastName')
+ });
+
+ var tom = Person.create({
+ firstName: "Tom",
+ lastName: "Dale"
+ });
+
+ tom.get('fullName') // "Tom Dale"
+ ```
+
+ You can also define what Ember should do when setting a computed property.
+ If you try to set a computed property, it will be invoked with the key and
+ value you want to set it to. You can also accept the previous value as the
+ third parameter.
+
+ ```javascript
+
+ Person = Ember.Object.extend({
+ // these will be supplied by `create`
+ firstName: null,
+ lastName: null,
+
+ fullName: function(key, value, oldValue) {
+ // getter
+ if (arguments.length === 1) {
+ var firstName = this.get('firstName');
+ var lastName = this.get('lastName');
+
+ return firstName + ' ' + lastName;
+
+ // setter
+ } else {
+ var name = value.split(" ");
+
+ this.set('firstName', name[0]);
+ this.set('lastName', name[1]);
+
+ return value;
+ }
+ }.property('firstName', 'lastName')
+ });
+
+ var person = Person.create();
+ person.set('fullName', "Peter Wagenet");
+ person.get('firstName') // Peter
+ person.get('lastName') // Wagenet
+ ```
+
+ @class ComputedProperty
+ @namespace Ember
+ @extends Ember.Descriptor
+ @constructor
+*/
+function ComputedProperty(func, opts) {
+ this.func = func;
+
+ this._dependentKeys = opts && opts.dependentKeys;
+
+
+ this._cacheable = (opts && opts.cacheable !== undefined) ? opts.cacheable : true;
+ this._readOnly = opts && (opts.readOnly !== undefined || !!opts.readOnly);
+}
+
+Ember.ComputedProperty = ComputedProperty;
+ComputedProperty.prototype = new Ember.Descriptor();
+
+var ComputedPropertyPrototype = ComputedProperty.prototype;
+
+
+/**
+ Properties are cacheable by default. Computed property will automatically
+ cache the return value of your function until one of the dependent keys changes.
+
+ Call `volatile()` to set it into non-cached mode. When in this mode
+ the computed property will not automatically cache the return value.
+
+ However, if a property is properly observable, there is no reason to disable
+ caching.
+
+ @method cacheable
+ @param {Boolean} aFlag optional set to `false` to disable caching
+ @return {Ember.ComputedProperty} this
+ @chainable
+*/
+ComputedPropertyPrototype.cacheable = function(aFlag) {
+ this._cacheable = aFlag !== false;
+ return this;
+};
+
+/**
+ Call on a computed property to set it into non-cached mode. When in this
+ mode the computed property will not automatically cache the return value.
+
+ ```javascript
+ MyApp.outsideService = Ember.Object.extend({
+ value: function() {
+ return OutsideService.getValue();
+ }.property().volatile()
+ }).create();
+ ```
+
+ @method volatile
+ @return {Ember.ComputedProperty} this
+ @chainable
+*/
+ComputedPropertyPrototype.volatile = function() {
+ return this.cacheable(false);
+};
+
+/**
+ Call on a computed property to set it into read-only mode. When in this
+ mode the computed property will throw an error when set.
+
+ ```javascript
+ MyApp.Person = Ember.Object.extend({
+ guid: function() {
+ return 'guid-guid-guid';
+ }.property().readOnly()
+ });
+
+ MyApp.person = MyApp.Person.create();
+
+ MyApp.person.set('guid', 'new-guid'); // will throw an exception
+ ```
+
+ @method readOnly
+ @return {Ember.ComputedProperty} this
+ @chainable
+*/
+ComputedPropertyPrototype.readOnly = function(readOnly) {
+ this._readOnly = readOnly === undefined || !!readOnly;
+ return this;
+};
+
+/**
+ Sets the dependent keys on this computed property. Pass any number of
+ arguments containing key paths that this computed property depends on.
+
+ ```javascript
+ MyApp.President = Ember.Object.extend({
+ fullName: Ember.computed(function() {
+ return this.get('firstName') + ' ' + this.get('lastName');
+
+ // Tell Ember that this computed property depends on firstName
+ // and lastName
+ }).property('firstName', 'lastName')
+ });
+
+ MyApp.president = MyApp.President.create({
+ firstName: 'Barack',
+ lastName: 'Obama',
+ });
+ MyApp.president.get('fullName'); // Barack Obama
+ ```
+
+ @method property
+ @param {String} path* zero or more property paths
+ @return {Ember.ComputedProperty} this
+ @chainable
+*/
+ComputedPropertyPrototype.property = function() {
+ var args;
+
+ var addArg = function (property) {
+ args.push(property);
+ };
+
+ args = [];
+ for (var i = 0, l = arguments.length; i < l; i++) {
+ expandProperties(arguments[i], addArg);
+ }
+
+
+ this._dependentKeys = args;
+
+
+ return this;
+};
+
+/**
+ In some cases, you may want to annotate computed properties with additional
+ metadata about how they function or what values they operate on. For example,
+ computed property functions may close over variables that are then no longer
+ available for introspection.
+
+ You can pass a hash of these values to a computed property like this:
+
+ ```
+ person: function() {
+ var personId = this.get('personId');
+ return App.Person.create({ id: personId });
+ }.property().meta({ type: App.Person })
+ ```
+
+ The hash that you pass to the `meta()` function will be saved on the
+ computed property descriptor under the `_meta` key. Ember runtime
+ exposes a public API for retrieving these values from classes,
+ via the `metaForProperty()` function.
+
+ @method meta
+ @param {Hash} meta
+ @chainable
+*/
+
+ComputedPropertyPrototype.meta = function(meta) {
+ if (arguments.length === 0) {
+ return this._meta || {};
+ } else {
+ this._meta = meta;
+ return this;
+ }
+};
+
+/* impl descriptor API */
+ComputedPropertyPrototype.didChange = function(obj, keyName) {
+ // _suspended is set via a CP.set to ensure we don't clear
+ // the cached value set by the setter
+ if (this._cacheable && this._suspended !== obj) {
+ var meta = metaFor(obj);
+ if (keyName in meta.cache) {
+ delete meta.cache[keyName];
+ removeDependentKeys(this, obj, keyName, meta);
+ }
+ }
+};
+
+function finishChains(chainNodes)
+{
+ for (var i=0, l=chainNodes.length; i 1) {
+ args = a_slice.call(arguments, 0, -1);
+ func = a_slice.call(arguments, -1)[0];
+ }
+
+ if (typeof func !== "function") {
+ throw new Ember.Error("Computed Property declared without a property function");
+ }
+
+ var cp = new ComputedProperty(func);
+
+ if (args) {
+ cp.property.apply(cp, args);
+ }
+
+ return cp;
+};
+
+/**
+ Returns the cached value for a property, if one exists.
+ This can be useful for peeking at the value of a computed
+ property that is generated lazily, without accidentally causing
+ it to be created.
+
+ @method cacheFor
+ @for Ember
+ @param {Object} obj the object whose property you want to check
+ @param {String} key the name of the property whose cached value you want
+ to return
+ @return {Object} the cached value
+*/
+Ember.cacheFor = function cacheFor(obj, key) {
+ var meta = obj[META_KEY],
+ cache = meta && meta.cache;
+
+ if (cache && key in cache) {
+ return cache[key];
+ }
+};
+
+function getProperties(self, propertyNames) {
+ var ret = {};
+ for(var i = 0; i < propertyNames.length; i++) {
+ ret[propertyNames[i]] = get(self, propertyNames[i]);
+ }
+ return ret;
+}
+
+var registerComputed, registerComputedWithProperties;
+
+
+
+ registerComputed = function (name, macro) {
+ Ember.computed[name] = function(dependentKey) {
+ var args = a_slice.call(arguments);
+ return Ember.computed(dependentKey, function() {
+ return macro.apply(this, args);
+ });
+ };
+ };
+
+ registerComputedWithProperties = function(name, macro) {
+ Ember.computed[name] = function() {
+ var properties = a_slice.call(arguments);
+
+ var computed = Ember.computed(function() {
+ return macro.apply(this, [getProperties(this, properties)]);
+ });
+
+ return computed.property.apply(computed, properties);
+ };
+ };
+
+
+
+
+/**
+ A computed property that returns true if the value of the dependent
+ property is null, an empty string, empty array, or empty function.
+
+ Note: When using `Ember.computed.empty` to watch an array make sure to
+ use the `array.[]` syntax so the computed can subscribe to transitions
+ from empty to non-empty states.
+
+ Example
+
+ ```javascript
+ var ToDoList = Ember.Object.extend({
+ done: Ember.computed.empty('todos.[]') // detect array changes
+ });
+ var todoList = ToDoList.create({todos: ['Unit Test', 'Documentation', 'Release']});
+ todoList.get('done'); // false
+ todoList.get('todos').clear(); // []
+ todoList.get('done'); // true
+ ```
+
+ @method computed.empty
+ @for Ember
+ @param {String} dependentKey
+ @return {Ember.ComputedProperty} computed property which negate
+ the original value for property
+*/
+registerComputed('empty', function(dependentKey) {
+ return Ember.isEmpty(get(this, dependentKey));
+});
+
+/**
+ A computed property that returns true if the value of the dependent
+ property is NOT null, an empty string, empty array, or empty function.
+
+ Note: When using `Ember.computed.notEmpty` to watch an array make sure to
+ use the `array.[]` syntax so the computed can subscribe to transitions
+ from empty to non-empty states.
+
+ Example
+
+ ```javascript
+ var Hamster = Ember.Object.extend({
+ hasStuff: Ember.computed.notEmpty('backpack.[]')
+ });
+ var hamster = Hamster.create({backpack: ['Food', 'Sleeping Bag', 'Tent']});
+ hamster.get('hasStuff'); // true
+ hamster.get('backpack').clear(); // []
+ hamster.get('hasStuff'); // false
+ ```
+
+ @method computed.notEmpty
+ @for Ember
+ @param {String} dependentKey
+ @return {Ember.ComputedProperty} computed property which returns true if
+ original value for property is not empty.
+*/
+registerComputed('notEmpty', function(dependentKey) {
+ return !Ember.isEmpty(get(this, dependentKey));
+});
+
+/**
+ A computed property that returns true if the value of the dependent
+ property is null or undefined. This avoids errors from JSLint complaining
+ about use of ==, which can be technically confusing.
+
+ Example
+
+ ```javascript
+ var Hamster = Ember.Object.extend({
+ isHungry: Ember.computed.none('food')
+ });
+ var hamster = Hamster.create();
+ hamster.get('isHungry'); // true
+ hamster.set('food', 'Banana');
+ hamster.get('isHungry'); // false
+ hamster.set('food', null);
+ hamster.get('isHungry'); // true
+ ```
+
+ @method computed.none
+ @for Ember
+ @param {String} dependentKey
+ @return {Ember.ComputedProperty} computed property which
+ returns true if original value for property is null or undefined.
+*/
+registerComputed('none', function(dependentKey) {
+ return Ember.isNone(get(this, dependentKey));
+});
+
+/**
+ A computed property that returns the inverse boolean value
+ of the original value for the dependent property.
+
+ Example
+
+ ```javascript
+ var User = Ember.Object.extend({
+ isAnonymous: Ember.computed.not('loggedIn')
+ });
+ var user = User.create({loggedIn: false});
+ user.get('isAnonymous'); // true
+ user.set('loggedIn', true);
+ user.get('isAnonymous'); // false
+ ```
+
+ @method computed.not
+ @for Ember
+ @param {String} dependentKey
+ @return {Ember.ComputedProperty} computed property which returns
+ inverse of the original value for property
+*/
+registerComputed('not', function(dependentKey) {
+ return !get(this, dependentKey);
+});
+
+/**
+ A computed property that converts the provided dependent property
+ into a boolean value.
+
+ ```javascript
+ var Hamster = Ember.Object.extend({
+ hasBananas: Ember.computed.bool('numBananas')
+ });
+ var hamster = Hamster.create();
+ hamster.get('hasBananas'); // false
+ hamster.set('numBananas', 0);
+ hamster.get('hasBananas'); // false
+ hamster.set('numBananas', 1);
+ hamster.get('hasBananas'); // true
+ hamster.set('numBananas', null);
+ hamster.get('hasBananas'); // false
+ ```
+
+ @method computed.bool
+ @for Ember
+ @param {String} dependentKey
+ @return {Ember.ComputedProperty} computed property which converts
+ to boolean the original value for property
+*/
+registerComputed('bool', function(dependentKey) {
+ return !!get(this, dependentKey);
+});
+
+/**
+ A computed property which matches the original value for the
+ dependent property against a given RegExp, returning `true`
+ if they values matches the RegExp and `false` if it does not.
+
+ Example
+
+ ```javascript
+ var User = Ember.Object.extend({
+ hasValidEmail: Ember.computed.match('email', /^.+@.+\..+$/)
+ });
+ var user = User.create({loggedIn: false});
+ user.get('hasValidEmail'); // false
+ user.set('email', '');
+ user.get('hasValidEmail'); // false
+ user.set('email', 'ember_hamster@example.com');
+ user.get('hasValidEmail'); // true
+ ```
+
+ @method computed.match
+ @for Ember
+ @param {String} dependentKey
+ @param {RegExp} regexp
+ @return {Ember.ComputedProperty} computed property which match
+ the original value for property against a given RegExp
+*/
+registerComputed('match', function(dependentKey, regexp) {
+ var value = get(this, dependentKey);
+ return typeof value === 'string' ? regexp.test(value) : false;
+});
+
+/**
+ A computed property that returns true if the provided dependent property
+ is equal to the given value.
+
+ Example
+
+ ```javascript
+ var Hamster = Ember.Object.extend({
+ napTime: Ember.computed.equal('state', 'sleepy')
+ });
+ var hamster = Hamster.create();
+ hamster.get('napTime'); // false
+ hamster.set('state', 'sleepy');
+ hamster.get('napTime'); // true
+ hamster.set('state', 'hungry');
+ hamster.get('napTime'); // false
+ ```
+
+ @method computed.equal
+ @for Ember
+ @param {String} dependentKey
+ @param {String|Number|Object} value
+ @return {Ember.ComputedProperty} computed property which returns true if
+ the original value for property is equal to the given value.
+*/
+registerComputed('equal', function(dependentKey, value) {
+ return get(this, dependentKey) === value;
+});
+
+/**
+ A computed property that returns true if the provied dependent property
+ is greater than the provided value.
+
+ Example
+
+ ```javascript
+ var Hamster = Ember.Object.extend({
+ hasTooManyBananas: Ember.computed.gt('numBananas', 10)
+ });
+ var hamster = Hamster.create();
+ hamster.get('hasTooManyBananas'); // false
+ hamster.set('numBananas', 3);
+ hamster.get('hasTooManyBananas'); // false
+ hamster.set('numBananas', 11);
+ hamster.get('hasTooManyBananas'); // true
+ ```
+
+ @method computed.gt
+ @for Ember
+ @param {String} dependentKey
+ @param {Number} value
+ @return {Ember.ComputedProperty} computed property which returns true if
+ the original value for property is greater then given value.
+*/
+registerComputed('gt', function(dependentKey, value) {
+ return get(this, dependentKey) > value;
+});
+
+/**
+ A computed property that returns true if the provided dependent property
+ is greater than or equal to the provided value.
+
+ Example
+
+ ```javascript
+ var Hamster = Ember.Object.extend({
+ hasTooManyBananas: Ember.computed.gte('numBananas', 10)
+ });
+ var hamster = Hamster.create();
+ hamster.get('hasTooManyBananas'); // false
+ hamster.set('numBananas', 3);
+ hamster.get('hasTooManyBananas'); // false
+ hamster.set('numBananas', 10);
+ hamster.get('hasTooManyBananas'); // true
+ ```
+
+ @method computed.gte
+ @for Ember
+ @param {String} dependentKey
+ @param {Number} value
+ @return {Ember.ComputedProperty} computed property which returns true if
+ the original value for property is greater or equal then given value.
+*/
+registerComputed('gte', function(dependentKey, value) {
+ return get(this, dependentKey) >= value;
+});
+
+/**
+ A computed property that returns true if the provided dependent property
+ is less than the provided value.
+
+ Example
+
+ ```javascript
+ var Hamster = Ember.Object.extend({
+ needsMoreBananas: Ember.computed.lt('numBananas', 3)
+ });
+ var hamster = Hamster.create();
+ hamster.get('needsMoreBananas'); // true
+ hamster.set('numBananas', 3);
+ hamster.get('needsMoreBananas'); // false
+ hamster.set('numBananas', 2);
+ hamster.get('needsMoreBananas'); // true
+ ```
+
+ @method computed.lt
+ @for Ember
+ @param {String} dependentKey
+ @param {Number} value
+ @return {Ember.ComputedProperty} computed property which returns true if
+ the original value for property is less then given value.
+*/
+registerComputed('lt', function(dependentKey, value) {
+ return get(this, dependentKey) < value;
+});
+
+/**
+ A computed property that returns true if the provided dependent property
+ is less than or equal to the provided value.
+
+ Example
+
+ ```javascript
+ var Hamster = Ember.Object.extend({
+ needsMoreBananas: Ember.computed.lte('numBananas', 3)
+ });
+ var hamster = Hamster.create();
+ hamster.get('needsMoreBananas'); // true
+ hamster.set('numBananas', 5);
+ hamster.get('needsMoreBananas'); // false
+ hamster.set('numBananas', 3);
+ hamster.get('needsMoreBananas'); // true
+ ```
+
+ @method computed.lte
+ @for Ember
+ @param {String} dependentKey
+ @param {Number} value
+ @return {Ember.ComputedProperty} computed property which returns true if
+ the original value for property is less or equal then given value.
+*/
+registerComputed('lte', function(dependentKey, value) {
+ return get(this, dependentKey) <= value;
+});
+
+/**
+ A computed property that performs a logical `and` on the
+ original values for the provided dependent properties.
+
+ Example
+
+ ```javascript
+ var Hamster = Ember.Object.extend({
+ readyForCamp: Ember.computed.and('hasTent', 'hasBackpack')
+ });
+ var hamster = Hamster.create();
+ hamster.get('readyForCamp'); // false
+ hamster.set('hasTent', true);
+ hamster.get('readyForCamp'); // false
+ hamster.set('hasBackpack', true);
+ hamster.get('readyForCamp'); // true
+ ```
+
+ @method computed.and
+ @for Ember
+ @param {String} dependentKey*
+ @return {Ember.ComputedProperty} computed property which performs
+ a logical `and` on the values of all the original values for properties.
+*/
+registerComputedWithProperties('and', function(properties) {
+ for (var key in properties) {
+ if (properties.hasOwnProperty(key) && !properties[key]) {
+ return false;
+ }
+ }
+ return true;
+});
+
+/**
+ A computed property which performs a logical `or` on the
+ original values for the provided dependent properties.
+
+ Example
+
+ ```javascript
+ var Hamster = Ember.Object.extend({
+ readyForRain: Ember.computed.or('hasJacket', 'hasUmbrella')
+ });
+ var hamster = Hamster.create();
+ hamster.get('readyForRain'); // false
+ hamster.set('hasJacket', true);
+ hamster.get('readyForRain'); // true
+ ```
+
+ @method computed.or
+ @for Ember
+ @param {String} dependentKey*
+ @return {Ember.ComputedProperty} computed property which performs
+ a logical `or` on the values of all the original values for properties.
+*/
+registerComputedWithProperties('or', function(properties) {
+ for (var key in properties) {
+ if (properties.hasOwnProperty(key) && properties[key]) {
+ return true;
+ }
+ }
+ return false;
+});
+
+/**
+ A computed property that returns the first truthy value
+ from a list of dependent properties.
+
+ Example
+
+ ```javascript
+ var Hamster = Ember.Object.extend({
+ hasClothes: Ember.computed.any('hat', 'shirt')
+ });
+ var hamster = Hamster.create();
+ hamster.get('hasClothes'); // null
+ hamster.set('shirt', 'Hawaiian Shirt');
+ hamster.get('hasClothes'); // 'Hawaiian Shirt'
+ ```
+
+ @method computed.any
+ @for Ember
+ @param {String} dependentKey*
+ @return {Ember.ComputedProperty} computed property which returns
+ the first truthy value of given list of properties.
+*/
+registerComputedWithProperties('any', function(properties) {
+ for (var key in properties) {
+ if (properties.hasOwnProperty(key) && properties[key]) {
+ return properties[key];
+ }
+ }
+ return null;
+});
+
+/**
+ A computed property that returns the array of values
+ for the provided dependent properties.
+
+ Example
+
+ ```javascript
+ var Hamster = Ember.Object.extend({
+ clothes: Ember.computed.collect('hat', 'shirt')
+ });
+ var hamster = Hamster.create();
+ hamster.get('clothes'); // [null, null]
+ hamster.set('hat', 'Camp Hat');
+ hamster.set('shirt', 'Camp Shirt');
+ hamster.get('clothes'); // ['Camp Hat', 'Camp Shirt']
+ ```
+
+ @method computed.collect
+ @for Ember
+ @param {String} dependentKey*
+ @return {Ember.ComputedProperty} computed property which maps
+ values of all passed properties in to an array.
+*/
+registerComputedWithProperties('collect', function(properties) {
+ var res = [];
+ for (var key in properties) {
+ if (properties.hasOwnProperty(key)) {
+ if (Ember.isNone(properties[key])) {
+ res.push(null);
+ } else {
+ res.push(properties[key]);
+ }
+ }
+ }
+ return res;
+});
+
+/**
+ Creates a new property that is an alias for another property
+ on an object. Calls to `get` or `set` this property behave as
+ though they were called on the original property.
+
+ ```javascript
+ Person = Ember.Object.extend({
+ name: 'Alex Matchneer',
+ nomen: Ember.computed.alias('name')
+ });
+
+ alex = Person.create();
+ alex.get('nomen'); // 'Alex Matchneer'
+ alex.get('name'); // 'Alex Matchneer'
+
+ alex.set('nomen', '@machty');
+ alex.get('name'); // '@machty'
+ ```
+ @method computed.alias
+ @for Ember
+ @param {String} dependentKey
+ @return {Ember.ComputedProperty} computed property which creates an
+ alias to the original value for property.
+*/
+Ember.computed.alias = function(dependentKey) {
+ return Ember.computed(dependentKey, function(key, value) {
+ if (arguments.length > 1) {
+ set(this, dependentKey, value);
+ return value;
+ } else {
+ return get(this, dependentKey);
+ }
+ });
+};
+
+/**
+ Where `computed.alias` aliases `get` and `set`, and allows for bidirectional
+ data flow, `computed.oneWay` only provides an aliased `get`. The `set` will
+ not mutate the upstream property, rather causes the current property to
+ become the value set. This causes the downstream property to permentantly
+ diverge from the upstream property.
+
+ Example
+
+ ```javascript
+ User = Ember.Object.extend({
+ firstName: null,
+ lastName: null,
+ nickName: Ember.computed.oneWay('firstName')
+ });
+
+ user = User.create({
+ firstName: 'Teddy',
+ lastName: 'Zeenny'
+ });
+
+ user.get('nickName');
+ # 'Teddy'
+
+ user.set('nickName', 'TeddyBear');
+ # 'TeddyBear'
+
+ user.get('firstName');
+ # 'Teddy'
+ ```
+
+ @method computed.oneWay
+ @for Ember
+ @param {String} dependentKey
+ @return {Ember.ComputedProperty} computed property which creates a
+ one way computed property to the original value for property.
+*/
+Ember.computed.oneWay = function(dependentKey) {
+ return Ember.computed(dependentKey, function() {
+ return get(this, dependentKey);
+ });
+};
+
+/**
+ A computed property that acts like a standard getter and setter,
+ but returns the value at the provided `defaultPath` if the
+ property itself has not been set to a value
+
+ Example
+
+ ```javascript
+ var Hamster = Ember.Object.extend({
+ wishList: Ember.computed.defaultTo('favoriteFood')
+ });
+ var hamster = Hamster.create({favoriteFood: 'Banana'});
+ hamster.get('wishList'); // 'Banana'
+ hamster.set('wishList', 'More Unit Tests');
+ hamster.get('wishList'); // 'More Unit Tests'
+ hamster.get('favoriteFood'); // 'Banana'
+ ```
+
+ @method computed.defaultTo
+ @for Ember
+ @param {String} defaultPath
+ @return {Ember.ComputedProperty} computed property which acts like
+ a standard getter and setter, but defaults to the value from `defaultPath`.
+*/
+Ember.computed.defaultTo = function(defaultPath) {
+ return Ember.computed(function(key, newValue, cachedValue) {
+ if (arguments.length === 1) {
+ return cachedValue != null ? cachedValue : get(this, defaultPath);
+ }
+ return newValue != null ? newValue : get(this, defaultPath);
+ });
+};
+
+
+})();
+
+
+
+(function() {
+// Ember.tryFinally
+/**
+@module ember-metal
+*/
+
+var AFTER_OBSERVERS = ':change',
+ BEFORE_OBSERVERS = ':before';
+
+function changeEvent(keyName) {
+ return keyName+AFTER_OBSERVERS;
+}
+
+function beforeEvent(keyName) {
+ return keyName+BEFORE_OBSERVERS;
+}
+
+/**
+ @method addObserver
+ @param obj
+ @param {String} path
+ @param {Object|Function} targetOrMethod
+ @param {Function|String} [method]
+*/
+Ember.addObserver = function(obj, _path, target, method) {
+ Ember.addListener(obj, changeEvent(_path), target, method);
+ Ember.watch(obj, _path);
+
+ return this;
+};
+
+Ember.observersFor = function(obj, path) {
+ return Ember.listenersFor(obj, changeEvent(path));
+};
+
+/**
+ @method removeObserver
+ @param obj
+ @param {String} path
+ @param {Object|Function} targetOrMethod
+ @param {Function|String} [method]
+*/
+Ember.removeObserver = function(obj, _path, target, method) {
+ Ember.unwatch(obj, _path);
+ Ember.removeListener(obj, changeEvent(_path), target, method);
+
+ return this;
+};
+
+/**
+ @method addBeforeObserver
+ @param obj
+ @param {String} path
+ @param {Object|Function} targetOrMethod
+ @param {Function|String} [method]
+*/
+Ember.addBeforeObserver = function(obj, _path, target, method) {
+ Ember.addListener(obj, beforeEvent(_path), target, method);
+ Ember.watch(obj, _path);
+
+ return this;
+};
+
+// Suspend observer during callback.
+//
+// This should only be used by the target of the observer
+// while it is setting the observed path.
+Ember._suspendBeforeObserver = function(obj, path, target, method, callback) {
+ return Ember._suspendListener(obj, beforeEvent(path), target, method, callback);
+};
+
+Ember._suspendObserver = function(obj, path, target, method, callback) {
+ return Ember._suspendListener(obj, changeEvent(path), target, method, callback);
+};
+
+var map = Ember.ArrayPolyfills.map;
+
+Ember._suspendBeforeObservers = function(obj, paths, target, method, callback) {
+ var events = map.call(paths, beforeEvent);
+ return Ember._suspendListeners(obj, events, target, method, callback);
+};
+
+Ember._suspendObservers = function(obj, paths, target, method, callback) {
+ var events = map.call(paths, changeEvent);
+ return Ember._suspendListeners(obj, events, target, method, callback);
+};
+
+Ember.beforeObserversFor = function(obj, path) {
+ return Ember.listenersFor(obj, beforeEvent(path));
+};
+
+/**
+ @method removeBeforeObserver
+ @param obj
+ @param {String} path
+ @param {Object|Function} targetOrMethod
+ @param {Function|String} [method]
+*/
+Ember.removeBeforeObserver = function(obj, _path, target, method) {
+ Ember.unwatch(obj, _path);
+ Ember.removeListener(obj, beforeEvent(_path), target, method);
+
+ return this;
+};
+
+})();
+
+
+
+(function() {
+define("backburner/queue",
+ ["exports"],
+ function(__exports__) {
+ "use strict";
+ function Queue(daq, name, options) {
+ this.daq = daq;
+ this.name = name;
+ this.options = options;
+ this._queue = [];
+ }
+
+ Queue.prototype = {
+ daq: null,
+ name: null,
+ options: null,
+ _queue: null,
+
+ push: function(target, method, args, stack) {
+ var queue = this._queue;
+ queue.push(target, method, args, stack);
+ return {queue: this, target: target, method: method};
+ },
+
+ pushUnique: function(target, method, args, stack) {
+ var queue = this._queue, currentTarget, currentMethod, i, l;
+
+ for (i = 0, l = queue.length; i < l; i += 4) {
+ currentTarget = queue[i];
+ currentMethod = queue[i+1];
+
+ if (currentTarget === target && currentMethod === method) {
+ queue[i+2] = args; // replace args
+ queue[i+3] = stack; // replace stack
+ return {queue: this, target: target, method: method}; // TODO: test this code path
+ }
+ }
+
+ this._queue.push(target, method, args, stack);
+ return {queue: this, target: target, method: method};
+ },
+
+ // TODO: remove me, only being used for Ember.run.sync
+ flush: function() {
+ var queue = this._queue,
+ options = this.options,
+ before = options && options.before,
+ after = options && options.after,
+ target, method, args, stack, i, l = queue.length;
+
+ if (l && before) { before(); }
+ for (i = 0; i < l; i += 4) {
+ target = queue[i];
+ method = queue[i+1];
+ args = queue[i+2];
+ stack = queue[i+3]; // Debugging assistance
+
+ // TODO: error handling
+ if (args && args.length > 0) {
+ method.apply(target, args);
+ } else {
+ method.call(target);
+ }
+ }
+ if (l && after) { after(); }
+
+ // check if new items have been added
+ if (queue.length > l) {
+ this._queue = queue.slice(l);
+ this.flush();
+ } else {
+ this._queue.length = 0;
+ }
+ },
+
+ cancel: function(actionToCancel) {
+ var queue = this._queue, currentTarget, currentMethod, i, l;
+
+ for (i = 0, l = queue.length; i < l; i += 4) {
+ currentTarget = queue[i];
+ currentMethod = queue[i+1];
+
+ if (currentTarget === actionToCancel.target && currentMethod === actionToCancel.method) {
+ queue.splice(i, 4);
+ return true;
+ }
+ }
+
+ // if not found in current queue
+ // could be in the queue that is being flushed
+ queue = this._queueBeingFlushed;
+ if (!queue) {
+ return;
+ }
+ for (i = 0, l = queue.length; i < l; i += 4) {
+ currentTarget = queue[i];
+ currentMethod = queue[i+1];
+
+ if (currentTarget === actionToCancel.target && currentMethod === actionToCancel.method) {
+ // don't mess with array during flush
+ // just nullify the method
+ queue[i+1] = null;
+ return true;
+ }
+ }
+ }
+ };
+
+
+ __exports__.Queue = Queue;
+ });
+
+define("backburner/deferred_action_queues",
+ ["backburner/queue","exports"],
+ function(__dependency1__, __exports__) {
+ "use strict";
+ var Queue = __dependency1__.Queue;
+
+ function DeferredActionQueues(queueNames, options) {
+ var queues = this.queues = {};
+ this.queueNames = queueNames = queueNames || [];
+
+ var queueName;
+ for (var i = 0, l = queueNames.length; i < l; i++) {
+ queueName = queueNames[i];
+ queues[queueName] = new Queue(this, queueName, options[queueName]);
+ }
+ }
+
+ DeferredActionQueues.prototype = {
+ queueNames: null,
+ queues: null,
+
+ schedule: function(queueName, target, method, args, onceFlag, stack) {
+ var queues = this.queues,
+ queue = queues[queueName];
+
+ if (!queue) { throw new Error("You attempted to schedule an action in a queue (" + queueName + ") that doesn't exist"); }
+
+ if (onceFlag) {
+ return queue.pushUnique(target, method, args, stack);
+ } else {
+ return queue.push(target, method, args, stack);
+ }
+ },
+
+ flush: function() {
+ var queues = this.queues,
+ queueNames = this.queueNames,
+ queueName, queue, queueItems, priorQueueNameIndex,
+ queueNameIndex = 0, numberOfQueues = queueNames.length;
+
+ outerloop:
+ while (queueNameIndex < numberOfQueues) {
+ queueName = queueNames[queueNameIndex];
+ queue = queues[queueName];
+ queueItems = queue._queueBeingFlushed = queue._queue.slice();
+ queue._queue = [];
+
+ var options = queue.options,
+ before = options && options.before,
+ after = options && options.after,
+ target, method, args, stack,
+ queueIndex = 0, numberOfQueueItems = queueItems.length;
+
+ if (numberOfQueueItems && before) { before(); }
+ while (queueIndex < numberOfQueueItems) {
+ target = queueItems[queueIndex];
+ method = queueItems[queueIndex+1];
+ args = queueItems[queueIndex+2];
+ stack = queueItems[queueIndex+3]; // Debugging assistance
+
+ if (typeof method === 'string') { method = target[method]; }
+
+ // method could have been nullified / canceled during flush
+ if (method) {
+ // TODO: error handling
+ if (args && args.length > 0) {
+ method.apply(target, args);
+ } else {
+ method.call(target);
+ }
+ }
+
+ queueIndex += 4;
+ }
+ queue._queueBeingFlushed = null;
+ if (numberOfQueueItems && after) { after(); }
+
+ if ((priorQueueNameIndex = indexOfPriorQueueWithActions(this, queueNameIndex)) !== -1) {
+ queueNameIndex = priorQueueNameIndex;
+ continue outerloop;
+ }
+
+ queueNameIndex++;
+ }
+ }
+ };
+
+ function indexOfPriorQueueWithActions(daq, currentQueueIndex) {
+ var queueName, queue;
+
+ for (var i = 0, l = currentQueueIndex; i <= l; i++) {
+ queueName = daq.queueNames[i];
+ queue = daq.queues[queueName];
+ if (queue._queue.length) { return i; }
+ }
+
+ return -1;
+ }
+
+
+ __exports__.DeferredActionQueues = DeferredActionQueues;
+ });
+
+define("backburner",
+ ["backburner/deferred_action_queues","exports"],
+ function(__dependency1__, __exports__) {
+ "use strict";
+ var DeferredActionQueues = __dependency1__.DeferredActionQueues;
+
+ var slice = [].slice,
+ pop = [].pop,
+ throttlers = [],
+ debouncees = [],
+ timers = [],
+ autorun, laterTimer, laterTimerExpiresAt,
+ global = this,
+ NUMBER = /\d+/;
+
+ function isCoercableNumber(number) {
+ return typeof number === 'number' || NUMBER.test(number);
+ }
+
+ function Backburner(queueNames, options) {
+ this.queueNames = queueNames;
+ this.options = options || {};
+ if (!this.options.defaultQueue) {
+ this.options.defaultQueue = queueNames[0];
+ }
+ this.instanceStack = [];
+ }
+
+ Backburner.prototype = {
+ queueNames: null,
+ options: null,
+ currentInstance: null,
+ instanceStack: null,
+
+ begin: function() {
+ var onBegin = this.options && this.options.onBegin,
+ previousInstance = this.currentInstance;
+
+ if (previousInstance) {
+ this.instanceStack.push(previousInstance);
+ }
+
+ this.currentInstance = new DeferredActionQueues(this.queueNames, this.options);
+ if (onBegin) {
+ onBegin(this.currentInstance, previousInstance);
+ }
+ },
+
+ end: function() {
+ var onEnd = this.options && this.options.onEnd,
+ currentInstance = this.currentInstance,
+ nextInstance = null;
+
+ try {
+ currentInstance.flush();
+ } finally {
+ this.currentInstance = null;
+
+ if (this.instanceStack.length) {
+ nextInstance = this.instanceStack.pop();
+ this.currentInstance = nextInstance;
+ }
+
+ if (onEnd) {
+ onEnd(currentInstance, nextInstance);
+ }
+ }
+ },
+
+ run: function(target, method /*, args */) {
+ var ret;
+ this.begin();
+
+ if (!method) {
+ method = target;
+ target = null;
+ }
+
+ if (typeof method === 'string') {
+ method = target[method];
+ }
+
+ // Prevent Safari double-finally.
+ var finallyAlreadyCalled = false;
+ try {
+ if (arguments.length > 2) {
+ ret = method.apply(target, slice.call(arguments, 2));
+ } else {
+ ret = method.call(target);
+ }
+ } finally {
+ if (!finallyAlreadyCalled) {
+ finallyAlreadyCalled = true;
+ this.end();
+ }
+ }
+ return ret;
+ },
+
+ defer: function(queueName, target, method /* , args */) {
+ if (!method) {
+ method = target;
+ target = null;
+ }
+
+ if (typeof method === 'string') {
+ method = target[method];
+ }
+
+ var stack = this.DEBUG ? new Error() : undefined,
+ args = arguments.length > 3 ? slice.call(arguments, 3) : undefined;
+ if (!this.currentInstance) { createAutorun(this); }
+ return this.currentInstance.schedule(queueName, target, method, args, false, stack);
+ },
+
+ deferOnce: function(queueName, target, method /* , args */) {
+ if (!method) {
+ method = target;
+ target = null;
+ }
+
+ if (typeof method === 'string') {
+ method = target[method];
+ }
+
+ var stack = this.DEBUG ? new Error() : undefined,
+ args = arguments.length > 3 ? slice.call(arguments, 3) : undefined;
+ if (!this.currentInstance) { createAutorun(this); }
+ return this.currentInstance.schedule(queueName, target, method, args, true, stack);
+ },
+
+ setTimeout: function() {
+ var args = slice.call(arguments);
+ var length = args.length;
+ var method, wait, target;
+ var self = this;
+ var methodOrTarget, methodOrWait, methodOrArgs;
+
+ if (length === 0) {
+ return;
+ } else if (length === 1) {
+ method = args.shift();
+ wait = 0;
+ } else if (length === 2) {
+ methodOrTarget = args[0];
+ methodOrWait = args[1];
+
+ if (typeof methodOrWait === 'function' || typeof methodOrTarget[methodOrWait] === 'function') {
+ target = args.shift();
+ method = args.shift();
+ wait = 0;
+ } else if (isCoercableNumber(methodOrWait)) {
+ method = args.shift();
+ wait = args.shift();
+ } else {
+ method = args.shift();
+ wait = 0;
+ }
+ } else {
+ var last = args[args.length - 1];
+
+ if (isCoercableNumber(last)) {
+ wait = args.pop();
+ }
+
+ methodOrTarget = args[0];
+ methodOrArgs = args[1];
+
+ if (typeof methodOrArgs === 'function' || (typeof methodOrArgs === 'string' &&
+ methodOrTarget !== null &&
+ methodOrArgs in methodOrTarget)) {
+ target = args.shift();
+ method = args.shift();
+ } else {
+ method = args.shift();
+ }
+ }
+
+ var executeAt = (+new Date()) + parseInt(wait, 10);
+
+ if (typeof method === 'string') {
+ method = target[method];
+ }
+
+ function fn() {
+ method.apply(target, args);
+ }
+
+ // find position to insert - TODO: binary search
+ var i, l;
+ for (i = 0, l = timers.length; i < l; i += 2) {
+ if (executeAt < timers[i]) { break; }
+ }
+
+ timers.splice(i, 0, executeAt, fn);
+
+ updateLaterTimer(self, executeAt, wait);
+
+ return fn;
+ },
+
+ throttle: function(target, method /* , args, wait */) {
+ var self = this,
+ args = arguments,
+ wait = parseInt(pop.call(args), 10),
+ throttler,
+ index,
+ timer;
+
+ index = findThrottler(target, method);
+ if (index > -1) { return throttlers[index]; } // throttled
+
+ timer = global.setTimeout(function() {
+ self.run.apply(self, args);
+
+ var index = findThrottler(target, method);
+ if (index > -1) { throttlers.splice(index, 1); }
+ }, wait);
+
+ throttler = [target, method, timer];
+
+ throttlers.push(throttler);
+
+ return throttler;
+ },
+
+ debounce: function(target, method /* , args, wait, [immediate] */) {
+ var self = this,
+ args = arguments,
+ immediate = pop.call(args),
+ wait,
+ index,
+ debouncee,
+ timer;
+
+ if (typeof immediate === "number" || typeof immediate === "string") {
+ wait = immediate;
+ immediate = false;
+ } else {
+ wait = pop.call(args);
+ }
+
+ wait = parseInt(wait, 10);
+ // Remove debouncee
+ index = findDebouncee(target, method);
+
+ if (index > -1) {
+ debouncee = debouncees[index];
+ debouncees.splice(index, 1);
+ clearTimeout(debouncee[2]);
+ }
+
+ timer = global.setTimeout(function() {
+ if (!immediate) {
+ self.run.apply(self, args);
+ }
+ var index = findDebouncee(target, method);
+ if (index > -1) {
+ debouncees.splice(index, 1);
+ }
+ }, wait);
+
+ if (immediate && index === -1) {
+ self.run.apply(self, args);
+ }
+
+ debouncee = [target, method, timer];
+
+ debouncees.push(debouncee);
+
+ return debouncee;
+ },
+
+ cancelTimers: function() {
+ var i, len;
+
+ for (i = 0, len = throttlers.length; i < len; i++) {
+ clearTimeout(throttlers[i][2]);
+ }
+ throttlers = [];
+
+ for (i = 0, len = debouncees.length; i < len; i++) {
+ clearTimeout(debouncees[i][2]);
+ }
+ debouncees = [];
+
+ if (laterTimer) {
+ clearTimeout(laterTimer);
+ laterTimer = null;
+ }
+ timers = [];
+
+ if (autorun) {
+ clearTimeout(autorun);
+ autorun = null;
+ }
+ },
+
+ hasTimers: function() {
+ return !!timers.length || autorun;
+ },
+
+ cancel: function(timer) {
+ var timerType = typeof timer;
+
+ if (timer && timerType === 'object' && timer.queue && timer.method) { // we're cancelling a deferOnce
+ return timer.queue.cancel(timer);
+ } else if (timerType === 'function') { // we're cancelling a setTimeout
+ for (var i = 0, l = timers.length; i < l; i += 2) {
+ if (timers[i + 1] === timer) {
+ timers.splice(i, 2); // remove the two elements
+ return true;
+ }
+ }
+ } else if (Object.prototype.toString.call(timer) === "[object Array]"){ // we're cancelling a throttle or debounce
+ return this._cancelItem(findThrottler, throttlers, timer) ||
+ this._cancelItem(findDebouncee, debouncees, timer);
+ } else {
+ return; // timer was null or not a timer
+ }
+ },
+
+ _cancelItem: function(findMethod, array, timer){
+ var item,
+ index;
+
+ if (timer.length < 3) { return false; }
+
+ index = findMethod(timer[0], timer[1]);
+
+ if(index > -1) {
+
+ item = array[index];
+
+ if(item[2] === timer[2]){
+ array.splice(index, 1);
+ clearTimeout(timer[2]);
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ };
+
+ Backburner.prototype.schedule = Backburner.prototype.defer;
+ Backburner.prototype.scheduleOnce = Backburner.prototype.deferOnce;
+ Backburner.prototype.later = Backburner.prototype.setTimeout;
+
+ function createAutorun(backburner) {
+ backburner.begin();
+ autorun = global.setTimeout(function() {
+ autorun = null;
+ backburner.end();
+ });
+ }
+
+ function updateLaterTimer(self, executeAt, wait) {
+ if (!laterTimer || executeAt < laterTimerExpiresAt) {
+ if (laterTimer) {
+ clearTimeout(laterTimer);
+ }
+ laterTimer = global.setTimeout(function() {
+ laterTimer = null;
+ laterTimerExpiresAt = null;
+ executeTimers(self);
+ }, wait);
+ laterTimerExpiresAt = executeAt;
+ }
+ }
+
+ function executeTimers(self) {
+ var now = +new Date(),
+ time, fns, i, l;
+
+ self.run(function() {
+ // TODO: binary search
+ for (i = 0, l = timers.length; i < l; i += 2) {
+ time = timers[i];
+ if (time > now) { break; }
+ }
+
+ fns = timers.splice(0, i);
+
+ for (i = 1, l = fns.length; i < l; i += 2) {
+ self.schedule(self.options.defaultQueue, null, fns[i]);
+ }
+ });
+
+ if (timers.length) {
+ updateLaterTimer(self, timers[0], timers[0] - now);
+ }
+ }
+
+ function findDebouncee(target, method) {
+ var debouncee,
+ index = -1;
+
+ for (var i = 0, l = debouncees.length; i < l; i++) {
+ debouncee = debouncees[i];
+ if (debouncee[0] === target && debouncee[1] === method) {
+ index = i;
+ break;
+ }
+ }
+
+ return index;
+ }
+
+ function findThrottler(target, method) {
+ var throttler,
+ index = -1;
+
+ for (var i = 0, l = throttlers.length; i < l; i++) {
+ throttler = throttlers[i];
+ if (throttler[0] === target && throttler[1] === method) {
+ index = i;
+ break;
+ }
+ }
+
+ return index;
+ }
+
+
+ __exports__.Backburner = Backburner;
+ });
+
+})();
+
+
+
+(function() {
+var onBegin = function(current) {
+ Ember.run.currentRunLoop = current;
+};
+
+var onEnd = function(current, next) {
+ Ember.run.currentRunLoop = next;
+};
+
+var Backburner = requireModule('backburner').Backburner,
+ backburner = new Backburner(['sync', 'actions', 'destroy'], {
+ sync: {
+ before: Ember.beginPropertyChanges,
+ after: Ember.endPropertyChanges
+ },
+ defaultQueue: 'actions',
+ onBegin: onBegin,
+ onEnd: onEnd
+ }),
+ slice = [].slice,
+ concat = [].concat;
+
+// ..........................................................
+// Ember.run - this is ideally the only public API the dev sees
+//
+
+/**
+ Runs the passed target and method inside of a RunLoop, ensuring any
+ deferred actions including bindings and views updates are flushed at the
+ end.
+
+ Normally you should not need to invoke this method yourself. However if
+ you are implementing raw event handlers when interfacing with other
+ libraries or plugins, you should probably wrap all of your code inside this
+ call.
+
+ ```javascript
+ Ember.run(function() {
+ // code to be execute within a RunLoop
+ });
+ ```
+
+ @class run
+ @namespace Ember
+ @static
+ @constructor
+ @param {Object} [target] target of method to call
+ @param {Function|String} method Method to invoke.
+ May be a function or a string. If you pass a string
+ then it will be looked up on the passed target.
+ @param {Object} [args*] Any additional arguments you wish to pass to the method.
+ @return {Object} return value from invoking the passed function.
+*/
+Ember.run = function(target, method) {
+ var ret;
+
+ if (Ember.onerror) {
+ try {
+ ret = backburner.run.apply(backburner, arguments);
+ } catch (e) {
+ Ember.onerror(e);
+ }
+ } else {
+ ret = backburner.run.apply(backburner, arguments);
+ }
+
+ return ret;
+};
+
+/**
+ If no run-loop is present, it creates a new one. If a run loop is
+ present it will queue itself to run on the existing run-loops action
+ queue.
+
+ Please note: This is not for normal usage, and should be used sparingly.
+
+ If invoked when not within a run loop:
+
+ ```javascript
+ Ember.run.join(function() {
+ // creates a new run-loop
+ });
+ ```
+
+ Alternatively, if called within an existing run loop:
+
+ ```javascript
+ Ember.run(function() {
+ // creates a new run-loop
+ Ember.run.join(function() {
+ // joins with the existing run-loop, and queues for invocation on
+ // the existing run-loops action queue.
+ });
+ });
+ ```
+
+ @method join
+ @namespace Ember
+ @param {Object} [target] target of method to call
+ @param {Function|String} method Method to invoke.
+ May be a function or a string. If you pass a string
+ then it will be looked up on the passed target.
+ @param {Object} [args*] Any additional arguments you wish to pass to the method.
+ @return {Object} Return value from invoking the passed function. Please note,
+ when called within an existing loop, no return value is possible.
+*/
+Ember.run.join = function(target, method /* args */) {
+ if (!Ember.run.currentRunLoop) {
+ return Ember.run.apply(Ember.run, arguments);
+ }
+
+ var args = slice.call(arguments);
+ args.unshift('actions');
+ Ember.run.schedule.apply(Ember.run, args);
+};
+
+/**
+ Provides a useful utility for when integrating with non-Ember libraries
+ that provide asynchronous callbacks.
+
+ Ember utilizes a run-loop to batch and coalesce changes. This works by
+ marking the start and end of Ember-related Javascript execution.
+
+ When using events such as a View's click handler, Ember wraps the event
+ handler in a run-loop, but when integrating with non-Ember libraries this
+ can be tedious.
+
+ For example, the following is rather verbose but is the correct way to combine
+ third-party events and Ember code.
+
+ ```javascript
+ var that = this;
+ jQuery(window).on('resize', function(){
+ Ember.run(function(){
+ that.handleResize();
+ });
+ });
+ ```
+
+ To reduce the boilerplate, the following can be used to construct a
+ run-loop-wrapped callback handler.
+
+ ```javascript
+ jQuery(window).on('resize', Ember.run.bind(this, this.triggerResize));
+ ```
+
+ @method bind
+ @namespace Ember.run
+ @param {Object} [target] target of method to call
+ @param {Function|String} method Method to invoke.
+ May be a function or a string. If you pass a string
+ then it will be looked up on the passed target.
+ @param {Object} [args*] Any additional arguments you wish to pass to the method.
+ @return {Object} return value from invoking the passed function. Please note,
+ when called within an existing loop, no return value is possible.
+*/
+Ember.run.bind = function(target, method /* args*/) {
+ var args = arguments;
+ return function() {
+ return Ember.run.join.apply(Ember.run, args);
+ };
+};
+
+Ember.run.backburner = backburner;
+
+var run = Ember.run;
+
+Ember.run.currentRunLoop = null;
+
+Ember.run.queues = backburner.queueNames;
+
+/**
+ Begins a new RunLoop. Any deferred actions invoked after the begin will
+ be buffered until you invoke a matching call to `Ember.run.end()`. This is
+ a lower-level way to use a RunLoop instead of using `Ember.run()`.
+
+ ```javascript
+ Ember.run.begin();
+ // code to be execute within a RunLoop
+ Ember.run.end();
+ ```
+
+ @method begin
+ @return {void}
+*/
+Ember.run.begin = function() {
+ backburner.begin();
+};
+
+/**
+ Ends a RunLoop. This must be called sometime after you call
+ `Ember.run.begin()` to flush any deferred actions. This is a lower-level way
+ to use a RunLoop instead of using `Ember.run()`.
+
+ ```javascript
+ Ember.run.begin();
+ // code to be execute within a RunLoop
+ Ember.run.end();
+ ```
+
+ @method end
+ @return {void}
+*/
+Ember.run.end = function() {
+ backburner.end();
+};
+
+/**
+ Array of named queues. This array determines the order in which queues
+ are flushed at the end of the RunLoop. You can define your own queues by
+ simply adding the queue name to this array. Normally you should not need
+ to inspect or modify this property.
+
+ @property queues
+ @type Array
+ @default ['sync', 'actions', 'destroy']
+*/
+
+/**
+ Adds the passed target/method and any optional arguments to the named
+ queue to be executed at the end of the RunLoop. If you have not already
+ started a RunLoop when calling this method one will be started for you
+ automatically.
+
+ At the end of a RunLoop, any methods scheduled in this way will be invoked.
+ Methods will be invoked in an order matching the named queues defined in
+ the `Ember.run.queues` property.
+
+ ```javascript
+ Ember.run.schedule('sync', this, function() {
+ // this will be executed in the first RunLoop queue, when bindings are synced
+ console.log("scheduled on sync queue");
+ });
+
+ Ember.run.schedule('actions', this, function() {
+ // this will be executed in the 'actions' queue, after bindings have synced.
+ console.log("scheduled on actions queue");
+ });
+
+ // Note the functions will be run in order based on the run queues order.
+ // Output would be:
+ // scheduled on sync queue
+ // scheduled on actions queue
+ ```
+
+ @method schedule
+ @param {String} queue The name of the queue to schedule against.
+ Default queues are 'sync' and 'actions'
+ @param {Object} [target] target object to use as the context when invoking a method.
+ @param {String|Function} method The method to invoke. If you pass a string it
+ will be resolved on the target object at the time the scheduled item is
+ invoked allowing you to change the target function.
+ @param {Object} [arguments*] Optional arguments to be passed to the queued method.
+ @return {void}
+*/
+Ember.run.schedule = function(queue, target, method) {
+ checkAutoRun();
+ backburner.schedule.apply(backburner, arguments);
+};
+
+// Used by global test teardown
+Ember.run.hasScheduledTimers = function() {
+ return backburner.hasTimers();
+};
+
+// Used by global test teardown
+Ember.run.cancelTimers = function () {
+ backburner.cancelTimers();
+};
+
+/**
+ Immediately flushes any events scheduled in the 'sync' queue. Bindings
+ use this queue so this method is a useful way to immediately force all
+ bindings in the application to sync.
+
+ You should call this method anytime you need any changed state to propagate
+ throughout the app immediately without repainting the UI (which happens
+ in the later 'render' queue added by the `ember-views` package).
+
+ ```javascript
+ Ember.run.sync();
+ ```
+
+ @method sync
+ @return {void}
+*/
+Ember.run.sync = function() {
+ if (backburner.currentInstance) {
+ backburner.currentInstance.queues.sync.flush();
+ }
+};
+
+/**
+ Invokes the passed target/method and optional arguments after a specified
+ period if time. The last parameter of this method must always be a number
+ of milliseconds.
+
+ You should use this method whenever you need to run some action after a
+ period of time instead of using `setTimeout()`. This method will ensure that
+ items that expire during the same script execution cycle all execute
+ together, which is often more efficient than using a real setTimeout.
+
+ ```javascript
+ Ember.run.later(myContext, function() {
+ // code here will execute within a RunLoop in about 500ms with this == myContext
+ }, 500);
+ ```
+
+ @method later
+ @param {Object} [target] target of method to invoke
+ @param {Function|String} method The method to invoke.
+ If you pass a string it will be resolved on the
+ target at the time the method is invoked.
+ @param {Object} [args*] Optional arguments to pass to the timeout.
+ @param {Number} wait Number of milliseconds to wait.
+ @return {String} a string you can use to cancel the timer in
+ `Ember.run.cancel` later.
+*/
+Ember.run.later = function(target, method) {
+ return backburner.later.apply(backburner, arguments);
+};
+
+/**
+ Schedule a function to run one time during the current RunLoop. This is equivalent
+ to calling `scheduleOnce` with the "actions" queue.
+
+ @method once
+ @param {Object} [target] The target of the method to invoke.
+ @param {Function|String} method The method to invoke.
+ If you pass a string it will be resolved on the
+ target at the time the method is invoked.
+ @param {Object} [args*] Optional arguments to pass to the timeout.
+ @return {Object} Timer information for use in cancelling, see `Ember.run.cancel`.
+*/
+Ember.run.once = function(target, method) {
+ checkAutoRun();
+ var args = slice.call(arguments);
+ args.unshift('actions');
+ return backburner.scheduleOnce.apply(backburner, args);
+};
+
+/**
+ Schedules a function to run one time in a given queue of the current RunLoop.
+ Calling this method with the same queue/target/method combination will have
+ no effect (past the initial call).
+
+ Note that although you can pass optional arguments these will not be
+ considered when looking for duplicates. New arguments will replace previous
+ calls.
+
+ ```javascript
+ Ember.run(function() {
+ var sayHi = function() { console.log('hi'); }
+ Ember.run.scheduleOnce('afterRender', myContext, sayHi);
+ Ember.run.scheduleOnce('afterRender', myContext, sayHi);
+ // sayHi will only be executed once, in the afterRender queue of the RunLoop
+ });
+ ```
+
+ Also note that passing an anonymous function to `Ember.run.scheduleOnce` will
+ not prevent additional calls with an identical anonymous function from
+ scheduling the items multiple times, e.g.:
+
+ ```javascript
+ function scheduleIt() {
+ Ember.run.scheduleOnce('actions', myContext, function() { console.log("Closure"); });
+ }
+ scheduleIt();
+ scheduleIt();
+ // "Closure" will print twice, even though we're using `Ember.run.scheduleOnce`,
+ // because the function we pass to it is anonymous and won't match the
+ // previously scheduled operation.
+ ```
+
+ Available queues, and their order, can be found at `Ember.run.queues`
+
+ @method scheduleOnce
+ @param {String} [queue] The name of the queue to schedule against. Default queues are 'sync' and 'actions'.
+ @param {Object} [target] The target of the method to invoke.
+ @param {Function|String} method The method to invoke.
+ If you pass a string it will be resolved on the
+ target at the time the method is invoked.
+ @param {Object} [args*] Optional arguments to pass to the timeout.
+ @return {Object} Timer information for use in cancelling, see `Ember.run.cancel`.
+*/
+Ember.run.scheduleOnce = function(queue, target, method) {
+ checkAutoRun();
+ return backburner.scheduleOnce.apply(backburner, arguments);
+};
+
+/**
+ Schedules an item to run from within a separate run loop, after
+ control has been returned to the system. This is equivalent to calling
+ `Ember.run.later` with a wait time of 1ms.
+
+ ```javascript
+ Ember.run.next(myContext, function() {
+ // code to be executed in the next run loop,
+ // which will be scheduled after the current one
+ });
+ ```
+
+ Multiple operations scheduled with `Ember.run.next` will coalesce
+ into the same later run loop, along with any other operations
+ scheduled by `Ember.run.later` that expire right around the same
+ time that `Ember.run.next` operations will fire.
+
+ Note that there are often alternatives to using `Ember.run.next`.
+ For instance, if you'd like to schedule an operation to happen
+ after all DOM element operations have completed within the current
+ run loop, you can make use of the `afterRender` run loop queue (added
+ by the `ember-views` package, along with the preceding `render` queue
+ where all the DOM element operations happen). Example:
+
+ ```javascript
+ App.MyCollectionView = Ember.CollectionView.extend({
+ didInsertElement: function() {
+ Ember.run.scheduleOnce('afterRender', this, 'processChildElements');
+ },
+ processChildElements: function() {
+ // ... do something with collectionView's child view
+ // elements after they've finished rendering, which
+ // can't be done within the CollectionView's
+ // `didInsertElement` hook because that gets run
+ // before the child elements have been added to the DOM.
+ }
+ });
+ ```
+
+ One benefit of the above approach compared to using `Ember.run.next` is
+ that you will be able to perform DOM/CSS operations before unprocessed
+ elements are rendered to the screen, which may prevent flickering or
+ other artifacts caused by delaying processing until after rendering.
+
+ The other major benefit to the above approach is that `Ember.run.next`
+ introduces an element of non-determinism, which can make things much
+ harder to test, due to its reliance on `setTimeout`; it's much harder
+ to guarantee the order of scheduled operations when they are scheduled
+ outside of the current run loop, i.e. with `Ember.run.next`.
+
+ @method next
+ @param {Object} [target] target of method to invoke
+ @param {Function|String} method The method to invoke.
+ If you pass a string it will be resolved on the
+ target at the time the method is invoked.
+ @param {Object} [args*] Optional arguments to pass to the timeout.
+ @return {Object} Timer information for use in cancelling, see `Ember.run.cancel`.
+*/
+Ember.run.next = function() {
+ var args = slice.call(arguments);
+ args.push(1);
+ return backburner.later.apply(backburner, args);
+};
+
+/**
+ Cancels a scheduled item. Must be a value returned by `Ember.run.later()`,
+ `Ember.run.once()`, `Ember.run.next()`, `Ember.run.debounce()`, or
+ `Ember.run.throttle()`.
+
+ ```javascript
+ var runNext = Ember.run.next(myContext, function() {
+ // will not be executed
+ });
+ Ember.run.cancel(runNext);
+
+ var runLater = Ember.run.later(myContext, function() {
+ // will not be executed
+ }, 500);
+ Ember.run.cancel(runLater);
+
+ var runOnce = Ember.run.once(myContext, function() {
+ // will not be executed
+ });
+ Ember.run.cancel(runOnce);
+
+ var throttle = Ember.run.throttle(myContext, function() {
+ // will not be executed
+ }, 1);
+ Ember.run.cancel(throttle);
+
+ var debounce = Ember.run.debounce(myContext, function() {
+ // will not be executed
+ }, 1);
+ Ember.run.cancel(debounce);
+
+ var debounceImmediate = Ember.run.debounce(myContext, function() {
+ // will be executed since we passed in true (immediate)
+ }, 100, true);
+ // the 100ms delay until this method can be called again will be cancelled
+ Ember.run.cancel(debounceImmediate);
+ ```
+ ```
+ ```
+
+ @method cancel
+ @param {Object} timer Timer object to cancel
+ @return {Boolean} true if cancelled or false/undefined if it wasn't found
+*/
+Ember.run.cancel = function(timer) {
+ return backburner.cancel(timer);
+};
+
+/**
+ Delay calling the target method until the debounce period has elapsed
+ with no additional debounce calls. If `debounce` is called again before
+ the specified time has elapsed, the timer is reset and the entire period
+ must pass again before the target method is called.
+
+ This method should be used when an event may be called multiple times
+ but the action should only be called once when the event is done firing.
+ A common example is for scroll events where you only want updates to
+ happen once scrolling has ceased.
+
+ ```javascript
+ var myFunc = function() { console.log(this.name + ' ran.'); };
+ var myContext = {name: 'debounce'};
+
+ Ember.run.debounce(myContext, myFunc, 150);
+
+ // less than 150ms passes
+
+ Ember.run.debounce(myContext, myFunc, 150);
+
+ // 150ms passes
+ // myFunc is invoked with context myContext
+ // console logs 'debounce ran.' one time.
+ ```
+
+ Immediate allows you to run the function immediately, but debounce
+ other calls for this function until the wait time has elapsed. If
+ `debounce` is called again before the specified time has elapsed,
+ the timer is reset and the entire period msut pass again before
+ the method can be called again.
+
+ ```javascript
+ var myFunc = function() { console.log(this.name + ' ran.'); };
+ var myContext = {name: 'debounce'};
+
+ Ember.run.debounce(myContext, myFunc, 150, true);
+
+ // console logs 'debounce ran.' one time immediately.
+ // 100ms passes
+
+ Ember.run.debounce(myContext, myFunc, 150, true);
+
+ // 150ms passes and nothing else is logged to the console and
+ // the debouncee is no longer being watched
+
+ Ember.run.debounce(myContext, myFunc, 150, true);
+
+ // console logs 'debounce ran.' one time immediately.
+ // 150ms passes and nothing else is logged tot he console and
+ // the debouncee is no longer being watched
+
+ ```
+
+ @method debounce
+ @param {Object} [target] target of method to invoke
+ @param {Function|String} method The method to invoke.
+ May be a function or a string. If you pass a string
+ then it will be looked up on the passed target.
+ @param {Object} [args*] Optional arguments to pass to the timeout.
+ @param {Number} wait Number of milliseconds to wait.
+ @param {Boolean} immediate Trigger the function on the leading instead of the trailing edge of the wait interval.
+ @return {Array} Timer information for use in cancelling, see `Ember.run.cancel`.
+*/
+Ember.run.debounce = function() {
+ return backburner.debounce.apply(backburner, arguments);
+};
+
+/**
+ Ensure that the target method is never called more frequently than
+ the specified spacing period.
+
+ ```javascript
+ var myFunc = function() { console.log(this.name + ' ran.'); };
+ var myContext = {name: 'throttle'};
+
+ Ember.run.throttle(myContext, myFunc, 150);
+
+ // 50ms passes
+ Ember.run.throttle(myContext, myFunc, 150);
+
+ // 50ms passes
+ Ember.run.throttle(myContext, myFunc, 150);
+
+ // 50ms passes
+ Ember.run.throttle(myContext, myFunc, 150);
+
+ // 150ms passes
+ // myFunc is invoked with context myContext
+ // console logs 'throttle ran.' twice, 150ms apart.
+ ```
+
+ @method throttle
+ @param {Object} [target] target of method to invoke
+ @param {Function|String} method The method to invoke.
+ May be a function or a string. If you pass a string
+ then it will be looked up on the passed target.
+ @param {Object} [args*] Optional arguments to pass to the timeout.
+ @param {Number} spacing Number of milliseconds to space out requests.
+ @return {Array} Timer information for use in cancelling, see `Ember.run.cancel`.
+*/
+Ember.run.throttle = function() {
+ return backburner.throttle.apply(backburner, arguments);
+};
+
+// Make sure it's not an autorun during testing
+function checkAutoRun() {
+ if (!Ember.run.currentRunLoop) {
+ Ember.assert("You have turned on testing mode, which disabled the run-loop's autorun. You will need to wrap any code with asynchronous side-effects in an Ember.run", !Ember.testing);
+ }
+}
+
+})();
+
+
+
+(function() {
+// Ember.Logger
+// get
+// set
+// guidFor, meta
+// addObserver, removeObserver
+// Ember.run.schedule
+/**
+@module ember-metal
+*/
+
+// ..........................................................
+// CONSTANTS
+//
+
+/**
+ Debug parameter you can turn on. This will log all bindings that fire to
+ the console. This should be disabled in production code. Note that you
+ can also enable this from the console or temporarily.
+
+ @property LOG_BINDINGS
+ @for Ember
+ @type Boolean
+ @default false
+*/
+Ember.LOG_BINDINGS = false || !!Ember.ENV.LOG_BINDINGS;
+
+var get = Ember.get,
+ set = Ember.set,
+ guidFor = Ember.guidFor,
+ IS_GLOBAL = /^([A-Z$]|([0-9][A-Z$]))/;
+
+/**
+ Returns true if the provided path is global (e.g., `MyApp.fooController.bar`)
+ instead of local (`foo.bar.baz`).
+
+ @method isGlobalPath
+ @for Ember
+ @private
+ @param {String} path
+ @return Boolean
+*/
+var isGlobalPath = Ember.isGlobalPath = function(path) {
+ return IS_GLOBAL.test(path);
+};
+
+function getWithGlobals(obj, path) {
+ return get(isGlobalPath(path) ? Ember.lookup : obj, path);
+}
+
+// ..........................................................
+// BINDING
+//
+
+var Binding = function(toPath, fromPath) {
+ this._direction = 'fwd';
+ this._from = fromPath;
+ this._to = toPath;
+ this._directionMap = Ember.Map.create();
+};
+
+/**
+@class Binding
+@namespace Ember
+*/
+
+Binding.prototype = {
+ /**
+ This copies the Binding so it can be connected to another object.
+
+ @method copy
+ @return {Ember.Binding} `this`
+ */
+ copy: function () {
+ var copy = new Binding(this._to, this._from);
+ if (this._oneWay) { copy._oneWay = true; }
+ return copy;
+ },
+
+ // ..........................................................
+ // CONFIG
+ //
+
+ /**
+ This will set `from` property path to the specified value. It will not
+ attempt to resolve this property path to an actual object until you
+ connect the binding.
+
+ The binding will search for the property path starting at the root object
+ you pass when you `connect()` the binding. It follows the same rules as
+ `get()` - see that method for more information.
+
+ @method from
+ @param {String} path the property path to connect to
+ @return {Ember.Binding} `this`
+ */
+ from: function(path) {
+ this._from = path;
+ return this;
+ },
+
+ /**
+ This will set the `to` property path to the specified value. It will not
+ attempt to resolve this property path to an actual object until you
+ connect the binding.
+
+ The binding will search for the property path starting at the root object
+ you pass when you `connect()` the binding. It follows the same rules as
+ `get()` - see that method for more information.
+
+ @method to
+ @param {String|Tuple} path A property path or tuple
+ @return {Ember.Binding} `this`
+ */
+ to: function(path) {
+ this._to = path;
+ return this;
+ },
+
+ /**
+ Configures the binding as one way. A one-way binding will relay changes
+ on the `from` side to the `to` side, but not the other way around. This
+ means that if you change the `to` side directly, the `from` side may have
+ a different value.
+
+ @method oneWay
+ @return {Ember.Binding} `this`
+ */
+ oneWay: function() {
+ this._oneWay = true;
+ return this;
+ },
+
+ /**
+ @method toString
+ @return {String} string representation of binding
+ */
+ toString: function() {
+ var oneWay = this._oneWay ? '[oneWay]' : '';
+ return "Ember.Binding<" + guidFor(this) + ">(" + this._from + " -> " + this._to + ")" + oneWay;
+ },
+
+ // ..........................................................
+ // CONNECT AND SYNC
+ //
+
+ /**
+ Attempts to connect this binding instance so that it can receive and relay
+ changes. This method will raise an exception if you have not set the
+ from/to properties yet.
+
+ @method connect
+ @param {Object} obj The root object for this binding.
+ @return {Ember.Binding} `this`
+ */
+ connect: function(obj) {
+ Ember.assert('Must pass a valid object to Ember.Binding.connect()', !!obj);
+
+ var fromPath = this._from, toPath = this._to;
+ Ember.trySet(obj, toPath, getWithGlobals(obj, fromPath));
+
+ // add an observer on the object to be notified when the binding should be updated
+ Ember.addObserver(obj, fromPath, this, this.fromDidChange);
+
+ // if the binding is a two-way binding, also set up an observer on the target
+ if (!this._oneWay) { Ember.addObserver(obj, toPath, this, this.toDidChange); }
+
+ this._readyToSync = true;
+
+ return this;
+ },
+
+ /**
+ Disconnects the binding instance. Changes will no longer be relayed. You
+ will not usually need to call this method.
+
+ @method disconnect
+ @param {Object} obj The root object you passed when connecting the binding.
+ @return {Ember.Binding} `this`
+ */
+ disconnect: function(obj) {
+ Ember.assert('Must pass a valid object to Ember.Binding.disconnect()', !!obj);
+
+ var twoWay = !this._oneWay;
+
+ // remove an observer on the object so we're no longer notified of
+ // changes that should update bindings.
+ Ember.removeObserver(obj, this._from, this, this.fromDidChange);
+
+ // if the binding is two-way, remove the observer from the target as well
+ if (twoWay) { Ember.removeObserver(obj, this._to, this, this.toDidChange); }
+
+ this._readyToSync = false; // disable scheduled syncs...
+ return this;
+ },
+
+ // ..........................................................
+ // PRIVATE
+ //
+
+ /* called when the from side changes */
+ fromDidChange: function(target) {
+ this._scheduleSync(target, 'fwd');
+ },
+
+ /* called when the to side changes */
+ toDidChange: function(target) {
+ this._scheduleSync(target, 'back');
+ },
+
+ _scheduleSync: function(obj, dir) {
+ var directionMap = this._directionMap;
+ var existingDir = directionMap.get(obj);
+
+ // if we haven't scheduled the binding yet, schedule it
+ if (!existingDir) {
+ Ember.run.schedule('sync', this, this._sync, obj);
+ directionMap.set(obj, dir);
+ }
+
+ // If both a 'back' and 'fwd' sync have been scheduled on the same object,
+ // default to a 'fwd' sync so that it remains deterministic.
+ if (existingDir === 'back' && dir === 'fwd') {
+ directionMap.set(obj, 'fwd');
+ }
+ },
+
+ _sync: function(obj) {
+ var log = Ember.LOG_BINDINGS;
+
+ // don't synchronize destroyed objects or disconnected bindings
+ if (obj.isDestroyed || !this._readyToSync) { return; }
+
+ // get the direction of the binding for the object we are
+ // synchronizing from
+ var directionMap = this._directionMap;
+ var direction = directionMap.get(obj);
+
+ var fromPath = this._from, toPath = this._to;
+
+ directionMap.remove(obj);
+
+ // if we're synchronizing from the remote object...
+ if (direction === 'fwd') {
+ var fromValue = getWithGlobals(obj, this._from);
+ if (log) {
+ Ember.Logger.log(' ', this.toString(), '->', fromValue, obj);
+ }
+ if (this._oneWay) {
+ Ember.trySet(obj, toPath, fromValue);
+ } else {
+ Ember._suspendObserver(obj, toPath, this, this.toDidChange, function () {
+ Ember.trySet(obj, toPath, fromValue);
+ });
+ }
+ // if we're synchronizing *to* the remote object
+ } else if (direction === 'back') {
+ var toValue = get(obj, this._to);
+ if (log) {
+ Ember.Logger.log(' ', this.toString(), '<-', toValue, obj);
+ }
+ Ember._suspendObserver(obj, fromPath, this, this.fromDidChange, function () {
+ Ember.trySet(Ember.isGlobalPath(fromPath) ? Ember.lookup : obj, fromPath, toValue);
+ });
+ }
+ }
+
+};
+
+function mixinProperties(to, from) {
+ for (var key in from) {
+ if (from.hasOwnProperty(key)) {
+ to[key] = from[key];
+ }
+ }
+}
+
+mixinProperties(Binding, {
+
+ /*
+ See `Ember.Binding.from`.
+
+ @method from
+ @static
+ */
+ from: function() {
+ var C = this, binding = new C();
+ return binding.from.apply(binding, arguments);
+ },
+
+ /*
+ See `Ember.Binding.to`.
+
+ @method to
+ @static
+ */
+ to: function() {
+ var C = this, binding = new C();
+ return binding.to.apply(binding, arguments);
+ },
+
+ /**
+ Creates a new Binding instance and makes it apply in a single direction.
+ A one-way binding will relay changes on the `from` side object (supplied
+ as the `from` argument) the `to` side, but not the other way around.
+ This means that if you change the "to" side directly, the "from" side may have
+ a different value.
+
+ See `Binding.oneWay`.
+
+ @method oneWay
+ @param {String} from from path.
+ @param {Boolean} [flag] (Optional) passing nothing here will make the
+ binding `oneWay`. You can instead pass `false` to disable `oneWay`, making the
+ binding two way again.
+ @return {Ember.Binding} `this`
+ */
+ oneWay: function(from, flag) {
+ var C = this, binding = new C(null, from);
+ return binding.oneWay(flag);
+ }
+
+});
+
+/**
+ An `Ember.Binding` connects the properties of two objects so that whenever
+ the value of one property changes, the other property will be changed also.
+
+ ## Automatic Creation of Bindings with `/^*Binding/`-named Properties
+
+ You do not usually create Binding objects directly but instead describe
+ bindings in your class or object definition using automatic binding
+ detection.
+
+ Properties ending in a `Binding` suffix will be converted to `Ember.Binding`
+ instances. The value of this property should be a string representing a path
+ to another object or a custom binding instanced created using Binding helpers
+ (see "One Way Bindings"):
+
+ ```
+ valueBinding: "MyApp.someController.title"
+ ```
+
+ This will create a binding from `MyApp.someController.title` to the `value`
+ property of your object instance automatically. Now the two values will be
+ kept in sync.
+
+ ## One Way Bindings
+
+ One especially useful binding customization you can use is the `oneWay()`
+ helper. This helper tells Ember that you are only interested in
+ receiving changes on the object you are binding from. For example, if you
+ are binding to a preference and you want to be notified if the preference
+ has changed, but your object will not be changing the preference itself, you
+ could do:
+
+ ```
+ bigTitlesBinding: Ember.Binding.oneWay("MyApp.preferencesController.bigTitles")
+ ```
+
+ This way if the value of `MyApp.preferencesController.bigTitles` changes the
+ `bigTitles` property of your object will change also. However, if you
+ change the value of your `bigTitles` property, it will not update the
+ `preferencesController`.
+
+ One way bindings are almost twice as fast to setup and twice as fast to
+ execute because the binding only has to worry about changes to one side.
+
+ You should consider using one way bindings anytime you have an object that
+ may be created frequently and you do not intend to change a property; only
+ to monitor it for changes (such as in the example above).
+
+ ## Adding Bindings Manually
+
+ All of the examples above show you how to configure a custom binding, but the
+ result of these customizations will be a binding template, not a fully active
+ Binding instance. The binding will actually become active only when you
+ instantiate the object the binding belongs to. It is useful however, to
+ understand what actually happens when the binding is activated.
+
+ For a binding to function it must have at least a `from` property and a `to`
+ property. The `from` property path points to the object/key that you want to
+ bind from while the `to` path points to the object/key you want to bind to.
+
+ When you define a custom binding, you are usually describing the property
+ you want to bind from (such as `MyApp.someController.value` in the examples
+ above). When your object is created, it will automatically assign the value
+ you want to bind `to` based on the name of your binding key. In the
+ examples above, during init, Ember objects will effectively call
+ something like this on your binding:
+
+ ```javascript
+ binding = Ember.Binding.from(this.valueBinding).to("value");
+ ```
+
+ This creates a new binding instance based on the template you provide, and
+ sets the to path to the `value` property of the new object. Now that the
+ binding is fully configured with a `from` and a `to`, it simply needs to be
+ connected to become active. This is done through the `connect()` method:
+
+ ```javascript
+ binding.connect(this);
+ ```
+
+ Note that when you connect a binding you pass the object you want it to be
+ connected to. This object will be used as the root for both the from and
+ to side of the binding when inspecting relative paths. This allows the
+ binding to be automatically inherited by subclassed objects as well.
+
+ Now that the binding is connected, it will observe both the from and to side
+ and relay changes.
+
+ If you ever needed to do so (you almost never will, but it is useful to
+ understand this anyway), you could manually create an active binding by
+ using the `Ember.bind()` helper method. (This is the same method used by
+ to setup your bindings on objects):
+
+ ```javascript
+ Ember.bind(MyApp.anotherObject, "value", "MyApp.someController.value");
+ ```
+
+ Both of these code fragments have the same effect as doing the most friendly
+ form of binding creation like so:
+
+ ```javascript
+ MyApp.anotherObject = Ember.Object.create({
+ valueBinding: "MyApp.someController.value",
+
+ // OTHER CODE FOR THIS OBJECT...
+ });
+ ```
+
+ Ember's built in binding creation method makes it easy to automatically
+ create bindings for you. You should always use the highest-level APIs
+ available, even if you understand how it works underneath.
+
+ @class Binding
+ @namespace Ember
+ @since Ember 0.9
+*/
+Ember.Binding = Binding;
+
+
+/**
+ Global helper method to create a new binding. Just pass the root object
+ along with a `to` and `from` path to create and connect the binding.
+
+ @method bind
+ @for Ember
+ @param {Object} obj The root object of the transform.
+ @param {String} to The path to the 'to' side of the binding.
+ Must be relative to obj.
+ @param {String} from The path to the 'from' side of the binding.
+ Must be relative to obj or a global path.
+ @return {Ember.Binding} binding instance
+*/
+Ember.bind = function(obj, to, from) {
+ return new Ember.Binding(to, from).connect(obj);
+};
+
+/**
+ @method oneWay
+ @for Ember
+ @param {Object} obj The root object of the transform.
+ @param {String} to The path to the 'to' side of the binding.
+ Must be relative to obj.
+ @param {String} from The path to the 'from' side of the binding.
+ Must be relative to obj or a global path.
+ @return {Ember.Binding} binding instance
+*/
+Ember.oneWay = function(obj, to, from) {
+ return new Ember.Binding(to, from).oneWay().connect(obj);
+};
+
+})();
+
+
+
+(function() {
+/**
+@module ember
+@submodule ember-metal
+*/
+
+var Mixin, REQUIRED, Alias,
+ a_map = Ember.ArrayPolyfills.map,
+ a_indexOf = Ember.ArrayPolyfills.indexOf,
+ a_forEach = Ember.ArrayPolyfills.forEach,
+ a_slice = [].slice,
+ o_create = Ember.create,
+ defineProperty = Ember.defineProperty,
+ guidFor = Ember.guidFor,
+ metaFor = Ember.meta,
+ META_KEY = Ember.META_KEY;
+
+var expandProperties = Ember.expandProperties;
+
+function mixinsMeta(obj) {
+ var m = metaFor(obj, true), ret = m.mixins;
+ if (!ret) {
+ ret = m.mixins = {};
+ } else if (!m.hasOwnProperty('mixins')) {
+ ret = m.mixins = o_create(ret);
+ }
+ return ret;
+}
+
+function initMixin(mixin, args) {
+ if (args && args.length > 0) {
+ mixin.mixins = a_map.call(args, function(x) {
+ if (x instanceof Mixin) { return x; }
+
+ // Note: Manually setup a primitive mixin here. This is the only
+ // way to actually get a primitive mixin. This way normal creation
+ // of mixins will give you combined mixins...
+ var mixin = new Mixin();
+ mixin.properties = x;
+ return mixin;
+ });
+ }
+ return mixin;
+}
+
+function isMethod(obj) {
+ return 'function' === typeof obj &&
+ obj.isMethod !== false &&
+ obj !== Boolean && obj !== Object && obj !== Number && obj !== Array && obj !== Date && obj !== String;
+}
+
+var CONTINUE = {};
+
+function mixinProperties(mixinsMeta, mixin) {
+ var guid;
+
+ if (mixin instanceof Mixin) {
+ guid = guidFor(mixin);
+ if (mixinsMeta[guid]) { return CONTINUE; }
+ mixinsMeta[guid] = mixin;
+ return mixin.properties;
+ } else {
+ return mixin; // apply anonymous mixin properties
+ }
+}
+
+function concatenatedMixinProperties(concatProp, props, values, base) {
+ var concats;
+
+ // reset before adding each new mixin to pickup concats from previous
+ concats = values[concatProp] || base[concatProp];
+ if (props[concatProp]) {
+ concats = concats ? concats.concat(props[concatProp]) : props[concatProp];
+ }
+
+ return concats;
+}
+
+function giveDescriptorSuper(meta, key, property, values, descs) {
+ var superProperty;
+
+ // Computed properties override methods, and do not call super to them
+ if (values[key] === undefined) {
+ // Find the original descriptor in a parent mixin
+ superProperty = descs[key];
+ }
+
+ // If we didn't find the original descriptor in a parent mixin, find
+ // it on the original object.
+ superProperty = superProperty || meta.descs[key];
+
+ if (!superProperty || !(superProperty instanceof Ember.ComputedProperty)) {
+ return property;
+ }
+
+ // Since multiple mixins may inherit from the same parent, we need
+ // to clone the computed property so that other mixins do not receive
+ // the wrapped version.
+ property = o_create(property);
+ property.func = Ember.wrap(property.func, superProperty.func);
+
+ return property;
+}
+
+function giveMethodSuper(obj, key, method, values, descs) {
+ var superMethod;
+
+ // Methods overwrite computed properties, and do not call super to them.
+ if (descs[key] === undefined) {
+ // Find the original method in a parent mixin
+ superMethod = values[key];
+ }
+
+ // If we didn't find the original value in a parent mixin, find it in
+ // the original object
+ superMethod = superMethod || obj[key];
+
+ // Only wrap the new method if the original method was a function
+ if ('function' !== typeof superMethod) {
+ return method;
+ }
+
+ return Ember.wrap(method, superMethod);
+}
+
+function applyConcatenatedProperties(obj, key, value, values) {
+ var baseValue = values[key] || obj[key];
+
+ if (baseValue) {
+ if ('function' === typeof baseValue.concat) {
+ return baseValue.concat(value);
+ } else {
+ return Ember.makeArray(baseValue).concat(value);
+ }
+ } else {
+ return Ember.makeArray(value);
+ }
+}
+
+function applyMergedProperties(obj, key, value, values) {
+ var baseValue = values[key] || obj[key];
+
+ if (!baseValue) { return value; }
+
+ var newBase = Ember.merge({}, baseValue);
+ for (var prop in value) {
+ if (!value.hasOwnProperty(prop)) { continue; }
+
+ var propValue = value[prop];
+ if (isMethod(propValue)) {
+ // TODO: support for Computed Properties, etc?
+ newBase[prop] = giveMethodSuper(obj, prop, propValue, baseValue, {});
+ } else {
+ newBase[prop] = propValue;
+ }
+ }
+
+ return newBase;
+}
+
+function addNormalizedProperty(base, key, value, meta, descs, values, concats, mergings) {
+ if (value instanceof Ember.Descriptor) {
+ if (value === REQUIRED && descs[key]) { return CONTINUE; }
+
+ // Wrap descriptor function to implement
+ // _super() if needed
+ if (value.func) {
+ value = giveDescriptorSuper(meta, key, value, values, descs);
+ }
+
+ descs[key] = value;
+ values[key] = undefined;
+ } else {
+ if ((concats && a_indexOf.call(concats, key) >= 0) ||
+ key === 'concatenatedProperties' ||
+ key === 'mergedProperties') {
+ value = applyConcatenatedProperties(base, key, value, values);
+ } else if ((mergings && a_indexOf.call(mergings, key) >= 0)) {
+ value = applyMergedProperties(base, key, value, values);
+ } else if (isMethod(value)) {
+ value = giveMethodSuper(base, key, value, values, descs);
+ }
+
+ descs[key] = undefined;
+ values[key] = value;
+ }
+}
+
+function mergeMixins(mixins, m, descs, values, base, keys) {
+ var mixin, props, key, concats, mergings, meta;
+
+ function removeKeys(keyName) {
+ delete descs[keyName];
+ delete values[keyName];
+ }
+
+ for(var i=0, l=mixins.length; i= 0) {
+ if (_detect(mixins[loc], targetMixin, seen)) { return true; }
+ }
+ return false;
+}
+
+/**
+ @method detect
+ @param obj
+ @return {Boolean}
+*/
+MixinPrototype.detect = function(obj) {
+ if (!obj) { return false; }
+ if (obj instanceof Mixin) { return _detect(obj, this, {}); }
+ var m = obj[META_KEY],
+ mixins = m && m.mixins;
+ if (mixins) {
+ return !!mixins[guidFor(this)];
+ }
+ return false;
+};
+
+MixinPrototype.without = function() {
+ var ret = new Mixin(this);
+ ret._without = a_slice.call(arguments);
+ return ret;
+};
+
+function _keys(ret, mixin, seen) {
+ if (seen[guidFor(mixin)]) { return; }
+ seen[guidFor(mixin)] = true;
+
+ if (mixin.properties) {
+ var props = mixin.properties;
+ for (var key in props) {
+ if (props.hasOwnProperty(key)) { ret[key] = true; }
+ }
+ } else if (mixin.mixins) {
+ a_forEach.call(mixin.mixins, function(x) { _keys(ret, x, seen); });
+ }
+}
+
+MixinPrototype.keys = function() {
+ var keys = {}, seen = {}, ret = [];
+ _keys(keys, this, seen);
+ for(var key in keys) {
+ if (keys.hasOwnProperty(key)) { ret.push(key); }
+ }
+ return ret;
+};
+
+// returns the mixins currently applied to the specified object
+// TODO: Make Ember.mixin
+Mixin.mixins = function(obj) {
+ var m = obj[META_KEY],
+ mixins = m && m.mixins, ret = [];
+
+ if (!mixins) { return ret; }
+
+ for (var key in mixins) {
+ var mixin = mixins[key];
+
+ // skip primitive mixins since these are always anonymous
+ if (!mixin.properties) { ret.push(mixin); }
+ }
+
+ return ret;
+};
+
+REQUIRED = new Ember.Descriptor();
+REQUIRED.toString = function() { return '(Required Property)'; };
+
+/**
+ Denotes a required property for a mixin
+
+ @method required
+ @for Ember
+*/
+Ember.required = function() {
+ return REQUIRED;
+};
+
+Alias = function(methodName) {
+ this.methodName = methodName;
+};
+Alias.prototype = new Ember.Descriptor();
+
+/**
+ Makes a method available via an additional name.
+
+ ```javascript
+ App.Person = Ember.Object.extend({
+ name: function() {
+ return 'Tomhuda Katzdale';
+ },
+ moniker: Ember.aliasMethod('name')
+ });
+
+ var goodGuy = App.Person.create()
+ ```
+
+ @method aliasMethod
+ @for Ember
+ @param {String} methodName name of the method to alias
+ @return {Ember.Descriptor}
+*/
+Ember.aliasMethod = function(methodName) {
+ return new Alias(methodName);
+};
+
+// ..........................................................
+// OBSERVER HELPER
+//
+
+/**
+ Specify a method that observes property changes.
+
+ ```javascript
+ Ember.Object.extend({
+ valueObserver: Ember.observer('value', function() {
+ // Executes whenever the "value" property changes
+ })
+ });
+ ```
+
+ In the future this method may become asynchronous. If you want to ensure
+ synchronous behavior, use `immediateObserver`.
+
+ Also available as `Function.prototype.observes` if prototype extensions are
+ enabled.
+
+ @method observer
+ @for Ember
+ @param {String} propertyNames*
+ @param {Function} func
+ @return func
+*/
+Ember.observer = function() {
+ var func = a_slice.call(arguments, -1)[0];
+ var paths;
+
+ var addWatchedProperty = function (path) { paths.push(path); };
+ var _paths = a_slice.call(arguments, 0, -1);
+
+ if (typeof func !== "function") {
+ // revert to old, soft-deprecated argument ordering
+
+ func = arguments[0];
+ _paths = a_slice.call(arguments, 1);
+ }
+
+ paths = [];
+
+ for (var i=0; i<_paths.length; ++i) {
+ expandProperties(_paths[i], addWatchedProperty);
+ }
+
+ if (typeof func !== "function") {
+ throw new Ember.Error("Ember.observer called without a function");
+ }
+
+ func.__ember_observes__ = paths;
+ return func;
+};
+
+/**
+ Specify a method that observes property changes.
+
+ ```javascript
+ Ember.Object.extend({
+ valueObserver: Ember.immediateObserver('value', function() {
+ // Executes whenever the "value" property changes
+ })
+ });
+ ```
+
+ In the future, `Ember.observer` may become asynchronous. In this event,
+ `Ember.immediateObserver` will maintain the synchronous behavior.
+
+ Also available as `Function.prototype.observesImmediately` if prototype extensions are
+ enabled.
+
+ @method immediateObserver
+ @for Ember
+ @param {String} propertyNames*
+ @param {Function} func
+ @return func
+*/
+Ember.immediateObserver = function() {
+ for (var i=0, l=arguments.length; i this.changingFrom ? 'green' : 'red';
+ // logic
+ }
+ }),
+
+ friendsDidChange: Ember.observer('friends.@each.name', function(obj, keyName) {
+ // some logic
+ // obj.get(keyName) returns friends array
+ })
+ });
+ ```
+
+ Also available as `Function.prototype.observesBefore` if prototype extensions are
+ enabled.
+
+ @method beforeObserver
+ @for Ember
+ @param {String} propertyNames*
+ @param {Function} func
+ @return func
+*/
+Ember.beforeObserver = function() {
+ var func = a_slice.call(arguments, -1)[0];
+ var paths;
+
+ var addWatchedProperty = function(path) { paths.push(path); };
+
+ var _paths = a_slice.call(arguments, 0, -1);
+
+ if (typeof func !== "function") {
+ // revert to old, soft-deprecated argument ordering
+
+ func = arguments[0];
+ _paths = a_slice.call(arguments, 1);
+ }
+
+ paths = [];
+
+ for (var i=0; i<_paths.length; ++i) {
+ expandProperties(_paths[i], addWatchedProperty);
+ }
+
+ if (typeof func !== "function") {
+ throw new Ember.Error("Ember.beforeObserver called without a function");
+ }
+
+ func.__ember_observesBefore__ = paths;
+ return func;
+};
+
+})();
+
+
+
+(function() {
+// Provides a way to register library versions with ember.
+var forEach = Ember.EnumerableUtils.forEach,
+ indexOf = Ember.EnumerableUtils.indexOf;
+
+Ember.libraries = function() {
+ var libraries = [];
+ var coreLibIndex = 0;
+
+ var getLibrary = function(name) {
+ for (var i = 0; i < libraries.length; i++) {
+ if (libraries[i].name === name) {
+ return libraries[i];
+ }
+ }
+ };
+
+ libraries.register = function(name, version) {
+ if (!getLibrary(name)) {
+ libraries.push({name: name, version: version});
+ }
+ };
+
+ libraries.registerCoreLibrary = function(name, version) {
+ if (!getLibrary(name)) {
+ libraries.splice(coreLibIndex++, 0, {name: name, version: version});
+ }
+ };
+
+ libraries.deRegister = function(name) {
+ var lib = getLibrary(name);
+ if (lib) libraries.splice(indexOf(libraries, lib), 1);
+ };
+
+ libraries.each = function (callback) {
+ forEach(libraries, function(lib) {
+ callback(lib.name, lib.version);
+ });
+ };
+
+ return libraries;
+}();
+
+Ember.libraries.registerCoreLibrary('Ember', Ember.VERSION);
+
+})();
+
+
+
+(function() {
+/**
+Ember Metal
+
+@module ember
+@submodule ember-metal
+*/
+
+})();
+
+(function() {
+/**
+ @class RSVP
+ @module RSVP
+ */
+define("rsvp/all",
+ ["./promise","exports"],
+ function(__dependency1__, __exports__) {
+ "use strict";
+ var Promise = __dependency1__["default"];
+
+ /**
+ This is a convenient alias for `RSVP.Promise.all`.
+
+ @method all
+ @for RSVP
+ @param {Array} array Array of promises.
+ @param {String} label An optional label. This is useful
+ for tooling.
+ @static
+ */
+ __exports__["default"] = function all(array, label) {
+ return Promise.all(array, label);
+ };
+ });
+define("rsvp/all_settled",
+ ["./promise","./utils","exports"],
+ function(__dependency1__, __dependency2__, __exports__) {
+ "use strict";
+ var Promise = __dependency1__["default"];
+ var isArray = __dependency2__.isArray;
+ var isNonThenable = __dependency2__.isNonThenable;
+
+ /**
+ `RSVP.allSettled` is similar to `RSVP.all`, but instead of implementing
+ a fail-fast method, it waits until all the promises have returned and
+ shows you all the results. This is useful if you want to handle multiple
+ promises' failure states together as a set.
+
+ Returns a promise that is fulfilled when all the given promises have been
+ settled. The return promise is fulfilled with an array of the states of
+ the promises passed into the `promises` array argument.
+
+ Each state object will either indicate fulfillment or rejection, and
+ provide the corresponding value or reason. The states will take one of
+ the following formats:
+
+ ```javascript
+ { state: 'fulfilled', value: value }
+ or
+ { state: 'rejected', reason: reason }
+ ```
+
+ Example:
+
+ ```javascript
+ var promise1 = RSVP.Promise.resolve(1);
+ var promise2 = RSVP.Promise.reject(new Error('2'));
+ var promise3 = RSVP.Promise.reject(new Error('3'));
+ var promises = [ promise1, promise2, promise3 ];
+
+ RSVP.allSettled(promises).then(function(array){
+ // array == [
+ // { state: 'fulfilled', value: 1 },
+ // { state: 'rejected', reason: Error },
+ // { state: 'rejected', reason: Error }
+ // ]
+ // Note that for the second item, reason.message will be "2", and for the
+ // third item, reason.message will be "3".
+ }, function(error) {
+ // Not run. (This block would only be called if allSettled had failed,
+ // for instance if passed an incorrect argument type.)
+ });
+ ```
+
+ @method allSettled
+ @for RSVP
+ @param {Array} promises
+ @param {String} label - optional string that describes the promise.
+ Useful for tooling.
+ @return {Promise} promise that is fulfilled with an array of the settled
+ states of the constituent promises.
+ @static
+ */
+
+ __exports__["default"] = function allSettled(entries, label) {
+ return new Promise(function(resolve, reject) {
+ if (!isArray(entries)) {
+ throw new TypeError('You must pass an array to allSettled.');
+ }
+
+ var remaining = entries.length;
+ var entry;
+
+ if (remaining === 0) {
+ resolve([]);
+ return;
+ }
+
+ var results = new Array(remaining);
+
+ function fulfilledResolver(index) {
+ return function(value) {
+ resolveAll(index, fulfilled(value));
+ };
+ }
+
+ function rejectedResolver(index) {
+ return function(reason) {
+ resolveAll(index, rejected(reason));
+ };
+ }
+
+ function resolveAll(index, value) {
+ results[index] = value;
+ if (--remaining === 0) {
+ resolve(results);
+ }
+ }
+
+ for (var index = 0; index < entries.length; index++) {
+ entry = entries[index];
+
+ if (isNonThenable(entry)) {
+ resolveAll(index, fulfilled(entry));
+ } else {
+ Promise.cast(entry).then(fulfilledResolver(index), rejectedResolver(index));
+ }
+ }
+ }, label);
+ };
+
+ function fulfilled(value) {
+ return { state: 'fulfilled', value: value };
+ }
+
+ function rejected(reason) {
+ return { state: 'rejected', reason: reason };
+ }
+ });
+define("rsvp/config",
+ ["./events","exports"],
+ function(__dependency1__, __exports__) {
+ "use strict";
+ var EventTarget = __dependency1__["default"];
+
+ var config = {
+ instrument: false
+ };
+
+ EventTarget.mixin(config);
+
+ function configure(name, value) {
+ if (name === 'onerror') {
+ // handle for legacy users that expect the actual
+ // error to be passed to their function added via
+ // `RSVP.configure('onerror', someFunctionHere);`
+ config.on('error', value);
+ return;
+ }
+
+ if (arguments.length === 2) {
+ config[name] = value;
+ } else {
+ return config[name];
+ }
+ }
+
+ __exports__.config = config;
+ __exports__.configure = configure;
+ });
+define("rsvp/defer",
+ ["./promise","exports"],
+ function(__dependency1__, __exports__) {
+ "use strict";
+ var Promise = __dependency1__["default"];
+
+ /**
+ `RSVP.defer` returns an object similar to jQuery's `$.Deferred`.
+ `RSVP.defer` should be used when porting over code reliant on `$.Deferred`'s
+ interface. New code should use the `RSVP.Promise` constructor instead.
+
+ The object returned from `RSVP.defer` is a plain object with three properties:
+
+ * promise - an `RSVP.Promise`.
+ * reject - a function that causes the `promise` property on this object to
+ become rejected
+ * resolve - a function that causes the `promise` property on this object to
+ become fulfilled.
+
+ Example:
+
+ ```javascript
+ var deferred = RSVP.defer();
+
+ deferred.resolve("Success!");
+
+ defered.promise.then(function(value){
+ // value here is "Success!"
+ });
+ ```
+
+ @method defer
+ @for RSVP
+ @param {String} label optional string for labeling the promise.
+ Useful for tooling.
+ @return {Object}
+ */
+
+ __exports__["default"] = function defer(label) {
+ var deferred = { };
+
+ deferred.promise = new Promise(function(resolve, reject) {
+ deferred.resolve = resolve;
+ deferred.reject = reject;
+ }, label);
+
+ return deferred;
+ };
+ });
+define("rsvp/events",
+ ["exports"],
+ function(__exports__) {
+ "use strict";
+ var indexOf = function(callbacks, callback) {
+ for (var i=0, l=callbacks.length; i 1;
+ };
+
+ RSVP.filter(promises, filterFn).then(function(result){
+ // result is [ 2, 3 ]
+ });
+ ```
+
+ If any of the `promises` given to `RSVP.filter` are rejected, the first promise
+ that is rejected will be given as an argument to the returned promise's
+ rejection handler. For example:
+
+ ```javascript
+ var promise1 = RSVP.resolve(1);
+ var promise2 = RSVP.reject(new Error("2"));
+ var promise3 = RSVP.reject(new Error("3"));
+ var promises = [ promise1, promise2, promise3 ];
+
+ var filterFn = function(item){
+ return item > 1;
+ };
+
+ RSVP.filter(promises, filterFn).then(function(array){
+ // Code here never runs because there are rejected promises!
+ }, function(reason) {
+ // reason.message === "2"
+ });
+ ```
+
+ `RSVP.filter` will also wait for any promises returned from `filterFn`.
+ For instance, you may want to fetch a list of users then return a subset
+ of those users based on some asynchronous operation:
+
+ ```javascript
+
+ var alice = { name: 'alice' };
+ var bob = { name: 'bob' };
+ var users = [ alice, bob ];
+
+ var promises = users.map(function(user){
+ return RSVP.resolve(user);
+ });
+
+ var filterFn = function(user){
+ // Here, Alice has permissions to create a blog post, but Bob does not.
+ return getPrivilegesForUser(user).then(function(privs){
+ return privs.can_create_blog_post === true;
+ });
+ };
+ RSVP.filter(promises, filterFn).then(function(users){
+ // true, because the server told us only Alice can create a blog post.
+ users.length === 1;
+ // false, because Alice is the only user present in `users`
+ users[0] === bob;
+ });
+ ```
+
+ @method filter
+ @for RSVP
+ @param {Array} promises
+ @param {Function} filterFn - function to be called on each resolved value to
+ filter the final results.
+ @param {String} label optional string describing the promise. Useful for
+ tooling.
+ @return {Promise}
+ */
+ function filter(promises, filterFn, label) {
+ return all(promises, label).then(function(values){
+ if (!isArray(promises)) {
+ throw new TypeError('You must pass an array to filter.');
+ }
+
+ if (!isFunction(filterFn)){
+ throw new TypeError("You must pass a function to filter's second argument.");
+ }
+
+ return map(promises, filterFn, label).then(function(filterResults){
+ var i,
+ valuesLen = values.length,
+ filtered = [];
+
+ for (i = 0; i < valuesLen; i++){
+ if(filterResults[i]) filtered.push(values[i]);
+ }
+ return filtered;
+ });
+ });
+ }
+
+ __exports__["default"] = filter;
+ });
+define("rsvp/hash",
+ ["./promise","./utils","exports"],
+ function(__dependency1__, __dependency2__, __exports__) {
+ "use strict";
+ var Promise = __dependency1__["default"];
+ var isNonThenable = __dependency2__.isNonThenable;
+ var keysOf = __dependency2__.keysOf;
+
+ /**
+ `RSVP.hash` is similar to `RSVP.all`, but takes an object instead of an array
+ for its `promises` argument.
+
+ Returns a promise that is fulfilled when all the given promises have been
+ fulfilled, or rejected if any of them become rejected. The returned promise
+ is fulfilled with a hash that has the same key names as the `promises` object
+ argument. If any of the values in the object are not promises, they will
+ simply be copied over to the fulfilled object.
+
+ Example:
+
+ ```javascript
+ var promises = {
+ myPromise: RSVP.resolve(1),
+ yourPromise: RSVP.resolve(2),
+ theirPromise: RSVP.resolve(3),
+ notAPromise: 4
+ };
+
+ RSVP.hash(promises).then(function(hash){
+ // hash here is an object that looks like:
+ // {
+ // myPromise: 1,
+ // yourPromise: 2,
+ // theirPromise: 3,
+ // notAPromise: 4
+ // }
+ });
+ ````
+
+ If any of the `promises` given to `RSVP.hash` are rejected, the first promise
+ that is rejected will be given as the reason to the rejection handler.
+
+ Example:
+
+ ```javascript
+ var promises = {
+ myPromise: RSVP.resolve(1),
+ rejectedPromise: RSVP.reject(new Error("rejectedPromise")),
+ anotherRejectedPromise: RSVP.reject(new Error("anotherRejectedPromise")),
+ };
+
+ RSVP.hash(promises).then(function(hash){
+ // Code here never runs because there are rejected promises!
+ }, function(reason) {
+ // reason.message === "rejectedPromise"
+ });
+ ```
+
+ An important note: `RSVP.hash` is intended for plain JavaScript objects that
+ are just a set of keys and values. `RSVP.hash` will NOT preserve prototype
+ chains.
+
+ Example:
+
+ ```javascript
+ function MyConstructor(){
+ this.example = RSVP.resolve("Example");
+ }
+
+ MyConstructor.prototype = {
+ protoProperty: RSVP.resolve("Proto Property")
+ };
+
+ var myObject = new MyConstructor();
+
+ RSVP.hash(myObject).then(function(hash){
+ // protoProperty will not be present, instead you will just have an
+ // object that looks like:
+ // {
+ // example: "Example"
+ // }
+ //
+ // hash.hasOwnProperty('protoProperty'); // false
+ // 'undefined' === typeof hash.protoProperty
+ });
+ ```
+
+ @method hash
+ @for RSVP
+ @param {Object} promises
+ @param {String} label optional string that describes the promise.
+ Useful for tooling.
+ @return {Promise} promise that is fulfilled when all properties of `promises`
+ have been fulfilled, or rejected if any of them become rejected.
+ @static
+ */
+ __exports__["default"] = function hash(object, label) {
+ return new Promise(function(resolve, reject){
+ var results = {};
+ var keys = keysOf(object);
+ var remaining = keys.length;
+ var entry, property;
+
+ if (remaining === 0) {
+ resolve(results);
+ return;
+ }
+
+ function fulfilledTo(property) {
+ return function(value) {
+ results[property] = value;
+ if (--remaining === 0) {
+ resolve(results);
+ }
+ };
+ }
+
+ function onRejection(reason) {
+ remaining = 0;
+ reject(reason);
+ }
+
+ for (var i = 0; i < keys.length; i++) {
+ property = keys[i];
+ entry = object[property];
+
+ if (isNonThenable(entry)) {
+ results[property] = entry;
+ if (--remaining === 0) {
+ resolve(results);
+ }
+ } else {
+ Promise.cast(entry).then(fulfilledTo(property), onRejection);
+ }
+ }
+ });
+ };
+ });
+define("rsvp/instrument",
+ ["./config","./utils","exports"],
+ function(__dependency1__, __dependency2__, __exports__) {
+ "use strict";
+ var config = __dependency1__.config;
+ var now = __dependency2__.now;
+
+ __exports__["default"] = function instrument(eventName, promise, child) {
+ // instrumentation should not disrupt normal usage.
+ try {
+ config.trigger(eventName, {
+ guid: promise._guidKey + promise._id,
+ eventName: eventName,
+ detail: promise._detail,
+ childGuid: child && promise._guidKey + child._id,
+ label: promise._label,
+ timeStamp: now(),
+ stack: new Error(promise._label).stack
+ });
+ } catch(error) {
+ setTimeout(function(){
+ throw error;
+ }, 0);
+ }
+ };
+ });
+define("rsvp/map",
+ ["./promise","./all","./utils","exports"],
+ function(__dependency1__, __dependency2__, __dependency3__, __exports__) {
+ "use strict";
+ var Promise = __dependency1__["default"];
+ var all = __dependency2__["default"];
+ var isArray = __dependency3__.isArray;
+ var isFunction = __dependency3__.isFunction;
+
+ /**
+ `RSVP.map` is similar to JavaScript's native `map` method, except that it
+ waits for all promises to become fulfilled before running the `mapFn` on
+ each item in given to `promises`. `RSVP.map` returns a promise that will
+ become fulfilled with the result of running `mapFn` on the values the promises
+ become fulfilled with.
+
+ For example:
+
+ ```javascript
+
+ var promise1 = RSVP.resolve(1);
+ var promise2 = RSVP.resolve(2);
+ var promise3 = RSVP.resolve(3);
+ var promises = [ promise1, promise2, promise3 ];
+
+ var mapFn = function(item){
+ return item + 1;
+ };
+
+ RSVP.map(promises, mapFn).then(function(result){
+ // result is [ 2, 3, 4 ]
+ });
+ ```
+
+ If any of the `promises` given to `RSVP.map` are rejected, the first promise
+ that is rejected will be given as an argument to the returned promise's
+ rejection handler. For example:
+
+ ```javascript
+ var promise1 = RSVP.resolve(1);
+ var promise2 = RSVP.reject(new Error("2"));
+ var promise3 = RSVP.reject(new Error("3"));
+ var promises = [ promise1, promise2, promise3 ];
+
+ var mapFn = function(item){
+ return item + 1;
+ };
+
+ RSVP.map(promises, mapFn).then(function(array){
+ // Code here never runs because there are rejected promises!
+ }, function(reason) {
+ // reason.message === "2"
+ });
+ ```
+
+ `RSVP.map` will also wait if a promise is returned from `mapFn`. For example,
+ say you want to get all comments from a set of blog posts, but you need
+ the blog posts first becuase they contain a url to those comments.
+
+ ```javscript
+
+ var mapFn = function(blogPost){
+ // getComments does some ajax and returns an RSVP.Promise that is fulfilled
+ // with some comments data
+ return getComments(blogPost.comments_url);
+ };
+
+ // getBlogPosts does some ajax and returns an RSVP.Promise that is fulfilled
+ // with some blog post data
+ RSVP.map(getBlogPosts(), mapFn).then(function(comments){
+ // comments is the result of asking the server for the comments
+ // of all blog posts returned from getBlogPosts()
+ });
+ ```
+
+ @method map
+ @for RSVP
+ @param {Array} promises
+ @param {Function} mapFn function to be called on each fulfilled promise.
+ @param {String} label optional string for labeling the promise.
+ Useful for tooling.
+ @return {Promise} promise that is fulfilled with the result of calling
+ `mapFn` on each fulfilled promise or value when they become fulfilled.
+ The promise will be rejected if any of the given `promises` become rejected.
+ @static
+ */
+ __exports__["default"] = function map(promises, mapFn, label) {
+ return all(promises, label).then(function(results){
+ if (!isArray(promises)) {
+ throw new TypeError('You must pass an array to map.');
+ }
+
+ if (!isFunction(mapFn)){
+ throw new TypeError("You must pass a function to map's second argument.");
+ }
+
+
+ var resultLen = results.length,
+ mappedResults = [],
+ i;
+
+ for (i = 0; i < resultLen; i++){
+ mappedResults.push(mapFn(results[i]));
+ }
+
+ return all(mappedResults, label);
+ });
+ };
+ });
+define("rsvp/node",
+ ["./promise","exports"],
+ function(__dependency1__, __exports__) {
+ "use strict";
+ var Promise = __dependency1__["default"];
+
+ var slice = Array.prototype.slice;
+
+ function makeNodeCallbackFor(resolve, reject) {
+ return function (error, value) {
+ if (error) {
+ reject(error);
+ } else if (arguments.length > 2) {
+ resolve(slice.call(arguments, 1));
+ } else {
+ resolve(value);
+ }
+ };
+ }
+
+ /**
+ `RSVP.denodeify` takes a "node-style" function and returns a function that
+ will return an `RSVP.Promise`. You can use `denodeify` in Node.js or the
+ browser when you'd prefer to use promises over using callbacks. For example,
+ `denodeify` transforms the following:
+
+ ```javascript
+ var fs = require('fs');
+
+ fs.readFile('myfile.txt', function(err, data){
+ if (err) return handleError(err);
+ handleData(data);
+ });
+ ```
+
+ into:
+
+ ```javascript
+ var fs = require('fs');
+
+ var readFile = RSVP.denodeify(fs.readFile);
+
+ readFile('myfile.txt').then(handleData, handleError);
+ ```
+
+ Using `denodeify` makes it easier to compose asynchronous operations instead
+ of using callbacks. For example, instead of:
+
+ ```javascript
+ var fs = require('fs');
+ var log = require('some-async-logger');
+
+ fs.readFile('myfile.txt', function(err, data){
+ if (err) return handleError(err);
+ fs.writeFile('myfile2.txt', data, function(err){
+ if (err) throw err;
+ log('success', function(err) {
+ if (err) throw err;
+ });
+ });
+ });
+ ```
+
+ You can chain the operations together using `then` from the returned promise:
+
+ ```javascript
+ var fs = require('fs');
+ var denodeify = RSVP.denodeify;
+ var readFile = denodeify(fs.readFile);
+ var writeFile = denodeify(fs.writeFile);
+ var log = denodeify(require('some-async-logger'));
+
+ readFile('myfile.txt').then(function(data){
+ return writeFile('myfile2.txt', data);
+ }).then(function(){
+ return log('SUCCESS');
+ }).then(function(){
+ // success handler
+ }, function(reason){
+ // rejection handler
+ });
+ ```
+
+ @method denodeify
+ @for RSVP
+ @param {Function} nodeFunc a "node-style" function that takes a callback as
+ its last argument. The callback expects an error to be passed as its first
+ argument (if an error occurred, otherwise null), and the value from the
+ operation as its second argument ("function(err, value){ }").
+ @param {Any} binding optional argument for binding the "this" value when
+ calling the `nodeFunc` function.
+ @return {Function} a function that wraps `nodeFunc` to return an
+ `RSVP.Promise`
+ @static
+ */
+ __exports__["default"] = function denodeify(nodeFunc, binding) {
+ return function() {
+ var nodeArgs = slice.call(arguments), resolve, reject;
+ var thisArg = this || binding;
+
+ return new Promise(function(resolve, reject) {
+ Promise.all(nodeArgs).then(function(nodeArgs) {
+ try {
+ nodeArgs.push(makeNodeCallbackFor(resolve, reject));
+ nodeFunc.apply(thisArg, nodeArgs);
+ } catch(e) {
+ reject(e);
+ }
+ });
+ });
+ };
+ };
+ });
+define("rsvp/promise",
+ ["./config","./events","./instrument","./utils","./promise/cast","./promise/all","./promise/race","./promise/resolve","./promise/reject","exports"],
+ function(__dependency1__, __dependency2__, __dependency3__, __dependency4__, __dependency5__, __dependency6__, __dependency7__, __dependency8__, __dependency9__, __exports__) {
+ "use strict";
+ var config = __dependency1__.config;
+ var EventTarget = __dependency2__["default"];
+ var instrument = __dependency3__["default"];
+ var objectOrFunction = __dependency4__.objectOrFunction;
+ var isFunction = __dependency4__.isFunction;
+ var now = __dependency4__.now;
+ var cast = __dependency5__["default"];
+ var all = __dependency6__["default"];
+ var race = __dependency7__["default"];
+ var Resolve = __dependency8__["default"];
+ var Reject = __dependency9__["default"];
+
+ var guidKey = 'rsvp_' + now() + '-';
+ var counter = 0;
+
+ function noop() {}
+
+ __exports__["default"] = Promise;
+
+
+ /**
+ Promise objects represent the eventual result of an asynchronous operation. The
+ primary way of interacting with a promise is through its `then` method, which
+ registers callbacks to receive either a promise’s eventual value or the reason
+ why the promise cannot be fulfilled.
+
+ Terminology
+ -----------
+
+ - `promise` is an object or function with a `then` method whose behavior conforms to this specification.
+ - `thenable` is an object or function that defines a `then` method.
+ - `value` is any legal JavaScript value (including undefined, a thenable, or a promise).
+ - `exception` is a value that is thrown using the throw statement.
+ - `reason` is a value that indicates why a promise was rejected.
+ - `settled` the final resting state of a promise, fulfilled or rejected.
+
+ A promise can be in one of three states: pending, fulfilled, or rejected.
+
+ Promises that are fulfilled have a fulfillment value and are in the fulfilled
+ state. Promises that are rejected have a rejection reason and are in the
+ rejected state. A fulfillment value is never a thenable. Similarly, a
+ rejection reason is never a thenable.
+
+ Promises can also be said to *resolve* a value. If this value is also a
+ promise, then the original promise's settled state will match the value's
+ settled state. So a promise that *resolves* a promise that rejects will
+ itself reject, and a promise that *resolves* a promise that fulfills will
+ itself fulfill.
+
+
+ Basic Usage:
+ ------------
+
+ ```js
+ var promise = new Promise(function(resolve, reject) {
+ // on success
+ resolve(value);
+
+ // on failure
+ reject(reason);
+ });
+
+ promise.then(function(value) {
+ // on fulfillment
+ }, function(reason) {
+ // on rejection
+ });
+ ```
+
+ Advanced Usage:
+ ---------------
+
+ Promises shine when abstracting away asynchronous interactions such as
+ `XMLHttpRequest`s.
+
+ ```js
+ function getJSON(url) {
+ return new Promise(function(resolve, reject){
+ var xhr = new XMLHttpRequest();
+
+ xhr.open('GET', url);
+ xhr.onreadystatechange = handler;
+ xhr.responseType = 'json';
+ xhr.setRequestHeader('Accept', 'application/json');
+ xhr.send();
+
+ function handler() {
+ if (this.readyState === this.DONE) {
+ if (this.status === 200) {
+ resolve(this.response);
+ } else {
+ reject(new Error("getJSON: `" + url + "` failed with status: [" + this.status + "]");
+ }
+ }
+ };
+ });
+ }
+
+ getJSON('/posts.json').then(function(json) {
+ // on fulfillment
+ }, function(reason) {
+ // on rejection
+ });
+ ```
+
+ Unlike callbacks, promises are great composable primitives.
+
+ ```js
+ Promise.all([
+ getJSON('/posts'),
+ getJSON('/comments')
+ ]).then(function(values){
+ values[0] // => postsJSON
+ values[1] // => commentsJSON
+
+ return values;
+ });
+ ```
+
+ @class RSVP.Promise
+ @param {function}
+ @param {String} label optional string for labeling the promise.
+ Useful for tooling.
+ @constructor
+ */
+ function Promise(resolver, label) {
+ if (!isFunction(resolver)) {
+ throw new TypeError('You must pass a resolver function as the first argument to the promise constructor');
+ }
+
+ if (!(this instanceof Promise)) {
+ throw new TypeError("Failed to construct 'Promise': Please use the 'new' operator, this object constructor cannot be called as a function.");
+ }
+
+ this._id = counter++;
+ this._label = label;
+ this._subscribers = [];
+
+ if (config.instrument) {
+ instrument('created', this);
+ }
+
+ if (noop !== resolver) {
+ invokeResolver(resolver, this);
+ }
+ }
+
+ function invokeResolver(resolver, promise) {
+ function resolvePromise(value) {
+ resolve(promise, value);
+ }
+
+ function rejectPromise(reason) {
+ reject(promise, reason);
+ }
+
+ try {
+ resolver(resolvePromise, rejectPromise);
+ } catch(e) {
+ rejectPromise(e);
+ }
+ }
+
+ Promise.cast = cast;
+ Promise.all = all;
+ Promise.race = race;
+ Promise.resolve = Resolve;
+ Promise.reject = Reject;
+
+ var PENDING = void 0;
+ var SEALED = 0;
+ var FULFILLED = 1;
+ var REJECTED = 2;
+
+ function subscribe(parent, child, onFulfillment, onRejection) {
+ var subscribers = parent._subscribers;
+ var length = subscribers.length;
+
+ subscribers[length] = child;
+ subscribers[length + FULFILLED] = onFulfillment;
+ subscribers[length + REJECTED] = onRejection;
+ }
+
+ function publish(promise, settled) {
+ var child, callback, subscribers = promise._subscribers, detail = promise._detail;
+
+ if (config.instrument) {
+ instrument(settled === FULFILLED ? 'fulfilled' : 'rejected', promise);
+ }
+
+ for (var i = 0; i < subscribers.length; i += 3) {
+ child = subscribers[i];
+ callback = subscribers[i + settled];
+
+ invokeCallback(settled, child, callback, detail);
+ }
+
+ promise._subscribers = null;
+ }
+
+ Promise.prototype = {
+ constructor: Promise,
+
+ _id: undefined,
+ _guidKey: guidKey,
+ _label: undefined,
+
+ _state: undefined,
+ _detail: undefined,
+ _subscribers: undefined,
+
+ _onerror: function (reason) {
+ config.trigger('error', reason);
+ },
+
+ /**
+ The primary way of interacting with a promise is through its `then` method,
+ which registers callbacks to receive either a promise's eventual value or the
+ reason why the promise cannot be fulfilled.
+
+ ```js
+ findUser().then(function(user){
+ // user is available
+ }, function(reason){
+ // user is unavailable, and you are given the reason why
+ });
+ ```
+
+ Chaining
+ --------
+
+ The return value of `then` is itself a promise. This second, "downstream"
+ promise is resolved with the return value of the first promise's fulfillment
+ or rejection handler, or rejected if the handler throws an exception.
+
+ ```js
+ findUser().then(function (user) {
+ return user.name;
+ }, function (reason) {
+ return "default name";
+ }).then(function (userName) {
+ // If `findUser` fulfilled, `userName` will be the user's name, otherwise it
+ // will be `"default name"`
+ });
+
+ findUser().then(function (user) {
+ throw new Error("Found user, but still unhappy");
+ }, function (reason) {
+ throw new Error("`findUser` rejected and we're unhappy");
+ }).then(function (value) {
+ // never reached
+ }, function (reason) {
+ // if `findUser` fulfilled, `reason` will be "Found user, but still unhappy".
+ // If `findUser` rejected, `reason` will be "`findUser` rejected and we're unhappy".
+ });
+ ```
+ If the downstream promise does not specify a rejection handler, rejection reasons will be propagated further downstream.
+
+ ```js
+ findUser().then(function (user) {
+ throw new PedagogicalException("Upstream error");
+ }).then(function (value) {
+ // never reached
+ }).then(function (value) {
+ // never reached
+ }, function (reason) {
+ // The `PedgagocialException` is propagated all the way down to here
+ });
+ ```
+
+ Assimilation
+ ------------
+
+ Sometimes the value you want to propagate to a downstream promise can only be
+ retrieved asynchronously. This can be achieved by returning a promise in the
+ fulfillment or rejection handler. The downstream promise will then be pending
+ until the returned promise is settled. This is called *assimilation*.
+
+ ```js
+ findUser().then(function (user) {
+ return findCommentsByAuthor(user);
+ }).then(function (comments) {
+ // The user's comments are now available
+ });
+ ```
+
+ If the assimliated promise rejects, then the downstream promise will also reject.
+
+ ```js
+ findUser().then(function (user) {
+ return findCommentsByAuthor(user);
+ }).then(function (comments) {
+ // If `findCommentsByAuthor` fulfills, we'll have the value here
+ }, function (reason) {
+ // If `findCommentsByAuthor` rejects, we'll have the reason here
+ });
+ ```
+
+ Simple Example
+ --------------
+
+ Synchronous Example
+
+ ```javascript
+ var result;
+
+ try {
+ result = findResult();
+ // success
+ } catch(reason) {
+ // failure
+ }
+ ```
+
+ Errback Example
+
+ ```js
+ findResult(function(result, err){
+ if (err) {
+ // failure
+ } else {
+ // success
+ }
+ });
+ ```
+
+ Promise Example;
+
+ ```javascript
+ findResult().then(function(result){
+ // success
+ }, function(reason){
+ // failure
+ });
+ ```
+
+ Advanced Example
+ --------------
+
+ Synchronous Example
+
+ ```javascript
+ var author, books;
+
+ try {
+ author = findAuthor();
+ books = findBooksByAuthor(author);
+ // success
+ } catch(reason) {
+ // failure
+ }
+ ```
+
+ Errback Example
+
+ ```js
+
+ function foundBooks(books) {
+
+ }
+
+ function failure(reason) {
+
+ }
+
+ findAuthor(function(author, err){
+ if (err) {
+ failure(err);
+ // failure
+ } else {
+ try {
+ findBoooksByAuthor(author, function(books, err) {
+ if (err) {
+ failure(err);
+ } else {
+ try {
+ foundBooks(books);
+ } catch(reason) {
+ failure(reason);
+ }
+ }
+ });
+ } catch(error) {
+ failure(err);
+ }
+ // success
+ }
+ });
+ ```
+
+ Promise Example;
+
+ ```javascript
+ findAuthor().
+ then(findBooksByAuthor).
+ then(function(books){
+ // found books
+ }).catch(function(reason){
+ // something went wrong
+ });
+ ```
+
+ @method then
+ @param {Function} onFulfilled
+ @param {Function} onRejected
+ @param {String} label optional string for labeling the promise.
+ Useful for tooling.
+ @return {Promise}
+ */
+ then: function(onFulfillment, onRejection, label) {
+ var promise = this;
+ this._onerror = null;
+
+ var thenPromise = new this.constructor(noop, label);
+
+ if (this._state) {
+ var callbacks = arguments;
+ config.async(function invokePromiseCallback() {
+ invokeCallback(promise._state, thenPromise, callbacks[promise._state - 1], promise._detail);
+ });
+ } else {
+ subscribe(this, thenPromise, onFulfillment, onRejection);
+ }
+
+ if (config.instrument) {
+ instrument('chained', promise, thenPromise);
+ }
+
+ return thenPromise;
+ },
+
+ /**
+ `catch` is simply sugar for `then(undefined, onRejection)` which makes it the same
+ as the catch block of a try/catch statement.
+
+ ```js
+ function findAuthor(){
+ throw new Error("couldn't find that author");
+ }
+
+ // synchronous
+ try {
+ findAuthor();
+ } catch(reason) {
+ // something went wrong
+ }
+
+ // async with promises
+ findAuthor().catch(function(reason){
+ // something went wrong
+ });
+ ```
+
+ @method catch
+ @param {Function} onRejection
+ @param {String} label optional string for labeling the promise.
+ Useful for tooling.
+ @return {Promise}
+ */
+ 'catch': function(onRejection, label) {
+ return this.then(null, onRejection, label);
+ },
+
+ /**
+ `finally` will be invoked regardless of the promise's fate just as native
+ try/catch/finally behaves
+
+ Synchronous example:
+
+ ```js
+ findAuthor() {
+ if (Math.random() > 0.5) {
+ throw new Error();
+ }
+ return new Author();
+ }
+
+ try {
+ return findAuthor(); // succeed or fail
+ } catch(error) {
+ return findOtherAuther();
+ } finally {
+ // always runs
+ // doesn't affect the return value
+ }
+ ```
+
+ Asynchronous example:
+
+ ```js
+ findAuthor().catch(function(reason){
+ return findOtherAuther();
+ }).finally(function(){
+ // author was either found, or not
+ });
+ ```
+
+ @method finally
+ @param {Function} callback
+ @param {String} label optional string for labeling the promise.
+ Useful for tooling.
+ @return {Promise}
+ */
+ 'finally': function(callback, label) {
+ var constructor = this.constructor;
+
+ return this.then(function(value) {
+ return constructor.cast(callback()).then(function(){
+ return value;
+ });
+ }, function(reason) {
+ return constructor.cast(callback()).then(function(){
+ throw reason;
+ });
+ }, label);
+ }
+ };
+
+ function invokeCallback(settled, promise, callback, detail) {
+ var hasCallback = isFunction(callback),
+ value, error, succeeded, failed;
+
+ if (hasCallback) {
+ try {
+ value = callback(detail);
+ succeeded = true;
+ } catch(e) {
+ failed = true;
+ error = e;
+ }
+ } else {
+ value = detail;
+ succeeded = true;
+ }
+
+ if (handleThenable(promise, value)) {
+ return;
+ } else if (hasCallback && succeeded) {
+ resolve(promise, value);
+ } else if (failed) {
+ reject(promise, error);
+ } else if (settled === FULFILLED) {
+ resolve(promise, value);
+ } else if (settled === REJECTED) {
+ reject(promise, value);
+ }
+ }
+
+ function handleThenable(promise, value) {
+ var then = null,
+ resolved;
+
+ try {
+ if (promise === value) {
+ throw new TypeError("A promises callback cannot return that same promise.");
+ }
+
+ if (objectOrFunction(value)) {
+ then = value.then;
+
+ if (isFunction(then)) {
+ then.call(value, function(val) {
+ if (resolved) { return true; }
+ resolved = true;
+
+ if (value !== val) {
+ resolve(promise, val);
+ } else {
+ fulfill(promise, val);
+ }
+ }, function(val) {
+ if (resolved) { return true; }
+ resolved = true;
+
+ reject(promise, val);
+ }, 'derived from: ' + (promise._label || ' unknown promise'));
+
+ return true;
+ }
+ }
+ } catch (error) {
+ if (resolved) { return true; }
+ reject(promise, error);
+ return true;
+ }
+
+ return false;
+ }
+
+ function resolve(promise, value) {
+ if (promise === value) {
+ fulfill(promise, value);
+ } else if (!handleThenable(promise, value)) {
+ fulfill(promise, value);
+ }
+ }
+
+ function fulfill(promise, value) {
+ if (promise._state !== PENDING) { return; }
+ promise._state = SEALED;
+ promise._detail = value;
+
+ config.async(publishFulfillment, promise);
+ }
+
+ function reject(promise, reason) {
+ if (promise._state !== PENDING) { return; }
+ promise._state = SEALED;
+ promise._detail = reason;
+
+ config.async(publishRejection, promise);
+ }
+
+ function publishFulfillment(promise) {
+ publish(promise, promise._state = FULFILLED);
+ }
+
+ function publishRejection(promise) {
+ if (promise._onerror) {
+ promise._onerror(promise._detail);
+ }
+
+ publish(promise, promise._state = REJECTED);
+ }
+ });
+define("rsvp/promise/all",
+ ["../utils","exports"],
+ function(__dependency1__, __exports__) {
+ "use strict";
+ var isArray = __dependency1__.isArray;
+ var isNonThenable = __dependency1__.isNonThenable;
+
+ /**
+ `RSVP.Promise.all` accepts an array of promises, and returns a new promise which
+ is fulfilled with an array of fulfillment values for the passed promises, or
+ rejected with the reason of the first passed promise to be rejected. It casts all
+ elements of the passed iterable to promises as it runs this algorithm.
+
+ Example:
+
+ ```javascript
+ var promise1 = RSVP.resolve(1);
+ var promise2 = RSVP.resolve(2);
+ var promise3 = RSVP.resolve(3);
+ var promises = [ promise1, promise2, promise3 ];
+
+ RSVP.Promise.all(promises).then(function(array){
+ // The array here would be [ 1, 2, 3 ];
+ });
+ ```
+
+ If any of the `promises` given to `RSVP.all` are rejected, the first promise
+ that is rejected will be given as an argument to the returned promises's
+ rejection handler. For example:
+
+ Example:
+
+ ```javascript
+ var promise1 = RSVP.resolve(1);
+ var promise2 = RSVP.reject(new Error("2"));
+ var promise3 = RSVP.reject(new Error("3"));
+ var promises = [ promise1, promise2, promise3 ];
+
+ RSVP.Promise.all(promises).then(function(array){
+ // Code here never runs because there are rejected promises!
+ }, function(error) {
+ // error.message === "2"
+ });
+ ```
+
+ @method all
+ @for Ember.RSVP.Promise
+ @param {Array} entries array of promises
+ @param {String} label optional string for labeling the promise.
+ Useful for tooling.
+ @return {Promise} promise that is fulfilled when all `promises` have been
+ fulfilled, or rejected if any of them become rejected.
+ @static
+ */
+ __exports__["default"] = function all(entries, label) {
+
+ /*jshint validthis:true */
+ var Constructor = this;
+
+ return new Constructor(function(resolve, reject) {
+ if (!isArray(entries)) {
+ throw new TypeError('You must pass an array to all.');
+ }
+
+ var remaining = entries.length;
+ var results = new Array(remaining);
+ var entry, pending = true;
+
+ if (remaining === 0) {
+ resolve(results);
+ return;
+ }
+
+ function fulfillmentAt(index) {
+ return function(value) {
+ results[index] = value;
+ if (--remaining === 0) {
+ resolve(results);
+ }
+ };
+ }
+
+ function onRejection(reason) {
+ remaining = 0;
+ reject(reason);
+ }
+
+ for (var index = 0; index < entries.length; index++) {
+ entry = entries[index];
+ if (isNonThenable(entry)) {
+ results[index] = entry;
+ if (--remaining === 0) {
+ resolve(results);
+ }
+ } else {
+ Constructor.cast(entry).then(fulfillmentAt(index), onRejection);
+ }
+ }
+ }, label);
+ };
+ });
+define("rsvp/promise/cast",
+ ["exports"],
+ function(__exports__) {
+ "use strict";
+ /**
+ `RSVP.Promise.cast` coerces its argument to a promise, or returns the
+ argument if it is already a promise which shares a constructor with the caster.
+
+ Example:
+
+ ```javascript
+ var promise = RSVP.Promise.resolve(1);
+ var casted = RSVP.Promise.cast(promise);
+
+ console.log(promise === casted); // true
+ ```
+
+ In the case of a promise whose constructor does not match, it is assimilated.
+ The resulting promise will fulfill or reject based on the outcome of the
+ promise being casted.
+
+ Example:
+
+ ```javascript
+ var thennable = $.getJSON('/api/foo');
+ var casted = RSVP.Promise.cast(thennable);
+
+ console.log(thennable === casted); // false
+ console.log(casted instanceof RSVP.Promise) // true
+
+ casted.then(function(data) {
+ // data is the value getJSON fulfills with
+ });
+ ```
+
+ In the case of a non-promise, a promise which will fulfill with that value is
+ returned.
+
+ Example:
+
+ ```javascript
+ var value = 1; // could be a number, boolean, string, undefined...
+ var casted = RSVP.Promise.cast(value);
+
+ console.log(value === casted); // false
+ console.log(casted instanceof RSVP.Promise) // true
+
+ casted.then(function(val) {
+ val === value // => true
+ });
+ ```
+
+ `RSVP.Promise.cast` is similar to `RSVP.Promise.resolve`, but `RSVP.Promise.cast` differs in the
+ following ways:
+
+ * `RSVP.Promise.cast` serves as a memory-efficient way of getting a promise, when you
+ have something that could either be a promise or a value. RSVP.resolve
+ will have the same effect but will create a new promise wrapper if the
+ argument is a promise.
+ * `RSVP.Promise.cast` is a way of casting incoming thenables or promise subclasses to
+ promises of the exact class specified, so that the resulting object's `then` is
+ ensured to have the behavior of the constructor you are calling cast on (i.e., RSVP.Promise).
+
+ @method cast
+ @param {Object} object to be casted
+ @param {String} label optional string for labeling the promise.
+ Useful for tooling.
+ @return {Promise} promise
+ @static
+ */
+
+ __exports__["default"] = function cast(object, label) {
+ /*jshint validthis:true */
+ var Constructor = this;
+
+ if (object && typeof object === 'object' && object.constructor === Constructor) {
+ return object;
+ }
+
+ return new Constructor(function(resolve) {
+ resolve(object);
+ }, label);
+ };
+ });
+define("rsvp/promise/race",
+ ["../utils","exports"],
+ function(__dependency1__, __exports__) {
+ "use strict";
+ /* global toString */
+
+ var isArray = __dependency1__.isArray;
+ var isFunction = __dependency1__.isFunction;
+ var isNonThenable = __dependency1__.isNonThenable;
+
+ /**
+ `RSVP.Promise.race` returns a new promise which is settled in the same way as the
+ first passed promise to settle.
+
+ Example:
+
+ ```javascript
+ var promise1 = new RSVP.Promise(function(resolve, reject){
+ setTimeout(function(){
+ resolve("promise 1");
+ }, 200);
+ });
+
+ var promise2 = new RSVP.Promise(function(resolve, reject){
+ setTimeout(function(){
+ resolve("promise 2");
+ }, 100);
+ });
+
+ RSVP.Promise.race([promise1, promise2]).then(function(result){
+ // result === "promise 2" because it was resolved before promise1
+ // was resolved.
+ });
+ ```
+
+ `RSVP.Promise.race` is deterministic in that only the state of the first
+ settled promise matters. For example, even if other promises given to the
+ `promises` array argument are resolved, but the first settled promise has
+ become rejected before the other promises became fulfilled, the returned
+ promise will become rejected:
+
+ ```javascript
+ var promise1 = new RSVP.Promise(function(resolve, reject){
+ setTimeout(function(){
+ resolve("promise 1");
+ }, 200);
+ });
+
+ var promise2 = new RSVP.Promise(function(resolve, reject){
+ setTimeout(function(){
+ reject(new Error("promise 2"));
+ }, 100);
+ });
+
+ RSVP.Promise.race([promise1, promise2]).then(function(result){
+ // Code here never runs
+ }, function(reason){
+ // reason.message === "promise2" because promise 2 became rejected before
+ // promise 1 became fulfilled
+ });
+ ```
+
+ An example real-world use case is implementing timeouts:
+
+ ```javascript
+ RSVP.Promise.race([ajax('foo.json'), timeout(5000)])
+ ```
+
+ @method race
+ @param {Array} promises array of promises to observe
+ @param {String} label optional string for describing the promise returned.
+ Useful for tooling.
+ @return {Promise} a promise which settles in the same way as the first passed
+ promise to settle.
+ @static
+ */
+ __exports__["default"] = function race(entries, label) {
+ /*jshint validthis:true */
+ var Constructor = this, entry;
+
+ return new Constructor(function(resolve, reject) {
+ if (!isArray(entries)) {
+ throw new TypeError('You must pass an array to race.');
+ }
+
+ var pending = true;
+
+ function onFulfillment(value) { if (pending) { pending = false; resolve(value); } }
+ function onRejection(reason) { if (pending) { pending = false; reject(reason); } }
+
+ for (var i = 0; i < entries.length; i++) {
+ entry = entries[i];
+ if (isNonThenable(entry)) {
+ pending = false;
+ resolve(entry);
+ return;
+ } else {
+ Constructor.cast(entry).then(onFulfillment, onRejection);
+ }
+ }
+ }, label);
+ };
+ });
+define("rsvp/promise/reject",
+ ["exports"],
+ function(__exports__) {
+ "use strict";
+ /**
+ `RSVP.Promise.reject` returns a promise rejected with the passed `reason`.
+ It is shorthand for the following:
+
+ ```javascript
+ var promise = new RSVP.Promise(function(resolve, reject){
+ reject(new Error('WHOOPS'));
+ });
+
+ promise.then(function(value){
+ // Code here doesn't run because the promise is rejected!
+ }, function(reason){
+ // reason.message === 'WHOOPS'
+ });
+ ```
+
+ Instead of writing the above, your code now simply becomes the following:
+
+ ```javascript
+ var promise = RSVP.Promise.reject(new Error('WHOOPS'));
+
+ promise.then(function(value){
+ // Code here doesn't run because the promise is rejected!
+ }, function(reason){
+ // reason.message === 'WHOOPS'
+ });
+ ```
+
+ @method reject
+ @param {Any} reason value that the returned promise will be rejected with.
+ @param {String} label optional string for identifying the returned promise.
+ Useful for tooling.
+ @return {Promise} a promise rejected with the given `reason`.
+ @static
+ */
+ __exports__["default"] = function reject(reason, label) {
+ /*jshint validthis:true */
+ var Constructor = this;
+
+ return new Constructor(function (resolve, reject) {
+ reject(reason);
+ }, label);
+ };
+ });
+define("rsvp/promise/resolve",
+ ["exports"],
+ function(__exports__) {
+ "use strict";
+ /**
+ `RSVP.Promise.resolve` returns a promise that will become resolved with the
+ passed `value`. It is shorthand for the following:
+
+ ```javascript
+ var promise = new RSVP.Promise(function(resolve, reject){
+ resolve(1);
+ });
+
+ promise.then(function(value){
+ // value === 1
+ });
+ ```
+
+ Instead of writing the above, your code now simply becomes the following:
+
+ ```javascript
+ var promise = RSVP.Promise.resolve(1);
+
+ promise.then(function(value){
+ // value === 1
+ });
+ ```
+
+ @method resolve
+ @param {Any} value value that the returned promise will be resolved with
+ @param {String} label optional string for identifying the returned promise.
+ Useful for tooling.
+ @return {Promise} a promise that will become fulfilled with the given
+ `value`
+ @static
+ */
+ __exports__["default"] = function resolve(value, label) {
+ /*jshint validthis:true */
+ var Constructor = this;
+
+ return new Constructor(function(resolve, reject) {
+ resolve(value);
+ }, label);
+ };
+ });
+define("rsvp/race",
+ ["./promise","exports"],
+ function(__dependency1__, __exports__) {
+ "use strict";
+ var Promise = __dependency1__["default"];
+
+ /**
+ This is a convenient alias for `RSVP.Promise.race`.
+
+ @method race
+ @param {Array} array Array of promises.
+ @param {String} label An optional label. This is useful
+ for tooling.
+ @static
+ */
+ __exports__["default"] = function race(array, label) {
+ return Promise.race(array, label);
+ };
+ });
+define("rsvp/reject",
+ ["./promise","exports"],
+ function(__dependency1__, __exports__) {
+ "use strict";
+ var Promise = __dependency1__["default"];
+
+ /**
+ This is a convenient alias for `RSVP.Promise.reject`.
+
+ @method reject
+ @for RSVP
+ @param {Any} reason value that the returned promise will be rejected with.
+ @param {String} label optional string for identifying the returned promise.
+ Useful for tooling.
+ @return {Promise} a promise rejected with the given `reason`.
+ @static
+ */
+ __exports__["default"] = function reject(reason, label) {
+ return Promise.reject(reason, label);
+ };
+ });
+define("rsvp/resolve",
+ ["./promise","exports"],
+ function(__dependency1__, __exports__) {
+ "use strict";
+ var Promise = __dependency1__["default"];
+
+ /**
+ This is a convenient alias for `RSVP.Promise.resolve`.
+
+ @method resolve
+ @for RSVP
+ @param {Any} value value that the returned promise will be resolved with
+ @param {String} label optional string for identifying the returned promise.
+ Useful for tooling.
+ @return {Promise} a promise that will become fulfilled with the given
+ `value`
+ @static
+ */
+ __exports__["default"] = function resolve(value, label) {
+ return Promise.resolve(value, label);
+ };
+ });
+define("rsvp/rethrow",
+ ["exports"],
+ function(__exports__) {
+ "use strict";
+ /**
+ `RSVP.rethrow` will rethrow an error on the next turn of the JavaScript event
+ loop in order to aid debugging.
+
+ Promises A+ specifies that any exceptions that occur with a promise must be
+ caught by the promises implementation and bubbled to the last handler. For
+ this reason, it is recommended that you always specify a second rejection
+ handler function to `then`. However, `RSVP.rethrow` will throw the exception
+ outside of the promise, so it bubbles up to your console if in the browser,
+ or domain/cause uncaught exception in Node. `rethrow` will also throw the
+ error again so the error can be handled by the promise per the spec.
+
+ ```javascript
+ function throws(){
+ throw new Error('Whoops!');
+ }
+
+ var promise = new RSVP.Promise(function(resolve, reject){
+ throws();
+ });
+
+ promise.catch(RSVP.rethrow).then(function(){
+ // Code here doesn't run because the promise became rejected due to an
+ // error!
+ }, function (err){
+ // handle the error here
+ });
+ ```
+
+ The 'Whoops' error will be thrown on the next turn of the event loop
+ and you can watch for it in your console. You can also handle it using a
+ rejection handler given to `.then` or `.catch` on the returned promise.
+
+ @method rethrow
+ @for RSVP
+ @param {Error} reason reason the promise became rejected.
+ @throws Error
+ @static
+ */
+ __exports__["default"] = function rethrow(reason) {
+ setTimeout(function() {
+ throw reason;
+ });
+ throw reason;
+ };
+ });
+define("rsvp/utils",
+ ["exports"],
+ function(__exports__) {
+ "use strict";
+ function objectOrFunction(x) {
+ return typeof x === "function" || (typeof x === "object" && x !== null);
+ }
+
+ __exports__.objectOrFunction = objectOrFunction;function isFunction(x) {
+ return typeof x === "function";
+ }
+
+ __exports__.isFunction = isFunction;function isNonThenable(x) {
+ return !objectOrFunction(x);
+ }
+
+ __exports__.isNonThenable = isNonThenable;function isArray(x) {
+ return Object.prototype.toString.call(x) === "[object Array]";
+ }
+
+ __exports__.isArray = isArray;// Date.now is not available in browsers < IE9
+ // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/now#Compatibility
+ var now = Date.now || function() { return new Date().getTime(); };
+ __exports__.now = now;
+ var keysOf = Object.keys || function(object) {
+ var result = [];
+
+ for (var prop in object) {
+ result.push(prop);
+ }
+
+ return result;
+ };
+ __exports__.keysOf = keysOf;
+ });
+define("rsvp",
+ ["./rsvp/promise","./rsvp/events","./rsvp/node","./rsvp/all","./rsvp/all_settled","./rsvp/race","./rsvp/hash","./rsvp/rethrow","./rsvp/defer","./rsvp/config","./rsvp/map","./rsvp/resolve","./rsvp/reject","./rsvp/filter","exports"],
+ function(__dependency1__, __dependency2__, __dependency3__, __dependency4__, __dependency5__, __dependency6__, __dependency7__, __dependency8__, __dependency9__, __dependency10__, __dependency11__, __dependency12__, __dependency13__, __dependency14__, __exports__) {
+ "use strict";
+ var Promise = __dependency1__["default"];
+ var EventTarget = __dependency2__["default"];
+ var denodeify = __dependency3__["default"];
+ var all = __dependency4__["default"];
+ var allSettled = __dependency5__["default"];
+ var race = __dependency6__["default"];
+ var hash = __dependency7__["default"];
+ var rethrow = __dependency8__["default"];
+ var defer = __dependency9__["default"];
+ var config = __dependency10__.config;
+ var configure = __dependency10__.configure;
+ var map = __dependency11__["default"];
+ var resolve = __dependency12__["default"];
+ var reject = __dependency13__["default"];
+ var filter = __dependency14__["default"];
+
+ function async(callback, arg) {
+ config.async(callback, arg);
+ }
+
+ function on() {
+ config.on.apply(config, arguments);
+ }
+
+ function off() {
+ config.off.apply(config, arguments);
+ }
+
+ // Set up instrumentation through `window.__PROMISE_INTRUMENTATION__`
+ if (typeof window !== 'undefined' && typeof window.__PROMISE_INSTRUMENTATION__ === 'object') {
+ var callbacks = window.__PROMISE_INSTRUMENTATION__;
+ configure('instrument', true);
+ for (var eventName in callbacks) {
+ if (callbacks.hasOwnProperty(eventName)) {
+ on(eventName, callbacks[eventName]);
+ }
+ }
+ }
+
+ __exports__.Promise = Promise;
+ __exports__.EventTarget = EventTarget;
+ __exports__.all = all;
+ __exports__.allSettled = allSettled;
+ __exports__.race = race;
+ __exports__.hash = hash;
+ __exports__.rethrow = rethrow;
+ __exports__.defer = defer;
+ __exports__.denodeify = denodeify;
+ __exports__.configure = configure;
+ __exports__.on = on;
+ __exports__.off = off;
+ __exports__.resolve = resolve;
+ __exports__.reject = reject;
+ __exports__.async = async;
+ __exports__.map = map;
+ __exports__.filter = filter;
+ });
+
+})();
+
+(function() {
+/**
+Public api for the container is still in flux.
+The public api, specified on the application namespace should be considered the stable api.
+// @module container
+ @private
+*/
+
+/*
+ Flag to enable/disable model factory injections (disabled by default)
+ If model factory injections are enabled, models should not be
+ accessed globally (only through `container.lookupFactory('model:modelName'))`);
+*/
+Ember.MODEL_FACTORY_INJECTIONS = false || !!Ember.ENV.MODEL_FACTORY_INJECTIONS;
+
+define("container",
+ [],
+ function() {
+ "use strict";
+
+ // A safe and simple inheriting object.
+ function InheritingDict(parent) {
+ this.parent = parent;
+ this.dict = {};
+ }
+
+ InheritingDict.prototype = {
+
+ /**
+ @property parent
+ @type InheritingDict
+ @default null
+ */
+
+ parent: null,
+
+ /**
+ Object used to store the current nodes data.
+
+ @property dict
+ @type Object
+ @default Object
+ */
+ dict: null,
+
+ /**
+ Retrieve the value given a key, if the value is present at the current
+ level use it, otherwise walk up the parent hierarchy and try again. If
+ no matching key is found, return undefined.
+
+ @method get
+ @param {String} key
+ @return {any}
+ */
+ get: function(key) {
+ var dict = this.dict;
+
+ if (dict.hasOwnProperty(key)) {
+ return dict[key];
+ }
+
+ if (this.parent) {
+ return this.parent.get(key);
+ }
+ },
+
+ /**
+ Set the given value for the given key, at the current level.
+
+ @method set
+ @param {String} key
+ @param {Any} value
+ */
+ set: function(key, value) {
+ this.dict[key] = value;
+ },
+
+ /**
+ Delete the given key
+
+ @method remove
+ @param {String} key
+ */
+ remove: function(key) {
+ delete this.dict[key];
+ },
+
+ /**
+ Check for the existence of given a key, if the key is present at the current
+ level return true, otherwise walk up the parent hierarchy and try again. If
+ no matching key is found, return false.
+
+ @method has
+ @param {String} key
+ @return {Boolean}
+ */
+ has: function(key) {
+ var dict = this.dict;
+
+ if (dict.hasOwnProperty(key)) {
+ return true;
+ }
+
+ if (this.parent) {
+ return this.parent.has(key);
+ }
+
+ return false;
+ },
+
+ /**
+ Iterate and invoke a callback for each local key-value pair.
+
+ @method eachLocal
+ @param {Function} callback
+ @param {Object} binding
+ */
+ eachLocal: function(callback, binding) {
+ var dict = this.dict;
+
+ for (var prop in dict) {
+ if (dict.hasOwnProperty(prop)) {
+ callback.call(binding, prop, dict[prop]);
+ }
+ }
+ }
+ };
+
+
+ // A lightweight container that helps to assemble and decouple components.
+ // Public api for the container is still in flux.
+ // The public api, specified on the application namespace should be considered the stable api.
+ function Container(parent) {
+ this.parent = parent;
+ this.children = [];
+
+ this.resolver = parent && parent.resolver || function() {};
+
+ this.registry = new InheritingDict(parent && parent.registry);
+ this.cache = new InheritingDict(parent && parent.cache);
+ this.factoryCache = new InheritingDict(parent && parent.factoryCache);
+ this.resolveCache = new InheritingDict(parent && parent.resolveCache);
+ this.typeInjections = new InheritingDict(parent && parent.typeInjections);
+ this.injections = {};
+
+ this.factoryTypeInjections = new InheritingDict(parent && parent.factoryTypeInjections);
+ this.factoryInjections = {};
+
+ this._options = new InheritingDict(parent && parent._options);
+ this._typeOptions = new InheritingDict(parent && parent._typeOptions);
+ }
+
+ Container.prototype = {
+
+ /**
+ @property parent
+ @type Container
+ @default null
+ */
+ parent: null,
+
+ /**
+ @property children
+ @type Array
+ @default []
+ */
+ children: null,
+
+ /**
+ @property resolver
+ @type function
+ */
+ resolver: null,
+
+ /**
+ @property registry
+ @type InheritingDict
+ */
+ registry: null,
+
+ /**
+ @property cache
+ @type InheritingDict
+ */
+ cache: null,
+
+ /**
+ @property typeInjections
+ @type InheritingDict
+ */
+ typeInjections: null,
+
+ /**
+ @property injections
+ @type Object
+ @default {}
+ */
+ injections: null,
+
+ /**
+ @private
+
+ @property _options
+ @type InheritingDict
+ @default null
+ */
+ _options: null,
+
+ /**
+ @private
+
+ @property _typeOptions
+ @type InheritingDict
+ */
+ _typeOptions: null,
+
+ /**
+ Returns a new child of the current container. These children are configured
+ to correctly inherit from the current container.
+
+ @method child
+ @return {Container}
+ */
+ child: function() {
+ var container = new Container(this);
+ this.children.push(container);
+ return container;
+ },
+
+ /**
+ Sets a key-value pair on the current container. If a parent container,
+ has the same key, once set on a child, the parent and child will diverge
+ as expected.
+
+ @method set
+ @param {Object} object
+ @param {String} key
+ @param {any} value
+ */
+ set: function(object, key, value) {
+ object[key] = value;
+ },
+
+ /**
+ Registers a factory for later injection.
+
+ Example:
+
+ ```javascript
+ var container = new Container();
+
+ container.register('model:user', Person, {singleton: false });
+ container.register('fruit:favorite', Orange);
+ container.register('communication:main', Email, {singleton: false});
+ ```
+
+ @method register
+ @param {String} fullName
+ @param {Function} factory
+ @param {Object} options
+ */
+ register: function(fullName, factory, options) {
+ validateFullName(fullName);
+
+ if (factory === undefined) {
+ throw new TypeError('Attempting to register an unknown factory: `' + fullName + '`');
+ }
+
+ var normalizedName = this.normalize(fullName);
+
+ if (this.cache.has(normalizedName)) {
+ throw new Error('Cannot re-register: `' + fullName +'`, as it has already been looked up.');
+ }
+
+ this.registry.set(normalizedName, factory);
+ this._options.set(normalizedName, options || {});
+ },
+
+ /**
+ Unregister a fullName
+
+ ```javascript
+ var container = new Container();
+ container.register('model:user', User);
+
+ container.lookup('model:user') instanceof User //=> true
+
+ container.unregister('model:user')
+ container.lookup('model:user') === undefined //=> true
+ ```
+
+ @method unregister
+ @param {String} fullName
+ */
+ unregister: function(fullName) {
+ validateFullName(fullName);
+
+ var normalizedName = this.normalize(fullName);
+
+ this.registry.remove(normalizedName);
+ this.cache.remove(normalizedName);
+ this.factoryCache.remove(normalizedName);
+ this.resolveCache.remove(normalizedName);
+ this._options.remove(normalizedName);
+ },
+
+ /**
+ Given a fullName return the corresponding factory.
+
+ By default `resolve` will retrieve the factory from
+ its container's registry.
+
+ ```javascript
+ var container = new Container();
+ container.register('api:twitter', Twitter);
+
+ container.resolve('api:twitter') // => Twitter
+ ```
+
+ Optionally the container can be provided with a custom resolver.
+ If provided, `resolve` will first provide the custom resolver
+ the oppertunity to resolve the fullName, otherwise it will fallback
+ to the registry.
+
+ ```javascript
+ var container = new Container();
+ container.resolver = function(fullName) {
+ // lookup via the module system of choice
+ };
+
+ // the twitter factory is added to the module system
+ container.resolve('api:twitter') // => Twitter
+ ```
+
+ @method resolve
+ @param {String} fullName
+ @return {Function} fullName's factory
+ */
+ resolve: function(fullName) {
+ validateFullName(fullName);
+
+ var normalizedName = this.normalize(fullName);
+ var cached = this.resolveCache.get(normalizedName);
+
+ if (cached) { return cached; }
+
+ var resolved = this.resolver(normalizedName) || this.registry.get(normalizedName);
+
+ this.resolveCache.set(normalizedName, resolved);
+
+ return resolved;
+ },
+
+ /**
+ A hook that can be used to describe how the resolver will
+ attempt to find the factory.
+
+ For example, the default Ember `.describe` returns the full
+ class name (including namespace) where Ember's resolver expects
+ to find the `fullName`.
+
+ @method describe
+ @param {String} fullName
+ @return {string} described fullName
+ */
+ describe: function(fullName) {
+ return fullName;
+ },
+
+ /**
+ A hook to enable custom fullName normalization behaviour
+
+ @method normalize
+ @param {String} fullName
+ @return {string} normalized fullName
+ */
+ normalize: function(fullName) {
+ return fullName;
+ },
+
+ /**
+ @method makeToString
+
+ @param {any} factory
+ @param {string} fullName
+ @return {function} toString function
+ */
+ makeToString: function(factory, fullName) {
+ return factory.toString();
+ },
+
+ /**
+ Given a fullName return a corresponding instance.
+
+ The default behaviour is for lookup to return a singleton instance.
+ The singleton is scoped to the container, allowing multiple containers
+ to all have their own locally scoped singletons.
+
+ ```javascript
+ var container = new Container();
+ container.register('api:twitter', Twitter);
+
+ var twitter = container.lookup('api:twitter');
+
+ twitter instanceof Twitter; // => true
+
+ // by default the container will return singletons
+ var twitter2 = container.lookup('api:twitter');
+ twitter instanceof Twitter; // => true
+
+ twitter === twitter2; //=> true
+ ```
+
+ If singletons are not wanted an optional flag can be provided at lookup.
+
+ ```javascript
+ var container = new Container();
+ container.register('api:twitter', Twitter);
+
+ var twitter = container.lookup('api:twitter', { singleton: false });
+ var twitter2 = container.lookup('api:twitter', { singleton: false });
+
+ twitter === twitter2; //=> false
+ ```
+
+ @method lookup
+ @param {String} fullName
+ @param {Object} options
+ @return {any}
+ */
+ lookup: function(fullName, options) {
+ validateFullName(fullName);
+ return lookup(this, this.normalize(fullName), options);
+ },
+
+ /**
+ Given a fullName return the corresponding factory.
+
+ @method lookupFactory
+ @param {String} fullName
+ @return {any}
+ */
+ lookupFactory: function(fullName) {
+ validateFullName(fullName);
+ return factoryFor(this, this.normalize(fullName));
+ },
+
+ /**
+ Given a fullName check if the container is aware of its factory
+ or singleton instance.
+
+ @method has
+ @param {String} fullName
+ @return {Boolean}
+ */
+ has: function(fullName) {
+ validateFullName(fullName);
+ return has(this, this.normalize(fullName));
+ },
+
+ /**
+ Allow registering options for all factories of a type.
+
+ ```javascript
+ var container = new Container();
+
+ // if all of type `connection` must not be singletons
+ container.optionsForType('connection', { singleton: false });
+
+ container.register('connection:twitter', TwitterConnection);
+ container.register('connection:facebook', FacebookConnection);
+
+ var twitter = container.lookup('connection:twitter');
+ var twitter2 = container.lookup('connection:twitter');
+
+ twitter === twitter2; // => false
+
+ var facebook = container.lookup('connection:facebook');
+ var facebook2 = container.lookup('connection:facebook');
+
+ facebook === facebook2; // => false
+ ```
+
+ @method optionsForType
+ @param {String} type
+ @param {Object} options
+ */
+ optionsForType: function(type, options) {
+ if (this.parent) { illegalChildOperation('optionsForType'); }
+
+ this._typeOptions.set(type, options);
+ },
+
+ /**
+ @method options
+ @param {String} type
+ @param {Object} options
+ */
+ options: function(type, options) {
+ this.optionsForType(type, options);
+ },
+
+ /**
+ Used only via `injection`.
+
+ Provides a specialized form of injection, specifically enabling
+ all objects of one type to be injected with a reference to another
+ object.
+
+ For example, provided each object of type `controller` needed a `router`.
+ one would do the following:
+
+ ```javascript
+ var container = new Container();
+
+ container.register('router:main', Router);
+ container.register('controller:user', UserController);
+ container.register('controller:post', PostController);
+
+ container.typeInjection('controller', 'router', 'router:main');
+
+ var user = container.lookup('controller:user');
+ var post = container.lookup('controller:post');
+
+ user.router instanceof Router; //=> true
+ post.router instanceof Router; //=> true
+
+ // both controllers share the same router
+ user.router === post.router; //=> true
+ ```
+
+ @private
+ @method typeInjection
+ @param {String} type
+ @param {String} property
+ @param {String} fullName
+ */
+ typeInjection: function(type, property, fullName) {
+ validateFullName(fullName);
+ if (this.parent) { illegalChildOperation('typeInjection'); }
+
+ addTypeInjection(this.typeInjections, type, property, fullName);
+ },
+
+ /**
+ Defines injection rules.
+
+ These rules are used to inject dependencies onto objects when they
+ are instantiated.
+
+ Two forms of injections are possible:
+
+ * Injecting one fullName on another fullName
+ * Injecting one fullName on a type
+
+ Example:
+
+ ```javascript
+ var container = new Container();
+
+ container.register('source:main', Source);
+ container.register('model:user', User);
+ container.register('model:post', Post);
+
+ // injecting one fullName on another fullName
+ // eg. each user model gets a post model
+ container.injection('model:user', 'post', 'model:post');
+
+ // injecting one fullName on another type
+ container.injection('model', 'source', 'source:main');
+
+ var user = container.lookup('model:user');
+ var post = container.lookup('model:post');
+
+ user.source instanceof Source; //=> true
+ post.source instanceof Source; //=> true
+
+ user.post instanceof Post; //=> true
+
+ // and both models share the same source
+ user.source === post.source; //=> true
+ ```
+
+ @method injection
+ @param {String} factoryName
+ @param {String} property
+ @param {String} injectionName
+ */
+ injection: function(fullName, property, injectionName) {
+ if (this.parent) { illegalChildOperation('injection'); }
+
+ validateFullName(injectionName);
+ var normalizedInjectionName = this.normalize(injectionName);
+
+ if (fullName.indexOf(':') === -1) {
+ return this.typeInjection(fullName, property, normalizedInjectionName);
+ }
+
+ validateFullName(fullName);
+ var normalizedName = this.normalize(fullName);
+
+ addInjection(this.injections, normalizedName, property, normalizedInjectionName);
+ },
+
+
+ /**
+ Used only via `factoryInjection`.
+
+ Provides a specialized form of injection, specifically enabling
+ all factory of one type to be injected with a reference to another
+ object.
+
+ For example, provided each factory of type `model` needed a `store`.
+ one would do the following:
+
+ ```javascript
+ var container = new Container();
+
+ container.register('store:main', SomeStore);
+
+ container.factoryTypeInjection('model', 'store', 'store:main');
+
+ var store = container.lookup('store:main');
+ var UserFactory = container.lookupFactory('model:user');
+
+ UserFactory.store instanceof SomeStore; //=> true
+ ```
+
+ @private
+ @method factoryTypeInjection
+ @param {String} type
+ @param {String} property
+ @param {String} fullName
+ */
+ factoryTypeInjection: function(type, property, fullName) {
+ if (this.parent) { illegalChildOperation('factoryTypeInjection'); }
+
+ addTypeInjection(this.factoryTypeInjections, type, property, this.normalize(fullName));
+ },
+
+ /**
+ Defines factory injection rules.
+
+ Similar to regular injection rules, but are run against factories, via
+ `Container#lookupFactory`.
+
+ These rules are used to inject objects onto factories when they
+ are looked up.
+
+ Two forms of injections are possible:
+
+ * Injecting one fullName on another fullName
+ * Injecting one fullName on a type
+
+ Example:
+
+ ```javascript
+ var container = new Container();
+
+ container.register('store:main', Store);
+ container.register('store:secondary', OtherStore);
+ container.register('model:user', User);
+ container.register('model:post', Post);
+
+ // injecting one fullName on another type
+ container.factoryInjection('model', 'store', 'store:main');
+
+ // injecting one fullName on another fullName
+ container.factoryInjection('model:post', 'secondaryStore', 'store:secondary');
+
+ var UserFactory = container.lookupFactory('model:user');
+ var PostFactory = container.lookupFactory('model:post');
+ var store = container.lookup('store:main');
+
+ UserFactory.store instanceof Store; //=> true
+ UserFactory.secondaryStore instanceof OtherStore; //=> false
+
+ PostFactory.store instanceof Store; //=> true
+ PostFactory.secondaryStore instanceof OtherStore; //=> true
+
+ // and both models share the same source instance
+ UserFactory.store === PostFactory.store; //=> true
+ ```
+
+ @method factoryInjection
+ @param {String} factoryName
+ @param {String} property
+ @param {String} injectionName
+ */
+ factoryInjection: function(fullName, property, injectionName) {
+ if (this.parent) { illegalChildOperation('injection'); }
+
+ var normalizedName = this.normalize(fullName);
+ var normalizedInjectionName = this.normalize(injectionName);
+
+ validateFullName(injectionName);
+
+ if (fullName.indexOf(':') === -1) {
+ return this.factoryTypeInjection(normalizedName, property, normalizedInjectionName);
+ }
+
+ validateFullName(fullName);
+
+ addInjection(this.factoryInjections, normalizedName, property, normalizedInjectionName);
+ },
+
+ /**
+ A depth first traversal, destroying the container, its descendant containers and all
+ their managed objects.
+
+ @method destroy
+ */
+ destroy: function() {
+ for (var i=0, l=this.children.length; i w.
+*/
+Ember.compare = function compare(v, w) {
+ if (v === w) { return 0; }
+
+ var type1 = Ember.typeOf(v);
+ var type2 = Ember.typeOf(w);
+
+ var Comparable = Ember.Comparable;
+ if (Comparable) {
+ if (type1==='instance' && Comparable.detect(v.constructor)) {
+ return v.constructor.compare(v, w);
+ }
+
+ if (type2 === 'instance' && Comparable.detect(w.constructor)) {
+ return 1-w.constructor.compare(w, v);
+ }
+ }
+
+ // If we haven't yet generated a reverse-mapping of Ember.ORDER_DEFINITION,
+ // do so now.
+ var mapping = Ember.ORDER_DEFINITION_MAPPING;
+ if (!mapping) {
+ var order = Ember.ORDER_DEFINITION;
+ mapping = Ember.ORDER_DEFINITION_MAPPING = {};
+ var idx, len;
+ for (idx = 0, len = order.length; idx < len; ++idx) {
+ mapping[order[idx]] = idx;
+ }
+
+ // We no longer need Ember.ORDER_DEFINITION.
+ delete Ember.ORDER_DEFINITION;
+ }
+
+ var type1Index = mapping[type1];
+ var type2Index = mapping[type2];
+
+ if (type1Index < type2Index) { return -1; }
+ if (type1Index > type2Index) { return 1; }
+
+ // types are equal - so we have to check values now
+ switch (type1) {
+ case 'boolean':
+ case 'number':
+ if (v < w) { return -1; }
+ if (v > w) { return 1; }
+ return 0;
+
+ case 'string':
+ var comp = v.localeCompare(w);
+ if (comp < 0) { return -1; }
+ if (comp > 0) { return 1; }
+ return 0;
+
+ case 'array':
+ var vLen = v.length;
+ var wLen = w.length;
+ var l = Math.min(vLen, wLen);
+ var r = 0;
+ var i = 0;
+ while (r === 0 && i < l) {
+ r = compare(v[i],w[i]);
+ i++;
+ }
+ if (r !== 0) { return r; }
+
+ // all elements are equal now
+ // shorter array should be ordered first
+ if (vLen < wLen) { return -1; }
+ if (vLen > wLen) { return 1; }
+ // arrays are equal now
+ return 0;
+
+ case 'instance':
+ if (Ember.Comparable && Ember.Comparable.detect(v)) {
+ return v.compare(v, w);
+ }
+ return 0;
+
+ case 'date':
+ var vNum = v.getTime();
+ var wNum = w.getTime();
+ if (vNum < wNum) { return -1; }
+ if (vNum > wNum) { return 1; }
+ return 0;
+
+ default:
+ return 0;
+ }
+};
+
+function _copy(obj, deep, seen, copies) {
+ var ret, loc, key;
+
+ // primitive data types are immutable, just return them.
+ if ('object' !== typeof obj || obj===null) return obj;
+
+ // avoid cyclical loops
+ if (deep && (loc=indexOf(seen, obj))>=0) return copies[loc];
+
+ Ember.assert('Cannot clone an Ember.Object that does not implement Ember.Copyable', !(obj instanceof Ember.Object) || (Ember.Copyable && Ember.Copyable.detect(obj)));
+
+ // IMPORTANT: this specific test will detect a native array only. Any other
+ // object will need to implement Copyable.
+ if (Ember.typeOf(obj) === 'array') {
+ ret = obj.slice();
+ if (deep) {
+ loc = ret.length;
+ while(--loc>=0) ret[loc] = _copy(ret[loc], deep, seen, copies);
+ }
+ } else if (Ember.Copyable && Ember.Copyable.detect(obj)) {
+ ret = obj.copy(deep, seen, copies);
+ } else {
+ ret = {};
+ for(key in obj) {
+ if (!obj.hasOwnProperty(key)) continue;
+
+ // Prevents browsers that don't respect non-enumerability from
+ // copying internal Ember properties
+ if (key.substring(0,2) === '__') continue;
+
+ ret[key] = deep ? _copy(obj[key], deep, seen, copies) : obj[key];
+ }
+ }
+
+ if (deep) {
+ seen.push(obj);
+ copies.push(ret);
+ }
+
+ return ret;
+}
+
+/**
+ Creates a clone of the passed object. This function can take just about
+ any type of object and create a clone of it, including primitive values
+ (which are not actually cloned because they are immutable).
+
+ If the passed object implements the `clone()` method, then this function
+ will simply call that method and return the result.
+
+ @method copy
+ @for Ember
+ @param {Object} obj The object to clone
+ @param {Boolean} deep If true, a deep copy of the object is made
+ @return {Object} The cloned object
+*/
+Ember.copy = function(obj, deep) {
+ // fast paths
+ if ('object' !== typeof obj || obj===null) return obj; // can't copy primitives
+ if (Ember.Copyable && Ember.Copyable.detect(obj)) return obj.copy(deep);
+ return _copy(obj, deep, deep ? [] : null, deep ? [] : null);
+};
+
+/**
+ Compares two objects, returning true if they are logically equal. This is
+ a deeper comparison than a simple triple equal. For sets it will compare the
+ internal objects. For any other object that implements `isEqual()` it will
+ respect that method.
+
+ ```javascript
+ Ember.isEqual('hello', 'hello'); // true
+ Ember.isEqual(1, 2); // false
+ Ember.isEqual([4,2], [4,2]); // false
+ ```
+
+ @method isEqual
+ @for Ember
+ @param {Object} a first object to compare
+ @param {Object} b second object to compare
+ @return {Boolean}
+*/
+Ember.isEqual = function(a, b) {
+ if (a && 'function'===typeof a.isEqual) return a.isEqual(b);
+ return a === b;
+};
+
+// Used by Ember.compare
+Ember.ORDER_DEFINITION = Ember.ENV.ORDER_DEFINITION || [
+ 'undefined',
+ 'null',
+ 'boolean',
+ 'number',
+ 'string',
+ 'array',
+ 'object',
+ 'instance',
+ 'function',
+ 'class',
+ 'date'
+];
+
+/**
+ Returns all of the keys defined on an object or hash. This is useful
+ when inspecting objects for debugging. On browsers that support it, this
+ uses the native `Object.keys` implementation.
+
+ @method keys
+ @for Ember
+ @param {Object} obj
+ @return {Array} Array containing keys of obj
+*/
+Ember.keys = Object.keys;
+
+if (!Ember.keys || Ember.create.isSimulated) {
+ var prototypeProperties = [
+ 'constructor',
+ 'hasOwnProperty',
+ 'isPrototypeOf',
+ 'propertyIsEnumerable',
+ 'valueOf',
+ 'toLocaleString',
+ 'toString'
+ ],
+ pushPropertyName = function(obj, array, key) {
+ // Prevents browsers that don't respect non-enumerability from
+ // copying internal Ember properties
+ if (key.substring(0,2) === '__') return;
+ if (key === '_super') return;
+ if (indexOf(array, key) >= 0) return;
+ if (!obj.hasOwnProperty(key)) return;
+
+ array.push(key);
+ };
+
+ Ember.keys = function(obj) {
+ var ret = [], key;
+ for (key in obj) {
+ pushPropertyName(obj, ret, key);
+ }
+
+ // IE8 doesn't enumerate property that named the same as prototype properties.
+ for (var i = 0, l = prototypeProperties.length; i < l; i++) {
+ key = prototypeProperties[i];
+
+ pushPropertyName(obj, ret, key);
+ }
+
+ return ret;
+ };
+}
+
+})();
+
+
+
+(function() {
+/**
+@module ember
+@submodule ember-runtime
+*/
+
+var STRING_DASHERIZE_REGEXP = (/[ _]/g);
+var STRING_DASHERIZE_CACHE = {};
+var STRING_DECAMELIZE_REGEXP = (/([a-z\d])([A-Z])/g);
+var STRING_CAMELIZE_REGEXP = (/(\-|_|\.|\s)+(.)?/g);
+var STRING_UNDERSCORE_REGEXP_1 = (/([a-z\d])([A-Z]+)/g);
+var STRING_UNDERSCORE_REGEXP_2 = (/\-|\s+/g);
+var STRING_PARAMETERIZE_REGEXP_1 = (/[_|\/|\s]+/g);
+var STRING_PARAMETERIZE_REGEXP_2 = (/[^a-z0-9\-]+/gi);
+var STRING_PARAMETERIZE_REGEXP_3 = (/[\-]+/g);
+var STRING_PARAMETERIZE_REGEXP_4 = (/^-+|-+$/g);
+
+/**
+ Defines the hash of localized strings for the current language. Used by
+ the `Ember.String.loc()` helper. To localize, add string values to this
+ hash.
+
+ @property STRINGS
+ @for Ember
+ @type Hash
+*/
+Ember.STRINGS = {};
+
+/**
+ Defines string helper methods including string formatting and localization.
+ Unless `Ember.EXTEND_PROTOTYPES.String` is `false` these methods will also be
+ added to the `String.prototype` as well.
+
+ @class String
+ @namespace Ember
+ @static
+*/
+Ember.String = {
+
+ /**
+ Apply formatting options to the string. This will look for occurrences
+ of "%@" in your string and substitute them with the arguments you pass into
+ this method. If you want to control the specific order of replacement,
+ you can add a number after the key as well to indicate which argument
+ you want to insert.
+
+ Ordered insertions are most useful when building loc strings where values
+ you need to insert may appear in different orders.
+
+ ```javascript
+ "Hello %@ %@".fmt('John', 'Doe'); // "Hello John Doe"
+ "Hello %@2, %@1".fmt('John', 'Doe'); // "Hello Doe, John"
+ ```
+
+ @method fmt
+ @param {String} str The string to format
+ @param {Array} formats An array of parameters to interpolate into string.
+ @return {String} formatted string
+ */
+ fmt: function(str, formats) {
+ // first, replace any ORDERED replacements.
+ var idx = 0; // the current index for non-numerical replacements
+ return str.replace(/%@([0-9]+)?/g, function(s, argIndex) {
+ argIndex = (argIndex) ? parseInt(argIndex, 10) - 1 : idx++;
+ s = formats[argIndex];
+ return (s === null) ? '(null)' : (s === undefined) ? '' : Ember.inspect(s);
+ }) ;
+ },
+
+ /**
+ Formats the passed string, but first looks up the string in the localized
+ strings hash. This is a convenient way to localize text. See
+ `Ember.String.fmt()` for more information on formatting.
+
+ Note that it is traditional but not required to prefix localized string
+ keys with an underscore or other character so you can easily identify
+ localized strings.
+
+ ```javascript
+ Ember.STRINGS = {
+ '_Hello World': 'Bonjour le monde',
+ '_Hello %@ %@': 'Bonjour %@ %@'
+ };
+
+ Ember.String.loc("_Hello World"); // 'Bonjour le monde';
+ Ember.String.loc("_Hello %@ %@", ["John", "Smith"]); // "Bonjour John Smith";
+ ```
+
+ @method loc
+ @param {String} str The string to format
+ @param {Array} formats Optional array of parameters to interpolate into string.
+ @return {String} formatted string
+ */
+ loc: function(str, formats) {
+ str = Ember.STRINGS[str] || str;
+ return Ember.String.fmt(str, formats) ;
+ },
+
+ /**
+ Splits a string into separate units separated by spaces, eliminating any
+ empty strings in the process. This is a convenience method for split that
+ is mostly useful when applied to the `String.prototype`.
+
+ ```javascript
+ Ember.String.w("alpha beta gamma").forEach(function(key) {
+ console.log(key);
+ });
+
+ // > alpha
+ // > beta
+ // > gamma
+ ```
+
+ @method w
+ @param {String} str The string to split
+ @return {String} split string
+ */
+ w: function(str) { return str.split(/\s+/); },
+
+ /**
+ Converts a camelized string into all lower case separated by underscores.
+
+ ```javascript
+ 'innerHTML'.decamelize(); // 'inner_html'
+ 'action_name'.decamelize(); // 'action_name'
+ 'css-class-name'.decamelize(); // 'css-class-name'
+ 'my favorite items'.decamelize(); // 'my favorite items'
+ ```
+
+ @method decamelize
+ @param {String} str The string to decamelize.
+ @return {String} the decamelized string.
+ */
+ decamelize: function(str) {
+ return str.replace(STRING_DECAMELIZE_REGEXP, '$1_$2').toLowerCase();
+ },
+
+ /**
+ Replaces underscores, spaces, or camelCase with dashes.
+
+ ```javascript
+ 'innerHTML'.dasherize(); // 'inner-html'
+ 'action_name'.dasherize(); // 'action-name'
+ 'css-class-name'.dasherize(); // 'css-class-name'
+ 'my favorite items'.dasherize(); // 'my-favorite-items'
+ ```
+
+ @method dasherize
+ @param {String} str The string to dasherize.
+ @return {String} the dasherized string.
+ */
+ dasherize: function(str) {
+ var cache = STRING_DASHERIZE_CACHE,
+ hit = cache.hasOwnProperty(str),
+ ret;
+
+ if (hit) {
+ return cache[str];
+ } else {
+ ret = Ember.String.decamelize(str).replace(STRING_DASHERIZE_REGEXP,'-');
+ cache[str] = ret;
+ }
+
+ return ret;
+ },
+
+ /**
+ Returns the lowerCamelCase form of a string.
+
+ ```javascript
+ 'innerHTML'.camelize(); // 'innerHTML'
+ 'action_name'.camelize(); // 'actionName'
+ 'css-class-name'.camelize(); // 'cssClassName'
+ 'my favorite items'.camelize(); // 'myFavoriteItems'
+ 'My Favorite Items'.camelize(); // 'myFavoriteItems'
+ ```
+
+ @method camelize
+ @param {String} str The string to camelize.
+ @return {String} the camelized string.
+ */
+ camelize: function(str) {
+ return str.replace(STRING_CAMELIZE_REGEXP, function(match, separator, chr) {
+ return chr ? chr.toUpperCase() : '';
+ }).replace(/^([A-Z])/, function(match, separator, chr) {
+ return match.toLowerCase();
+ });
+ },
+
+ /**
+ Returns the UpperCamelCase form of a string.
+
+ ```javascript
+ 'innerHTML'.classify(); // 'InnerHTML'
+ 'action_name'.classify(); // 'ActionName'
+ 'css-class-name'.classify(); // 'CssClassName'
+ 'my favorite items'.classify(); // 'MyFavoriteItems'
+ ```
+
+ @method classify
+ @param {String} str the string to classify
+ @return {String} the classified string
+ */
+ classify: function(str) {
+ var parts = str.split("."),
+ out = [];
+
+ for (var i=0, l=parts.length; i= 0) {
+ var baseValue = this[keyName];
+
+ if (baseValue) {
+ if ('function' === typeof baseValue.concat) {
+ value = baseValue.concat(value);
+ } else {
+ value = Ember.makeArray(baseValue).concat(value);
+ }
+ } else {
+ value = Ember.makeArray(value);
+ }
+ }
+
+ if (desc) {
+ desc.set(this, keyName, value);
+ } else {
+ if (typeof this.setUnknownProperty === 'function' && !(keyName in this)) {
+ this.setUnknownProperty(keyName, value);
+ } else if (MANDATORY_SETTER) {
+ Ember.defineProperty(this, keyName, null, value); // setup mandatory setter
+ } else {
+ this[keyName] = value;
+ }
+ }
+ }
+ }
+ }
+ finishPartial(this, m);
+ this.init.apply(this, arguments);
+ m.proto = proto;
+ finishChains(this);
+ sendEvent(this, "init");
+ };
+
+ Class.toString = Mixin.prototype.toString;
+ Class.willReopen = function() {
+ if (wasApplied) {
+ Class.PrototypeMixin = Mixin.create(Class.PrototypeMixin);
+ }
+
+ wasApplied = false;
+ };
+ Class._initMixins = function(args) { initMixins = args; };
+ Class._initProperties = function(args) { initProperties = args; };
+
+ Class.proto = function() {
+ var superclass = Class.superclass;
+ if (superclass) { superclass.proto(); }
+
+ if (!wasApplied) {
+ wasApplied = true;
+ Class.PrototypeMixin.applyPartial(Class.prototype);
+ rewatch(Class.prototype);
+ }
+
+ return this.prototype;
+ };
+
+ return Class;
+
+}
+
+/**
+ @class CoreObject
+ @namespace Ember
+*/
+var CoreObject = makeCtor();
+CoreObject.toString = function() { return "Ember.CoreObject"; };
+
+CoreObject.PrototypeMixin = Mixin.create({
+ reopen: function() {
+ applyMixin(this, arguments, true);
+ return this;
+ },
+
+ /**
+ An overridable method called when objects are instantiated. By default,
+ does nothing unless it is overridden during class definition.
+
+ Example:
+
+ ```javascript
+ App.Person = Ember.Object.extend({
+ init: function() {
+ alert('Name is ' + this.get('name'));
+ }
+ });
+
+ var steve = App.Person.create({
+ name: "Steve"
+ });
+
+ // alerts 'Name is Steve'.
+ ```
+
+ NOTE: If you do override `init` for a framework class like `Ember.View` or
+ `Ember.ArrayController`, be sure to call `this._super()` in your
+ `init` declaration! If you don't, Ember may not have an opportunity to
+ do important setup work, and you'll see strange behavior in your
+ application.
+
+ @method init
+ */
+ init: function() {},
+
+ /**
+ Defines the properties that will be concatenated from the superclass
+ (instead of overridden).
+
+ By default, when you extend an Ember class a property defined in
+ the subclass overrides a property with the same name that is defined
+ in the superclass. However, there are some cases where it is preferable
+ to build up a property's value by combining the superclass' property
+ value with the subclass' value. An example of this in use within Ember
+ is the `classNames` property of `Ember.View`.
+
+ Here is some sample code showing the difference between a concatenated
+ property and a normal one:
+
+ ```javascript
+ App.BarView = Ember.View.extend({
+ someNonConcatenatedProperty: ['bar'],
+ classNames: ['bar']
+ });
+
+ App.FooBarView = App.BarView.extend({
+ someNonConcatenatedProperty: ['foo'],
+ classNames: ['foo'],
+ });
+
+ var fooBarView = App.FooBarView.create();
+ fooBarView.get('someNonConcatenatedProperty'); // ['foo']
+ fooBarView.get('classNames'); // ['ember-view', 'bar', 'foo']
+ ```
+
+ This behavior extends to object creation as well. Continuing the
+ above example:
+
+ ```javascript
+ var view = App.FooBarView.create({
+ someNonConcatenatedProperty: ['baz'],
+ classNames: ['baz']
+ })
+ view.get('someNonConcatenatedProperty'); // ['baz']
+ view.get('classNames'); // ['ember-view', 'bar', 'foo', 'baz']
+ ```
+ Adding a single property that is not an array will just add it in the array:
+
+ ```javascript
+ var view = App.FooBarView.create({
+ classNames: 'baz'
+ })
+ view.get('classNames'); // ['ember-view', 'bar', 'foo', 'baz']
+ ```
+
+ Using the `concatenatedProperties` property, we can tell to Ember that mix
+ the content of the properties.
+
+ In `Ember.View` the `classNameBindings` and `attributeBindings` properties
+ are also concatenated, in addition to `classNames`.
+
+ This feature is available for you to use throughout the Ember object model,
+ although typical app developers are likely to use it infrequently. Since
+ it changes expectations about behavior of properties, you should properly
+ document its usage in each individual concatenated property (to not
+ mislead your users to think they can override the property in a subclass).
+
+ @property concatenatedProperties
+ @type Array
+ @default null
+ */
+ concatenatedProperties: null,
+
+ /**
+ Destroyed object property flag.
+
+ if this property is `true` the observers and bindings were already
+ removed by the effect of calling the `destroy()` method.
+
+ @property isDestroyed
+ @default false
+ */
+ isDestroyed: false,
+
+ /**
+ Destruction scheduled flag. The `destroy()` method has been called.
+
+ The object stays intact until the end of the run loop at which point
+ the `isDestroyed` flag is set.
+
+ @property isDestroying
+ @default false
+ */
+ isDestroying: false,
+
+ /**
+ Destroys an object by setting the `isDestroyed` flag and removing its
+ metadata, which effectively destroys observers and bindings.
+
+ If you try to set a property on a destroyed object, an exception will be
+ raised.
+
+ Note that destruction is scheduled for the end of the run loop and does not
+ happen immediately. It will set an isDestroying flag immediately.
+
+ @method destroy
+ @return {Ember.Object} receiver
+ */
+ destroy: function() {
+ if (this.isDestroying) { return; }
+ this.isDestroying = true;
+
+ schedule('actions', this, this.willDestroy);
+ schedule('destroy', this, this._scheduledDestroy);
+ return this;
+ },
+
+ /**
+ Override to implement teardown.
+
+ @method willDestroy
+ */
+ willDestroy: Ember.K,
+
+ /**
+ Invoked by the run loop to actually destroy the object. This is
+ scheduled for execution by the `destroy` method.
+
+ @private
+ @method _scheduledDestroy
+ */
+ _scheduledDestroy: function() {
+ if (this.isDestroyed) { return; }
+ destroy(this);
+ this.isDestroyed = true;
+ },
+
+ bind: function(to, from) {
+ if (!(from instanceof Ember.Binding)) { from = Ember.Binding.from(from); }
+ from.to(to).connect(this);
+ return from;
+ },
+
+ /**
+ Returns a string representation which attempts to provide more information
+ than Javascript's `toString` typically does, in a generic way for all Ember
+ objects.
+
+ ```javascript
+ App.Person = Em.Object.extend()
+ person = App.Person.create()
+ person.toString() //=> ""
+ ```
+
+ If the object's class is not defined on an Ember namespace, it will
+ indicate it is a subclass of the registered superclass:
+
+ ```javascript
+ Student = App.Person.extend()
+ student = Student.create()
+ student.toString() //=> "<(subclass of App.Person):ember1025>"
+ ```
+
+ If the method `toStringExtension` is defined, its return value will be
+ included in the output.
+
+ ```javascript
+ App.Teacher = App.Person.extend({
+ toStringExtension: function() {
+ return this.get('fullName');
+ }
+ });
+ teacher = App.Teacher.create()
+ teacher.toString(); //=> ""
+ ```
+
+ @method toString
+ @return {String} string representation
+ */
+ toString: function toString() {
+ var hasToStringExtension = typeof this.toStringExtension === 'function',
+ extension = hasToStringExtension ? ":" + this.toStringExtension() : '';
+ var ret = '<'+this.constructor.toString()+':'+guidFor(this)+extension+'>';
+ this.toString = makeToString(ret);
+ return ret;
+ }
+});
+
+CoreObject.PrototypeMixin.ownerConstructor = CoreObject;
+
+function makeToString(ret) {
+ return function() { return ret; };
+}
+
+if (Ember.config.overridePrototypeMixin) {
+ Ember.config.overridePrototypeMixin(CoreObject.PrototypeMixin);
+}
+
+CoreObject.__super__ = null;
+
+var ClassMixin = Mixin.create({
+
+ ClassMixin: Ember.required(),
+
+ PrototypeMixin: Ember.required(),
+
+ isClass: true,
+
+ isMethod: false,
+
+ /**
+ Creates a new subclass.
+
+ ```javascript
+ App.Person = Ember.Object.extend({
+ say: function(thing) {
+ alert(thing);
+ }
+ });
+ ```
+
+ This defines a new subclass of Ember.Object: `App.Person`. It contains one method: `say()`.
+
+ You can also create a subclass from any existing class by calling its `extend()` method. For example, you might want to create a subclass of Ember's built-in `Ember.View` class:
+
+ ```javascript
+ App.PersonView = Ember.View.extend({
+ tagName: 'li',
+ classNameBindings: ['isAdministrator']
+ });
+ ```
+
+ When defining a subclass, you can override methods but still access the implementation of your parent class by calling the special `_super()` method:
+
+ ```javascript
+ App.Person = Ember.Object.extend({
+ say: function(thing) {
+ var name = this.get('name');
+ alert(name + ' says: ' + thing);
+ }
+ });
+
+ App.Soldier = App.Person.extend({
+ say: function(thing) {
+ this._super(thing + ", sir!");
+ },
+ march: function(numberOfHours) {
+ alert(this.get('name') + ' marches for ' + numberOfHours + ' hours.')
+ }
+ });
+
+ var yehuda = App.Soldier.create({
+ name: "Yehuda Katz"
+ });
+
+ yehuda.say("Yes"); // alerts "Yehuda Katz says: Yes, sir!"
+ ```
+
+ The `create()` on line #17 creates an *instance* of the `App.Soldier` class. The `extend()` on line #8 creates a *subclass* of `App.Person`. Any instance of the `App.Person` class will *not* have the `march()` method.
+
+ You can also pass `Ember.Mixin` classes to add additional properties to the subclass.
+
+ ```javascript
+ App.Person = Ember.Object.extend({
+ say: function(thing) {
+ alert(this.get('name') + ' says: ' + thing);
+ }
+ });
+
+ App.SingingMixin = Ember.Mixin.create({
+ sing: function(thing){
+ alert(this.get('name') + ' sings: la la la ' + thing);
+ }
+ });
+
+ App.BroadwayStar = App.Person.extend(App.SingingMixin, {
+ dance: function() {
+ alert(this.get('name') + ' dances: tap tap tap tap ');
+ }
+ });
+ ```
+
+ The `App.BroadwayStar` class contains three methods: `say()`, `sing()`, and `dance()`.
+
+ @method extend
+ @static
+
+ @param {Ember.Mixin} [mixins]* One or more Ember.Mixin classes
+ @param {Object} [arguments]* Object containing values to use within the new class
+ */
+ extend: function() {
+ var Class = makeCtor(), proto;
+ Class.ClassMixin = Mixin.create(this.ClassMixin);
+ Class.PrototypeMixin = Mixin.create(this.PrototypeMixin);
+
+ Class.ClassMixin.ownerConstructor = Class;
+ Class.PrototypeMixin.ownerConstructor = Class;
+
+ reopen.apply(Class.PrototypeMixin, arguments);
+
+ Class.superclass = this;
+ Class.__super__ = this.prototype;
+
+ proto = Class.prototype = o_create(this.prototype);
+ proto.constructor = Class;
+ generateGuid(proto);
+ meta(proto).proto = proto; // this will disable observers on prototype
+
+ Class.ClassMixin.apply(Class);
+ return Class;
+ },
+
+ /**
+ Equivalent to doing `extend(arguments).create()`.
+ If possible use the normal `create` method instead.
+
+ @method createWithMixins
+ @static
+ @param [arguments]*
+ */
+ createWithMixins: function() {
+ var C = this;
+ if (arguments.length>0) { this._initMixins(arguments); }
+ return new C();
+ },
+
+ /**
+ Creates an instance of a class. Accepts either no arguments, or an object
+ containing values to initialize the newly instantiated object with.
+
+ ```javascript
+ App.Person = Ember.Object.extend({
+ helloWorld: function() {
+ alert("Hi, my name is " + this.get('name'));
+ }
+ });
+
+ var tom = App.Person.create({
+ name: 'Tom Dale'
+ });
+
+ tom.helloWorld(); // alerts "Hi, my name is Tom Dale".
+ ```
+
+ `create` will call the `init` function if defined during
+ `Ember.AnyObject.extend`
+
+ If no arguments are passed to `create`, it will not set values to the new
+ instance during initialization:
+
+ ```javascript
+ var noName = App.Person.create();
+ noName.helloWorld(); // alerts undefined
+ ```
+
+ NOTE: For performance reasons, you cannot declare methods or computed
+ properties during `create`. You should instead declare methods and computed
+ properties when using `extend` or use the `createWithMixins` shorthand.
+
+ @method create
+ @static
+ @param [arguments]*
+ */
+ create: function() {
+ var C = this;
+ if (arguments.length>0) { this._initProperties(arguments); }
+ return new C();
+ },
+
+ /**
+ Augments a constructor's prototype with additional
+ properties and functions:
+
+ ```javascript
+ MyObject = Ember.Object.extend({
+ name: 'an object'
+ });
+
+ o = MyObject.create();
+ o.get('name'); // 'an object'
+
+ MyObject.reopen({
+ say: function(msg){
+ console.log(msg);
+ }
+ })
+
+ o2 = MyObject.create();
+ o2.say("hello"); // logs "hello"
+
+ o.say("goodbye"); // logs "goodbye"
+ ```
+
+ To add functions and properties to the constructor itself,
+ see `reopenClass`
+
+ @method reopen
+ */
+ reopen: function() {
+ this.willReopen();
+ reopen.apply(this.PrototypeMixin, arguments);
+ return this;
+ },
+
+ /**
+ Augments a constructor's own properties and functions:
+
+ ```javascript
+ MyObject = Ember.Object.extend({
+ name: 'an object'
+ });
+
+ MyObject.reopenClass({
+ canBuild: false
+ });
+
+ MyObject.canBuild; // false
+ o = MyObject.create();
+ ```
+
+ In other words, this creates static properties and functions for the class. These are only available on the class
+ and not on any instance of that class.
+
+ ```javascript
+ App.Person = Ember.Object.extend({
+ name : "",
+ sayHello : function(){
+ alert("Hello. My name is " + this.get('name'));
+ }
+ });
+
+ App.Person.reopenClass({
+ species : "Homo sapiens",
+ createPerson: function(newPersonsName){
+ return App.Person.create({
+ name:newPersonsName
+ });
+ }
+ });
+
+ var tom = App.Person.create({
+ name : "Tom Dale"
+ });
+ var yehuda = App.Person.createPerson("Yehuda Katz");
+
+ tom.sayHello(); // "Hello. My name is Tom Dale"
+ yehuda.sayHello(); // "Hello. My name is Yehuda Katz"
+ alert(App.Person.species); // "Homo sapiens"
+ ```
+
+ Note that `species` and `createPerson` are *not* valid on the `tom` and `yehuda`
+ variables. They are only valid on `App.Person`.
+
+ To add functions and properties to instances of
+ a constructor by extending the constructor's prototype
+ see `reopen`
+
+ @method reopenClass
+ */
+ reopenClass: function() {
+ reopen.apply(this.ClassMixin, arguments);
+ applyMixin(this, arguments, false);
+ return this;
+ },
+
+ detect: function(obj) {
+ if ('function' !== typeof obj) { return false; }
+ while(obj) {
+ if (obj===this) { return true; }
+ obj = obj.superclass;
+ }
+ return false;
+ },
+
+ detectInstance: function(obj) {
+ return obj instanceof this;
+ },
+
+ /**
+ In some cases, you may want to annotate computed properties with additional
+ metadata about how they function or what values they operate on. For
+ example, computed property functions may close over variables that are then
+ no longer available for introspection.
+
+ You can pass a hash of these values to a computed property like this:
+
+ ```javascript
+ person: function() {
+ var personId = this.get('personId');
+ return App.Person.create({ id: personId });
+ }.property().meta({ type: App.Person })
+ ```
+
+ Once you've done this, you can retrieve the values saved to the computed
+ property from your class like this:
+
+ ```javascript
+ MyClass.metaForProperty('person');
+ ```
+
+ This will return the original hash that was passed to `meta()`.
+
+ @method metaForProperty
+ @param key {String} property name
+ */
+ metaForProperty: function(key) {
+ var meta = this.proto()[META_KEY],
+ desc = meta && meta.descs[key];
+
+ Ember.assert("metaForProperty() could not find a computed property with key '"+key+"'.", !!desc && desc instanceof Ember.ComputedProperty);
+ return desc._meta || {};
+ },
+
+ /**
+ Iterate over each computed property for the class, passing its name
+ and any associated metadata (see `metaForProperty`) to the callback.
+
+ @method eachComputedProperty
+ @param {Function} callback
+ @param {Object} binding
+ */
+ eachComputedProperty: function(callback, binding) {
+ var proto = this.proto(),
+ descs = meta(proto).descs,
+ empty = {},
+ property;
+
+ for (var name in descs) {
+ property = descs[name];
+
+ if (property instanceof Ember.ComputedProperty) {
+ callback.call(binding || this, name, property._meta || empty);
+ }
+ }
+ }
+
+});
+
+ClassMixin.ownerConstructor = CoreObject;
+
+if (Ember.config.overrideClassMixin) {
+ Ember.config.overrideClassMixin(ClassMixin);
+}
+
+CoreObject.ClassMixin = ClassMixin;
+ClassMixin.apply(CoreObject);
+
+Ember.CoreObject = CoreObject;
+
+})();
+
+
+
+(function() {
+/**
+@module ember
+@submodule ember-runtime
+*/
+
+/**
+ `Ember.Object` is the main base class for all Ember objects. It is a subclass
+ of `Ember.CoreObject` with the `Ember.Observable` mixin applied. For details,
+ see the documentation for each of these.
+
+ @class Object
+ @namespace Ember
+ @extends Ember.CoreObject
+ @uses Ember.Observable
+*/
+Ember.Object = Ember.CoreObject.extend(Ember.Observable);
+Ember.Object.toString = function() { return "Ember.Object"; };
+
+})();
+
+
+
+(function() {
+/**
+@module ember
+@submodule ember-runtime
+*/
+
+var get = Ember.get, indexOf = Ember.ArrayPolyfills.indexOf;
+
+/**
+ A Namespace is an object usually used to contain other objects or methods
+ such as an application or framework. Create a namespace anytime you want
+ to define one of these new containers.
+
+ # Example Usage
+
+ ```javascript
+ MyFramework = Ember.Namespace.create({
+ VERSION: '1.0.0'
+ });
+ ```
+
+ @class Namespace
+ @namespace Ember
+ @extends Ember.Object
+*/
+var Namespace = Ember.Namespace = Ember.Object.extend({
+ isNamespace: true,
+
+ init: function() {
+ Ember.Namespace.NAMESPACES.push(this);
+ Ember.Namespace.PROCESSED = false;
+ },
+
+ toString: function() {
+ var name = get(this, 'name');
+ if (name) { return name; }
+
+ findNamespaces();
+ return this[Ember.GUID_KEY+'_name'];
+ },
+
+ nameClasses: function() {
+ processNamespace([this.toString()], this, {});
+ },
+
+ destroy: function() {
+ var namespaces = Ember.Namespace.NAMESPACES;
+ Ember.lookup[this.toString()] = undefined;
+ namespaces.splice(indexOf.call(namespaces, this), 1);
+ this._super();
+ }
+});
+
+Namespace.reopenClass({
+ NAMESPACES: [Ember],
+ NAMESPACES_BY_ID: {},
+ PROCESSED: false,
+ processAll: processAllNamespaces,
+ byName: function(name) {
+ if (!Ember.BOOTED) {
+ processAllNamespaces();
+ }
+
+ return NAMESPACES_BY_ID[name];
+ }
+});
+
+var NAMESPACES_BY_ID = Namespace.NAMESPACES_BY_ID;
+
+var hasOwnProp = ({}).hasOwnProperty,
+ guidFor = Ember.guidFor;
+
+function processNamespace(paths, root, seen) {
+ var idx = paths.length;
+
+ NAMESPACES_BY_ID[paths.join('.')] = root;
+
+ // Loop over all of the keys in the namespace, looking for classes
+ for(var key in root) {
+ if (!hasOwnProp.call(root, key)) { continue; }
+ var obj = root[key];
+
+ // If we are processing the `Ember` namespace, for example, the
+ // `paths` will start with `["Ember"]`. Every iteration through
+ // the loop will update the **second** element of this list with
+ // the key, so processing `Ember.View` will make the Array
+ // `['Ember', 'View']`.
+ paths[idx] = key;
+
+ // If we have found an unprocessed class
+ if (obj && obj.toString === classToString) {
+ // Replace the class' `toString` with the dot-separated path
+ // and set its `NAME_KEY`
+ obj.toString = makeToString(paths.join('.'));
+ obj[NAME_KEY] = paths.join('.');
+
+ // Support nested namespaces
+ } else if (obj && obj.isNamespace) {
+ // Skip aliased namespaces
+ if (seen[guidFor(obj)]) { continue; }
+ seen[guidFor(obj)] = true;
+
+ // Process the child namespace
+ processNamespace(paths, obj, seen);
+ }
+ }
+
+ paths.length = idx; // cut out last item
+}
+
+function findNamespaces() {
+ var Namespace = Ember.Namespace, lookup = Ember.lookup, obj, isNamespace;
+
+ if (Namespace.PROCESSED) { return; }
+
+ for (var prop in lookup) {
+ // These don't raise exceptions but can cause warnings
+ if (prop === "parent" || prop === "top" || prop === "frameElement" || prop === "webkitStorageInfo") { continue; }
+
+ // get(window.globalStorage, 'isNamespace') would try to read the storage for domain isNamespace and cause exception in Firefox.
+ // globalStorage is a storage obsoleted by the WhatWG storage specification. See https://developer.mozilla.org/en/DOM/Storage#globalStorage
+ if (prop === "globalStorage" && lookup.StorageList && lookup.globalStorage instanceof lookup.StorageList) { continue; }
+ // Unfortunately, some versions of IE don't support window.hasOwnProperty
+ if (lookup.hasOwnProperty && !lookup.hasOwnProperty(prop)) { continue; }
+
+ // At times we are not allowed to access certain properties for security reasons.
+ // There are also times where even if we can access them, we are not allowed to access their properties.
+ try {
+ obj = Ember.lookup[prop];
+ isNamespace = obj && obj.isNamespace;
+ } catch (e) {
+ continue;
+ }
+
+ if (isNamespace) {
+ Ember.deprecate("Namespaces should not begin with lowercase.", /^[A-Z]/.test(prop));
+ obj[NAME_KEY] = prop;
+ }
+ }
+}
+
+var NAME_KEY = Ember.NAME_KEY = Ember.GUID_KEY + '_name';
+
+function superClassString(mixin) {
+ var superclass = mixin.superclass;
+ if (superclass) {
+ if (superclass[NAME_KEY]) { return superclass[NAME_KEY]; }
+ else { return superClassString(superclass); }
+ } else {
+ return;
+ }
+}
+
+function classToString() {
+ if (!Ember.BOOTED && !this[NAME_KEY]) {
+ processAllNamespaces();
+ }
+
+ var ret;
+
+ if (this[NAME_KEY]) {
+ ret = this[NAME_KEY];
+ } else if (this._toString) {
+ ret = this._toString;
+ } else {
+ var str = superClassString(this);
+ if (str) {
+ ret = "(subclass of " + str + ")";
+ } else {
+ ret = "(unknown mixin)";
+ }
+ this.toString = makeToString(ret);
+ }
+
+ return ret;
+}
+
+function processAllNamespaces() {
+ var unprocessedNamespaces = !Namespace.PROCESSED,
+ unprocessedMixins = Ember.anyUnprocessedMixins;
+
+ if (unprocessedNamespaces) {
+ findNamespaces();
+ Namespace.PROCESSED = true;
+ }
+
+ if (unprocessedNamespaces || unprocessedMixins) {
+ var namespaces = Namespace.NAMESPACES, namespace;
+ for (var i=0, l=namespaces.length; i1) args = a_slice.call(arguments, 1);
+
+ this.forEach(function(x, idx) {
+ var method = x && x[methodName];
+ if ('function' === typeof method) {
+ ret[idx] = args ? method.apply(x, args) : x[methodName]();
+ }
+ }, this);
+
+ return ret;
+ },
+
+ /**
+ Simply converts the enumerable into a genuine array. The order is not
+ guaranteed. Corresponds to the method implemented by Prototype.
+
+ @method toArray
+ @return {Array} the enumerable as an array.
+ */
+ toArray: function() {
+ var ret = Ember.A();
+ this.forEach(function(o, idx) { ret[idx] = o; });
+ return ret ;
+ },
+
+ /**
+ Returns a copy of the array with all null and undefined elements removed.
+
+ ```javascript
+ var arr = ["a", null, "c", undefined];
+ arr.compact(); // ["a", "c"]
+ ```
+
+ @method compact
+ @return {Array} the array without null and undefined elements.
+ */
+ compact: function() {
+ return this.filter(function(value) { return value != null; });
+ },
+
+ /**
+ Returns a new enumerable that excludes the passed value. The default
+ implementation returns an array regardless of the receiver type unless
+ the receiver does not contain the value.
+
+ ```javascript
+ var arr = ["a", "b", "a", "c"];
+ arr.without("a"); // ["b", "c"]
+ ```
+
+ @method without
+ @param {Object} value
+ @return {Ember.Enumerable}
+ */
+ without: function(value) {
+ if (!this.contains(value)) return this; // nothing to do
+ var ret = Ember.A();
+ this.forEach(function(k) {
+ if (k !== value) ret[ret.length] = k;
+ }) ;
+ return ret ;
+ },
+
+ /**
+ Returns a new enumerable that contains only unique values. The default
+ implementation returns an array regardless of the receiver type.
+
+ ```javascript
+ var arr = ["a", "a", "b", "b"];
+ arr.uniq(); // ["a", "b"]
+ ```
+
+ @method uniq
+ @return {Ember.Enumerable}
+ */
+ uniq: function() {
+ var ret = Ember.A();
+ this.forEach(function(k) {
+ if (a_indexOf(ret, k)<0) ret.push(k);
+ });
+ return ret;
+ },
+
+ /**
+ This property will trigger anytime the enumerable's content changes.
+ You can observe this property to be notified of changes to the enumerables
+ content.
+
+ For plain enumerables, this property is read only. `Ember.Array` overrides
+ this method.
+
+ @property []
+ @type Ember.Array
+ @return this
+ */
+ '[]': Ember.computed(function(key, value) {
+ return this;
+ }),
+
+ // ..........................................................
+ // ENUMERABLE OBSERVERS
+ //
+
+ /**
+ Registers an enumerable observer. Must implement `Ember.EnumerableObserver`
+ mixin.
+
+ @method addEnumerableObserver
+ @param {Object} target
+ @param {Hash} [opts]
+ @return this
+ */
+ addEnumerableObserver: function(target, opts) {
+ var willChange = (opts && opts.willChange) || 'enumerableWillChange',
+ didChange = (opts && opts.didChange) || 'enumerableDidChange';
+
+ var hasObservers = get(this, 'hasEnumerableObservers');
+ if (!hasObservers) Ember.propertyWillChange(this, 'hasEnumerableObservers');
+ Ember.addListener(this, '@enumerable:before', target, willChange);
+ Ember.addListener(this, '@enumerable:change', target, didChange);
+ if (!hasObservers) Ember.propertyDidChange(this, 'hasEnumerableObservers');
+ return this;
+ },
+
+ /**
+ Removes a registered enumerable observer.
+
+ @method removeEnumerableObserver
+ @param {Object} target
+ @param {Hash} [opts]
+ @return this
+ */
+ removeEnumerableObserver: function(target, opts) {
+ var willChange = (opts && opts.willChange) || 'enumerableWillChange',
+ didChange = (opts && opts.didChange) || 'enumerableDidChange';
+
+ var hasObservers = get(this, 'hasEnumerableObservers');
+ if (hasObservers) Ember.propertyWillChange(this, 'hasEnumerableObservers');
+ Ember.removeListener(this, '@enumerable:before', target, willChange);
+ Ember.removeListener(this, '@enumerable:change', target, didChange);
+ if (hasObservers) Ember.propertyDidChange(this, 'hasEnumerableObservers');
+ return this;
+ },
+
+ /**
+ Becomes true whenever the array currently has observers watching changes
+ on the array.
+
+ @property hasEnumerableObservers
+ @type Boolean
+ */
+ hasEnumerableObservers: Ember.computed(function() {
+ return Ember.hasListeners(this, '@enumerable:change') || Ember.hasListeners(this, '@enumerable:before');
+ }),
+
+
+ /**
+ Invoke this method just before the contents of your enumerable will
+ change. You can either omit the parameters completely or pass the objects
+ to be removed or added if available or just a count.
+
+ @method enumerableContentWillChange
+ @param {Ember.Enumerable|Number} removing An enumerable of the objects to
+ be removed or the number of items to be removed.
+ @param {Ember.Enumerable|Number} adding An enumerable of the objects to be
+ added or the number of items to be added.
+ @chainable
+ */
+ enumerableContentWillChange: function(removing, adding) {
+
+ var removeCnt, addCnt, hasDelta;
+
+ if ('number' === typeof removing) removeCnt = removing;
+ else if (removing) removeCnt = get(removing, 'length');
+ else removeCnt = removing = -1;
+
+ if ('number' === typeof adding) addCnt = adding;
+ else if (adding) addCnt = get(adding,'length');
+ else addCnt = adding = -1;
+
+ hasDelta = addCnt<0 || removeCnt<0 || addCnt-removeCnt!==0;
+
+ if (removing === -1) removing = null;
+ if (adding === -1) adding = null;
+
+ Ember.propertyWillChange(this, '[]');
+ if (hasDelta) Ember.propertyWillChange(this, 'length');
+ Ember.sendEvent(this, '@enumerable:before', [this, removing, adding]);
+
+ return this;
+ },
+
+ /**
+ Invoke this method when the contents of your enumerable has changed.
+ This will notify any observers watching for content changes. If your are
+ implementing an ordered enumerable (such as an array), also pass the
+ start and end values where the content changed so that it can be used to
+ notify range observers.
+
+ @method enumerableContentDidChange
+ @param {Ember.Enumerable|Number} removing An enumerable of the objects to
+ be removed or the number of items to be removed.
+ @param {Ember.Enumerable|Number} adding An enumerable of the objects to
+ be added or the number of items to be added.
+ @chainable
+ */
+ enumerableContentDidChange: function(removing, adding) {
+ var removeCnt, addCnt, hasDelta;
+
+ if ('number' === typeof removing) removeCnt = removing;
+ else if (removing) removeCnt = get(removing, 'length');
+ else removeCnt = removing = -1;
+
+ if ('number' === typeof adding) addCnt = adding;
+ else if (adding) addCnt = get(adding, 'length');
+ else addCnt = adding = -1;
+
+ hasDelta = addCnt<0 || removeCnt<0 || addCnt-removeCnt!==0;
+
+ if (removing === -1) removing = null;
+ if (adding === -1) adding = null;
+
+ Ember.sendEvent(this, '@enumerable:change', [this, removing, adding]);
+ if (hasDelta) Ember.propertyDidChange(this, 'length');
+ Ember.propertyDidChange(this, '[]');
+
+ return this ;
+ },
+
+ /**
+ Converts the enumerable into an array and sorts by the keys
+ specified in the argument.
+
+ You may provide multiple arguments to sort by multiple properties.
+
+ @method sortBy
+ @param {String} property name(s) to sort on
+ @return {Array} The sorted array.
+ */
+ sortBy: function() {
+ var sortKeys = arguments;
+ return this.toArray().sort(function(a, b){
+ for(var i = 0; i < sortKeys.length; i++) {
+ var key = sortKeys[i],
+ propA = get(a, key),
+ propB = get(b, key);
+ // return 1 or -1 else continue to the next sortKey
+ var compareValue = Ember.compare(propA, propB);
+ if (compareValue) { return compareValue; }
+ }
+ return 0;
+ });
+ }
+});
+
+})();
+
+
+
+(function() {
+/**
+@module ember
+@submodule ember-runtime
+*/
+
+// ..........................................................
+// HELPERS
+//
+
+var get = Ember.get, set = Ember.set, isNone = Ember.isNone, map = Ember.EnumerableUtils.map, cacheFor = Ember.cacheFor;
+
+// ..........................................................
+// ARRAY
+//
+/**
+ This module implements Observer-friendly Array-like behavior. This mixin is
+ picked up by the Array class as well as other controllers, etc. that want to
+ appear to be arrays.
+
+ Unlike `Ember.Enumerable,` this mixin defines methods specifically for
+ collections that provide index-ordered access to their contents. When you
+ are designing code that needs to accept any kind of Array-like object, you
+ should use these methods instead of Array primitives because these will
+ properly notify observers of changes to the array.
+
+ Although these methods are efficient, they do add a layer of indirection to
+ your application so it is a good idea to use them only when you need the
+ flexibility of using both true JavaScript arrays and "virtual" arrays such
+ as controllers and collections.
+
+ You can use the methods defined in this module to access and modify array
+ contents in a KVO-friendly way. You can also be notified whenever the
+ membership of an array changes by changing the syntax of the property to
+ `.observes('*myProperty.[]')`.
+
+ To support `Ember.Array` in your own class, you must override two
+ primitives to use it: `replace()` and `objectAt()`.
+
+ Note that the Ember.Array mixin also incorporates the `Ember.Enumerable`
+ mixin. All `Ember.Array`-like objects are also enumerable.
+
+ @class Array
+ @namespace Ember
+ @uses Ember.Enumerable
+ @since Ember 0.9.0
+*/
+Ember.Array = Ember.Mixin.create(Ember.Enumerable, {
+
+ /**
+ Your array must support the `length` property. Your replace methods should
+ set this property whenever it changes.
+
+ @property {Number} length
+ */
+ length: Ember.required(),
+
+ /**
+ Returns the object at the given `index`. If the given `index` is negative
+ or is greater or equal than the array length, returns `undefined`.
+
+ This is one of the primitives you must implement to support `Ember.Array`.
+ If your object supports retrieving the value of an array item using `get()`
+ (i.e. `myArray.get(0)`), then you do not need to implement this method
+ yourself.
+
+ ```javascript
+ var arr = ['a', 'b', 'c', 'd'];
+ arr.objectAt(0); // "a"
+ arr.objectAt(3); // "d"
+ arr.objectAt(-1); // undefined
+ arr.objectAt(4); // undefined
+ arr.objectAt(5); // undefined
+ ```
+
+ @method objectAt
+ @param {Number} idx The index of the item to return.
+ @return {*} item at index or undefined
+ */
+ objectAt: function(idx) {
+ if ((idx < 0) || (idx>=get(this, 'length'))) return undefined ;
+ return get(this, idx);
+ },
+
+ /**
+ This returns the objects at the specified indexes, using `objectAt`.
+
+ ```javascript
+ var arr =Â ['a', 'b', 'c', 'd'];
+ arr.objectsAt([0, 1, 2]); // ["a", "b", "c"]
+ arr.objectsAt([2, 3, 4]); // ["c", "d", undefined]
+ ```
+
+ @method objectsAt
+ @param {Array} indexes An array of indexes of items to return.
+ @return {Array}
+ */
+ objectsAt: function(indexes) {
+ var self = this;
+ return map(indexes, function(idx) { return self.objectAt(idx); });
+ },
+
+ // overrides Ember.Enumerable version
+ nextObject: function(idx) {
+ return this.objectAt(idx);
+ },
+
+ /**
+ This is the handler for the special array content property. If you get
+ this property, it will return this. If you set this property it a new
+ array, it will replace the current content.
+
+ This property overrides the default property defined in `Ember.Enumerable`.
+
+ @property []
+ @return this
+ */
+ '[]': Ember.computed(function(key, value) {
+ if (value !== undefined) this.replace(0, get(this, 'length'), value) ;
+ return this ;
+ }),
+
+ firstObject: Ember.computed(function() {
+ return this.objectAt(0);
+ }),
+
+ lastObject: Ember.computed(function() {
+ return this.objectAt(get(this, 'length')-1);
+ }),
+
+ // optimized version from Enumerable
+ contains: function(obj) {
+ return this.indexOf(obj) >= 0;
+ },
+
+ // Add any extra methods to Ember.Array that are native to the built-in Array.
+ /**
+ Returns a new array that is a slice of the receiver. This implementation
+ uses the observable array methods to retrieve the objects for the new
+ slice.
+
+ ```javascript
+ var arr = ['red', 'green', 'blue'];
+ arr.slice(0); // ['red', 'green', 'blue']
+ arr.slice(0, 2); // ['red', 'green']
+ arr.slice(1, 100); // ['green', 'blue']
+ ```
+
+ @method slice
+ @param {Integer} beginIndex (Optional) index to begin slicing from.
+ @param {Integer} endIndex (Optional) index to end the slice at (but not included).
+ @return {Array} New array with specified slice
+ */
+ slice: function(beginIndex, endIndex) {
+ var ret = Ember.A();
+ var length = get(this, 'length') ;
+ if (isNone(beginIndex)) beginIndex = 0 ;
+ if (isNone(endIndex) || (endIndex > length)) endIndex = length ;
+
+ if (beginIndex < 0) beginIndex = length + beginIndex;
+ if (endIndex < 0) endIndex = length + endIndex;
+
+ while(beginIndex < endIndex) {
+ ret[ret.length] = this.objectAt(beginIndex++) ;
+ }
+ return ret ;
+ },
+
+ /**
+ Returns the index of the given object's first occurrence.
+ If no `startAt` argument is given, the starting location to
+ search is 0. If it's negative, will count backward from
+ the end of the array. Returns -1 if no match is found.
+
+ ```javascript
+ var arr = ["a", "b", "c", "d", "a"];
+ arr.indexOf("a"); // 0
+ arr.indexOf("z"); // -1
+ arr.indexOf("a", 2); // 4
+ arr.indexOf("a", -1); // 4
+ arr.indexOf("b", 3); // -1
+ arr.indexOf("a", 100); // -1
+ ```
+
+ @method indexOf
+ @param {Object} object the item to search for
+ @param {Number} startAt optional starting location to search, default 0
+ @return {Number} index or -1 if not found
+ */
+ indexOf: function(object, startAt) {
+ var idx, len = get(this, 'length');
+
+ if (startAt === undefined) startAt = 0;
+ if (startAt < 0) startAt += len;
+
+ for(idx=startAt;idx= len) startAt = len-1;
+ if (startAt < 0) startAt += len;
+
+ for(idx=startAt;idx>=0;idx--) {
+ if (this.objectAt(idx) === object) return idx ;
+ }
+ return -1;
+ },
+
+ // ..........................................................
+ // ARRAY OBSERVERS
+ //
+
+ /**
+ Adds an array observer to the receiving array. The array observer object
+ normally must implement two methods:
+
+ * `arrayWillChange(observedObj, start, removeCount, addCount)` - This method will be
+ called just before the array is modified.
+ * `arrayDidChange(observedObj, start, removeCount, addCount)` - This method will be
+ called just after the array is modified.
+
+ Both callbacks will be passed the observed object, starting index of the
+ change as well a a count of the items to be removed and added. You can use
+ these callbacks to optionally inspect the array during the change, clear
+ caches, or do any other bookkeeping necessary.
+
+ In addition to passing a target, you can also include an options hash
+ which you can use to override the method names that will be invoked on the
+ target.
+
+ @method addArrayObserver
+ @param {Object} target The observer object.
+ @param {Hash} opts Optional hash of configuration options including
+ `willChange` and `didChange` option.
+ @return {Ember.Array} receiver
+ */
+ addArrayObserver: function(target, opts) {
+ var willChange = (opts && opts.willChange) || 'arrayWillChange',
+ didChange = (opts && opts.didChange) || 'arrayDidChange';
+
+ var hasObservers = get(this, 'hasArrayObservers');
+ if (!hasObservers) Ember.propertyWillChange(this, 'hasArrayObservers');
+ Ember.addListener(this, '@array:before', target, willChange);
+ Ember.addListener(this, '@array:change', target, didChange);
+ if (!hasObservers) Ember.propertyDidChange(this, 'hasArrayObservers');
+ return this;
+ },
+
+ /**
+ Removes an array observer from the object if the observer is current
+ registered. Calling this method multiple times with the same object will
+ have no effect.
+
+ @method removeArrayObserver
+ @param {Object} target The object observing the array.
+ @param {Hash} opts Optional hash of configuration options including
+ `willChange` and `didChange` option.
+ @return {Ember.Array} receiver
+ */
+ removeArrayObserver: function(target, opts) {
+ var willChange = (opts && opts.willChange) || 'arrayWillChange',
+ didChange = (opts && opts.didChange) || 'arrayDidChange';
+
+ var hasObservers = get(this, 'hasArrayObservers');
+ if (hasObservers) Ember.propertyWillChange(this, 'hasArrayObservers');
+ Ember.removeListener(this, '@array:before', target, willChange);
+ Ember.removeListener(this, '@array:change', target, didChange);
+ if (hasObservers) Ember.propertyDidChange(this, 'hasArrayObservers');
+ return this;
+ },
+
+ /**
+ Becomes true whenever the array currently has observers watching changes
+ on the array.
+
+ @property {Boolean} hasArrayObservers
+ */
+ hasArrayObservers: Ember.computed(function() {
+ return Ember.hasListeners(this, '@array:change') || Ember.hasListeners(this, '@array:before');
+ }),
+
+ /**
+ If you are implementing an object that supports `Ember.Array`, call this
+ method just before the array content changes to notify any observers and
+ invalidate any related properties. Pass the starting index of the change
+ as well as a delta of the amounts to change.
+
+ @method arrayContentWillChange
+ @param {Number} startIdx The starting index in the array that will change.
+ @param {Number} removeAmt The number of items that will be removed. If you
+ pass `null` assumes 0
+ @param {Number} addAmt The number of items that will be added. If you
+ pass `null` assumes 0.
+ @return {Ember.Array} receiver
+ */
+ arrayContentWillChange: function(startIdx, removeAmt, addAmt) {
+
+ // if no args are passed assume everything changes
+ if (startIdx===undefined) {
+ startIdx = 0;
+ removeAmt = addAmt = -1;
+ } else {
+ if (removeAmt === undefined) removeAmt=-1;
+ if (addAmt === undefined) addAmt=-1;
+ }
+
+ // Make sure the @each proxy is set up if anyone is observing @each
+ if (Ember.isWatching(this, '@each')) { get(this, '@each'); }
+
+ Ember.sendEvent(this, '@array:before', [this, startIdx, removeAmt, addAmt]);
+
+ var removing, lim;
+ if (startIdx>=0 && removeAmt>=0 && get(this, 'hasEnumerableObservers')) {
+ removing = [];
+ lim = startIdx+removeAmt;
+ for(var idx=startIdx;idx=0 && addAmt>=0 && get(this, 'hasEnumerableObservers')) {
+ adding = [];
+ lim = startIdx+addAmt;
+ for(var idx=startIdx;idx Ember.TrackedArray instances. We use
+ // this to lazily recompute indexes for item property observers.
+ this.trackedArraysByGuid = {};
+
+ // We suspend observers to ignore replacements from `reset` when totally
+ // recomputing. Unfortunately we cannot properly suspend the observers
+ // because we only have the key; instead we make the observers no-ops
+ this.suspended = false;
+
+ // This is used to coalesce item changes from property observers.
+ this.changedItems = {};
+}
+
+function ItemPropertyObserverContext (dependentArray, index, trackedArray) {
+ Ember.assert("Internal error: trackedArray is null or undefined", trackedArray);
+
+ this.dependentArray = dependentArray;
+ this.index = index;
+ this.item = dependentArray.objectAt(index);
+ this.trackedArray = trackedArray;
+ this.beforeObserver = null;
+ this.observer = null;
+
+ this.destroyed = false;
+}
+
+DependentArraysObserver.prototype = {
+ setValue: function (newValue) {
+ this.instanceMeta.setValue(newValue, true);
+ },
+ getValue: function () {
+ return this.instanceMeta.getValue();
+ },
+
+ setupObservers: function (dependentArray, dependentKey) {
+ Ember.assert("dependent array must be an `Ember.Array`", Ember.Array.detect(dependentArray));
+
+ this.dependentKeysByGuid[guidFor(dependentArray)] = dependentKey;
+
+ dependentArray.addArrayObserver(this, {
+ willChange: 'dependentArrayWillChange',
+ didChange: 'dependentArrayDidChange'
+ });
+
+ if (this.cp._itemPropertyKeys[dependentKey]) {
+ this.setupPropertyObservers(dependentKey, this.cp._itemPropertyKeys[dependentKey]);
+ }
+ },
+
+ teardownObservers: function (dependentArray, dependentKey) {
+ var itemPropertyKeys = this.cp._itemPropertyKeys[dependentKey] || [];
+
+ delete this.dependentKeysByGuid[guidFor(dependentArray)];
+
+ this.teardownPropertyObservers(dependentKey, itemPropertyKeys);
+
+ dependentArray.removeArrayObserver(this, {
+ willChange: 'dependentArrayWillChange',
+ didChange: 'dependentArrayDidChange'
+ });
+ },
+
+ suspendArrayObservers: function (callback, binding) {
+ var oldSuspended = this.suspended;
+ this.suspended = true;
+ callback.call(binding);
+ this.suspended = oldSuspended;
+ },
+
+ setupPropertyObservers: function (dependentKey, itemPropertyKeys) {
+ var dependentArray = get(this.instanceMeta.context, dependentKey),
+ length = get(dependentArray, 'length'),
+ observerContexts = new Array(length);
+
+ this.resetTransformations(dependentKey, observerContexts);
+
+ forEach(dependentArray, function (item, index) {
+ var observerContext = this.createPropertyObserverContext(dependentArray, index, this.trackedArraysByGuid[dependentKey]);
+ observerContexts[index] = observerContext;
+
+ forEach(itemPropertyKeys, function (propertyKey) {
+ addBeforeObserver(item, propertyKey, this, observerContext.beforeObserver);
+ addObserver(item, propertyKey, this, observerContext.observer);
+ }, this);
+ }, this);
+ },
+
+ teardownPropertyObservers: function (dependentKey, itemPropertyKeys) {
+ var dependentArrayObserver = this,
+ trackedArray = this.trackedArraysByGuid[dependentKey],
+ beforeObserver,
+ observer,
+ item;
+
+ if (!trackedArray) { return; }
+
+ trackedArray.apply(function (observerContexts, offset, operation) {
+ if (operation === Ember.TrackedArray.DELETE) { return; }
+
+ forEach(observerContexts, function (observerContext) {
+ observerContext.destroyed = true;
+ beforeObserver = observerContext.beforeObserver;
+ observer = observerContext.observer;
+ item = observerContext.item;
+
+ forEach(itemPropertyKeys, function (propertyKey) {
+ removeBeforeObserver(item, propertyKey, dependentArrayObserver, beforeObserver);
+ removeObserver(item, propertyKey, dependentArrayObserver, observer);
+ });
+ });
+ });
+ },
+
+ createPropertyObserverContext: function (dependentArray, index, trackedArray) {
+ var observerContext = new ItemPropertyObserverContext(dependentArray, index, trackedArray);
+
+ this.createPropertyObserver(observerContext);
+
+ return observerContext;
+ },
+
+ createPropertyObserver: function (observerContext) {
+ var dependentArrayObserver = this;
+
+ observerContext.beforeObserver = function (obj, keyName) {
+ return dependentArrayObserver.itemPropertyWillChange(obj, keyName, observerContext.dependentArray, observerContext);
+ };
+ observerContext.observer = function (obj, keyName) {
+ return dependentArrayObserver.itemPropertyDidChange(obj, keyName, observerContext.dependentArray, observerContext);
+ };
+ },
+
+ resetTransformations: function (dependentKey, observerContexts) {
+ this.trackedArraysByGuid[dependentKey] = new Ember.TrackedArray(observerContexts);
+ },
+
+ trackAdd: function (dependentKey, index, newItems) {
+ var trackedArray = this.trackedArraysByGuid[dependentKey];
+ if (trackedArray) {
+ trackedArray.addItems(index, newItems);
+ }
+ },
+
+ trackRemove: function (dependentKey, index, removedCount) {
+ var trackedArray = this.trackedArraysByGuid[dependentKey];
+
+ if (trackedArray) {
+ return trackedArray.removeItems(index, removedCount);
+ }
+
+ return [];
+ },
+
+ updateIndexes: function (trackedArray, array) {
+ var length = get(array, 'length');
+ // OPTIMIZE: we could stop updating once we hit the object whose observer
+ // fired; ie partially apply the transformations
+ trackedArray.apply(function (observerContexts, offset, operation) {
+ // we don't even have observer contexts for removed items, even if we did,
+ // they no longer have any index in the array
+ if (operation === Ember.TrackedArray.DELETE) { return; }
+ if (operation === Ember.TrackedArray.RETAIN && observerContexts.length === length && offset === 0) {
+ // If we update many items we don't want to walk the array each time: we
+ // only need to update the indexes at most once per run loop.
+ return;
+ }
+
+ forEach(observerContexts, function (context, index) {
+ context.index = index + offset;
+ });
+ });
+ },
+
+ dependentArrayWillChange: function (dependentArray, index, removedCount, addedCount) {
+ if (this.suspended) { return; }
+
+ var removedItem = this.callbacks.removedItem,
+ changeMeta,
+ guid = guidFor(dependentArray),
+ dependentKey = this.dependentKeysByGuid[guid],
+ itemPropertyKeys = this.cp._itemPropertyKeys[dependentKey] || [],
+ length = get(dependentArray, 'length'),
+ normalizedIndex = normalizeIndex(index, length, 0),
+ normalizedRemoveCount = normalizeRemoveCount(normalizedIndex, length, removedCount),
+ item,
+ itemIndex,
+ sliceIndex,
+ observerContexts;
+
+ observerContexts = this.trackRemove(dependentKey, normalizedIndex, normalizedRemoveCount);
+
+ function removeObservers(propertyKey) {
+ observerContexts[sliceIndex].destroyed = true;
+ removeBeforeObserver(item, propertyKey, this, observerContexts[sliceIndex].beforeObserver);
+ removeObserver(item, propertyKey, this, observerContexts[sliceIndex].observer);
+ }
+
+ for (sliceIndex = normalizedRemoveCount - 1; sliceIndex >= 0; --sliceIndex) {
+ itemIndex = normalizedIndex + sliceIndex;
+ if (itemIndex >= length) { break; }
+
+ item = dependentArray.objectAt(itemIndex);
+
+ forEach(itemPropertyKeys, removeObservers, this);
+
+ changeMeta = createChangeMeta(dependentArray, item, itemIndex, this.instanceMeta.propertyName, this.cp);
+ this.setValue( removedItem.call(
+ this.instanceMeta.context, this.getValue(), item, changeMeta, this.instanceMeta.sugarMeta));
+ }
+ },
+
+ dependentArrayDidChange: function (dependentArray, index, removedCount, addedCount) {
+ if (this.suspended) { return; }
+
+ var addedItem = this.callbacks.addedItem,
+ guid = guidFor(dependentArray),
+ dependentKey = this.dependentKeysByGuid[guid],
+ observerContexts = new Array(addedCount),
+ itemPropertyKeys = this.cp._itemPropertyKeys[dependentKey],
+ length = get(dependentArray, 'length'),
+ normalizedIndex = normalizeIndex(index, length, addedCount),
+ changeMeta,
+ observerContext;
+
+ forEach(dependentArray.slice(normalizedIndex, normalizedIndex + addedCount), function (item, sliceIndex) {
+ if (itemPropertyKeys) {
+ observerContext =
+ observerContexts[sliceIndex] =
+ this.createPropertyObserverContext(dependentArray, normalizedIndex + sliceIndex, this.trackedArraysByGuid[dependentKey]);
+ forEach(itemPropertyKeys, function (propertyKey) {
+ addBeforeObserver(item, propertyKey, this, observerContext.beforeObserver);
+ addObserver(item, propertyKey, this, observerContext.observer);
+ }, this);
+ }
+
+ changeMeta = createChangeMeta(dependentArray, item, normalizedIndex + sliceIndex, this.instanceMeta.propertyName, this.cp);
+ this.setValue( addedItem.call(
+ this.instanceMeta.context, this.getValue(), item, changeMeta, this.instanceMeta.sugarMeta));
+ }, this);
+
+ this.trackAdd(dependentKey, normalizedIndex, observerContexts);
+ },
+
+ itemPropertyWillChange: function (obj, keyName, array, observerContext) {
+ var guid = guidFor(obj);
+
+ if (!this.changedItems[guid]) {
+ this.changedItems[guid] = {
+ array: array,
+ observerContext: observerContext,
+ obj: obj,
+ previousValues: {}
+ };
+ }
+
+ this.changedItems[guid].previousValues[keyName] = get(obj, keyName);
+ },
+
+ itemPropertyDidChange: function(obj, keyName, array, observerContext) {
+ this.flushChanges();
+ },
+
+ flushChanges: function() {
+ var changedItems = this.changedItems, key, c, changeMeta;
+
+ for (key in changedItems) {
+ c = changedItems[key];
+ if (c.observerContext.destroyed) { continue; }
+
+ this.updateIndexes(c.observerContext.trackedArray, c.observerContext.dependentArray);
+
+ changeMeta = createChangeMeta(c.array, c.obj, c.observerContext.index, this.instanceMeta.propertyName, this.cp, c.previousValues);
+ this.setValue(
+ this.callbacks.removedItem.call(this.instanceMeta.context, this.getValue(), c.obj, changeMeta, this.instanceMeta.sugarMeta));
+ this.setValue(
+ this.callbacks.addedItem.call(this.instanceMeta.context, this.getValue(), c.obj, changeMeta, this.instanceMeta.sugarMeta));
+ }
+ this.changedItems = {};
+ }
+};
+
+function normalizeIndex(index, length, newItemsOffset) {
+ if (index < 0) {
+ return Math.max(0, length + index);
+ } else if (index < length) {
+ return index;
+ } else /* index > length */ {
+ return Math.min(length - newItemsOffset, index);
+ }
+}
+
+function normalizeRemoveCount(index, length, removedCount) {
+ return Math.min(removedCount, length - index);
+}
+
+function createChangeMeta(dependentArray, item, index, propertyName, property, previousValues) {
+ var meta = {
+ arrayChanged: dependentArray,
+ index: index,
+ item: item,
+ propertyName: propertyName,
+ property: property
+ };
+
+ if (previousValues) {
+ // previous values only available for item property changes
+ meta.previousValues = previousValues;
+ }
+
+ return meta;
+}
+
+function addItems (dependentArray, callbacks, cp, propertyName, meta) {
+ forEach(dependentArray, function (item, index) {
+ meta.setValue( callbacks.addedItem.call(
+ this, meta.getValue(), item, createChangeMeta(dependentArray, item, index, propertyName, cp), meta.sugarMeta));
+ }, this);
+}
+
+function reset(cp, propertyName) {
+ var callbacks = cp._callbacks(),
+ meta;
+
+ if (cp._hasInstanceMeta(this, propertyName)) {
+ meta = cp._instanceMeta(this, propertyName);
+ meta.setValue(cp.resetValue(meta.getValue()));
+ } else {
+ meta = cp._instanceMeta(this, propertyName);
+ }
+
+ if (cp.options.initialize) {
+ cp.options.initialize.call(this, meta.getValue(), { property: cp, propertyName: propertyName }, meta.sugarMeta);
+ }
+}
+
+function partiallyRecomputeFor(obj, dependentKey) {
+ if (arrayBracketPattern.test(dependentKey)) {
+ return false;
+ }
+
+ var value = get(obj, dependentKey);
+ return Ember.Array.detect(value);
+}
+
+function ReduceComputedPropertyInstanceMeta(context, propertyName, initialValue) {
+ this.context = context;
+ this.propertyName = propertyName;
+ this.cache = metaFor(context).cache;
+
+ this.dependentArrays = {};
+ this.sugarMeta = {};
+
+ this.initialValue = initialValue;
+}
+
+ReduceComputedPropertyInstanceMeta.prototype = {
+ getValue: function () {
+ if (this.propertyName in this.cache) {
+ return this.cache[this.propertyName];
+ } else {
+ return this.initialValue;
+ }
+ },
+
+ setValue: function(newValue, triggerObservers) {
+ // This lets sugars force a recomputation, handy for very simple
+ // implementations of eg max.
+ if (newValue === this.cache[this.propertyName]) {
+ return;
+ }
+
+ if (triggerObservers) {
+ propertyWillChange(this.context, this.propertyName);
+ }
+
+ if (newValue === undefined) {
+ delete this.cache[this.propertyName];
+ } else {
+ this.cache[this.propertyName] = newValue;
+ }
+
+ if (triggerObservers) {
+ propertyDidChange(this.context, this.propertyName);
+ }
+ }
+};
+
+/**
+ A computed property whose dependent keys are arrays and which is updated with
+ "one at a time" semantics.
+
+ @class ReduceComputedProperty
+ @namespace Ember
+ @extends Ember.ComputedProperty
+ @constructor
+*/
+function ReduceComputedProperty(options) {
+ var cp = this;
+
+ this.options = options;
+ this._instanceMetas = {};
+
+ this._dependentKeys = null;
+ // A map of dependentKey -> [itemProperty, ...] that tracks what properties of
+ // items in the array we must track to update this property.
+ this._itemPropertyKeys = {};
+ this._previousItemPropertyKeys = {};
+
+ this.readOnly();
+ this.cacheable();
+
+ this.recomputeOnce = function(propertyName) {
+ // What we really want to do is coalesce by .
+ // We need a form of `scheduleOnce` that accepts an arbitrary token to
+ // coalesce by, in addition to the target and method.
+ Ember.run.once(this, recompute, propertyName);
+ };
+ var recompute = function(propertyName) {
+ var dependentKeys = cp._dependentKeys,
+ meta = cp._instanceMeta(this, propertyName),
+ callbacks = cp._callbacks();
+
+ reset.call(this, cp, propertyName);
+
+ meta.dependentArraysObserver.suspendArrayObservers(function () {
+ forEach(cp._dependentKeys, function (dependentKey) {
+ if (!partiallyRecomputeFor(this, dependentKey)) { return; }
+
+ var dependentArray = get(this, dependentKey),
+ previousDependentArray = meta.dependentArrays[dependentKey];
+
+ if (dependentArray === previousDependentArray) {
+ // The array may be the same, but our item property keys may have
+ // changed, so we set them up again. We can't easily tell if they've
+ // changed: the array may be the same object, but with different
+ // contents.
+ if (cp._previousItemPropertyKeys[dependentKey]) {
+ delete cp._previousItemPropertyKeys[dependentKey];
+ meta.dependentArraysObserver.setupPropertyObservers(dependentKey, cp._itemPropertyKeys[dependentKey]);
+ }
+ } else {
+ meta.dependentArrays[dependentKey] = dependentArray;
+
+ if (previousDependentArray) {
+ meta.dependentArraysObserver.teardownObservers(previousDependentArray, dependentKey);
+ }
+
+ if (dependentArray) {
+ meta.dependentArraysObserver.setupObservers(dependentArray, dependentKey);
+ }
+ }
+ }, this);
+ }, this);
+
+ forEach(cp._dependentKeys, function(dependentKey) {
+ if (!partiallyRecomputeFor(this, dependentKey)) { return; }
+
+ var dependentArray = get(this, dependentKey);
+ if (dependentArray) {
+ addItems.call(this, dependentArray, callbacks, cp, propertyName, meta);
+ }
+ }, this);
+ };
+
+
+ this.func = function (propertyName) {
+ Ember.assert("Computed reduce values require at least one dependent key", cp._dependentKeys);
+
+ recompute.call(this, propertyName);
+
+ return cp._instanceMeta(this, propertyName).getValue();
+ };
+}
+
+Ember.ReduceComputedProperty = ReduceComputedProperty;
+ReduceComputedProperty.prototype = o_create(ComputedProperty.prototype);
+
+function defaultCallback(computedValue) {
+ return computedValue;
+}
+
+ReduceComputedProperty.prototype._callbacks = function () {
+ if (!this.callbacks) {
+ var options = this.options;
+ this.callbacks = {
+ removedItem: options.removedItem || defaultCallback,
+ addedItem: options.addedItem || defaultCallback
+ };
+ }
+ return this.callbacks;
+};
+
+ReduceComputedProperty.prototype._hasInstanceMeta = function (context, propertyName) {
+ var guid = guidFor(context),
+ key = guid + ':' + propertyName;
+
+ return !!this._instanceMetas[key];
+};
+
+ReduceComputedProperty.prototype._instanceMeta = function (context, propertyName) {
+ var guid = guidFor(context),
+ key = guid + ':' + propertyName,
+ meta = this._instanceMetas[key];
+
+ if (!meta) {
+ meta = this._instanceMetas[key] = new ReduceComputedPropertyInstanceMeta(context, propertyName, this.initialValue());
+ meta.dependentArraysObserver = new DependentArraysObserver(this._callbacks(), this, meta, context, propertyName, meta.sugarMeta);
+ }
+
+ return meta;
+};
+
+ReduceComputedProperty.prototype.initialValue = function () {
+ if (typeof this.options.initialValue === 'function') {
+ return this.options.initialValue();
+ }
+ else {
+ return this.options.initialValue;
+ }
+};
+
+ReduceComputedProperty.prototype.resetValue = function (value) {
+ return this.initialValue();
+};
+
+ReduceComputedProperty.prototype.itemPropertyKey = function (dependentArrayKey, itemPropertyKey) {
+ this._itemPropertyKeys[dependentArrayKey] = this._itemPropertyKeys[dependentArrayKey] || [];
+ this._itemPropertyKeys[dependentArrayKey].push(itemPropertyKey);
+};
+
+ReduceComputedProperty.prototype.clearItemPropertyKeys = function (dependentArrayKey) {
+ if (this._itemPropertyKeys[dependentArrayKey]) {
+ this._previousItemPropertyKeys[dependentArrayKey] = this._itemPropertyKeys[dependentArrayKey];
+ this._itemPropertyKeys[dependentArrayKey] = [];
+ }
+};
+
+ReduceComputedProperty.prototype.property = function () {
+ var cp = this,
+ args = a_slice.call(arguments),
+ propertyArgs = new Ember.Set(),
+ match,
+ dependentArrayKey,
+ itemPropertyKey;
+
+ forEach(a_slice.call(arguments), function (dependentKey) {
+ if (doubleEachPropertyPattern.test(dependentKey)) {
+ throw new Ember.Error("Nested @each properties not supported: " + dependentKey);
+ } else if (match = eachPropertyPattern.exec(dependentKey)) {
+ dependentArrayKey = match[1];
+
+ var itemPropertyKeyPattern = match[2],
+ addItemPropertyKey = function (itemPropertyKey) {
+ cp.itemPropertyKey(dependentArrayKey, itemPropertyKey);
+ };
+
+ expandProperties(itemPropertyKeyPattern, addItemPropertyKey);
+ propertyArgs.add(dependentArrayKey);
+ } else {
+ propertyArgs.add(dependentKey);
+ }
+ });
+
+ return ComputedProperty.prototype.property.apply(this, propertyArgs.toArray());
+
+};
+
+/**
+ Creates a computed property which operates on dependent arrays and
+ is updated with "one at a time" semantics. When items are added or
+ removed from the dependent array(s) a reduce computed only operates
+ on the change instead of re-evaluating the entire array.
+
+ If there are more than one arguments the first arguments are
+ considered to be dependent property keys. The last argument is
+ required to be an options object. The options object can have the
+ following four properties:
+
+ `initialValue` - A value or function that will be used as the initial
+ value for the computed. If this property is a function the result of calling
+ the function will be used as the initial value. This property is required.
+
+ `initialize` - An optional initialize function. Typically this will be used
+ to set up state on the instanceMeta object.
+
+ `removedItem` - A function that is called each time an element is removed
+ from the array.
+
+ `addedItem` - A function that is called each time an element is added to
+ the array.
+
+
+ The `initialize` function has the following signature:
+
+ ```javascript
+ function (initialValue, changeMeta, instanceMeta)
+ ```
+
+ `initialValue` - The value of the `initialValue` property from the
+ options object.
+
+ `changeMeta` - An object which contains meta information about the
+ computed. It contains the following properties:
+
+ - `property` the computed property
+ - `propertyName` the name of the property on the object
+
+ `instanceMeta` - An object that can be used to store meta
+ information needed for calculating your computed. For example a
+ unique computed might use this to store the number of times a given
+ element is found in the dependent array.
+
+
+ The `removedItem` and `addedItem` functions both have the following signature:
+
+ ```javascript
+ function (accumulatedValue, item, changeMeta, instanceMeta)
+ ```
+
+ `accumulatedValue` - The value returned from the last time
+ `removedItem` or `addedItem` was called or `initialValue`.
+
+ `item` - the element added or removed from the array
+
+ `changeMeta` - An object which contains meta information about the
+ change. It contains the following properties:
+
+ - `property` the computed property
+ - `propertyName` the name of the property on the object
+ - `index` the index of the added or removed item
+ - `item` the added or removed item: this is exactly the same as
+ the second arg
+ - `arrayChanged` the array that triggered the change. Can be
+ useful when depending on multiple arrays.
+
+ For property changes triggered on an item property change (when
+ depKey is something like `someArray.@each.someProperty`),
+ `changeMeta` will also contain the following property:
+
+ - `previousValues` an object whose keys are the properties that changed on
+ the item, and whose values are the item's previous values.
+
+ `previousValues` is important Ember coalesces item property changes via
+ Ember.run.once. This means that by the time removedItem gets called, item has
+ the new values, but you may need the previous value (eg for sorting &
+ filtering).
+
+ `instanceMeta` - An object that can be used to store meta
+ information needed for calculating your computed. For example a
+ unique computed might use this to store the number of times a given
+ element is found in the dependent array.
+
+ The `removedItem` and `addedItem` functions should return the accumulated
+ value. It is acceptable to not return anything (ie return undefined)
+ to invalidate the computation. This is generally not a good idea for
+ arrayComputed but it's used in eg max and min.
+
+ Note that observers will be fired if either of these functions return a value
+ that differs from the accumulated value. When returning an object that
+ mutates in response to array changes, for example an array that maps
+ everything from some other array (see `Ember.computed.map`), it is usually
+ important that the *same* array be returned to avoid accidentally triggering observers.
+
+ Example
+
+ ```javascript
+ Ember.computed.max = function (dependentKey) {
+ return Ember.reduceComputed(dependentKey, {
+ initialValue: -Infinity,
+
+ addedItem: function (accumulatedValue, item, changeMeta, instanceMeta) {
+ return Math.max(accumulatedValue, item);
+ },
+
+ removedItem: function (accumulatedValue, item, changeMeta, instanceMeta) {
+ if (item < accumulatedValue) {
+ return accumulatedValue;
+ }
+ }
+ });
+ };
+ ```
+
+ Dependent keys may refer to `@this` to observe changes to the object itself,
+ which must be array-like, rather than a property of the object. This is
+ mostly useful for array proxies, to ensure objects are retrieved via
+ `objectAtContent`. This is how you could sort items by properties defined on an item controller.
+
+ Example
+
+ ```javascript
+ App.PeopleController = Ember.ArrayController.extend({
+ itemController: 'person',
+
+ sortedPeople: Ember.computed.sort('@this.@each.reversedName', function(personA, personB) {
+ // `reversedName` isn't defined on Person, but we have access to it via
+ // the item controller App.PersonController. If we'd used
+ // `content.@each.reversedName` above, we would be getting the objects
+ // directly and not have access to `reversedName`.
+ //
+ var reversedNameA = get(personA, 'reversedName'),
+ reversedNameB = get(personB, 'reversedName');
+
+ return Ember.compare(reversedNameA, reversedNameB);
+ })
+ });
+
+ App.PersonController = Ember.ObjectController.extend({
+ reversedName: function () {
+ return reverse(get(this, 'name'));
+ }.property('name')
+ })
+ ```
+
+ Dependent keys whose values are not arrays are treated as regular
+ dependencies: when they change, the computed property is completely
+ recalculated. It is sometimes useful to have dependent arrays with similar
+ semantics. Dependent keys which end in `.[]` do not use "one at a time"
+ semantics. When an item is added or removed from such a dependency, the
+ computed property is completely recomputed.
+
+ Example
+
+ ```javascript
+ Ember.Object.extend({
+ // When `string` is changed, `computed` is completely recomputed.
+ string: 'a string',
+
+ // When an item is added to `array`, `addedItem` is called.
+ array: [],
+
+ // When an item is added to `anotherArray`, `computed` is completely
+ // recomputed.
+ anotherArray: [],
+
+ computed: Ember.reduceComputed('string', 'array', 'anotherArray.[]', {
+ addedItem: addedItemCallback,
+ removedItem: removedItemCallback
+ })
+ });
+ ```
+
+ @method reduceComputed
+ @for Ember
+ @param {String} [dependentKeys*]
+ @param {Object} options
+ @return {Ember.ComputedProperty}
+*/
+Ember.reduceComputed = function (options) {
+ var args;
+
+ if (arguments.length > 1) {
+ args = a_slice.call(arguments, 0, -1);
+ options = a_slice.call(arguments, -1)[0];
+ }
+
+ if (typeof options !== "object") {
+ throw new Ember.Error("Reduce Computed Property declared without an options hash");
+ }
+
+ if (!('initialValue' in options)) {
+ throw new Ember.Error("Reduce Computed Property declared without an initial value");
+ }
+
+ var cp = new ReduceComputedProperty(options);
+
+ if (args) {
+ cp.property.apply(cp, args);
+ }
+
+ return cp;
+};
+
+})();
+
+
+
+(function() {
+var ReduceComputedProperty = Ember.ReduceComputedProperty,
+ a_slice = [].slice,
+ o_create = Ember.create,
+ forEach = Ember.EnumerableUtils.forEach;
+
+function ArrayComputedProperty() {
+ var cp = this;
+
+ ReduceComputedProperty.apply(this, arguments);
+
+ this.func = (function(reduceFunc) {
+ return function (propertyName) {
+ if (!cp._hasInstanceMeta(this, propertyName)) {
+ // When we recompute an array computed property, we need already
+ // retrieved arrays to be updated; we can't simply empty the cache and
+ // hope the array is re-retrieved.
+ forEach(cp._dependentKeys, function(dependentKey) {
+ Ember.addObserver(this, dependentKey, function() {
+ cp.recomputeOnce.call(this, propertyName);
+ });
+ }, this);
+ }
+
+ return reduceFunc.apply(this, arguments);
+ };
+ })(this.func);
+
+ return this;
+}
+Ember.ArrayComputedProperty = ArrayComputedProperty;
+ArrayComputedProperty.prototype = o_create(ReduceComputedProperty.prototype);
+ArrayComputedProperty.prototype.initialValue = function () {
+ return Ember.A();
+};
+ArrayComputedProperty.prototype.resetValue = function (array) {
+ array.clear();
+ return array;
+};
+
+// This is a stopgap to keep the reference counts correct with lazy CPs.
+ArrayComputedProperty.prototype.didChange = function (obj, keyName) {
+ return;
+};
+
+/**
+ Creates a computed property which operates on dependent arrays and
+ is updated with "one at a time" semantics. When items are added or
+ removed from the dependent array(s) an array computed only operates
+ on the change instead of re-evaluating the entire array. This should
+ return an array, if you'd like to use "one at a time" semantics and
+ compute some value other then an array look at
+ `Ember.reduceComputed`.
+
+ If there are more than one arguments the first arguments are
+ considered to be dependent property keys. The last argument is
+ required to be an options object. The options object can have the
+ following three properties.
+
+ `initialize` - An optional initialize function. Typically this will be used
+ to set up state on the instanceMeta object.
+
+ `removedItem` - A function that is called each time an element is
+ removed from the array.
+
+ `addedItem` - A function that is called each time an element is
+ added to the array.
+
+
+ The `initialize` function has the following signature:
+
+ ```javascript
+ function (array, changeMeta, instanceMeta)
+ ```
+
+ `array` - The initial value of the arrayComputed, an empty array.
+
+ `changeMeta` - An object which contains meta information about the
+ computed. It contains the following properties:
+
+ - `property` the computed property
+ - `propertyName` the name of the property on the object
+
+ `instanceMeta` - An object that can be used to store meta
+ information needed for calculating your computed. For example a
+ unique computed might use this to store the number of times a given
+ element is found in the dependent array.
+
+
+ The `removedItem` and `addedItem` functions both have the following signature:
+
+ ```javascript
+ function (accumulatedValue, item, changeMeta, instanceMeta)
+ ```
+
+ `accumulatedValue` - The value returned from the last time
+ `removedItem` or `addedItem` was called or an empty array.
+
+ `item` - the element added or removed from the array
+
+ `changeMeta` - An object which contains meta information about the
+ change. It contains the following properties:
+
+ - `property` the computed property
+ - `propertyName` the name of the property on the object
+ - `index` the index of the added or removed item
+ - `item` the added or removed item: this is exactly the same as
+ the second arg
+ - `arrayChanged` the array that triggered the change. Can be
+ useful when depending on multiple arrays.
+
+ For property changes triggered on an item property change (when
+ depKey is something like `someArray.@each.someProperty`),
+ `changeMeta` will also contain the following property:
+
+ - `previousValues` an object whose keys are the properties that changed on
+ the item, and whose values are the item's previous values.
+
+ `previousValues` is important Ember coalesces item property changes via
+ Ember.run.once. This means that by the time removedItem gets called, item has
+ the new values, but you may need the previous value (eg for sorting &
+ filtering).
+
+ `instanceMeta` - An object that can be used to store meta
+ information needed for calculating your computed. For example a
+ unique computed might use this to store the number of times a given
+ element is found in the dependent array.
+
+ The `removedItem` and `addedItem` functions should return the accumulated
+ value. It is acceptable to not return anything (ie return undefined)
+ to invalidate the computation. This is generally not a good idea for
+ arrayComputed but it's used in eg max and min.
+
+ Example
+
+ ```javascript
+ Ember.computed.map = function(dependentKey, callback) {
+ var options = {
+ addedItem: function(array, item, changeMeta, instanceMeta) {
+ var mapped = callback(item);
+ array.insertAt(changeMeta.index, mapped);
+ return array;
+ },
+ removedItem: function(array, item, changeMeta, instanceMeta) {
+ array.removeAt(changeMeta.index, 1);
+ return array;
+ }
+ };
+
+ return Ember.arrayComputed(dependentKey, options);
+ };
+ ```
+
+ @method arrayComputed
+ @for Ember
+ @param {String} [dependentKeys*]
+ @param {Object} options
+ @return {Ember.ComputedProperty}
+*/
+Ember.arrayComputed = function (options) {
+ var args;
+
+ if (arguments.length > 1) {
+ args = a_slice.call(arguments, 0, -1);
+ options = a_slice.call(arguments, -1)[0];
+ }
+
+ if (typeof options !== "object") {
+ throw new Ember.Error("Array Computed Property declared without an options hash");
+ }
+
+ var cp = new ArrayComputedProperty(options);
+
+ if (args) {
+ cp.property.apply(cp, args);
+ }
+
+ return cp;
+};
+
+})();
+
+
+
+(function() {
+/**
+@module ember
+@submodule ember-runtime
+*/
+
+var get = Ember.get,
+ set = Ember.set,
+ guidFor = Ember.guidFor,
+ merge = Ember.merge,
+ a_slice = [].slice,
+ forEach = Ember.EnumerableUtils.forEach,
+ map = Ember.EnumerableUtils.map,
+ SearchProxy;
+
+/**
+ A computed property that returns the sum of the value
+ in the dependent array.
+
+ @method computed.sum
+ @for Ember
+ @param {String} dependentKey
+ @return {Ember.ComputedProperty} computes the sum of all values in the dependentKey's array
+*/
+
+Ember.computed.sum = function(dependentKey){
+ return Ember.reduceComputed(dependentKey, {
+ initialValue: 0,
+
+ addedItem: function(accumulatedValue, item, changeMeta, instanceMeta){
+ return accumulatedValue + item;
+ },
+
+ removedItem: function(accumulatedValue, item, changeMeta, instanceMeta){
+ return accumulatedValue - item;
+ }
+ });
+};
+
+/**
+ A computed property that calculates the maximum value in the
+ dependent array. This will return `-Infinity` when the dependent
+ array is empty.
+
+ ```javascript
+ App.Person = Ember.Object.extend({
+ childAges: Ember.computed.mapBy('children', 'age'),
+ maxChildAge: Ember.computed.max('childAges')
+ });
+
+ var lordByron = App.Person.create({children: []});
+ lordByron.get('maxChildAge'); // -Infinity
+ lordByron.get('children').pushObject({
+ name: 'Augusta Ada Byron', age: 7
+ });
+ lordByron.get('maxChildAge'); // 7
+ lordByron.get('children').pushObjects([{
+ name: 'Allegra Byron',
+ age: 5
+ }, {
+ name: 'Elizabeth Medora Leigh',
+ age: 8
+ }]);
+ lordByron.get('maxChildAge'); // 8
+ ```
+
+ @method computed.max
+ @for Ember
+ @param {String} dependentKey
+ @return {Ember.ComputedProperty} computes the largest value in the dependentKey's array
+*/
+Ember.computed.max = function (dependentKey) {
+ return Ember.reduceComputed(dependentKey, {
+ initialValue: -Infinity,
+
+ addedItem: function (accumulatedValue, item, changeMeta, instanceMeta) {
+ return Math.max(accumulatedValue, item);
+ },
+
+ removedItem: function (accumulatedValue, item, changeMeta, instanceMeta) {
+ if (item < accumulatedValue) {
+ return accumulatedValue;
+ }
+ }
+ });
+};
+
+/**
+ A computed property that calculates the minimum value in the
+ dependent array. This will return `Infinity` when the dependent
+ array is empty.
+
+ ```javascript
+ App.Person = Ember.Object.extend({
+ childAges: Ember.computed.mapBy('children', 'age'),
+ minChildAge: Ember.computed.min('childAges')
+ });
+
+ var lordByron = App.Person.create({children: []});
+ lordByron.get('minChildAge'); // Infinity
+ lordByron.get('children').pushObject({
+ name: 'Augusta Ada Byron', age: 7
+ });
+ lordByron.get('minChildAge'); // 7
+ lordByron.get('children').pushObjects([{
+ name: 'Allegra Byron',
+ age: 5
+ }, {
+ name: 'Elizabeth Medora Leigh',
+ age: 8
+ }]);
+ lordByron.get('minChildAge'); // 5
+ ```
+
+ @method computed.min
+ @for Ember
+ @param {String} dependentKey
+ @return {Ember.ComputedProperty} computes the smallest value in the dependentKey's array
+*/
+Ember.computed.min = function (dependentKey) {
+ return Ember.reduceComputed(dependentKey, {
+ initialValue: Infinity,
+
+ addedItem: function (accumulatedValue, item, changeMeta, instanceMeta) {
+ return Math.min(accumulatedValue, item);
+ },
+
+ removedItem: function (accumulatedValue, item, changeMeta, instanceMeta) {
+ if (item > accumulatedValue) {
+ return accumulatedValue;
+ }
+ }
+ });
+};
+
+/**
+ Returns an array mapped via the callback
+
+ The callback method you provide should have the following signature.
+ `item` is the current item in the iteration.
+
+ ```javascript
+ function(item);
+ ```
+
+ Example
+
+ ```javascript
+ App.Hamster = Ember.Object.extend({
+ excitingChores: Ember.computed.map('chores', function(chore) {
+ return chore.toUpperCase() + '!';
+ })
+ });
+
+ var hamster = App.Hamster.create({
+ chores: ['clean', 'write more unit tests']
+ });
+ hamster.get('excitingChores'); // ['CLEAN!', 'WRITE MORE UNIT TESTS!']
+ ```
+
+ @method computed.map
+ @for Ember
+ @param {String} dependentKey
+ @param {Function} callback
+ @return {Ember.ComputedProperty} an array mapped via the callback
+*/
+Ember.computed.map = function(dependentKey, callback) {
+ var options = {
+ addedItem: function(array, item, changeMeta, instanceMeta) {
+ var mapped = callback.call(this, item);
+ array.insertAt(changeMeta.index, mapped);
+ return array;
+ },
+ removedItem: function(array, item, changeMeta, instanceMeta) {
+ array.removeAt(changeMeta.index, 1);
+ return array;
+ }
+ };
+
+ return Ember.arrayComputed(dependentKey, options);
+};
+
+/**
+ Returns an array mapped to the specified key.
+
+ ```javascript
+ App.Person = Ember.Object.extend({
+ childAges: Ember.computed.mapBy('children', 'age')
+ });
+
+ var lordByron = App.Person.create({children: []});
+ lordByron.get('childAges'); // []
+ lordByron.get('children').pushObject({name: 'Augusta Ada Byron', age: 7});
+ lordByron.get('childAges'); // [7]
+ lordByron.get('children').pushObjects([{
+ name: 'Allegra Byron',
+ age: 5
+ }, {
+ name: 'Elizabeth Medora Leigh',
+ age: 8
+ }]);
+ lordByron.get('childAges'); // [7, 5, 8]
+ ```
+
+ @method computed.mapBy
+ @for Ember
+ @param {String} dependentKey
+ @param {String} propertyKey
+ @return {Ember.ComputedProperty} an array mapped to the specified key
+*/
+Ember.computed.mapBy = function(dependentKey, propertyKey) {
+ var callback = function(item) { return get(item, propertyKey); };
+ return Ember.computed.map(dependentKey + '.@each.' + propertyKey, callback);
+};
+
+/**
+ @method computed.mapProperty
+ @for Ember
+ @deprecated Use `Ember.computed.mapBy` instead
+ @param dependentKey
+ @param propertyKey
+*/
+Ember.computed.mapProperty = Ember.computed.mapBy;
+
+/**
+ Filters the array by the callback.
+
+ The callback method you provide should have the following signature.
+ `item` is the current item in the iteration.
+
+ ```javascript
+ function(item);
+ ```
+
+ ```javascript
+ App.Hamster = Ember.Object.extend({
+ remainingChores: Ember.computed.filter('chores', function(chore) {
+ return !chore.done;
+ })
+ });
+
+ var hamster = App.Hamster.create({chores: [
+ {name: 'cook', done: true},
+ {name: 'clean', done: true},
+ {name: 'write more unit tests', done: false}
+ ]});
+ hamster.get('remainingChores'); // [{name: 'write more unit tests', done: false}]
+ ```
+
+ @method computed.filter
+ @for Ember
+ @param {String} dependentKey
+ @param {Function} callback
+ @return {Ember.ComputedProperty} the filtered array
+*/
+Ember.computed.filter = function(dependentKey, callback) {
+ var options = {
+ initialize: function (array, changeMeta, instanceMeta) {
+ instanceMeta.filteredArrayIndexes = new Ember.SubArray();
+ },
+
+ addedItem: function(array, item, changeMeta, instanceMeta) {
+ var match = !!callback.call(this, item),
+ filterIndex = instanceMeta.filteredArrayIndexes.addItem(changeMeta.index, match);
+
+ if (match) {
+ array.insertAt(filterIndex, item);
+ }
+
+ return array;
+ },
+
+ removedItem: function(array, item, changeMeta, instanceMeta) {
+ var filterIndex = instanceMeta.filteredArrayIndexes.removeItem(changeMeta.index);
+
+ if (filterIndex > -1) {
+ array.removeAt(filterIndex);
+ }
+
+ return array;
+ }
+ };
+
+ return Ember.arrayComputed(dependentKey, options);
+};
+
+/**
+ Filters the array by the property and value
+
+ ```javascript
+ App.Hamster = Ember.Object.extend({
+ remainingChores: Ember.computed.filterBy('chores', 'done', false)
+ });
+
+ var hamster = App.Hamster.create({chores: [
+ {name: 'cook', done: true},
+ {name: 'clean', done: true},
+ {name: 'write more unit tests', done: false}
+ ]});
+ hamster.get('remainingChores'); // [{name: 'write more unit tests', done: false}]
+ ```
+
+ @method computed.filterBy
+ @for Ember
+ @param {String} dependentKey
+ @param {String} propertyKey
+ @param {String} value
+ @return {Ember.ComputedProperty} the filtered array
+*/
+Ember.computed.filterBy = function(dependentKey, propertyKey, value) {
+ var callback;
+
+ if (arguments.length === 2) {
+ callback = function(item) {
+ return get(item, propertyKey);
+ };
+ } else {
+ callback = function(item) {
+ return get(item, propertyKey) === value;
+ };
+ }
+
+ return Ember.computed.filter(dependentKey + '.@each.' + propertyKey, callback);
+};
+
+/**
+ @method computed.filterProperty
+ @for Ember
+ @param dependentKey
+ @param propertyKey
+ @param value
+ @deprecated Use `Ember.computed.filterBy` instead
+*/
+Ember.computed.filterProperty = Ember.computed.filterBy;
+
+/**
+ A computed property which returns a new array with all the unique
+ elements from one or more dependent arrays.
+
+ Example
+
+ ```javascript
+ App.Hamster = Ember.Object.extend({
+ uniqueFruits: Ember.computed.uniq('fruits')
+ });
+
+ var hamster = App.Hamster.create({fruits: [
+ 'banana',
+ 'grape',
+ 'kale',
+ 'banana'
+ ]});
+ hamster.get('uniqueFruits'); // ['banana', 'grape', 'kale']
+ ```
+
+ @method computed.uniq
+ @for Ember
+ @param {String} propertyKey*
+ @return {Ember.ComputedProperty} computes a new array with all the
+ unique elements from the dependent array
+*/
+Ember.computed.uniq = function() {
+ var args = a_slice.call(arguments);
+ args.push({
+ initialize: function(array, changeMeta, instanceMeta) {
+ instanceMeta.itemCounts = {};
+ },
+
+ addedItem: function(array, item, changeMeta, instanceMeta) {
+ var guid = guidFor(item);
+
+ if (!instanceMeta.itemCounts[guid]) {
+ instanceMeta.itemCounts[guid] = 1;
+ } else {
+ ++instanceMeta.itemCounts[guid];
+ }
+ array.addObject(item);
+ return array;
+ },
+ removedItem: function(array, item, _, instanceMeta) {
+ var guid = guidFor(item),
+ itemCounts = instanceMeta.itemCounts;
+
+ if (--itemCounts[guid] === 0) {
+ array.removeObject(item);
+ }
+ return array;
+ }
+ });
+ return Ember.arrayComputed.apply(null, args);
+};
+
+/**
+ Alias for [Ember.computed.uniq](/api/#method_computed_uniq).
+
+ @method computed.union
+ @for Ember
+ @param {String} propertyKey*
+ @return {Ember.ComputedProperty} computes a new array with all the
+ unique elements from the dependent array
+*/
+Ember.computed.union = Ember.computed.uniq;
+
+/**
+ A computed property which returns a new array with all the duplicated
+ elements from two or more dependent arrays.
+
+ Example
+
+ ```javascript
+ var obj = Ember.Object.createWithMixins({
+ adaFriends: ['Charles Babbage', 'John Hobhouse', 'William King', 'Mary Somerville'],
+ charlesFriends: ['William King', 'Mary Somerville', 'Ada Lovelace', 'George Peacock'],
+ friendsInCommon: Ember.computed.intersect('adaFriends', 'charlesFriends')
+ });
+
+ obj.get('friendsInCommon'); // ['William King', 'Mary Somerville']
+ ```
+
+ @method computed.intersect
+ @for Ember
+ @param {String} propertyKey*
+ @return {Ember.ComputedProperty} computes a new array with all the
+ duplicated elements from the dependent arrays
+*/
+Ember.computed.intersect = function () {
+ var getDependentKeyGuids = function (changeMeta) {
+ return map(changeMeta.property._dependentKeys, function (dependentKey) {
+ return guidFor(dependentKey);
+ });
+ };
+
+ var args = a_slice.call(arguments);
+ args.push({
+ initialize: function (array, changeMeta, instanceMeta) {
+ instanceMeta.itemCounts = {};
+ },
+
+ addedItem: function(array, item, changeMeta, instanceMeta) {
+ var itemGuid = guidFor(item),
+ dependentGuids = getDependentKeyGuids(changeMeta),
+ dependentGuid = guidFor(changeMeta.arrayChanged),
+ numberOfDependentArrays = changeMeta.property._dependentKeys.length,
+ itemCounts = instanceMeta.itemCounts;
+
+ if (!itemCounts[itemGuid]) { itemCounts[itemGuid] = {}; }
+ if (itemCounts[itemGuid][dependentGuid] === undefined) { itemCounts[itemGuid][dependentGuid] = 0; }
+
+ if (++itemCounts[itemGuid][dependentGuid] === 1 &&
+ numberOfDependentArrays === Ember.keys(itemCounts[itemGuid]).length) {
+
+ array.addObject(item);
+ }
+ return array;
+ },
+ removedItem: function(array, item, changeMeta, instanceMeta) {
+ var itemGuid = guidFor(item),
+ dependentGuids = getDependentKeyGuids(changeMeta),
+ dependentGuid = guidFor(changeMeta.arrayChanged),
+ numberOfDependentArrays = changeMeta.property._dependentKeys.length,
+ numberOfArraysItemAppearsIn,
+ itemCounts = instanceMeta.itemCounts;
+
+ if (itemCounts[itemGuid][dependentGuid] === undefined) { itemCounts[itemGuid][dependentGuid] = 0; }
+ if (--itemCounts[itemGuid][dependentGuid] === 0) {
+ delete itemCounts[itemGuid][dependentGuid];
+ numberOfArraysItemAppearsIn = Ember.keys(itemCounts[itemGuid]).length;
+
+ if (numberOfArraysItemAppearsIn === 0) {
+ delete itemCounts[itemGuid];
+ }
+ array.removeObject(item);
+ }
+ return array;
+ }
+ });
+ return Ember.arrayComputed.apply(null, args);
+};
+
+/**
+ A computed property which returns a new array with all the
+ properties from the first dependent array that are not in the second
+ dependent array.
+
+ Example
+
+ ```javascript
+ App.Hamster = Ember.Object.extend({
+ likes: ['banana', 'grape', 'kale'],
+ wants: Ember.computed.setDiff('likes', 'fruits')
+ });
+
+ var hamster = App.Hamster.create({fruits: [
+ 'grape',
+ 'kale',
+ ]});
+ hamster.get('wants'); // ['banana']
+ ```
+
+ @method computed.setDiff
+ @for Ember
+ @param {String} setAProperty
+ @param {String} setBProperty
+ @return {Ember.ComputedProperty} computes a new array with all the
+ items from the first dependent array that are not in the second
+ dependent array
+*/
+Ember.computed.setDiff = function (setAProperty, setBProperty) {
+ if (arguments.length !== 2) {
+ throw new Ember.Error("setDiff requires exactly two dependent arrays.");
+ }
+ return Ember.arrayComputed(setAProperty, setBProperty, {
+ addedItem: function (array, item, changeMeta, instanceMeta) {
+ var setA = get(this, setAProperty),
+ setB = get(this, setBProperty);
+
+ if (changeMeta.arrayChanged === setA) {
+ if (!setB.contains(item)) {
+ array.addObject(item);
+ }
+ } else {
+ array.removeObject(item);
+ }
+ return array;
+ },
+
+ removedItem: function (array, item, changeMeta, instanceMeta) {
+ var setA = get(this, setAProperty),
+ setB = get(this, setBProperty);
+
+ if (changeMeta.arrayChanged === setB) {
+ if (setA.contains(item)) {
+ array.addObject(item);
+ }
+ } else {
+ array.removeObject(item);
+ }
+ return array;
+ }
+ });
+};
+
+function binarySearch(array, item, low, high) {
+ var mid, midItem, res, guidMid, guidItem;
+
+ if (arguments.length < 4) { high = get(array, 'length'); }
+ if (arguments.length < 3) { low = 0; }
+
+ if (low === high) {
+ return low;
+ }
+
+ mid = low + Math.floor((high - low) / 2);
+ midItem = array.objectAt(mid);
+
+ guidMid = _guidFor(midItem);
+ guidItem = _guidFor(item);
+
+ if (guidMid === guidItem) {
+ return mid;
+ }
+
+ res = this.order(midItem, item);
+ if (res === 0) {
+ res = guidMid < guidItem ? -1 : 1;
+ }
+
+
+ if (res < 0) {
+ return this.binarySearch(array, item, mid+1, high);
+ } else if (res > 0) {
+ return this.binarySearch(array, item, low, mid);
+ }
+
+ return mid;
+
+ function _guidFor(item) {
+ if (SearchProxy.detectInstance(item)) {
+ return guidFor(get(item, 'content'));
+ }
+ return guidFor(item);
+ }
+}
+
+
+SearchProxy = Ember.ObjectProxy.extend();
+
+/**
+ A computed property which returns a new array with all the
+ properties from the first dependent array sorted based on a property
+ or sort function.
+
+ The callback method you provide should have the following signature:
+
+ ```javascript
+ function(itemA, itemB);
+ ```
+
+ - `itemA` the first item to compare.
+ - `itemB` the second item to compare.
+
+ This function should return `-1` when `itemA` should come before
+ `itemB`. It should return `1` when `itemA` should come after
+ `itemB`. If the `itemA` and `itemB` are equal this function should return `0`.
+
+ Example
+
+ ```javascript
+ var ToDoList = Ember.Object.extend({
+ todosSorting: ['name'],
+ sortedTodos: Ember.computed.sort('todos', 'todosSorting'),
+ priorityTodos: Ember.computed.sort('todos', function(a, b){
+ if (a.priority > b.priority) {
+ return 1;
+ } else if (a.priority < b.priority) {
+ return -1;
+ }
+ return 0;
+ }),
+ });
+ var todoList = ToDoList.create({todos: [
+ {name: 'Unit Test', priority: 2},
+ {name: 'Documentation', priority: 3},
+ {name: 'Release', priority: 1}
+ ]});
+
+ todoList.get('sortedTodos'); // [{name:'Documentation', priority:3}, {name:'Release', priority:1}, {name:'Unit Test', priority:2}]
+ todoList.get('priorityTodos'); // [{name:'Release', priority:1}, {name:'Unit Test', priority:2}, {name:'Documentation', priority:3}]
+ ```
+
+ @method computed.sort
+ @for Ember
+ @param {String} dependentKey
+ @param {String or Function} sortDefinition a dependent key to an
+ array of sort properties or a function to use when sorting
+ @return {Ember.ComputedProperty} computes a new sorted array based
+ on the sort property array or callback function
+*/
+Ember.computed.sort = function (itemsKey, sortDefinition) {
+ Ember.assert("Ember.computed.sort requires two arguments: an array key to sort and either a sort properties key or sort function", arguments.length === 2);
+
+ var initFn, sortPropertiesKey;
+
+ if (typeof sortDefinition === 'function') {
+ initFn = function (array, changeMeta, instanceMeta) {
+ instanceMeta.order = sortDefinition;
+ instanceMeta.binarySearch = binarySearch;
+ };
+ } else {
+ sortPropertiesKey = sortDefinition;
+ initFn = function (array, changeMeta, instanceMeta) {
+ function setupSortProperties() {
+ var sortPropertyDefinitions = get(this, sortPropertiesKey),
+ sortProperty,
+ sortProperties = instanceMeta.sortProperties = [],
+ sortPropertyAscending = instanceMeta.sortPropertyAscending = {},
+ idx,
+ asc;
+
+ Ember.assert("Cannot sort: '" + sortPropertiesKey + "' is not an array.", Ember.isArray(sortPropertyDefinitions));
+
+ changeMeta.property.clearItemPropertyKeys(itemsKey);
+
+ forEach(sortPropertyDefinitions, function (sortPropertyDefinition) {
+ if ((idx = sortPropertyDefinition.indexOf(':')) !== -1) {
+ sortProperty = sortPropertyDefinition.substring(0, idx);
+ asc = sortPropertyDefinition.substring(idx+1).toLowerCase() !== 'desc';
+ } else {
+ sortProperty = sortPropertyDefinition;
+ asc = true;
+ }
+
+ sortProperties.push(sortProperty);
+ sortPropertyAscending[sortProperty] = asc;
+ changeMeta.property.itemPropertyKey(itemsKey, sortProperty);
+ });
+
+ sortPropertyDefinitions.addObserver('@each', this, updateSortPropertiesOnce);
+ }
+
+ function updateSortPropertiesOnce() {
+ Ember.run.once(this, updateSortProperties, changeMeta.propertyName);
+ }
+
+ function updateSortProperties(propertyName) {
+ setupSortProperties.call(this);
+ changeMeta.property.recomputeOnce.call(this, propertyName);
+ }
+
+ Ember.addObserver(this, sortPropertiesKey, updateSortPropertiesOnce);
+
+ setupSortProperties.call(this);
+
+
+ instanceMeta.order = function (itemA, itemB) {
+ var sortProperty, result, asc;
+ for (var i = 0; i < this.sortProperties.length; ++i) {
+ sortProperty = this.sortProperties[i];
+ result = Ember.compare(get(itemA, sortProperty), get(itemB, sortProperty));
+
+ if (result !== 0) {
+ asc = this.sortPropertyAscending[sortProperty];
+ return asc ? result : (-1 * result);
+ }
+ }
+
+ return 0;
+ };
+
+ instanceMeta.binarySearch = binarySearch;
+ };
+ }
+
+ return Ember.arrayComputed(itemsKey, {
+ initialize: initFn,
+
+ addedItem: function (array, item, changeMeta, instanceMeta) {
+ var index = instanceMeta.binarySearch(array, item);
+ array.insertAt(index, item);
+ return array;
+ },
+
+ removedItem: function (array, item, changeMeta, instanceMeta) {
+ var proxyProperties, index, searchItem;
+
+ if (changeMeta.previousValues) {
+ proxyProperties = merge({ content: item }, changeMeta.previousValues);
+
+ searchItem = SearchProxy.create(proxyProperties);
+ } else {
+ searchItem = item;
+ }
+
+ index = instanceMeta.binarySearch(array, searchItem);
+ array.removeAt(index);
+ return array;
+ }
+ });
+};
+
+})();
+
+
+
+(function() {
+Ember.RSVP = requireModule('rsvp');
+
+Ember.RSVP.onerrorDefault = function(error) {
+ if (error instanceof Error) {
+ if (Ember.testing) {
+ if (Ember.Test && Ember.Test.adapter) {
+ Ember.Test.adapter.exception(error);
+ } else {
+ throw error;
+ }
+ } else {
+ Ember.Logger.error(error.stack);
+ Ember.assert(error, false);
+ }
+ }
+};
+
+Ember.RSVP.on('error', Ember.RSVP.onerrorDefault);
+
+})();
+
+
+
+(function() {
+/**
+@module ember
+@submodule ember-runtime
+*/
+
+var a_slice = Array.prototype.slice;
+
+var expandProperties = Ember.expandProperties;
+
+if (Ember.EXTEND_PROTOTYPES === true || Ember.EXTEND_PROTOTYPES.Function) {
+
+ /**
+ The `property` extension of Javascript's Function prototype is available
+ when `Ember.EXTEND_PROTOTYPES` or `Ember.EXTEND_PROTOTYPES.Function` is
+ `true`, which is the default.
+
+ Computed properties allow you to treat a function like a property:
+
+ ```javascript
+ MyApp.President = Ember.Object.extend({
+ firstName: '',
+ lastName: '',
+
+ fullName: function() {
+ return this.get('firstName') + ' ' + this.get('lastName');
+
+ // Call this flag to mark the function as a property
+ }.property()
+ });
+
+ var president = MyApp.President.create({
+ firstName: "Barack",
+ lastName: "Obama"
+ });
+
+ president.get('fullName'); // "Barack Obama"
+ ```
+
+ Treating a function like a property is useful because they can work with
+ bindings, just like any other property.
+
+ Many computed properties have dependencies on other properties. For
+ example, in the above example, the `fullName` property depends on
+ `firstName` and `lastName` to determine its value. You can tell Ember
+ about these dependencies like this:
+
+ ```javascript
+ MyApp.President = Ember.Object.extend({
+ firstName: '',
+ lastName: '',
+
+ fullName: function() {
+ return this.get('firstName') + ' ' + this.get('lastName');
+
+ // Tell Ember.js that this computed property depends on firstName
+ // and lastName
+ }.property('firstName', 'lastName')
+ });
+ ```
+
+ Make sure you list these dependencies so Ember knows when to update
+ bindings that connect to a computed property. Changing a dependency
+ will not immediately trigger an update of the computed property, but
+ will instead clear the cache so that it is updated when the next `get`
+ is called on the property.
+
+ See [Ember.ComputedProperty](/api/classes/Ember.ComputedProperty.html), [Ember.computed](/api/#method_computed).
+
+ @method property
+ @for Function
+ */
+ Function.prototype.property = function() {
+ var ret = Ember.computed(this);
+ // ComputedProperty.prototype.property expands properties; no need for us to
+ // do so here.
+ return ret.property.apply(ret, arguments);
+ };
+
+ /**
+ The `observes` extension of Javascript's Function prototype is available
+ when `Ember.EXTEND_PROTOTYPES` or `Ember.EXTEND_PROTOTYPES.Function` is
+ true, which is the default.
+
+ You can observe property changes simply by adding the `observes`
+ call to the end of your method declarations in classes that you write.
+ For example:
+
+ ```javascript
+ Ember.Object.extend({
+ valueObserver: function() {
+ // Executes whenever the "value" property changes
+ }.observes('value')
+ });
+ ```
+
+ In the future this method may become asynchronous. If you want to ensure
+ synchronous behavior, use `observesImmediately`.
+
+ See `Ember.observer`.
+
+ @method observes
+ @for Function
+ */
+ Function.prototype.observes = function() {
+ var addWatchedProperty = function (obs) { watched.push(obs); };
+ var watched = [];
+
+ for (var i=0; i b`
+
+ Default implementation raises an exception.
+
+ @method compare
+ @param a {Object} the first object to compare
+ @param b {Object} the second object to compare
+ @return {Integer} the result of the comparison
+ */
+ compare: Ember.required(Function)
+
+});
+
+
+})();
+
+
+
+(function() {
+/**
+@module ember
+@submodule ember-runtime
+*/
+
+
+
+var get = Ember.get, set = Ember.set;
+
+/**
+ Implements some standard methods for copying an object. Add this mixin to
+ any object you create that can create a copy of itself. This mixin is
+ added automatically to the built-in array.
+
+ You should generally implement the `copy()` method to return a copy of the
+ receiver.
+
+ Note that `frozenCopy()` will only work if you also implement
+ `Ember.Freezable`.
+
+ @class Copyable
+ @namespace Ember
+ @since Ember 0.9
+*/
+Ember.Copyable = Ember.Mixin.create({
+
+ /**
+ Override to return a copy of the receiver. Default implementation raises
+ an exception.
+
+ @method copy
+ @param {Boolean} deep if `true`, a deep copy of the object should be made
+ @return {Object} copy of receiver
+ */
+ copy: Ember.required(Function),
+
+ /**
+ If the object implements `Ember.Freezable`, then this will return a new
+ copy if the object is not frozen and the receiver if the object is frozen.
+
+ Raises an exception if you try to call this method on a object that does
+ not support freezing.
+
+ You should use this method whenever you want a copy of a freezable object
+ since a freezable object can simply return itself without actually
+ consuming more memory.
+
+ @method frozenCopy
+ @return {Object} copy of receiver or receiver
+ */
+ frozenCopy: function() {
+ if (Ember.Freezable && Ember.Freezable.detect(this)) {
+ return get(this, 'isFrozen') ? this : this.copy().freeze();
+ } else {
+ throw new Ember.Error(Ember.String.fmt("%@ does not support freezing", [this]));
+ }
+ }
+});
+
+})();
+
+
+
+(function() {
+/**
+@module ember
+@submodule ember-runtime
+*/
+
+
+var get = Ember.get, set = Ember.set;
+
+/**
+ The `Ember.Freezable` mixin implements some basic methods for marking an
+ object as frozen. Once an object is frozen it should be read only. No changes
+ may be made the internal state of the object.
+
+ ## Enforcement
+
+ To fully support freezing in your subclass, you must include this mixin and
+ override any method that might alter any property on the object to instead
+ raise an exception. You can check the state of an object by checking the
+ `isFrozen` property.
+
+ Although future versions of JavaScript may support language-level freezing
+ object objects, that is not the case today. Even if an object is freezable,
+ it is still technically possible to modify the object, even though it could
+ break other parts of your application that do not expect a frozen object to
+ change. It is, therefore, very important that you always respect the
+ `isFrozen` property on all freezable objects.
+
+ ## Example Usage
+
+ The example below shows a simple object that implement the `Ember.Freezable`
+ protocol.
+
+ ```javascript
+ Contact = Ember.Object.extend(Ember.Freezable, {
+ firstName: null,
+ lastName: null,
+
+ // swaps the names
+ swapNames: function() {
+ if (this.get('isFrozen')) throw Ember.FROZEN_ERROR;
+ var tmp = this.get('firstName');
+ this.set('firstName', this.get('lastName'));
+ this.set('lastName', tmp);
+ return this;
+ }
+
+ });
+
+ c = Contact.create({ firstName: "John", lastName: "Doe" });
+ c.swapNames(); // returns c
+ c.freeze();
+ c.swapNames(); // EXCEPTION
+ ```
+
+ ## Copying
+
+ Usually the `Ember.Freezable` protocol is implemented in cooperation with the
+ `Ember.Copyable` protocol, which defines a `frozenCopy()` method that will
+ return a frozen object, if the object implements this method as well.
+
+ @class Freezable
+ @namespace Ember
+ @since Ember 0.9
+*/
+Ember.Freezable = Ember.Mixin.create({
+
+ /**
+ Set to `true` when the object is frozen. Use this property to detect
+ whether your object is frozen or not.
+
+ @property isFrozen
+ @type Boolean
+ */
+ isFrozen: false,
+
+ /**
+ Freezes the object. Once this method has been called the object should
+ no longer allow any properties to be edited.
+
+ @method freeze
+ @return {Object} receiver
+ */
+ freeze: function() {
+ if (get(this, 'isFrozen')) return this;
+ set(this, 'isFrozen', true);
+ return this;
+ }
+
+});
+
+Ember.FROZEN_ERROR = "Frozen object cannot be modified.";
+
+})();
+
+
+
+(function() {
+/**
+@module ember
+@submodule ember-runtime
+*/
+
+var forEach = Ember.EnumerableUtils.forEach;
+
+/**
+ This mixin defines the API for modifying generic enumerables. These methods
+ can be applied to an object regardless of whether it is ordered or
+ unordered.
+
+ Note that an Enumerable can change even if it does not implement this mixin.
+ For example, a MappedEnumerable cannot be directly modified but if its
+ underlying enumerable changes, it will change also.
+
+ ## Adding Objects
+
+ To add an object to an enumerable, use the `addObject()` method. This
+ method will only add the object to the enumerable if the object is not
+ already present and is of a type supported by the enumerable.
+
+ ```javascript
+ set.addObject(contact);
+ ```
+
+ ## Removing Objects
+
+ To remove an object from an enumerable, use the `removeObject()` method. This
+ will only remove the object if it is present in the enumerable, otherwise
+ this method has no effect.
+
+ ```javascript
+ set.removeObject(contact);
+ ```
+
+ ## Implementing In Your Own Code
+
+ If you are implementing an object and want to support this API, just include
+ this mixin in your class and implement the required methods. In your unit
+ tests, be sure to apply the Ember.MutableEnumerableTests to your object.
+
+ @class MutableEnumerable
+ @namespace Ember
+ @uses Ember.Enumerable
+*/
+Ember.MutableEnumerable = Ember.Mixin.create(Ember.Enumerable, {
+
+ /**
+ __Required.__ You must implement this method to apply this mixin.
+
+ Attempts to add the passed object to the receiver if the object is not
+ already present in the collection. If the object is present, this method
+ has no effect.
+
+ If the passed object is of a type not supported by the receiver,
+ then this method should raise an exception.
+
+ @method addObject
+ @param {Object} object The object to add to the enumerable.
+ @return {Object} the passed object
+ */
+ addObject: Ember.required(Function),
+
+ /**
+ Adds each object in the passed enumerable to the receiver.
+
+ @method addObjects
+ @param {Ember.Enumerable} objects the objects to add.
+ @return {Object} receiver
+ */
+ addObjects: function(objects) {
+ Ember.beginPropertyChanges(this);
+ forEach(objects, function(obj) { this.addObject(obj); }, this);
+ Ember.endPropertyChanges(this);
+ return this;
+ },
+
+ /**
+ __Required.__ You must implement this method to apply this mixin.
+
+ Attempts to remove the passed object from the receiver collection if the
+ object is present in the collection. If the object is not present,
+ this method has no effect.
+
+ If the passed object is of a type not supported by the receiver,
+ then this method should raise an exception.
+
+ @method removeObject
+ @param {Object} object The object to remove from the enumerable.
+ @return {Object} the passed object
+ */
+ removeObject: Ember.required(Function),
+
+
+ /**
+ Removes each object in the passed enumerable from the receiver.
+
+ @method removeObjects
+ @param {Ember.Enumerable} objects the objects to remove
+ @return {Object} receiver
+ */
+ removeObjects: function(objects) {
+ Ember.beginPropertyChanges(this);
+ forEach(objects, function(obj) { this.removeObject(obj); }, this);
+ Ember.endPropertyChanges(this);
+ return this;
+ }
+
+});
+
+})();
+
+
+
+(function() {
+/**
+@module ember
+@submodule ember-runtime
+*/
+// ..........................................................
+// CONSTANTS
+//
+
+var OUT_OF_RANGE_EXCEPTION = "Index out of range" ;
+var EMPTY = [];
+
+// ..........................................................
+// HELPERS
+//
+
+var get = Ember.get, set = Ember.set;
+
+/**
+ This mixin defines the API for modifying array-like objects. These methods
+ can be applied only to a collection that keeps its items in an ordered set.
+
+ Note that an Array can change even if it does not implement this mixin.
+ For example, one might implement a SparseArray that cannot be directly
+ modified, but if its underlying enumerable changes, it will change also.
+
+ @class MutableArray
+ @namespace Ember
+ @uses Ember.Array
+ @uses Ember.MutableEnumerable
+*/
+Ember.MutableArray = Ember.Mixin.create(Ember.Array, Ember.MutableEnumerable, {
+
+ /**
+ __Required.__ You must implement this method to apply this mixin.
+
+ This is one of the primitives you must implement to support `Ember.Array`.
+ You should replace amt objects started at idx with the objects in the
+ passed array. You should also call `this.enumerableContentDidChange()`
+
+ @method replace
+ @param {Number} idx Starting index in the array to replace. If
+ idx >= length, then append to the end of the array.
+ @param {Number} amt Number of elements that should be removed from
+ the array, starting at *idx*.
+ @param {Array} objects An array of zero or more objects that should be
+ inserted into the array at *idx*
+ */
+ replace: Ember.required(),
+
+ /**
+ Remove all elements from self. This is useful if you
+ want to reuse an existing array without having to recreate it.
+
+ ```javascript
+ var colors = ["red", "green", "blue"];
+ color.length(); // 3
+ colors.clear(); // []
+ colors.length(); // 0
+ ```
+
+ @method clear
+ @return {Ember.Array} An empty Array.
+ */
+ clear: function () {
+ var len = get(this, 'length');
+ if (len === 0) return this;
+ this.replace(0, len, EMPTY);
+ return this;
+ },
+
+ /**
+ This will use the primitive `replace()` method to insert an object at the
+ specified index.
+
+ ```javascript
+ var colors = ["red", "green", "blue"];
+ colors.insertAt(2, "yellow"); // ["red", "green", "yellow", "blue"]
+ colors.insertAt(5, "orange"); // Error: Index out of range
+ ```
+
+ @method insertAt
+ @param {Number} idx index of insert the object at.
+ @param {Object} object object to insert
+ @return this
+ */
+ insertAt: function(idx, object) {
+ if (idx > get(this, 'length')) throw new Ember.Error(OUT_OF_RANGE_EXCEPTION) ;
+ this.replace(idx, 0, [object]) ;
+ return this ;
+ },
+
+ /**
+ Remove an object at the specified index using the `replace()` primitive
+ method. You can pass either a single index, or a start and a length.
+
+ If you pass a start and length that is beyond the
+ length this method will throw an `OUT_OF_RANGE_EXCEPTION`.
+
+ ```javascript
+ var colors = ["red", "green", "blue", "yellow", "orange"];
+ colors.removeAt(0); // ["green", "blue", "yellow", "orange"]
+ colors.removeAt(2, 2); // ["green", "blue"]
+ colors.removeAt(4, 2); // Error: Index out of range
+ ```
+
+ @method removeAt
+ @param {Number} start index, start of range
+ @param {Number} len length of passing range
+ @return {Object} receiver
+ */
+ removeAt: function(start, len) {
+ if ('number' === typeof start) {
+
+ if ((start < 0) || (start >= get(this, 'length'))) {
+ throw new Ember.Error(OUT_OF_RANGE_EXCEPTION);
+ }
+
+ // fast case
+ if (len === undefined) len = 1;
+ this.replace(start, len, EMPTY);
+ }
+
+ return this ;
+ },
+
+ /**
+ Push the object onto the end of the array. Works just like `push()` but it
+ is KVO-compliant.
+
+ ```javascript
+ var colors = ["red", "green"];
+ colors.pushObject("black"); // ["red", "green", "black"]
+ colors.pushObject(["yellow"]); // ["red", "green", ["yellow"]]
+ ```
+
+ @method pushObject
+ @param {*} obj object to push
+ @return The same obj passed as param
+ */
+ pushObject: function(obj) {
+ this.insertAt(get(this, 'length'), obj) ;
+ return obj;
+ },
+
+ /**
+ Add the objects in the passed numerable to the end of the array. Defers
+ notifying observers of the change until all objects are added.
+
+ ```javascript
+ var colors = ["red"];
+ colors.pushObjects(["yellow", "orange"]); // ["red", "yellow", "orange"]
+ ```
+
+ @method pushObjects
+ @param {Ember.Enumerable} objects the objects to add
+ @return {Ember.Array} receiver
+ */
+ pushObjects: function(objects) {
+ if (!(Ember.Enumerable.detect(objects) || Ember.isArray(objects))) {
+ throw new TypeError("Must pass Ember.Enumerable to Ember.MutableArray#pushObjects");
+ }
+ this.replace(get(this, 'length'), 0, objects);
+ return this;
+ },
+
+ /**
+ Pop object from array or nil if none are left. Works just like `pop()` but
+ it is KVO-compliant.
+
+ ```javascript
+ var colors = ["red", "green", "blue"];
+ colors.popObject(); // "blue"
+ console.log(colors); // ["red", "green"]
+ ```
+
+ @method popObject
+ @return object
+ */
+ popObject: function() {
+ var len = get(this, 'length') ;
+ if (len === 0) return null ;
+
+ var ret = this.objectAt(len-1) ;
+ this.removeAt(len-1, 1) ;
+ return ret ;
+ },
+
+ /**
+ Shift an object from start of array or nil if none are left. Works just
+ like `shift()` but it is KVO-compliant.
+
+ ```javascript
+ var colors = ["red", "green", "blue"];
+ colors.shiftObject(); // "red"
+ console.log(colors); // ["green", "blue"]
+ ```
+
+ @method shiftObject
+ @return object
+ */
+ shiftObject: function() {
+ if (get(this, 'length') === 0) return null ;
+ var ret = this.objectAt(0) ;
+ this.removeAt(0) ;
+ return ret ;
+ },
+
+ /**
+ Unshift an object to start of array. Works just like `unshift()` but it is
+ KVO-compliant.
+
+ ```javascript
+ var colors = ["red"];
+ colors.unshiftObject("yellow"); // ["yellow", "red"]
+ colors.unshiftObject(["black"]); // [["black"], "yellow", "red"]
+ ```
+
+ @method unshiftObject
+ @param {*} obj object to unshift
+ @return The same obj passed as param
+ */
+ unshiftObject: function(obj) {
+ this.insertAt(0, obj) ;
+ return obj ;
+ },
+
+ /**
+ Adds the named objects to the beginning of the array. Defers notifying
+ observers until all objects have been added.
+
+ ```javascript
+ var colors = ["red"];
+ colors.unshiftObjects(["black", "white"]); // ["black", "white", "red"]
+ colors.unshiftObjects("yellow"); // Type Error: 'undefined' is not a function
+ ```
+
+ @method unshiftObjects
+ @param {Ember.Enumerable} objects the objects to add
+ @return {Ember.Array} receiver
+ */
+ unshiftObjects: function(objects) {
+ this.replace(0, 0, objects);
+ return this;
+ },
+
+ /**
+ Reverse objects in the array. Works just like `reverse()` but it is
+ KVO-compliant.
+
+ @method reverseObjects
+ @return {Ember.Array} receiver
+ */
+ reverseObjects: function() {
+ var len = get(this, 'length');
+ if (len === 0) return this;
+ var objects = this.toArray().reverse();
+ this.replace(0, len, objects);
+ return this;
+ },
+
+ /**
+ Replace all the the receiver's content with content of the argument.
+ If argument is an empty array receiver will be cleared.
+
+ ```javascript
+ var colors = ["red", "green", "blue"];
+ colors.setObjects(["black", "white"]); // ["black", "white"]
+ colors.setObjects([]); // []
+ ```
+
+ @method setObjects
+ @param {Ember.Array} objects array whose content will be used for replacing
+ the content of the receiver
+ @return {Ember.Array} receiver with the new content
+ */
+ setObjects: function(objects) {
+ if (objects.length === 0) return this.clear();
+
+ var len = get(this, 'length');
+ this.replace(0, len, objects);
+ return this;
+ },
+
+ // ..........................................................
+ // IMPLEMENT Ember.MutableEnumerable
+ //
+
+ removeObject: function(obj) {
+ var loc = get(this, 'length') || 0;
+ while(--loc >= 0) {
+ var curObject = this.objectAt(loc) ;
+ if (curObject === obj) this.removeAt(loc) ;
+ }
+ return this ;
+ },
+
+ addObject: function(obj) {
+ if (!this.contains(obj)) this.pushObject(obj);
+ return this ;
+ }
+
+});
+
+})();
+
+
+
+(function() {
+/**
+@module ember
+@submodule ember-runtime
+*/
+
+var get = Ember.get, set = Ember.set;
+
+/**
+`Ember.TargetActionSupport` is a mixin that can be included in a class
+to add a `triggerAction` method with semantics similar to the Handlebars
+`{{action}}` helper. In normal Ember usage, the `{{action}}` helper is
+usually the best choice. This mixin is most often useful when you are
+doing more complex event handling in View objects.
+
+See also `Ember.ViewTargetActionSupport`, which has
+view-aware defaults for target and actionContext.
+
+@class TargetActionSupport
+@namespace Ember
+@extends Ember.Mixin
+*/
+Ember.TargetActionSupport = Ember.Mixin.create({
+ target: null,
+ action: null,
+ actionContext: null,
+
+ targetObject: Ember.computed(function() {
+ var target = get(this, 'target');
+
+ if (Ember.typeOf(target) === "string") {
+ var value = get(this, target);
+ if (value === undefined) { value = get(Ember.lookup, target); }
+ return value;
+ } else {
+ return target;
+ }
+ }).property('target'),
+
+ actionContextObject: Ember.computed(function() {
+ var actionContext = get(this, 'actionContext');
+
+ if (Ember.typeOf(actionContext) === "string") {
+ var value = get(this, actionContext);
+ if (value === undefined) { value = get(Ember.lookup, actionContext); }
+ return value;
+ } else {
+ return actionContext;
+ }
+ }).property('actionContext'),
+
+ /**
+ Send an `action` with an `actionContext` to a `target`. The action, actionContext
+ and target will be retrieved from properties of the object. For example:
+
+ ```javascript
+ App.SaveButtonView = Ember.View.extend(Ember.TargetActionSupport, {
+ target: Ember.computed.alias('controller'),
+ action: 'save',
+ actionContext: Ember.computed.alias('context'),
+ click: function() {
+ this.triggerAction(); // Sends the `save` action, along with the current context
+ // to the current controller
+ }
+ });
+ ```
+
+ The `target`, `action`, and `actionContext` can be provided as properties of
+ an optional object argument to `triggerAction` as well.
+
+ ```javascript
+ App.SaveButtonView = Ember.View.extend(Ember.TargetActionSupport, {
+ click: function() {
+ this.triggerAction({
+ action: 'save',
+ target: this.get('controller'),
+ actionContext: this.get('context'),
+ }); // Sends the `save` action, along with the current context
+ // to the current controller
+ }
+ });
+ ```
+
+ The `actionContext` defaults to the object you mixing `TargetActionSupport` into.
+ But `target` and `action` must be specified either as properties or with the argument
+ to `triggerAction`, or a combination:
+
+ ```javascript
+ App.SaveButtonView = Ember.View.extend(Ember.TargetActionSupport, {
+ target: Ember.computed.alias('controller'),
+ click: function() {
+ this.triggerAction({
+ action: 'save'
+ }); // Sends the `save` action, along with a reference to `this`,
+ // to the current controller
+ }
+ });
+ ```
+
+ @method triggerAction
+ @param opts {Hash} (optional, with the optional keys action, target and/or actionContext)
+ @return {Boolean} true if the action was sent successfully and did not return false
+ */
+ triggerAction: function(opts) {
+ opts = opts || {};
+ var action = opts.action || get(this, 'action'),
+ target = opts.target || get(this, 'targetObject'),
+ actionContext = opts.actionContext;
+
+ function args(options, actionName) {
+ var ret = [];
+ if (actionName) { ret.push(actionName); }
+
+ return ret.concat(options);
+ }
+
+ if (typeof actionContext === 'undefined') {
+ actionContext = get(this, 'actionContextObject') || this;
+ }
+
+ if (target && action) {
+ var ret;
+
+ if (target.send) {
+ ret = target.send.apply(target, args(actionContext, action));
+ } else {
+ Ember.assert("The action '" + action + "' did not exist on " + target, typeof target[action] === 'function');
+ ret = target[action].apply(target, args(actionContext));
+ }
+
+ if (ret !== false) ret = true;
+
+ return ret;
+ } else {
+ return false;
+ }
+ }
+});
+
+})();
+
+
+
+(function() {
+/**
+@module ember
+@submodule ember-runtime
+*/
+
+/**
+ This mixin allows for Ember objects to subscribe to and emit events.
+
+ ```javascript
+ App.Person = Ember.Object.extend(Ember.Evented, {
+ greet: function() {
+ // ...
+ this.trigger('greet');
+ }
+ });
+
+ var person = App.Person.create();
+
+ person.on('greet', function() {
+ console.log('Our person has greeted');
+ });
+
+ person.greet();
+
+ // outputs: 'Our person has greeted'
+ ```
+
+ You can also chain multiple event subscriptions:
+
+ ```javascript
+ person.on('greet', function() {
+ console.log('Our person has greeted');
+ }).one('greet', function() {
+ console.log('Offer one-time special');
+ }).off('event', this, forgetThis);
+ ```
+
+ @class Evented
+ @namespace Ember
+ */
+Ember.Evented = Ember.Mixin.create({
+
+ /**
+ Subscribes to a named event with given function.
+
+ ```javascript
+ person.on('didLoad', function() {
+ // fired once the person has loaded
+ });
+ ```
+
+ An optional target can be passed in as the 2nd argument that will
+ be set as the "this" for the callback. This is a good way to give your
+ function access to the object triggering the event. When the target
+ parameter is used the callback becomes the third argument.
+
+ @method on
+ @param {String} name The name of the event
+ @param {Object} [target] The "this" binding for the callback
+ @param {Function} method The callback to execute
+ @return this
+ */
+ on: function(name, target, method) {
+ Ember.addListener(this, name, target, method);
+ return this;
+ },
+
+ /**
+ Subscribes a function to a named event and then cancels the subscription
+ after the first time the event is triggered. It is good to use ``one`` when
+ you only care about the first time an event has taken place.
+
+ This function takes an optional 2nd argument that will become the "this"
+ value for the callback. If this argument is passed then the 3rd argument
+ becomes the function.
+
+ @method one
+ @param {String} name The name of the event
+ @param {Object} [target] The "this" binding for the callback
+ @param {Function} method The callback to execute
+ @return this
+ */
+ one: function(name, target, method) {
+ if (!method) {
+ method = target;
+ target = null;
+ }
+
+ Ember.addListener(this, name, target, method, true);
+ return this;
+ },
+
+ /**
+ Triggers a named event for the object. Any additional arguments
+ will be passed as parameters to the functions that are subscribed to the
+ event.
+
+ ```javascript
+ person.on('didEat', function(food) {
+ console.log('person ate some ' + food);
+ });
+
+ person.trigger('didEat', 'broccoli');
+
+ // outputs: person ate some broccoli
+ ```
+ @method trigger
+ @param {String} name The name of the event
+ @param {Object...} args Optional arguments to pass on
+ */
+ trigger: function(name) {
+ var args = [], i, l;
+ for (i = 1, l = arguments.length; i < l; i++) {
+ args.push(arguments[i]);
+ }
+ Ember.sendEvent(this, name, args);
+ },
+
+ /**
+ Cancels subscription for given name, target, and method.
+
+ @method off
+ @param {String} name The name of the event
+ @param {Object} target The target of the subscription
+ @param {Function} method The function of the subscription
+ @return this
+ */
+ off: function(name, target, method) {
+ Ember.removeListener(this, name, target, method);
+ return this;
+ },
+
+ /**
+ Checks to see if object has any subscriptions for named event.
+
+ @method has
+ @param {String} name The name of the event
+ @return {Boolean} does the object have a subscription for event
+ */
+ has: function(name) {
+ return Ember.hasListeners(this, name);
+ }
+});
+
+})();
+
+
+
+(function() {
+var RSVP = requireModule("rsvp");
+
+RSVP.configure('async', function(callback, promise) {
+ Ember.run.schedule('actions', promise, callback, promise);
+});
+
+RSVP.Promise.prototype.fail = function(callback, label){
+ Ember.deprecate('RSVP.Promise.fail has been renamed as RSVP.Promise.catch');
+ return this['catch'](callback, label);
+};
+
+/**
+@module ember
+@submodule ember-runtime
+*/
+
+var get = Ember.get;
+
+/**
+ @class Deferred
+ @namespace Ember
+ */
+Ember.DeferredMixin = Ember.Mixin.create({
+ /**
+ Add handlers to be called when the Deferred object is resolved or rejected.
+
+ @method then
+ @param {Function} resolve a callback function to be called when done
+ @param {Function} reject a callback function to be called when failed
+ */
+ then: function(resolve, reject, label) {
+ var deferred, promise, entity;
+
+ entity = this;
+ deferred = get(this, '_deferred');
+ promise = deferred.promise;
+
+ function fulfillmentHandler(fulfillment) {
+ if (fulfillment === promise) {
+ return resolve(entity);
+ } else {
+ return resolve(fulfillment);
+ }
+ }
+
+ return promise.then(resolve && fulfillmentHandler, reject, label);
+ },
+
+ /**
+ Resolve a Deferred object and call any `doneCallbacks` with the given args.
+
+ @method resolve
+ */
+ resolve: function(value) {
+ var deferred, promise;
+
+ deferred = get(this, '_deferred');
+ promise = deferred.promise;
+
+ if (value === this) {
+ deferred.resolve(promise);
+ } else {
+ deferred.resolve(value);
+ }
+ },
+
+ /**
+ Reject a Deferred object and call any `failCallbacks` with the given args.
+
+ @method reject
+ */
+ reject: function(value) {
+ get(this, '_deferred').reject(value);
+ },
+
+ _deferred: Ember.computed(function() {
+ return RSVP.defer('Ember: DeferredMixin - ' + this);
+ })
+});
+
+
+})();
+
+
+
+(function() {
+/**
+@module ember
+@submodule ember-runtime
+*/
+
+var get = Ember.get, typeOf = Ember.typeOf;
+
+/**
+ The `Ember.ActionHandler` mixin implements support for moving an `actions`
+ property to an `_actions` property at extend time, and adding `_actions`
+ to the object's mergedProperties list.
+
+ `Ember.ActionHandler` is used internally by Ember in `Ember.View`,
+ `Ember.Controller`, and `Ember.Route`.
+
+ @class ActionHandler
+ @namespace Ember
+*/
+Ember.ActionHandler = Ember.Mixin.create({
+ mergedProperties: ['_actions'],
+
+ /**
+ The collection of functions, keyed by name, available on this
+ `ActionHandler` as action targets.
+
+ These functions will be invoked when a matching `{{action}}` is triggered
+ from within a template and the application's current route is this route.
+
+ Actions can also be invoked from other parts of your application
+ via `ActionHandler#send`.
+
+ The `actions` hash will inherit action handlers from
+ the `actions` hash defined on extended parent classes
+ or mixins rather than just replace the entire hash, e.g.:
+
+ ```js
+ App.CanDisplayBanner = Ember.Mixin.create({
+ actions: {
+ displayBanner: function(msg) {
+ // ...
+ }
+ }
+ });
+
+ App.WelcomeRoute = Ember.Route.extend(App.CanDisplayBanner, {
+ actions: {
+ playMusic: function() {
+ // ...
+ }
+ }
+ });
+
+ // `WelcomeRoute`, when active, will be able to respond
+ // to both actions, since the actions hash is merged rather
+ // then replaced when extending mixins / parent classes.
+ this.send('displayBanner');
+ this.send('playMusic');
+ ```
+
+ Within a Controller, Route, View or Component's action handler,
+ the value of the `this` context is the Controller, Route, View or
+ Component object:
+
+ ```js
+ App.SongRoute = Ember.Route.extend({
+ actions: {
+ myAction: function() {
+ this.controllerFor("song");
+ this.transitionTo("other.route");
+ ...
+ }
+ }
+ });
+ ```
+
+ It is also possible to call `this._super()` from within an
+ action handler if it overrides a handler defined on a parent
+ class or mixin:
+
+ Take for example the following routes:
+
+ ```js
+ App.DebugRoute = Ember.Mixin.create({
+ actions: {
+ debugRouteInformation: function() {
+ console.debug("trololo");
+ }
+ }
+ });
+
+ App.AnnoyingDebugRoute = Ember.Route.extend(App.DebugRoute, {
+ actions: {
+ debugRouteInformation: function() {
+ // also call the debugRouteInformation of mixed in App.DebugRoute
+ this._super();
+
+ // show additional annoyance
+ window.alert(...);
+ }
+ }
+ });
+ ```
+
+ ## Bubbling
+
+ By default, an action will stop bubbling once a handler defined
+ on the `actions` hash handles it. To continue bubbling the action,
+ you must return `true` from the handler:
+
+ ```js
+ App.Router.map(function() {
+ this.resource("album", function() {
+ this.route("song");
+ });
+ });
+
+ App.AlbumRoute = Ember.Route.extend({
+ actions: {
+ startPlaying: function() {
+ }
+ }
+ });
+
+ App.AlbumSongRoute = Ember.Route.extend({
+ actions: {
+ startPlaying: function() {
+ // ...
+
+ if (actionShouldAlsoBeTriggeredOnParentRoute) {
+ return true;
+ }
+ }
+ }
+ });
+ ```
+
+ @property actions
+ @type Hash
+ @default null
+ */
+
+ /**
+ Moves `actions` to `_actions` at extend time. Note that this currently
+ modifies the mixin themselves, which is technically dubious but
+ is practically of little consequence. This may change in the future.
+
+ @private
+ @method willMergeMixin
+ */
+ willMergeMixin: function(props) {
+ var hashName;
+
+ if (!props._actions) {
+ Ember.assert("'actions' should not be a function", typeof(props.actions) !== 'function');
+
+ if (typeOf(props.actions) === 'object') {
+ hashName = 'actions';
+ } else if (typeOf(props.events) === 'object') {
+ Ember.deprecate('Action handlers contained in an `events` object are deprecated in favor of putting them in an `actions` object', false);
+ hashName = 'events';
+ }
+
+ if (hashName) {
+ props._actions = Ember.merge(props._actions || {}, props[hashName]);
+ }
+
+ delete props[hashName];
+ }
+ },
+
+ /**
+ Triggers a named action on the `ActionHandler`. Any parameters
+ supplied after the `actionName` string will be passed as arguments
+ to the action target function.
+
+ If the `ActionHandler` has its `target` property set, actions may
+ bubble to the `target`. Bubbling happens when an `actionName` can
+ not be found in the `ActionHandler`'s `actions` hash or if the
+ action target function returns `true`.
+
+ Example
+
+ ```js
+ App.WelcomeRoute = Ember.Route.extend({
+ actions: {
+ playTheme: function() {
+ this.send('playMusic', 'theme.mp3');
+ },
+ playMusic: function(track) {
+ // ...
+ }
+ }
+ });
+ ```
+
+ @method send
+ @param {String} actionName The action to trigger
+ @param {*} context a context to send with the action
+ */
+ send: function(actionName) {
+ var args = [].slice.call(arguments, 1), target;
+
+ if (this._actions && this._actions[actionName]) {
+ if (this._actions[actionName].apply(this, args) === true) {
+ // handler returned true, so this action will bubble
+ } else {
+ return;
+ }
+ } else if (this.deprecatedSend && this.deprecatedSendHandles && this.deprecatedSendHandles(actionName)) {
+ if (this.deprecatedSend.apply(this, [].slice.call(arguments)) === true) {
+ // handler return true, so this action will bubble
+ } else {
+ return;
+ }
+ }
+
+ if (target = get(this, 'target')) {
+ Ember.assert("The `target` for " + this + " (" + target + ") does not have a `send` method", typeof target.send === 'function');
+ target.send.apply(target, arguments);
+ }
+ }
+
+});
+
+})();
+
+
+
+(function() {
+var set = Ember.set, get = Ember.get,
+ not = Ember.computed.not,
+ or = Ember.computed.or;
+
+/**
+ @module ember
+ @submodule ember-runtime
+ */
+
+function tap(proxy, promise) {
+ return promise.then(function(value) {
+ set(proxy, 'isFulfilled', true);
+ set(proxy, 'content', value);
+ return value;
+ }, function(reason) {
+ set(proxy, 'isRejected', true);
+ set(proxy, 'reason', reason);
+ throw reason;
+ }, "Ember: PromiseProxy");
+}
+
+/**
+ A low level mixin making ObjectProxy, ObjectController or ArrayController's promise aware.
+
+ ```javascript
+ var ObjectPromiseController = Ember.ObjectController.extend(Ember.PromiseProxyMixin);
+
+ var controller = ObjectPromiseController.create({
+ promise: $.getJSON('/some/remote/data.json')
+ });
+
+ controller.then(function(json){
+ // the json
+ }, function(reason) {
+ // the reason why you have no json
+ });
+ ```
+
+ the controller has bindable attributes which
+ track the promises life cycle
+
+ ```javascript
+ controller.get('isPending') //=> true
+ controller.get('isSettled') //=> false
+ controller.get('isRejected') //=> false
+ controller.get('isFulfilled') //=> false
+ ```
+
+ When the the $.getJSON completes, and the promise is fulfilled
+ with json, the life cycle attributes will update accordingly.
+
+ ```javascript
+ controller.get('isPending') //=> false
+ controller.get('isSettled') //=> true
+ controller.get('isRejected') //=> false
+ controller.get('isFulfilled') //=> true
+ ```
+
+ As the controller is an ObjectController, and the json now its content,
+ all the json properties will be available directly from the controller.
+
+ ```javascript
+ // Assuming the following json:
+ {
+ firstName: 'Stefan',
+ lastName: 'Penner'
+ }
+
+ // both properties will accessible on the controller
+ controller.get('firstName') //=> 'Stefan'
+ controller.get('lastName') //=> 'Penner'
+ ```
+
+ If the controller is backing a template, the attributes are
+ bindable from within that template
+
+ ```handlebars
+ {{#if isPending}}
+ loading...
+ {{else}}
+ firstName: {{firstName}}
+ lastName: {{lastName}}
+ {{/if}}
+ ```
+ @class Ember.PromiseProxyMixin
+*/
+Ember.PromiseProxyMixin = Ember.Mixin.create({
+ /**
+ If the proxied promise is rejected this will contain the reason
+ provided.
+
+ @property reason
+ @default null
+ */
+ reason: null,
+
+ /**
+ Once the proxied promise has settled this will become `false`.
+
+ @property isPending
+ @default true
+ */
+ isPending: not('isSettled').readOnly(),
+
+ /**
+ Once the proxied promise has settled this will become `true`.
+
+ @property isSettled
+ @default false
+ */
+ isSettled: or('isRejected', 'isFulfilled').readOnly(),
+
+ /**
+ Will become `true` if the proxied promise is rejected.
+
+ @property isRejected
+ @default false
+ */
+ isRejected: false,
+
+ /**
+ Will become `true` if the proxied promise is fulfilled.
+
+ @property isFullfilled
+ @default false
+ */
+ isFulfilled: false,
+
+ /**
+ The promise whose fulfillment value is being proxied by this object.
+
+ This property must be specified upon creation, and should not be
+ changed once created.
+
+ Example:
+
+ ```javascript
+ Ember.ObjectController.extend(Ember.PromiseProxyMixin).create({
+ promise:
+ });
+ ```
+
+ @property promise
+ */
+ promise: Ember.computed(function(key, promise) {
+ if (arguments.length === 2) {
+ return tap(this, promise);
+ } else {
+ throw new Ember.Error("PromiseProxy's promise must be set");
+ }
+ }),
+
+ /**
+ An alias to the proxied promise's `then`.
+
+ See RSVP.Promise.then.
+
+ @method then
+ @param {Function} callback
+ @return {RSVP.Promise}
+ */
+ then: promiseAlias('then'),
+
+ /**
+ An alias to the proxied promise's `catch`.
+
+ See RSVP.Promise.catch.
+
+ @method catch
+ @param {Function} callback
+ @return {RSVP.Promise}
+ */
+ 'catch': promiseAlias('catch'),
+
+ /**
+ An alias to the proxied promise's `finally`.
+
+ See RSVP.Promise.finally.
+
+ @method finally
+ @param {Function} callback
+ @return {RSVP.Promise}
+ */
+ 'finally': promiseAlias('finally')
+
+});
+
+function promiseAlias(name) {
+ return function () {
+ var promise = get(this, 'promise');
+ return promise[name].apply(promise, arguments);
+ };
+}
+
+})();
+
+
+
+(function() {
+
+})();
+
+
+
+(function() {
+var get = Ember.get,
+ forEach = Ember.EnumerableUtils.forEach,
+ RETAIN = 'r',
+ INSERT = 'i',
+ DELETE = 'd';
+
+/**
+ An `Ember.TrackedArray` tracks array operations. It's useful when you want to
+ lazily compute the indexes of items in an array after they've been shifted by
+ subsequent operations.
+
+ @class TrackedArray
+ @namespace Ember
+ @param {array} [items=[]] The array to be tracked. This is used just to get
+ the initial items for the starting state of retain:n.
+*/
+Ember.TrackedArray = function (items) {
+ if (arguments.length < 1) { items = []; }
+
+ var length = get(items, 'length');
+
+ if (length) {
+ this._operations = [new ArrayOperation(RETAIN, length, items)];
+ } else {
+ this._operations = [];
+ }
+};
+
+Ember.TrackedArray.RETAIN = RETAIN;
+Ember.TrackedArray.INSERT = INSERT;
+Ember.TrackedArray.DELETE = DELETE;
+
+Ember.TrackedArray.prototype = {
+
+ /**
+ Track that `newItems` were added to the tracked array at `index`.
+
+ @method addItems
+ @param index
+ @param newItems
+ */
+ addItems: function (index, newItems) {
+ var count = get(newItems, 'length');
+ if (count < 1) { return; }
+
+ var match = this._findArrayOperation(index),
+ arrayOperation = match.operation,
+ arrayOperationIndex = match.index,
+ arrayOperationRangeStart = match.rangeStart,
+ composeIndex,
+ splitIndex,
+ splitItems,
+ splitArrayOperation,
+ newArrayOperation;
+
+ newArrayOperation = new ArrayOperation(INSERT, count, newItems);
+
+ if (arrayOperation) {
+ if (!match.split) {
+ // insert left of arrayOperation
+ this._operations.splice(arrayOperationIndex, 0, newArrayOperation);
+ composeIndex = arrayOperationIndex;
+ } else {
+ this._split(arrayOperationIndex, index - arrayOperationRangeStart, newArrayOperation);
+ composeIndex = arrayOperationIndex + 1;
+ }
+ } else {
+ // insert at end
+ this._operations.push(newArrayOperation);
+ composeIndex = arrayOperationIndex;
+ }
+
+ this._composeInsert(composeIndex);
+ },
+
+ /**
+ Track that `count` items were removed at `index`.
+
+ @method removeItems
+ @param index
+ @param count
+ */
+ removeItems: function (index, count) {
+ if (count < 1) { return; }
+
+ var match = this._findArrayOperation(index),
+ arrayOperation = match.operation,
+ arrayOperationIndex = match.index,
+ arrayOperationRangeStart = match.rangeStart,
+ newArrayOperation,
+ composeIndex;
+
+ newArrayOperation = new ArrayOperation(DELETE, count);
+ if (!match.split) {
+ // insert left of arrayOperation
+ this._operations.splice(arrayOperationIndex, 0, newArrayOperation);
+ composeIndex = arrayOperationIndex;
+ } else {
+ this._split(arrayOperationIndex, index - arrayOperationRangeStart, newArrayOperation);
+ composeIndex = arrayOperationIndex + 1;
+ }
+
+ return this._composeDelete(composeIndex);
+ },
+
+ /**
+ Apply all operations, reducing them to retain:n, for `n`, the number of
+ items in the array.
+
+ `callback` will be called for each operation and will be passed the following arguments:
+
+ * {array} items The items for the given operation
+ * {number} offset The computed offset of the items, ie the index in the
+ array of the first item for this operation.
+ * {string} operation The type of the operation. One of
+ `Ember.TrackedArray.{RETAIN, DELETE, INSERT}`
+
+ @method apply
+ @param {function} callback
+ */
+ apply: function (callback) {
+ var items = [],
+ offset = 0;
+
+ forEach(this._operations, function (arrayOperation) {
+ callback(arrayOperation.items, offset, arrayOperation.type);
+
+ if (arrayOperation.type !== DELETE) {
+ offset += arrayOperation.count;
+ items = items.concat(arrayOperation.items);
+ }
+ });
+
+ this._operations = [new ArrayOperation(RETAIN, items.length, items)];
+ },
+
+ /**
+ Return an `ArrayOperationMatch` for the operation that contains the item at `index`.
+
+ @method _findArrayOperation
+
+ @param {number} index the index of the item whose operation information
+ should be returned.
+ @private
+ */
+ _findArrayOperation: function (index) {
+ var arrayOperationIndex,
+ len,
+ split = false,
+ arrayOperation,
+ arrayOperationRangeStart,
+ arrayOperationRangeEnd;
+
+ // OPTIMIZE: we could search these faster if we kept a balanced tree.
+ // find leftmost arrayOperation to the right of `index`
+ for (arrayOperationIndex = arrayOperationRangeStart = 0, len = this._operations.length; arrayOperationIndex < len; ++arrayOperationIndex) {
+ arrayOperation = this._operations[arrayOperationIndex];
+
+ if (arrayOperation.type === DELETE) { continue; }
+
+ arrayOperationRangeEnd = arrayOperationRangeStart + arrayOperation.count - 1;
+
+ if (index === arrayOperationRangeStart) {
+ break;
+ } else if (index > arrayOperationRangeStart && index <= arrayOperationRangeEnd) {
+ split = true;
+ break;
+ } else {
+ arrayOperationRangeStart = arrayOperationRangeEnd + 1;
+ }
+ }
+
+ return new ArrayOperationMatch(arrayOperation, arrayOperationIndex, split, arrayOperationRangeStart);
+ },
+
+ _split: function (arrayOperationIndex, splitIndex, newArrayOperation) {
+ var arrayOperation = this._operations[arrayOperationIndex],
+ splitItems = arrayOperation.items.slice(splitIndex),
+ splitArrayOperation = new ArrayOperation(arrayOperation.type, splitItems.length, splitItems);
+
+ // truncate LHS
+ arrayOperation.count = splitIndex;
+ arrayOperation.items = arrayOperation.items.slice(0, splitIndex);
+
+ this._operations.splice(arrayOperationIndex + 1, 0, newArrayOperation, splitArrayOperation);
+ },
+
+ // see SubArray for a better implementation.
+ _composeInsert: function (index) {
+ var newArrayOperation = this._operations[index],
+ leftArrayOperation = this._operations[index-1], // may be undefined
+ rightArrayOperation = this._operations[index+1], // may be undefined
+ leftOp = leftArrayOperation && leftArrayOperation.type,
+ rightOp = rightArrayOperation && rightArrayOperation.type;
+
+ if (leftOp === INSERT) {
+ // merge left
+ leftArrayOperation.count += newArrayOperation.count;
+ leftArrayOperation.items = leftArrayOperation.items.concat(newArrayOperation.items);
+
+ if (rightOp === INSERT) {
+ // also merge right (we have split an insert with an insert)
+ leftArrayOperation.count += rightArrayOperation.count;
+ leftArrayOperation.items = leftArrayOperation.items.concat(rightArrayOperation.items);
+ this._operations.splice(index, 2);
+ } else {
+ // only merge left
+ this._operations.splice(index, 1);
+ }
+ } else if (rightOp === INSERT) {
+ // merge right
+ newArrayOperation.count += rightArrayOperation.count;
+ newArrayOperation.items = newArrayOperation.items.concat(rightArrayOperation.items);
+ this._operations.splice(index + 1, 1);
+ }
+ },
+
+ _composeDelete: function (index) {
+ var arrayOperation = this._operations[index],
+ deletesToGo = arrayOperation.count,
+ leftArrayOperation = this._operations[index-1], // may be undefined
+ leftOp = leftArrayOperation && leftArrayOperation.type,
+ nextArrayOperation,
+ nextOp,
+ nextCount,
+ removeNewAndNextOp = false,
+ removedItems = [];
+
+ if (leftOp === DELETE) {
+ arrayOperation = leftArrayOperation;
+ index -= 1;
+ }
+
+ for (var i = index + 1; deletesToGo > 0; ++i) {
+ nextArrayOperation = this._operations[i];
+ nextOp = nextArrayOperation.type;
+ nextCount = nextArrayOperation.count;
+
+ if (nextOp === DELETE) {
+ arrayOperation.count += nextCount;
+ continue;
+ }
+
+ if (nextCount > deletesToGo) {
+ // d:2 {r,i}:5 we reduce the retain or insert, but it stays
+ removedItems = removedItems.concat(nextArrayOperation.items.splice(0, deletesToGo));
+ nextArrayOperation.count -= deletesToGo;
+
+ // In the case where we truncate the last arrayOperation, we don't need to
+ // remove it; also the deletesToGo reduction is not the entirety of
+ // nextCount
+ i -= 1;
+ nextCount = deletesToGo;
+
+ deletesToGo = 0;
+ } else {
+ if (nextCount === deletesToGo) {
+ // Handle edge case of d:2 i:2 in which case both operations go away
+ // during composition.
+ removeNewAndNextOp = true;
+ }
+ removedItems = removedItems.concat(nextArrayOperation.items);
+ deletesToGo -= nextCount;
+ }
+
+ if (nextOp === INSERT) {
+ // d:2 i:3 will result in delete going away
+ arrayOperation.count -= nextCount;
+ }
+ }
+
+ if (arrayOperation.count > 0) {
+ // compose our new delete with possibly several operations to the right of
+ // disparate types
+ this._operations.splice(index+1, i-1-index);
+ } else {
+ // The delete operation can go away; it has merely reduced some other
+ // operation, as in d:3 i:4; it may also have eliminated that operation,
+ // as in d:3 i:3.
+ this._operations.splice(index, removeNewAndNextOp ? 2 : 1);
+ }
+
+ return removedItems;
+ },
+
+ toString: function () {
+ var str = "";
+ forEach(this._operations, function (operation) {
+ str += " " + operation.type + ":" + operation.count;
+ });
+ return str.substring(1);
+ }
+};
+
+/**
+ Internal data structure to represent an array operation.
+
+ @method ArrayOperation
+ @private
+ @param {string} type The type of the operation. One of
+ `Ember.TrackedArray.{RETAIN, INSERT, DELETE}`
+ @param {number} count The number of items in this operation.
+ @param {array} items The items of the operation, if included. RETAIN and
+ INSERT include their items, DELETE does not.
+*/
+function ArrayOperation (operation, count, items) {
+ this.type = operation; // RETAIN | INSERT | DELETE
+ this.count = count;
+ this.items = items;
+}
+
+/**
+ Internal data structure used to include information when looking up operations
+ by item index.
+
+ @method ArrayOperationMatch
+ @private
+ @param {ArrayOperation} operation
+ @param {number} index The index of `operation` in the array of operations.
+ @param {boolean} split Whether or not the item index searched for would
+ require a split for a new operation type.
+ @param {number} rangeStart The index of the first item in the operation,
+ with respect to the tracked array. The index of the last item can be computed
+ from `rangeStart` and `operation.count`.
+*/
+function ArrayOperationMatch(operation, index, split, rangeStart) {
+ this.operation = operation;
+ this.index = index;
+ this.split = split;
+ this.rangeStart = rangeStart;
+}
+
+})();
+
+
+
+(function() {
+var get = Ember.get,
+ forEach = Ember.EnumerableUtils.forEach,
+ RETAIN = 'r',
+ FILTER = 'f';
+
+function Operation (type, count) {
+ this.type = type;
+ this.count = count;
+}
+
+/**
+ An `Ember.SubArray` tracks an array in a way similar to, but more specialized
+ than, `Ember.TrackedArray`. It is useful for keeping track of the indexes of
+ items within a filtered array.
+
+ @class SubArray
+ @namespace Ember
+*/
+Ember.SubArray = function (length) {
+ if (arguments.length < 1) { length = 0; }
+
+ if (length > 0) {
+ this._operations = [new Operation(RETAIN, length)];
+ } else {
+ this._operations = [];
+ }
+};
+
+Ember.SubArray.prototype = {
+ /**
+ Track that an item was added to the tracked array.
+
+ @method addItem
+
+ @param {number} index The index of the item in the tracked array.
+ @param {boolean} match `true` iff the item is included in the subarray.
+
+ @return {number} The index of the item in the subarray.
+ */
+ addItem: function(index, match) {
+ var returnValue = -1,
+ itemType = match ? RETAIN : FILTER,
+ self = this;
+
+ this._findOperation(index, function(operation, operationIndex, rangeStart, rangeEnd, seenInSubArray) {
+ var newOperation, splitOperation;
+
+ if (itemType === operation.type) {
+ ++operation.count;
+ } else if (index === rangeStart) {
+ // insert to the left of `operation`
+ self._operations.splice(operationIndex, 0, new Operation(itemType, 1));
+ } else {
+ newOperation = new Operation(itemType, 1);
+ splitOperation = new Operation(operation.type, rangeEnd - index + 1);
+ operation.count = index - rangeStart;
+
+ self._operations.splice(operationIndex + 1, 0, newOperation, splitOperation);
+ }
+
+ if (match) {
+ if (operation.type === RETAIN) {
+ returnValue = seenInSubArray + (index - rangeStart);
+ } else {
+ returnValue = seenInSubArray;
+ }
+ }
+
+ self._composeAt(operationIndex);
+ }, function(seenInSubArray) {
+ self._operations.push(new Operation(itemType, 1));
+
+ if (match) {
+ returnValue = seenInSubArray;
+ }
+
+ self._composeAt(self._operations.length-1);
+ });
+
+ return returnValue;
+ },
+
+ /**
+ Track that an item was removed from the tracked array.
+
+ @method removeItem
+
+ @param {number} index The index of the item in the tracked array.
+
+ @return {number} The index of the item in the subarray, or `-1` if the item
+ was not in the subarray.
+ */
+ removeItem: function(index) {
+ var returnValue = -1,
+ self = this;
+
+ this._findOperation(index, function (operation, operationIndex, rangeStart, rangeEnd, seenInSubArray) {
+ if (operation.type === RETAIN) {
+ returnValue = seenInSubArray + (index - rangeStart);
+ }
+
+ if (operation.count > 1) {
+ --operation.count;
+ } else {
+ self._operations.splice(operationIndex, 1);
+ self._composeAt(operationIndex);
+ }
+ }, function() {
+ throw new Ember.Error("Can't remove an item that has never been added.");
+ });
+
+ return returnValue;
+ },
+
+
+ _findOperation: function (index, foundCallback, notFoundCallback) {
+ var operationIndex,
+ len,
+ operation,
+ rangeStart,
+ rangeEnd,
+ seenInSubArray = 0;
+
+ // OPTIMIZE: change to balanced tree
+ // find leftmost operation to the right of `index`
+ for (operationIndex = rangeStart = 0, len = this._operations.length; operationIndex < len; rangeStart = rangeEnd + 1, ++operationIndex) {
+ operation = this._operations[operationIndex];
+ rangeEnd = rangeStart + operation.count - 1;
+
+ if (index >= rangeStart && index <= rangeEnd) {
+ foundCallback(operation, operationIndex, rangeStart, rangeEnd, seenInSubArray);
+ return;
+ } else if (operation.type === RETAIN) {
+ seenInSubArray += operation.count;
+ }
+ }
+
+ notFoundCallback(seenInSubArray);
+ },
+
+ _composeAt: function(index) {
+ var op = this._operations[index],
+ otherOp;
+
+ if (!op) {
+ // Composing out of bounds is a no-op, as when removing the last operation
+ // in the list.
+ return;
+ }
+
+ if (index > 0) {
+ otherOp = this._operations[index-1];
+ if (otherOp.type === op.type) {
+ op.count += otherOp.count;
+ this._operations.splice(index-1, 1);
+ --index;
+ }
+ }
+
+ if (index < this._operations.length-1) {
+ otherOp = this._operations[index+1];
+ if (otherOp.type === op.type) {
+ op.count += otherOp.count;
+ this._operations.splice(index+1, 1);
+ }
+ }
+ },
+
+ toString: function () {
+ var str = "";
+ forEach(this._operations, function (operation) {
+ str += " " + operation.type + ":" + operation.count;
+ });
+ return str.substring(1);
+ }
+};
+
+})();
+
+
+
+(function() {
+Ember.Container = requireModule('container');
+Ember.Container.set = Ember.set;
+
+})();
+
+
+
+(function() {
+Ember.Application = Ember.Namespace.extend();
+
+})();
+
+
+
+(function() {
+/**
+@module ember
+@submodule ember-runtime
+*/
+
+var OUT_OF_RANGE_EXCEPTION = "Index out of range";
+var EMPTY = [];
+
+var get = Ember.get, set = Ember.set;
+
+/**
+ An ArrayProxy wraps any other object that implements `Ember.Array` and/or
+ `Ember.MutableArray,` forwarding all requests. This makes it very useful for
+ a number of binding use cases or other cases where being able to swap
+ out the underlying array is useful.
+
+ A simple example of usage:
+
+ ```javascript
+ var pets = ['dog', 'cat', 'fish'];
+ var ap = Ember.ArrayProxy.create({ content: Ember.A(pets) });
+
+ ap.get('firstObject'); // 'dog'
+ ap.set('content', ['amoeba', 'paramecium']);
+ ap.get('firstObject'); // 'amoeba'
+ ```
+
+ This class can also be useful as a layer to transform the contents of
+ an array, as they are accessed. This can be done by overriding
+ `objectAtContent`:
+
+ ```javascript
+ var pets = ['dog', 'cat', 'fish'];
+ var ap = Ember.ArrayProxy.create({
+ content: Ember.A(pets),
+ objectAtContent: function(idx) {
+ return this.get('content').objectAt(idx).toUpperCase();
+ }
+ });
+
+ ap.get('firstObject'); // . 'DOG'
+ ```
+
+ @class ArrayProxy
+ @namespace Ember
+ @extends Ember.Object
+ @uses Ember.MutableArray
+*/
+Ember.ArrayProxy = Ember.Object.extend(Ember.MutableArray, {
+
+ /**
+ The content array. Must be an object that implements `Ember.Array` and/or
+ `Ember.MutableArray.`
+
+ @property content
+ @type Ember.Array
+ */
+ content: null,
+
+ /**
+ The array that the proxy pretends to be. In the default `ArrayProxy`
+ implementation, this and `content` are the same. Subclasses of `ArrayProxy`
+ can override this property to provide things like sorting and filtering.
+
+ @property arrangedContent
+ */
+ arrangedContent: Ember.computed.alias('content'),
+
+ /**
+ Should actually retrieve the object at the specified index from the
+ content. You can override this method in subclasses to transform the
+ content item to something new.
+
+ This method will only be called if content is non-`null`.
+
+ @method objectAtContent
+ @param {Number} idx The index to retrieve.
+ @return {Object} the value or undefined if none found
+ */
+ objectAtContent: function(idx) {
+ return get(this, 'arrangedContent').objectAt(idx);
+ },
+
+ /**
+ Should actually replace the specified objects on the content array.
+ You can override this method in subclasses to transform the content item
+ into something new.
+
+ This method will only be called if content is non-`null`.
+
+ @method replaceContent
+ @param {Number} idx The starting index
+ @param {Number} amt The number of items to remove from the content.
+ @param {Array} objects Optional array of objects to insert or null if no
+ objects.
+ @return {void}
+ */
+ replaceContent: function(idx, amt, objects) {
+ get(this, 'content').replace(idx, amt, objects);
+ },
+
+ /**
+ Invoked when the content property is about to change. Notifies observers that the
+ entire array content will change.
+
+ @private
+ @method _contentWillChange
+ */
+ _contentWillChange: Ember.beforeObserver('content', function() {
+ this._teardownContent();
+ }),
+
+ _teardownContent: function() {
+ var content = get(this, 'content');
+
+ if (content) {
+ content.removeArrayObserver(this, {
+ willChange: 'contentArrayWillChange',
+ didChange: 'contentArrayDidChange'
+ });
+ }
+ },
+
+ contentArrayWillChange: Ember.K,
+ contentArrayDidChange: Ember.K,
+
+ /**
+ Invoked when the content property changes. Notifies observers that the
+ entire array content has changed.
+
+ @private
+ @method _contentDidChange
+ */
+ _contentDidChange: Ember.observer('content', function() {
+ var content = get(this, 'content');
+
+ Ember.assert("Can't set ArrayProxy's content to itself", content !== this);
+
+ this._setupContent();
+ }),
+
+ _setupContent: function() {
+ var content = get(this, 'content');
+
+ if (content) {
+ content.addArrayObserver(this, {
+ willChange: 'contentArrayWillChange',
+ didChange: 'contentArrayDidChange'
+ });
+ }
+ },
+
+ _arrangedContentWillChange: Ember.beforeObserver('arrangedContent', function() {
+ var arrangedContent = get(this, 'arrangedContent'),
+ len = arrangedContent ? get(arrangedContent, 'length') : 0;
+
+ this.arrangedContentArrayWillChange(this, 0, len, undefined);
+ this.arrangedContentWillChange(this);
+
+ this._teardownArrangedContent(arrangedContent);
+ }),
+
+ _arrangedContentDidChange: Ember.observer('arrangedContent', function() {
+ var arrangedContent = get(this, 'arrangedContent'),
+ len = arrangedContent ? get(arrangedContent, 'length') : 0;
+
+ Ember.assert("Can't set ArrayProxy's content to itself", arrangedContent !== this);
+
+ this._setupArrangedContent();
+
+ this.arrangedContentDidChange(this);
+ this.arrangedContentArrayDidChange(this, 0, undefined, len);
+ }),
+
+ _setupArrangedContent: function() {
+ var arrangedContent = get(this, 'arrangedContent');
+
+ if (arrangedContent) {
+ arrangedContent.addArrayObserver(this, {
+ willChange: 'arrangedContentArrayWillChange',
+ didChange: 'arrangedContentArrayDidChange'
+ });
+ }
+ },
+
+ _teardownArrangedContent: function() {
+ var arrangedContent = get(this, 'arrangedContent');
+
+ if (arrangedContent) {
+ arrangedContent.removeArrayObserver(this, {
+ willChange: 'arrangedContentArrayWillChange',
+ didChange: 'arrangedContentArrayDidChange'
+ });
+ }
+ },
+
+ arrangedContentWillChange: Ember.K,
+ arrangedContentDidChange: Ember.K,
+
+ objectAt: function(idx) {
+ return get(this, 'content') && this.objectAtContent(idx);
+ },
+
+ length: Ember.computed(function() {
+ var arrangedContent = get(this, 'arrangedContent');
+ return arrangedContent ? get(arrangedContent, 'length') : 0;
+ // No dependencies since Enumerable notifies length of change
+ }),
+
+ _replace: function(idx, amt, objects) {
+ var content = get(this, 'content');
+ Ember.assert('The content property of '+ this.constructor + ' should be set before modifying it', content);
+ if (content) this.replaceContent(idx, amt, objects);
+ return this;
+ },
+
+ replace: function() {
+ if (get(this, 'arrangedContent') === get(this, 'content')) {
+ this._replace.apply(this, arguments);
+ } else {
+ throw new Ember.Error("Using replace on an arranged ArrayProxy is not allowed.");
+ }
+ },
+
+ _insertAt: function(idx, object) {
+ if (idx > get(this, 'content.length')) throw new Ember.Error(OUT_OF_RANGE_EXCEPTION);
+ this._replace(idx, 0, [object]);
+ return this;
+ },
+
+ insertAt: function(idx, object) {
+ if (get(this, 'arrangedContent') === get(this, 'content')) {
+ return this._insertAt(idx, object);
+ } else {
+ throw new Ember.Error("Using insertAt on an arranged ArrayProxy is not allowed.");
+ }
+ },
+
+ removeAt: function(start, len) {
+ if ('number' === typeof start) {
+ var content = get(this, 'content'),
+ arrangedContent = get(this, 'arrangedContent'),
+ indices = [], i;
+
+ if ((start < 0) || (start >= get(this, 'length'))) {
+ throw new Ember.Error(OUT_OF_RANGE_EXCEPTION);
+ }
+
+ if (len === undefined) len = 1;
+
+ // Get a list of indices in original content to remove
+ for (i=start; i=idx) {
+ var item = content.objectAt(loc);
+ if (item) {
+ Ember.assert('When using @each to observe the array ' + content + ', the array must return an object', Ember.typeOf(item) === 'instance' || Ember.typeOf(item) === 'object');
+ Ember.addBeforeObserver(item, keyName, proxy, 'contentKeyWillChange');
+ Ember.addObserver(item, keyName, proxy, 'contentKeyDidChange');
+
+ // keep track of the index each item was found at so we can map
+ // it back when the obj changes.
+ guid = guidFor(item);
+ if (!objects[guid]) objects[guid] = [];
+ objects[guid].push(loc);
+ }
+ }
+}
+
+function removeObserverForContentKey(content, keyName, proxy, idx, loc) {
+ var objects = proxy._objects;
+ if (!objects) objects = proxy._objects = {};
+ var indicies, guid;
+
+ while(--loc>=idx) {
+ var item = content.objectAt(loc);
+ if (item) {
+ Ember.removeBeforeObserver(item, keyName, proxy, 'contentKeyWillChange');
+ Ember.removeObserver(item, keyName, proxy, 'contentKeyDidChange');
+
+ guid = guidFor(item);
+ indicies = objects[guid];
+ indicies[indexOf.call(indicies, loc)] = null;
+ }
+ }
+}
+
+/**
+ This is the object instance returned when you get the `@each` property on an
+ array. It uses the unknownProperty handler to automatically create
+ EachArray instances for property names.
+
+ @private
+ @class EachProxy
+ @namespace Ember
+ @extends Ember.Object
+*/
+Ember.EachProxy = Ember.Object.extend({
+
+ init: function(content) {
+ this._super();
+ this._content = content;
+ content.addArrayObserver(this);
+
+ // in case someone is already observing some keys make sure they are
+ // added
+ forEach(Ember.watchedEvents(this), function(eventName) {
+ this.didAddListener(eventName);
+ }, this);
+ },
+
+ /**
+ You can directly access mapped properties by simply requesting them.
+ The `unknownProperty` handler will generate an EachArray of each item.
+
+ @method unknownProperty
+ @param keyName {String}
+ @param value {*}
+ */
+ unknownProperty: function(keyName, value) {
+ var ret;
+ ret = new EachArray(this._content, keyName, this);
+ Ember.defineProperty(this, keyName, null, ret);
+ this.beginObservingContentKey(keyName);
+ return ret;
+ },
+
+ // ..........................................................
+ // ARRAY CHANGES
+ // Invokes whenever the content array itself changes.
+
+ arrayWillChange: function(content, idx, removedCnt, addedCnt) {
+ var keys = this._keys, key, lim;
+
+ lim = removedCnt>0 ? idx+removedCnt : -1;
+ Ember.beginPropertyChanges(this);
+
+ for(key in keys) {
+ if (!keys.hasOwnProperty(key)) { continue; }
+
+ if (lim>0) { removeObserverForContentKey(content, key, this, idx, lim); }
+
+ Ember.propertyWillChange(this, key);
+ }
+
+ Ember.propertyWillChange(this._content, '@each');
+ Ember.endPropertyChanges(this);
+ },
+
+ arrayDidChange: function(content, idx, removedCnt, addedCnt) {
+ var keys = this._keys, lim;
+
+ lim = addedCnt>0 ? idx+addedCnt : -1;
+ Ember.changeProperties(function() {
+ for(var key in keys) {
+ if (!keys.hasOwnProperty(key)) { continue; }
+
+ if (lim>0) { addObserverForContentKey(content, key, this, idx, lim); }
+
+ Ember.propertyDidChange(this, key);
+ }
+
+ Ember.propertyDidChange(this._content, '@each');
+ }, this);
+ },
+
+ // ..........................................................
+ // LISTEN FOR NEW OBSERVERS AND OTHER EVENT LISTENERS
+ // Start monitoring keys based on who is listening...
+
+ didAddListener: function(eventName) {
+ if (IS_OBSERVER.test(eventName)) {
+ this.beginObservingContentKey(eventName.slice(0, -7));
+ }
+ },
+
+ didRemoveListener: function(eventName) {
+ if (IS_OBSERVER.test(eventName)) {
+ this.stopObservingContentKey(eventName.slice(0, -7));
+ }
+ },
+
+ // ..........................................................
+ // CONTENT KEY OBSERVING
+ // Actual watch keys on the source content.
+
+ beginObservingContentKey: function(keyName) {
+ var keys = this._keys;
+ if (!keys) keys = this._keys = {};
+ if (!keys[keyName]) {
+ keys[keyName] = 1;
+ var content = this._content,
+ len = get(content, 'length');
+ addObserverForContentKey(content, keyName, this, 0, len);
+ } else {
+ keys[keyName]++;
+ }
+ },
+
+ stopObservingContentKey: function(keyName) {
+ var keys = this._keys;
+ if (keys && (keys[keyName]>0) && (--keys[keyName]<=0)) {
+ var content = this._content,
+ len = get(content, 'length');
+ removeObserverForContentKey(content, keyName, this, 0, len);
+ }
+ },
+
+ contentKeyWillChange: function(obj, keyName) {
+ Ember.propertyWillChange(this, keyName);
+ },
+
+ contentKeyDidChange: function(obj, keyName) {
+ Ember.propertyDidChange(this, keyName);
+ }
+
+});
+
+
+
+})();
+
+
+
+(function() {
+/**
+@module ember
+@submodule ember-runtime
+*/
+
+
+var get = Ember.get, set = Ember.set, replace = Ember.EnumerableUtils._replace;
+
+// Add Ember.Array to Array.prototype. Remove methods with native
+// implementations and supply some more optimized versions of generic methods
+// because they are so common.
+var NativeArray = Ember.Mixin.create(Ember.MutableArray, Ember.Observable, Ember.Copyable, {
+
+ // because length is a built-in property we need to know to just get the
+ // original property.
+ get: function(key) {
+ if (key==='length') return this.length;
+ else if ('number' === typeof key) return this[key];
+ else return this._super(key);
+ },
+
+ objectAt: function(idx) {
+ return this[idx];
+ },
+
+ // primitive for array support.
+ replace: function(idx, amt, objects) {
+
+ if (this.isFrozen) throw Ember.FROZEN_ERROR;
+
+ // if we replaced exactly the same number of items, then pass only the
+ // replaced range. Otherwise, pass the full remaining array length
+ // since everything has shifted
+ var len = objects ? get(objects, 'length') : 0;
+ this.arrayContentWillChange(idx, amt, len);
+
+ if (len === 0) {
+ this.splice(idx, amt);
+ } else {
+ replace(this, idx, amt, objects);
+ }
+
+ this.arrayContentDidChange(idx, amt, len);
+ return this;
+ },
+
+ // If you ask for an unknown property, then try to collect the value
+ // from member items.
+ unknownProperty: function(key, value) {
+ var ret;// = this.reducedProperty(key, value) ;
+ if ((value !== undefined) && ret === undefined) {
+ ret = this[key] = value;
+ }
+ return ret ;
+ },
+
+ // If browser did not implement indexOf natively, then override with
+ // specialized version
+ indexOf: function(object, startAt) {
+ var idx, len = this.length;
+
+ if (startAt === undefined) startAt = 0;
+ else startAt = (startAt < 0) ? Math.ceil(startAt) : Math.floor(startAt);
+ if (startAt < 0) startAt += len;
+
+ for(idx=startAt;idx=0;idx--) {
+ if (this[idx] === object) return idx ;
+ }
+ return -1;
+ },
+
+ copy: function(deep) {
+ if (deep) {
+ return this.map(function(item) { return Ember.copy(item, true); });
+ }
+
+ return this.slice();
+ }
+});
+
+// Remove any methods implemented natively so we don't override them
+var ignore = ['length'];
+Ember.EnumerableUtils.forEach(NativeArray.keys(), function(methodName) {
+ if (Array.prototype[methodName]) ignore.push(methodName);
+});
+
+if (ignore.length>0) {
+ NativeArray = NativeArray.without.apply(NativeArray, ignore);
+}
+
+/**
+ The NativeArray mixin contains the properties needed to to make the native
+ Array support Ember.MutableArray and all of its dependent APIs. Unless you
+ have `Ember.EXTEND_PROTOTYPES` or `Ember.EXTEND_PROTOTYPES.Array` set to
+ false, this will be applied automatically. Otherwise you can apply the mixin
+ at anytime by calling `Ember.NativeArray.activate`.
+
+ @class NativeArray
+ @namespace Ember
+ @uses Ember.MutableArray
+ @uses Ember.Observable
+ @uses Ember.Copyable
+*/
+Ember.NativeArray = NativeArray;
+
+/**
+ Creates an `Ember.NativeArray` from an Array like object.
+ Does not modify the original object. Ember.A is not needed if
+ `Ember.EXTEND_PROTOTYPES` is `true` (the default value). However,
+ it is recommended that you use Ember.A when creating addons for
+ ember or when you can not guarantee that `Ember.EXTEND_PROTOTYPES`
+ will be `true`.
+
+ Example
+
+ ```js
+ var Pagination = Ember.CollectionView.extend({
+ tagName: 'ul',
+ classNames: ['pagination'],
+ init: function() {
+ this._super();
+ if (!this.get('content')) {
+ this.set('content', Ember.A([]));
+ }
+ }
+ });
+ ```
+
+ @method A
+ @for Ember
+ @return {Ember.NativeArray}
+*/
+Ember.A = function(arr) {
+ if (arr === undefined) { arr = []; }
+ return Ember.Array.detect(arr) ? arr : Ember.NativeArray.apply(arr);
+};
+
+/**
+ Activates the mixin on the Array.prototype if not already applied. Calling
+ this method more than once is safe. This will be called when ember is loaded
+ unless you have `Ember.EXTEND_PROTOTYPES` or `Ember.EXTEND_PROTOTYPES.Array`
+ set to `false`.
+
+ Example
+
+ ```js
+ if (Ember.EXTEND_PROTOTYPES === true || Ember.EXTEND_PROTOTYPES.Array) {
+ Ember.NativeArray.activate();
+ }
+ ```
+
+ @method activate
+ @for Ember.NativeArray
+ @static
+ @return {void}
+*/
+Ember.NativeArray.activate = function() {
+ NativeArray.apply(Array.prototype);
+
+ Ember.A = function(arr) { return arr || []; };
+};
+
+if (Ember.EXTEND_PROTOTYPES === true || Ember.EXTEND_PROTOTYPES.Array) {
+ Ember.NativeArray.activate();
+}
+
+
+})();
+
+
+
+(function() {
+/**
+@module ember
+@submodule ember-runtime
+*/
+
+var get = Ember.get, set = Ember.set, guidFor = Ember.guidFor, isNone = Ember.isNone, fmt = Ember.String.fmt;
+
+/**
+ An unordered collection of objects.
+
+ A Set works a bit like an array except that its items are not ordered. You
+ can create a set to efficiently test for membership for an object. You can
+ also iterate through a set just like an array, even accessing objects by
+ index, however there is no guarantee as to their order.
+
+ All Sets are observable via the Enumerable Observer API - which works
+ on any enumerable object including both Sets and Arrays.
+
+ ## Creating a Set
+
+ You can create a set like you would most objects using
+ `new Ember.Set()`. Most new sets you create will be empty, but you can
+ also initialize the set with some content by passing an array or other
+ enumerable of objects to the constructor.
+
+ Finally, you can pass in an existing set and the set will be copied. You
+ can also create a copy of a set by calling `Ember.Set#copy()`.
+
+ ```javascript
+ // creates a new empty set
+ var foundNames = new Ember.Set();
+
+ // creates a set with four names in it.
+ var names = new Ember.Set(["Charles", "Tom", "Juan", "Alex"]); // :P
+
+ // creates a copy of the names set.
+ var namesCopy = new Ember.Set(names);
+
+ // same as above.
+ var anotherNamesCopy = names.copy();
+ ```
+
+ ## Adding/Removing Objects
+
+ You generally add or remove objects from a set using `add()` or
+ `remove()`. You can add any type of object including primitives such as
+ numbers, strings, and booleans.
+
+ Unlike arrays, objects can only exist one time in a set. If you call `add()`
+ on a set with the same object multiple times, the object will only be added
+ once. Likewise, calling `remove()` with the same object multiple times will
+ remove the object the first time and have no effect on future calls until
+ you add the object to the set again.
+
+ NOTE: You cannot add/remove `null` or `undefined` to a set. Any attempt to do
+ so will be ignored.
+
+ In addition to add/remove you can also call `push()`/`pop()`. Push behaves
+ just like `add()` but `pop()`, unlike `remove()` will pick an arbitrary
+ object, remove it and return it. This is a good way to use a set as a job
+ queue when you don't care which order the jobs are executed in.
+
+ ## Testing for an Object
+
+ To test for an object's presence in a set you simply call
+ `Ember.Set#contains()`.
+
+ ## Observing changes
+
+ When using `Ember.Set`, you can observe the `"[]"` property to be
+ alerted whenever the content changes. You can also add an enumerable
+ observer to the set to be notified of specific objects that are added and
+ removed from the set. See [Ember.Enumerable](/api/classes/Ember.Enumerable.html)
+ for more information on enumerables.
+
+ This is often unhelpful. If you are filtering sets of objects, for instance,
+ it is very inefficient to re-filter all of the items each time the set
+ changes. It would be better if you could just adjust the filtered set based
+ on what was changed on the original set. The same issue applies to merging
+ sets, as well.
+
+ ## Other Methods
+
+ `Ember.Set` primary implements other mixin APIs. For a complete reference
+ on the methods you will use with `Ember.Set`, please consult these mixins.
+ The most useful ones will be `Ember.Enumerable` and
+ `Ember.MutableEnumerable` which implement most of the common iterator
+ methods you are used to on Array.
+
+ Note that you can also use the `Ember.Copyable` and `Ember.Freezable`
+ APIs on `Ember.Set` as well. Once a set is frozen it can no longer be
+ modified. The benefit of this is that when you call `frozenCopy()` on it,
+ Ember will avoid making copies of the set. This allows you to write
+ code that can know with certainty when the underlying set data will or
+ will not be modified.
+
+ @class Set
+ @namespace Ember
+ @extends Ember.CoreObject
+ @uses Ember.MutableEnumerable
+ @uses Ember.Copyable
+ @uses Ember.Freezable
+ @since Ember 0.9
+*/
+Ember.Set = Ember.CoreObject.extend(Ember.MutableEnumerable, Ember.Copyable, Ember.Freezable,
+ {
+
+ // ..........................................................
+ // IMPLEMENT ENUMERABLE APIS
+ //
+
+ /**
+ This property will change as the number of objects in the set changes.
+
+ @property length
+ @type number
+ @default 0
+ */
+ length: 0,
+
+ /**
+ Clears the set. This is useful if you want to reuse an existing set
+ without having to recreate it.
+
+ ```javascript
+ var colors = new Ember.Set(["red", "green", "blue"]);
+ colors.length; // 3
+ colors.clear();
+ colors.length; // 0
+ ```
+
+ @method clear
+ @return {Ember.Set} An empty Set
+ */
+ clear: function() {
+ if (this.isFrozen) { throw new Ember.Error(Ember.FROZEN_ERROR); }
+
+ var len = get(this, 'length');
+ if (len === 0) { return this; }
+
+ var guid;
+
+ this.enumerableContentWillChange(len, 0);
+ Ember.propertyWillChange(this, 'firstObject');
+ Ember.propertyWillChange(this, 'lastObject');
+
+ for (var i=0; i < len; i++) {
+ guid = guidFor(this[i]);
+ delete this[guid];
+ delete this[i];
+ }
+
+ set(this, 'length', 0);
+
+ Ember.propertyDidChange(this, 'firstObject');
+ Ember.propertyDidChange(this, 'lastObject');
+ this.enumerableContentDidChange(len, 0);
+
+ return this;
+ },
+
+ /**
+ Returns true if the passed object is also an enumerable that contains the
+ same objects as the receiver.
+
+ ```javascript
+ var colors = ["red", "green", "blue"],
+ same_colors = new Ember.Set(colors);
+
+ same_colors.isEqual(colors); // true
+ same_colors.isEqual(["purple", "brown"]); // false
+ ```
+
+ @method isEqual
+ @param {Ember.Set} obj the other object.
+ @return {Boolean}
+ */
+ isEqual: function(obj) {
+ // fail fast
+ if (!Ember.Enumerable.detect(obj)) return false;
+
+ var loc = get(this, 'length');
+ if (get(obj, 'length') !== loc) return false;
+
+ while(--loc >= 0) {
+ if (!obj.contains(this[loc])) return false;
+ }
+
+ return true;
+ },
+
+ /**
+ Adds an object to the set. Only non-`null` objects can be added to a set
+ and those can only be added once. If the object is already in the set or
+ the passed value is null this method will have no effect.
+
+ This is an alias for `Ember.MutableEnumerable.addObject()`.
+
+ ```javascript
+ var colors = new Ember.Set();
+ colors.add("blue"); // ["blue"]
+ colors.add("blue"); // ["blue"]
+ colors.add("red"); // ["blue", "red"]
+ colors.add(null); // ["blue", "red"]
+ colors.add(undefined); // ["blue", "red"]
+ ```
+
+ @method add
+ @param {Object} obj The object to add.
+ @return {Ember.Set} The set itself.
+ */
+ add: Ember.aliasMethod('addObject'),
+
+ /**
+ Removes the object from the set if it is found. If you pass a `null` value
+ or an object that is already not in the set, this method will have no
+ effect. This is an alias for `Ember.MutableEnumerable.removeObject()`.
+
+ ```javascript
+ var colors = new Ember.Set(["red", "green", "blue"]);
+ colors.remove("red"); // ["blue", "green"]
+ colors.remove("purple"); // ["blue", "green"]
+ colors.remove(null); // ["blue", "green"]
+ ```
+
+ @method remove
+ @param {Object} obj The object to remove
+ @return {Ember.Set} The set itself.
+ */
+ remove: Ember.aliasMethod('removeObject'),
+
+ /**
+ Removes the last element from the set and returns it, or `null` if it's empty.
+
+ ```javascript
+ var colors = new Ember.Set(["green", "blue"]);
+ colors.pop(); // "blue"
+ colors.pop(); // "green"
+ colors.pop(); // null
+ ```
+
+ @method pop
+ @return {Object} The removed object from the set or null.
+ */
+ pop: function() {
+ if (get(this, 'isFrozen')) throw new Ember.Error(Ember.FROZEN_ERROR);
+ var obj = this.length > 0 ? this[this.length-1] : null;
+ this.remove(obj);
+ return obj;
+ },
+
+ /**
+ Inserts the given object on to the end of the set. It returns
+ the set itself.
+
+ This is an alias for `Ember.MutableEnumerable.addObject()`.
+
+ ```javascript
+ var colors = new Ember.Set();
+ colors.push("red"); // ["red"]
+ colors.push("green"); // ["red", "green"]
+ colors.push("blue"); // ["red", "green", "blue"]
+ ```
+
+ @method push
+ @return {Ember.Set} The set itself.
+ */
+ push: Ember.aliasMethod('addObject'),
+
+ /**
+ Removes the last element from the set and returns it, or `null` if it's empty.
+
+ This is an alias for `Ember.Set.pop()`.
+
+ ```javascript
+ var colors = new Ember.Set(["green", "blue"]);
+ colors.shift(); // "blue"
+ colors.shift(); // "green"
+ colors.shift(); // null
+ ```
+
+ @method shift
+ @return {Object} The removed object from the set or null.
+ */
+ shift: Ember.aliasMethod('pop'),
+
+ /**
+ Inserts the given object on to the end of the set. It returns
+ the set itself.
+
+ This is an alias of `Ember.Set.push()`
+
+ ```javascript
+ var colors = new Ember.Set();
+ colors.unshift("red"); // ["red"]
+ colors.unshift("green"); // ["red", "green"]
+ colors.unshift("blue"); // ["red", "green", "blue"]
+ ```
+
+ @method unshift
+ @return {Ember.Set} The set itself.
+ */
+ unshift: Ember.aliasMethod('push'),
+
+ /**
+ Adds each object in the passed enumerable to the set.
+
+ This is an alias of `Ember.MutableEnumerable.addObjects()`
+
+ ```javascript
+ var colors = new Ember.Set();
+ colors.addEach(["red", "green", "blue"]); // ["red", "green", "blue"]
+ ```
+
+ @method addEach
+ @param {Ember.Enumerable} objects the objects to add.
+ @return {Ember.Set} The set itself.
+ */
+ addEach: Ember.aliasMethod('addObjects'),
+
+ /**
+ Removes each object in the passed enumerable to the set.
+
+ This is an alias of `Ember.MutableEnumerable.removeObjects()`
+
+ ```javascript
+ var colors = new Ember.Set(["red", "green", "blue"]);
+ colors.removeEach(["red", "blue"]); // ["green"]
+ ```
+
+ @method removeEach
+ @param {Ember.Enumerable} objects the objects to remove.
+ @return {Ember.Set} The set itself.
+ */
+ removeEach: Ember.aliasMethod('removeObjects'),
+
+ // ..........................................................
+ // PRIVATE ENUMERABLE SUPPORT
+ //
+
+ init: function(items) {
+ this._super();
+ if (items) this.addObjects(items);
+ },
+
+ // implement Ember.Enumerable
+ nextObject: function(idx) {
+ return this[idx];
+ },
+
+ // more optimized version
+ firstObject: Ember.computed(function() {
+ return this.length > 0 ? this[0] : undefined;
+ }),
+
+ // more optimized version
+ lastObject: Ember.computed(function() {
+ return this.length > 0 ? this[this.length-1] : undefined;
+ }),
+
+ // implements Ember.MutableEnumerable
+ addObject: function(obj) {
+ if (get(this, 'isFrozen')) throw new Ember.Error(Ember.FROZEN_ERROR);
+ if (isNone(obj)) return this; // nothing to do
+
+ var guid = guidFor(obj),
+ idx = this[guid],
+ len = get(this, 'length'),
+ added ;
+
+ if (idx>=0 && idx=0 && idx=0;
+ },
+
+ copy: function() {
+ var C = this.constructor, ret = new C(), loc = get(this, 'length');
+ set(ret, 'length', loc);
+ while(--loc>=0) {
+ ret[loc] = this[loc];
+ ret[guidFor(this[loc])] = loc;
+ }
+ return ret;
+ },
+
+ toString: function() {
+ var len = this.length, idx, array = [];
+ for(idx = 0; idx < len; idx++) {
+ array[idx] = this[idx];
+ }
+ return fmt("Ember.Set<%@>", [array.join(',')]);
+ }
+
+});
+
+})();
+
+
+
+(function() {
+var DeferredMixin = Ember.DeferredMixin, // mixins/deferred
+ get = Ember.get;
+
+var Deferred = Ember.Object.extend(DeferredMixin);
+
+Deferred.reopenClass({
+ promise: function(callback, binding) {
+ var deferred = Deferred.create();
+ callback.call(binding, deferred);
+ return deferred;
+ }
+});
+
+Ember.Deferred = Deferred;
+
+})();
+
+
+
+(function() {
+var forEach = Ember.ArrayPolyfills.forEach;
+
+/**
+ @module ember
+ @submodule ember-runtime
+*/
+
+var loadHooks = Ember.ENV.EMBER_LOAD_HOOKS || {};
+var loaded = {};
+
+/**
+ Detects when a specific package of Ember (e.g. 'Ember.Handlebars')
+ has fully loaded and is available for extension.
+
+ The provided `callback` will be called with the `name` passed
+ resolved from a string into the object:
+
+ ``` javascript
+ Ember.onLoad('Ember.Handlebars' function(hbars){
+ hbars.registerHelper(...);
+ });
+ ```
+
+ @method onLoad
+ @for Ember
+ @param name {String} name of hook
+ @param callback {Function} callback to be called
+*/
+Ember.onLoad = function(name, callback) {
+ var object;
+
+ loadHooks[name] = loadHooks[name] || Ember.A();
+ loadHooks[name].pushObject(callback);
+
+ if (object = loaded[name]) {
+ callback(object);
+ }
+};
+
+/**
+ Called when an Ember.js package (e.g Ember.Handlebars) has finished
+ loading. Triggers any callbacks registered for this event.
+
+ @method runLoadHooks
+ @for Ember
+ @param name {String} name of hook
+ @param object {Object} object to pass to callbacks
+*/
+Ember.runLoadHooks = function(name, object) {
+ loaded[name] = object;
+
+ if (loadHooks[name]) {
+ forEach.call(loadHooks[name], function(callback) {
+ callback(object);
+ });
+ }
+};
+
+})();
+
+
+
+(function() {
+
+})();
+
+
+
+(function() {
+var get = Ember.get;
+
+/**
+@module ember
+@submodule ember-runtime
+*/
+
+/**
+ `Ember.ControllerMixin` provides a standard interface for all classes that
+ compose Ember's controller layer: `Ember.Controller`,
+ `Ember.ArrayController`, and `Ember.ObjectController`.
+
+ @class ControllerMixin
+ @namespace Ember
+ @uses Ember.ActionHandler
+*/
+Ember.ControllerMixin = Ember.Mixin.create(Ember.ActionHandler, {
+ /* ducktype as a controller */
+ isController: true,
+
+ /**
+ The object to which actions from the view should be sent.
+
+ For example, when a Handlebars template uses the `{{action}}` helper,
+ it will attempt to send the action to the view's controller's `target`.
+
+ By default, a controller's `target` is set to the router after it is
+ instantiated by `Ember.Application#initialize`.
+
+ @property target
+ @default null
+ */
+ target: null,
+
+ container: null,
+
+ parentController: null,
+
+ store: null,
+
+ model: Ember.computed.alias('content'),
+
+ deprecatedSendHandles: function(actionName) {
+ return !!this[actionName];
+ },
+
+ deprecatedSend: function(actionName) {
+ var args = [].slice.call(arguments, 1);
+ Ember.assert('' + this + " has the action " + actionName + " but it is not a function", typeof this[actionName] === 'function');
+ Ember.deprecate('Action handlers implemented directly on controllers are deprecated in favor of action handlers on an `actions` object ( action: `' + actionName + '` on ' + this + ')', false);
+ this[actionName].apply(this, args);
+ return;
+ }
+});
+
+/**
+ @class Controller
+ @namespace Ember
+ @extends Ember.Object
+ @uses Ember.ControllerMixin
+*/
+Ember.Controller = Ember.Object.extend(Ember.ControllerMixin);
+
+})();
+
+
+
+(function() {
+/**
+@module ember
+@submodule ember-runtime
+*/
+
+var get = Ember.get, set = Ember.set, forEach = Ember.EnumerableUtils.forEach;
+
+/**
+ `Ember.SortableMixin` provides a standard interface for array proxies
+ to specify a sort order and maintain this sorting when objects are added,
+ removed, or updated without changing the implicit order of their underlying
+ content array:
+
+ ```javascript
+ songs = [
+ {trackNumber: 4, title: 'Ob-La-Di, Ob-La-Da'},
+ {trackNumber: 2, title: 'Back in the U.S.S.R.'},
+ {trackNumber: 3, title: 'Glass Onion'},
+ ];
+
+ songsController = Ember.ArrayController.create({
+ content: songs,
+ sortProperties: ['trackNumber'],
+ sortAscending: true
+ });
+
+ songsController.get('firstObject'); // {trackNumber: 2, title: 'Back in the U.S.S.R.'}
+
+ songsController.addObject({trackNumber: 1, title: 'Dear Prudence'});
+ songsController.get('firstObject'); // {trackNumber: 1, title: 'Dear Prudence'}
+ ```
+
+ If you add or remove the properties to sort by or change the sort direction the content
+ sort order will be automatically updated.
+
+ ```javascript
+ songsController.set('sortProperties', ['title']);
+ songsController.get('firstObject'); // {trackNumber: 2, title: 'Back in the U.S.S.R.'}
+
+ songsController.toggleProperty('sortAscending');
+ songsController.get('firstObject'); // {trackNumber: 4, title: 'Ob-La-Di, Ob-La-Da'}
+ ```
+
+ SortableMixin works by sorting the arrangedContent array, which is the array that
+ arrayProxy displays. Due to the fact that the underlying 'content' array is not changed, that
+ array will not display the sorted list:
+
+ ```javascript
+ songsController.get('content').get('firstObject'); // Returns the unsorted original content
+ songsController.get('firstObject'); // Returns the sorted content.
+ ```
+
+ Although the sorted content can also be accessed through the arrangedContent property,
+ it is preferable to use the proxied class and not the arrangedContent array directly.
+
+ @class SortableMixin
+ @namespace Ember
+ @uses Ember.MutableEnumerable
+*/
+Ember.SortableMixin = Ember.Mixin.create(Ember.MutableEnumerable, {
+
+ /**
+ Specifies which properties dictate the arrangedContent's sort order.
+
+ When specifying multiple properties the sorting will use properties
+ from the `sortProperties` array prioritized from first to last.
+
+ @property {Array} sortProperties
+ */
+ sortProperties: null,
+
+ /**
+ Specifies the arrangedContent's sort direction
+
+ @property {Boolean} sortAscending
+ */
+ sortAscending: true,
+
+ /**
+ The function used to compare two values. You can override this if you
+ want to do custom comparisons. Functions must be of the type expected by
+ Array#sort, i.e.
+ return 0 if the two parameters are equal,
+ return a negative value if the first parameter is smaller than the second or
+ return a positive value otherwise:
+
+ ```javascript
+ function(x,y) { // These are assumed to be integers
+ if (x === y)
+ return 0;
+ return x < y ? -1 : 1;
+ }
+ ```
+
+ @property sortFunction
+ @type {Function}
+ @default Ember.compare
+ */
+ sortFunction: Ember.compare,
+
+ orderBy: function(item1, item2) {
+ var result = 0,
+ sortProperties = get(this, 'sortProperties'),
+ sortAscending = get(this, 'sortAscending'),
+ sortFunction = get(this, 'sortFunction');
+
+ Ember.assert("you need to define `sortProperties`", !!sortProperties);
+
+ forEach(sortProperties, function(propertyName) {
+ if (result === 0) {
+ result = sortFunction(get(item1, propertyName), get(item2, propertyName));
+ if ((result !== 0) && !sortAscending) {
+ result = (-1) * result;
+ }
+ }
+ });
+
+ return result;
+ },
+
+ destroy: function() {
+ var content = get(this, 'content'),
+ sortProperties = get(this, 'sortProperties');
+
+ if (content && sortProperties) {
+ forEach(content, function(item) {
+ forEach(sortProperties, function(sortProperty) {
+ Ember.removeObserver(item, sortProperty, this, 'contentItemSortPropertyDidChange');
+ }, this);
+ }, this);
+ }
+
+ return this._super();
+ },
+
+ isSorted: Ember.computed.bool('sortProperties'),
+
+ /**
+ Overrides the default arrangedContent from arrayProxy in order to sort by sortFunction.
+ Also sets up observers for each sortProperty on each item in the content Array.
+
+ @property arrangedContent
+ */
+
+ arrangedContent: Ember.computed('content', 'sortProperties.@each', function(key, value) {
+ var content = get(this, 'content'),
+ isSorted = get(this, 'isSorted'),
+ sortProperties = get(this, 'sortProperties'),
+ self = this;
+
+ if (content && isSorted) {
+ content = content.slice();
+ content.sort(function(item1, item2) {
+ return self.orderBy(item1, item2);
+ });
+ forEach(content, function(item) {
+ forEach(sortProperties, function(sortProperty) {
+ Ember.addObserver(item, sortProperty, this, 'contentItemSortPropertyDidChange');
+ }, this);
+ }, this);
+ return Ember.A(content);
+ }
+
+ return content;
+ }),
+
+ _contentWillChange: Ember.beforeObserver('content', function() {
+ var content = get(this, 'content'),
+ sortProperties = get(this, 'sortProperties');
+
+ if (content && sortProperties) {
+ forEach(content, function(item) {
+ forEach(sortProperties, function(sortProperty) {
+ Ember.removeObserver(item, sortProperty, this, 'contentItemSortPropertyDidChange');
+ }, this);
+ }, this);
+ }
+
+ this._super();
+ }),
+
+ sortAscendingWillChange: Ember.beforeObserver('sortAscending', function() {
+ this._lastSortAscending = get(this, 'sortAscending');
+ }),
+
+ sortAscendingDidChange: Ember.observer('sortAscending', function() {
+ if (get(this, 'sortAscending') !== this._lastSortAscending) {
+ var arrangedContent = get(this, 'arrangedContent');
+ arrangedContent.reverseObjects();
+ }
+ }),
+
+ contentArrayWillChange: function(array, idx, removedCount, addedCount) {
+ var isSorted = get(this, 'isSorted');
+
+ if (isSorted) {
+ var arrangedContent = get(this, 'arrangedContent');
+ var removedObjects = array.slice(idx, idx+removedCount);
+ var sortProperties = get(this, 'sortProperties');
+
+ forEach(removedObjects, function(item) {
+ arrangedContent.removeObject(item);
+
+ forEach(sortProperties, function(sortProperty) {
+ Ember.removeObserver(item, sortProperty, this, 'contentItemSortPropertyDidChange');
+ }, this);
+ }, this);
+ }
+
+ return this._super(array, idx, removedCount, addedCount);
+ },
+
+ contentArrayDidChange: function(array, idx, removedCount, addedCount) {
+ var isSorted = get(this, 'isSorted'),
+ sortProperties = get(this, 'sortProperties');
+
+ if (isSorted) {
+ var addedObjects = array.slice(idx, idx+addedCount);
+
+ forEach(addedObjects, function(item) {
+ this.insertItemSorted(item);
+
+ forEach(sortProperties, function(sortProperty) {
+ Ember.addObserver(item, sortProperty, this, 'contentItemSortPropertyDidChange');
+ }, this);
+ }, this);
+ }
+
+ return this._super(array, idx, removedCount, addedCount);
+ },
+
+ insertItemSorted: function(item) {
+ var arrangedContent = get(this, 'arrangedContent');
+ var length = get(arrangedContent, 'length');
+
+ var idx = this._binarySearch(item, 0, length);
+ arrangedContent.insertAt(idx, item);
+ },
+
+ contentItemSortPropertyDidChange: function(item) {
+ var arrangedContent = get(this, 'arrangedContent'),
+ oldIndex = arrangedContent.indexOf(item),
+ leftItem = arrangedContent.objectAt(oldIndex - 1),
+ rightItem = arrangedContent.objectAt(oldIndex + 1),
+ leftResult = leftItem && this.orderBy(item, leftItem),
+ rightResult = rightItem && this.orderBy(item, rightItem);
+
+ if (leftResult < 0 || rightResult > 0) {
+ arrangedContent.removeObject(item);
+ this.insertItemSorted(item);
+ }
+ },
+
+ _binarySearch: function(item, low, high) {
+ var mid, midItem, res, arrangedContent;
+
+ if (low === high) {
+ return low;
+ }
+
+ arrangedContent = get(this, 'arrangedContent');
+
+ mid = low + Math.floor((high - low) / 2);
+ midItem = arrangedContent.objectAt(mid);
+
+ res = this.orderBy(midItem, item);
+
+ if (res < 0) {
+ return this._binarySearch(item, mid+1, high);
+ } else if (res > 0) {
+ return this._binarySearch(item, low, mid);
+ }
+
+ return mid;
+ }
+});
+
+})();
+
+
+
+(function() {
+/**
+@module ember
+@submodule ember-runtime
+*/
+
+var get = Ember.get, set = Ember.set, forEach = Ember.EnumerableUtils.forEach,
+ replace = Ember.EnumerableUtils.replace;
+
+/**
+ `Ember.ArrayController` provides a way for you to publish a collection of
+ objects so that you can easily bind to the collection from a Handlebars
+ `#each` helper, an `Ember.CollectionView`, or other controllers.
+
+ The advantage of using an `ArrayController` is that you only have to set up
+ your view bindings once; to change what's displayed, simply swap out the
+ `content` property on the controller.
+
+ For example, imagine you wanted to display a list of items fetched via an XHR
+ request. Create an `Ember.ArrayController` and set its `content` property:
+
+ ```javascript
+ MyApp.listController = Ember.ArrayController.create();
+
+ $.get('people.json', function(data) {
+ MyApp.listController.set('content', data);
+ });
+ ```
+
+ Then, create a view that binds to your new controller:
+
+ ```handlebars
+ {{#each MyApp.listController}}
+ {{firstName}} {{lastName}}
+ {{/each}}
+ ```
+
+ Although you are binding to the controller, the behavior of this controller
+ is to pass through any methods or properties to the underlying array. This
+ capability comes from `Ember.ArrayProxy`, which this class inherits from.
+
+ Sometimes you want to display computed properties within the body of an
+ `#each` helper that depend on the underlying items in `content`, but are not
+ present on those items. To do this, set `itemController` to the name of a
+ controller (probably an `ObjectController`) that will wrap each individual item.
+
+ For example:
+
+ ```handlebars
+ {{#each post in controller}}
+
{{title}} ({{titleLength}} characters)
+ {{/each}}
+ ```
+
+ ```javascript
+ App.PostsController = Ember.ArrayController.extend({
+ itemController: 'post'
+ });
+
+ App.PostController = Ember.ObjectController.extend({
+ // the `title` property will be proxied to the underlying post.
+
+ titleLength: function() {
+ return this.get('title').length;
+ }.property('title')
+ });
+ ```
+
+ In some cases it is helpful to return a different `itemController` depending
+ on the particular item. Subclasses can do this by overriding
+ `lookupItemController`.
+
+ For example:
+
+ ```javascript
+ App.MyArrayController = Ember.ArrayController.extend({
+ lookupItemController: function( object ) {
+ if (object.get('isSpecial')) {
+ return "special"; // use App.SpecialController
+ } else {
+ return "regular"; // use App.RegularController
+ }
+ }
+ });
+ ```
+
+ The itemController instances will have a `parentController` property set to
+ the `ArrayController` instance.
+
+ @class ArrayController
+ @namespace Ember
+ @extends Ember.ArrayProxy
+ @uses Ember.SortableMixin
+ @uses Ember.ControllerMixin
+*/
+
+Ember.ArrayController = Ember.ArrayProxy.extend(Ember.ControllerMixin,
+ Ember.SortableMixin, {
+
+ /**
+ The controller used to wrap items, if any.
+
+ @property itemController
+ @type String
+ @default null
+ */
+ itemController: null,
+
+ /**
+ Return the name of the controller to wrap items, or `null` if items should
+ be returned directly. The default implementation simply returns the
+ `itemController` property, but subclasses can override this method to return
+ different controllers for different objects.
+
+ For example:
+
+ ```javascript
+ App.MyArrayController = Ember.ArrayController.extend({
+ lookupItemController: function( object ) {
+ if (object.get('isSpecial')) {
+ return "special"; // use App.SpecialController
+ } else {
+ return "regular"; // use App.RegularController
+ }
+ }
+ });
+ ```
+
+ @method lookupItemController
+ @param {Object} object
+ @return {String}
+ */
+ lookupItemController: function(object) {
+ return get(this, 'itemController');
+ },
+
+ objectAtContent: function(idx) {
+ var length = get(this, 'length'),
+ arrangedContent = get(this,'arrangedContent'),
+ object = arrangedContent && arrangedContent.objectAt(idx);
+
+ if (idx >= 0 && idx < length) {
+ var controllerClass = this.lookupItemController(object);
+ if (controllerClass) {
+ return this.controllerAt(idx, object, controllerClass);
+ }
+ }
+
+ // When `controllerClass` is falsy, we have not opted in to using item
+ // controllers, so return the object directly.
+
+ // When the index is out of range, we want to return the "out of range"
+ // value, whatever that might be. Rather than make assumptions
+ // (e.g. guessing `null` or `undefined`) we defer this to `arrangedContent`.
+ return object;
+ },
+
+ arrangedContentDidChange: function() {
+ this._super();
+ this._resetSubControllers();
+ },
+
+ arrayContentDidChange: function(idx, removedCnt, addedCnt) {
+ var subControllers = get(this, '_subControllers'),
+ subControllersToRemove = subControllers.slice(idx, idx+removedCnt);
+
+ forEach(subControllersToRemove, function(subController) {
+ if (subController) { subController.destroy(); }
+ });
+
+ replace(subControllers, idx, removedCnt, new Array(addedCnt));
+
+ // The shadow array of subcontrollers must be updated before we trigger
+ // observers, otherwise observers will get the wrong subcontainer when
+ // calling `objectAt`
+ this._super(idx, removedCnt, addedCnt);
+ },
+
+ init: function() {
+ this._super();
+
+ this.set('_subControllers', Ember.A());
+ },
+
+ content: Ember.computed(function () {
+ return Ember.A();
+ }),
+
+ /**
+ * Flag to mark as being "virtual". Used to keep this instance
+ * from participating in the parentController hierarchy.
+ *
+ * @private
+ * @type Boolean
+ */
+ _isVirtual: false,
+
+ controllerAt: function(idx, object, controllerClass) {
+ var container = get(this, 'container'),
+ subControllers = get(this, '_subControllers'),
+ subController = subControllers[idx],
+ factory, fullName;
+
+ if (subController) { return subController; }
+
+ fullName = "controller:" + controllerClass;
+
+ if (!container.has(fullName)) {
+ throw new Ember.Error('Could not resolve itemController: "' + controllerClass + '"');
+ }
+ var parentController;
+ if (this._isVirtual) {
+ parentController = get(this, 'parentController');
+ }
+ parentController = parentController || this;
+ subController = container.lookupFactory(fullName).create({
+ target: this,
+ parentController: parentController,
+ content: object
+ });
+
+ subControllers[idx] = subController;
+
+ return subController;
+ },
+
+ _subControllers: null,
+
+ _resetSubControllers: function() {
+ var subControllers = get(this, '_subControllers');
+ if (subControllers) {
+ forEach(subControllers, function(subController) {
+ if (subController) { subController.destroy(); }
+ });
+ }
+
+ this.set('_subControllers', Ember.A());
+ }
+});
+
+})();
+
+
+
+(function() {
+/**
+@module ember
+@submodule ember-runtime
+*/
+
+/**
+ `Ember.ObjectController` is part of Ember's Controller layer. It is intended
+ to wrap a single object, proxying unhandled attempts to `get` and `set` to the underlying
+ content object, and to forward unhandled action attempts to its `target`.
+
+ `Ember.ObjectController` derives this functionality from its superclass
+ `Ember.ObjectProxy` and the `Ember.ControllerMixin` mixin.
+
+ @class ObjectController
+ @namespace Ember
+ @extends Ember.ObjectProxy
+ @uses Ember.ControllerMixin
+**/
+Ember.ObjectController = Ember.ObjectProxy.extend(Ember.ControllerMixin);
+
+})();
+
+
+
+(function() {
+
+})();
+
+
+
+(function() {
+/**
+Ember Runtime
+
+@module ember
+@submodule ember-runtime
+@requires ember-metal
+*/
+
+})();
+
+(function() {
+/**
+@module ember
+@submodule ember-views
+*/
+
+var jQuery = (this && this.jQuery) || (Ember.imports && Ember.imports.jQuery);
+if (!jQuery && typeof require === 'function') {
+ jQuery = require('jquery');
+}
+
+Ember.assert("Ember Views require jQuery between 1.7 and 2.1", jQuery && (jQuery().jquery.match(/^((1\.(7|8|9|10|11))|(2\.(0|1)))(\.\d+)?(pre|rc\d?)?/) || Ember.ENV.FORCE_JQUERY));
+
+/**
+ Alias for jQuery
+
+ @method $
+ @for Ember
+*/
+Ember.$ = jQuery;
+
+})();
+
+
+
+(function() {
+/**
+@module ember
+@submodule ember-views
+*/
+if (Ember.$) {
+ // http://www.whatwg.org/specs/web-apps/current-work/multipage/dnd.html#dndevents
+ var dragEvents = Ember.String.w('dragstart drag dragenter dragleave dragover drop dragend');
+
+ // Copies the `dataTransfer` property from a browser event object onto the
+ // jQuery event object for the specified events
+ Ember.EnumerableUtils.forEach(dragEvents, function(eventName) {
+ Ember.$.event.fixHooks[eventName] = { props: ['dataTransfer'] };
+ });
+}
+
+})();
+
+
+
+(function() {
+/**
+@module ember
+@submodule ember-views
+*/
+
+/* BEGIN METAMORPH HELPERS */
+
+// Internet Explorer prior to 9 does not allow setting innerHTML if the first element
+// is a "zero-scope" element. This problem can be worked around by making
+// the first node an invisible text node. We, like Modernizr, use
+
+var needsShy = typeof document !== 'undefined' && (function() {
+ var testEl = document.createElement('div');
+ testEl.innerHTML = "";
+ testEl.firstChild.innerHTML = "";
+ return testEl.firstChild.innerHTML === '';
+})();
+
+// IE 8 (and likely earlier) likes to move whitespace preceeding
+// a script tag to appear after it. This means that we can
+// accidentally remove whitespace when updating a morph.
+var movesWhitespace = typeof document !== 'undefined' && (function() {
+ var testEl = document.createElement('div');
+ testEl.innerHTML = "Test: Value";
+ return testEl.childNodes[0].nodeValue === 'Test:' &&
+ testEl.childNodes[2].nodeValue === ' Value';
+})();
+
+// Use this to find children by ID instead of using jQuery
+var findChildById = function(element, id) {
+ if (element.getAttribute('id') === id) { return element; }
+
+ var len = element.childNodes.length, idx, node, found;
+ for (idx=0; idx 0) {
+ var len = matches.length, idx;
+ for (idx=0; idxTest');
+ canSet = el.options.length === 1;
+ }
+
+ innerHTMLTags[tagName] = canSet;
+
+ return canSet;
+};
+
+var setInnerHTML = function(element, html) {
+ var tagName = element.tagName;
+
+ if (canSetInnerHTML(tagName)) {
+ setInnerHTMLWithoutFix(element, html);
+ } else {
+ // Firefox versions < 11 do not have support for element.outerHTML.
+ var outerHTML = element.outerHTML || new XMLSerializer().serializeToString(element);
+ Ember.assert("Can't set innerHTML on "+element.tagName+" in this browser", outerHTML);
+
+ var startTag = outerHTML.match(new RegExp("<"+tagName+"([^>]*)>", 'i'))[0],
+ endTag = ''+tagName+'>';
+
+ var wrapper = document.createElement('div');
+ setInnerHTMLWithoutFix(wrapper, startTag + html + endTag);
+ element = wrapper.firstChild;
+ while (element.tagName !== tagName) {
+ element = element.nextSibling;
+ }
+ }
+
+ return element;
+};
+
+function isSimpleClick(event) {
+ var modifier = event.shiftKey || event.metaKey || event.altKey || event.ctrlKey,
+ secondaryClick = event.which > 1; // IE9 may return undefined
+
+ return !modifier && !secondaryClick;
+}
+
+Ember.ViewUtils = {
+ setInnerHTML: setInnerHTML,
+ isSimpleClick: isSimpleClick
+};
+
+})();
+
+
+
+(function() {
+/**
+@module ember
+@submodule ember-views
+*/
+
+var get = Ember.get, set = Ember.set;
+
+var ClassSet = function() {
+ this.seen = {};
+ this.list = [];
+};
+
+ClassSet.prototype = {
+ add: function(string) {
+ if (string in this.seen) { return; }
+ this.seen[string] = true;
+
+ this.list.push(string);
+ },
+
+ toDOM: function() {
+ return this.list.join(" ");
+ }
+};
+
+var BAD_TAG_NAME_TEST_REGEXP = /[^a-zA-Z0-9\-]/;
+var BAD_TAG_NAME_REPLACE_REGEXP = /[^a-zA-Z0-9\-]/g;
+
+function stripTagName(tagName) {
+ if (!tagName) {
+ return tagName;
+ }
+
+ if (!BAD_TAG_NAME_TEST_REGEXP.test(tagName)) {
+ return tagName;
+ }
+
+ return tagName.replace(BAD_TAG_NAME_REPLACE_REGEXP, '');
+}
+
+var BAD_CHARS_REGEXP = /&(?!\w+;)|[<>"'`]/g;
+var POSSIBLE_CHARS_REGEXP = /[&<>"'`]/;
+
+function escapeAttribute(value) {
+ // Stolen shamelessly from Handlebars
+
+ var escape = {
+ "<": "<",
+ ">": ">",
+ '"': """,
+ "'": "'",
+ "`": "`"
+ };
+
+ var escapeChar = function(chr) {
+ return escape[chr] || "&";
+ };
+
+ var string = value.toString();
+
+ if(!POSSIBLE_CHARS_REGEXP.test(string)) { return string; }
+ return string.replace(BAD_CHARS_REGEXP, escapeChar);
+}
+
+// IE 6/7 have bugs around setting names on inputs during creation.
+// From http://msdn.microsoft.com/en-us/library/ie/ms536389(v=vs.85).aspx:
+// "To include the NAME attribute at run time on objects created with the createElement method, use the eTag."
+var canSetNameOnInputs = (function() {
+ var div = document.createElement('div'),
+ el = document.createElement('input');
+
+ el.setAttribute('name', 'foo');
+ div.appendChild(el);
+
+ return !!div.innerHTML.match('foo');
+})();
+
+/**
+ `Ember.RenderBuffer` gathers information regarding the a view and generates the
+ final representation. `Ember.RenderBuffer` will generate HTML which can be pushed
+ to the DOM.
+
+ ```javascript
+ var buffer = Ember.RenderBuffer('div');
+ ```
+
+ @class RenderBuffer
+ @namespace Ember
+ @constructor
+ @param {String} tagName tag name (such as 'div' or 'p') used for the buffer
+*/
+Ember.RenderBuffer = function(tagName) {
+ return new Ember._RenderBuffer(tagName);
+};
+
+Ember._RenderBuffer = function(tagName) {
+ this.tagNames = [tagName || null];
+ this.buffer = "";
+};
+
+Ember._RenderBuffer.prototype = {
+
+ // The root view's element
+ _element: null,
+
+ _hasElement: true,
+
+ /**
+ An internal set used to de-dupe class names when `addClass()` is
+ used. After each call to `addClass()`, the `classes` property
+ will be updated.
+
+ @private
+ @property elementClasses
+ @type Array
+ @default []
+ */
+ elementClasses: null,
+
+ /**
+ Array of class names which will be applied in the class attribute.
+
+ You can use `setClasses()` to set this property directly. If you
+ use `addClass()`, it will be maintained for you.
+
+ @property classes
+ @type Array
+ @default []
+ */
+ classes: null,
+
+ /**
+ The id in of the element, to be applied in the id attribute.
+
+ You should not set this property yourself, rather, you should use
+ the `id()` method of `Ember.RenderBuffer`.
+
+ @property elementId
+ @type String
+ @default null
+ */
+ elementId: null,
+
+ /**
+ A hash keyed on the name of the attribute and whose value will be
+ applied to that attribute. For example, if you wanted to apply a
+ `data-view="Foo.bar"` property to an element, you would set the
+ elementAttributes hash to `{'data-view':'Foo.bar'}`.
+
+ You should not maintain this hash yourself, rather, you should use
+ the `attr()` method of `Ember.RenderBuffer`.
+
+ @property elementAttributes
+ @type Hash
+ @default {}
+ */
+ elementAttributes: null,
+
+ /**
+ A hash keyed on the name of the properties and whose value will be
+ applied to that property. For example, if you wanted to apply a
+ `checked=true` property to an element, you would set the
+ elementProperties hash to `{'checked':true}`.
+
+ You should not maintain this hash yourself, rather, you should use
+ the `prop()` method of `Ember.RenderBuffer`.
+
+ @property elementProperties
+ @type Hash
+ @default {}
+ */
+ elementProperties: null,
+
+ /**
+ The tagname of the element an instance of `Ember.RenderBuffer` represents.
+
+ Usually, this gets set as the first parameter to `Ember.RenderBuffer`. For
+ example, if you wanted to create a `p` tag, then you would call
+
+ ```javascript
+ Ember.RenderBuffer('p')
+ ```
+
+ @property elementTag
+ @type String
+ @default null
+ */
+ elementTag: null,
+
+ /**
+ A hash keyed on the name of the style attribute and whose value will
+ be applied to that attribute. For example, if you wanted to apply a
+ `background-color:black;` style to an element, you would set the
+ elementStyle hash to `{'background-color':'black'}`.
+
+ You should not maintain this hash yourself, rather, you should use
+ the `style()` method of `Ember.RenderBuffer`.
+
+ @property elementStyle
+ @type Hash
+ @default {}
+ */
+ elementStyle: null,
+
+ /**
+ Nested `RenderBuffers` will set this to their parent `RenderBuffer`
+ instance.
+
+ @property parentBuffer
+ @type Ember._RenderBuffer
+ */
+ parentBuffer: null,
+
+ /**
+ Adds a string of HTML to the `RenderBuffer`.
+
+ @method push
+ @param {String} string HTML to push into the buffer
+ @chainable
+ */
+ push: function(string) {
+ this.buffer += string;
+ return this;
+ },
+
+ /**
+ Adds a class to the buffer, which will be rendered to the class attribute.
+
+ @method addClass
+ @param {String} className Class name to add to the buffer
+ @chainable
+ */
+ addClass: function(className) {
+ // lazily create elementClasses
+ this.elementClasses = (this.elementClasses || new ClassSet());
+ this.elementClasses.add(className);
+ this.classes = this.elementClasses.list;
+
+ return this;
+ },
+
+ setClasses: function(classNames) {
+ this.elementClasses = null;
+ var len = classNames.length, i;
+ for (i = 0; i < len; i++) {
+ this.addClass(classNames[i]);
+ }
+ },
+
+ /**
+ Sets the elementID to be used for the element.
+
+ @method id
+ @param {String} id
+ @chainable
+ */
+ id: function(id) {
+ this.elementId = id;
+ return this;
+ },
+
+ // duck type attribute functionality like jQuery so a render buffer
+ // can be used like a jQuery object in attribute binding scenarios.
+
+ /**
+ Adds an attribute which will be rendered to the element.
+
+ @method attr
+ @param {String} name The name of the attribute
+ @param {String} value The value to add to the attribute
+ @chainable
+ @return {Ember.RenderBuffer|String} this or the current attribute value
+ */
+ attr: function(name, value) {
+ var attributes = this.elementAttributes = (this.elementAttributes || {});
+
+ if (arguments.length === 1) {
+ return attributes[name];
+ } else {
+ attributes[name] = value;
+ }
+
+ return this;
+ },
+
+ /**
+ Remove an attribute from the list of attributes to render.
+
+ @method removeAttr
+ @param {String} name The name of the attribute
+ @chainable
+ */
+ removeAttr: function(name) {
+ var attributes = this.elementAttributes;
+ if (attributes) { delete attributes[name]; }
+
+ return this;
+ },
+
+ /**
+ Adds a property which will be rendered to the element.
+
+ @method prop
+ @param {String} name The name of the property
+ @param {String} value The value to add to the property
+ @chainable
+ @return {Ember.RenderBuffer|String} this or the current property value
+ */
+ prop: function(name, value) {
+ var properties = this.elementProperties = (this.elementProperties || {});
+
+ if (arguments.length === 1) {
+ return properties[name];
+ } else {
+ properties[name] = value;
+ }
+
+ return this;
+ },
+
+ /**
+ Remove an property from the list of properties to render.
+
+ @method removeProp
+ @param {String} name The name of the property
+ @chainable
+ */
+ removeProp: function(name) {
+ var properties = this.elementProperties;
+ if (properties) { delete properties[name]; }
+
+ return this;
+ },
+
+ /**
+ Adds a style to the style attribute which will be rendered to the element.
+
+ @method style
+ @param {String} name Name of the style
+ @param {String} value
+ @chainable
+ */
+ style: function(name, value) {
+ this.elementStyle = (this.elementStyle || {});
+
+ this.elementStyle[name] = value;
+ return this;
+ },
+
+ begin: function(tagName) {
+ this.tagNames.push(tagName || null);
+ return this;
+ },
+
+ pushOpeningTag: function() {
+ var tagName = this.currentTagName();
+ if (!tagName) { return; }
+
+ if (this._hasElement && !this._element && this.buffer.length === 0) {
+ this._element = this.generateElement();
+ return;
+ }
+
+ var buffer = this.buffer,
+ id = this.elementId,
+ classes = this.classes,
+ attrs = this.elementAttributes,
+ props = this.elementProperties,
+ style = this.elementStyle,
+ attr, prop;
+
+ buffer += '<' + stripTagName(tagName);
+
+ if (id) {
+ buffer += ' id="' + escapeAttribute(id) + '"';
+ this.elementId = null;
+ }
+ if (classes) {
+ buffer += ' class="' + escapeAttribute(classes.join(' ')) + '"';
+ this.classes = null;
+ this.elementClasses = null;
+ }
+
+ if (style) {
+ buffer += ' style="';
+
+ for (prop in style) {
+ if (style.hasOwnProperty(prop)) {
+ buffer += prop + ':' + escapeAttribute(style[prop]) + ';';
+ }
+ }
+
+ buffer += '"';
+
+ this.elementStyle = null;
+ }
+
+ if (attrs) {
+ for (attr in attrs) {
+ if (attrs.hasOwnProperty(attr)) {
+ buffer += ' ' + attr + '="' + escapeAttribute(attrs[attr]) + '"';
+ }
+ }
+
+ this.elementAttributes = null;
+ }
+
+ if (props) {
+ for (prop in props) {
+ if (props.hasOwnProperty(prop)) {
+ var value = props[prop];
+ if (value || typeof(value) === 'number') {
+ if (value === true) {
+ buffer += ' ' + prop + '="' + prop + '"';
+ } else {
+ buffer += ' ' + prop + '="' + escapeAttribute(props[prop]) + '"';
+ }
+ }
+ }
+ }
+
+ this.elementProperties = null;
+ }
+
+ buffer += '>';
+ this.buffer = buffer;
+ },
+
+ pushClosingTag: function() {
+ var tagName = this.tagNames.pop();
+ if (tagName) { this.buffer += '' + stripTagName(tagName) + '>'; }
+ },
+
+ currentTagName: function() {
+ return this.tagNames[this.tagNames.length-1];
+ },
+
+ generateElement: function() {
+ var tagName = this.tagNames.pop(), // pop since we don't need to close
+ id = this.elementId,
+ classes = this.classes,
+ attrs = this.elementAttributes,
+ props = this.elementProperties,
+ style = this.elementStyle,
+ styleBuffer = '', attr, prop, tagString;
+
+ if (attrs && attrs.name && !canSetNameOnInputs) {
+ // IE allows passing a tag to createElement. See note on `canSetNameOnInputs` above as well.
+ tagString = '<'+stripTagName(tagName)+' name="'+escapeAttribute(attrs.name)+'">';
+ } else {
+ tagString = tagName;
+ }
+
+ var element = document.createElement(tagString),
+ $element = Ember.$(element);
+
+ if (id) {
+ $element.attr('id', id);
+ this.elementId = null;
+ }
+ if (classes) {
+ $element.attr('class', classes.join(' '));
+ this.classes = null;
+ this.elementClasses = null;
+ }
+
+ if (style) {
+ for (prop in style) {
+ if (style.hasOwnProperty(prop)) {
+ styleBuffer += (prop + ':' + style[prop] + ';');
+ }
+ }
+
+ $element.attr('style', styleBuffer);
+
+ this.elementStyle = null;
+ }
+
+ if (attrs) {
+ for (attr in attrs) {
+ if (attrs.hasOwnProperty(attr)) {
+ $element.attr(attr, attrs[attr]);
+ }
+ }
+
+ this.elementAttributes = null;
+ }
+
+ if (props) {
+ for (prop in props) {
+ if (props.hasOwnProperty(prop)) {
+ $element.prop(prop, props[prop]);
+ }
+ }
+
+ this.elementProperties = null;
+ }
+
+ return element;
+ },
+
+ /**
+ @method element
+ @return {DOMElement} The element corresponding to the generated HTML
+ of this buffer
+ */
+ element: function() {
+ var html = this.innerString();
+
+ if (html) {
+ this._element = Ember.ViewUtils.setInnerHTML(this._element, html);
+ }
+
+ return this._element;
+ },
+
+ /**
+ Generates the HTML content for this buffer.
+
+ @method string
+ @return {String} The generated HTML
+ */
+ string: function() {
+ if (this._hasElement && this._element) {
+ // Firefox versions < 11 do not have support for element.outerHTML.
+ var thisElement = this.element(), outerHTML = thisElement.outerHTML;
+ if (typeof outerHTML === 'undefined') {
+ return Ember.$('').append(thisElement).html();
+ }
+ return outerHTML;
+ } else {
+ return this.innerString();
+ }
+ },
+
+ innerString: function() {
+ return this.buffer;
+ }
+};
+
+})();
+
+
+
+(function() {
+/**
+@module ember
+@submodule ember-views
+*/
+
+var get = Ember.get, set = Ember.set, fmt = Ember.String.fmt;
+
+/**
+ `Ember.EventDispatcher` handles delegating browser events to their
+ corresponding `Ember.Views.` For example, when you click on a view,
+ `Ember.EventDispatcher` ensures that that view's `mouseDown` method gets
+ called.
+
+ @class EventDispatcher
+ @namespace Ember
+ @private
+ @extends Ember.Object
+*/
+Ember.EventDispatcher = Ember.Object.extend({
+
+ /**
+ The set of events names (and associated handler function names) to be setup
+ and dispatched by the `EventDispatcher`. Custom events can added to this list at setup
+ time, generally via the `Ember.Application.customEvents` hash. Only override this
+ default set to prevent the EventDispatcher from listening on some events all together.
+
+ This set will be modified by `setup` to also include any events added at that time.
+
+ @property events
+ @type Object
+ */
+ events: {
+ 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'
+ },
+
+ /**
+ The root DOM element to which event listeners should be attached. Event
+ listeners will be attached to the document unless this is overridden.
+
+ Can be specified as a DOMElement or a selector string.
+
+ The default body is a string since this may be evaluated before document.body
+ exists in the DOM.
+
+ @private
+ @property rootElement
+ @type DOMElement
+ @default 'body'
+ */
+ rootElement: 'body',
+
+ /**
+ Sets up event listeners for standard browser events.
+
+ This will be called after the browser sends a `DOMContentReady` event. By
+ default, it will set up all of the listeners on the document body. If you
+ would like to register the listeners on a different element, set the event
+ dispatcher's `root` property.
+
+ @private
+ @method setup
+ @param addedEvents {Hash}
+ */
+ setup: function(addedEvents, rootElement) {
+ var event, events = get(this, 'events');
+
+ Ember.$.extend(events, addedEvents || {});
+
+
+ if (!Ember.isNone(rootElement)) {
+ set(this, 'rootElement', rootElement);
+ }
+
+ rootElement = Ember.$(get(this, 'rootElement'));
+
+ Ember.assert(fmt('You cannot use the same root element (%@) multiple times in an Ember.Application', [rootElement.selector || rootElement[0].tagName]), !rootElement.is('.ember-application'));
+ Ember.assert('You cannot make a new Ember.Application using a root element that is a descendent of an existing Ember.Application', !rootElement.closest('.ember-application').length);
+ Ember.assert('You cannot make a new Ember.Application using a root element that is an ancestor of an existing Ember.Application', !rootElement.find('.ember-application').length);
+
+ rootElement.addClass('ember-application');
+
+ Ember.assert('Unable to add "ember-application" class to rootElement. Make sure you set rootElement to the body or an element in the body.', rootElement.is('.ember-application'));
+
+ for (event in events) {
+ if (events.hasOwnProperty(event)) {
+ this.setupHandler(rootElement, event, events[event]);
+ }
+ }
+ },
+
+ /**
+ Registers an event listener on the document. If the given event is
+ triggered, the provided event handler will be triggered on the target view.
+
+ If the target view does not implement the event handler, or if the handler
+ returns `false`, the parent view will be called. The event will continue to
+ bubble to each successive parent view until it reaches the top.
+
+ For example, to have the `mouseDown` method called on the target view when
+ a `mousedown` event is received from the browser, do the following:
+
+ ```javascript
+ setupHandler('mousedown', 'mouseDown');
+ ```
+
+ @private
+ @method setupHandler
+ @param {Element} rootElement
+ @param {String} event the browser-originated event to listen to
+ @param {String} eventName the name of the method to call on the view
+ */
+ setupHandler: function(rootElement, event, eventName) {
+ var self = this;
+
+ rootElement.on(event + '.ember', '.ember-view', function(evt, triggeringManager) {
+ return Ember.handleErrors(function handleViewEvent() {
+ var view = Ember.View.views[this.id],
+ result = true, manager = null;
+
+ manager = self._findNearestEventManager(view,eventName);
+
+ if (manager && manager !== triggeringManager) {
+ result = self._dispatchEvent(manager, evt, eventName, view);
+ } else if (view) {
+ result = self._bubbleEvent(view,evt,eventName);
+ } else {
+ evt.stopPropagation();
+ }
+
+ return result;
+ }, this);
+ });
+
+ rootElement.on(event + '.ember', '[data-ember-action]', function(evt) {
+ return Ember.handleErrors(function handleActionEvent() {
+ var actionId = Ember.$(evt.currentTarget).attr('data-ember-action'),
+ action = Ember.Handlebars.ActionHelper.registeredActions[actionId];
+
+ // We have to check for action here since in some cases, jQuery will trigger
+ // an event on `removeChild` (i.e. focusout) after we've already torn down the
+ // action handlers for the view.
+ if (action && action.eventName === eventName) {
+ return action.handler(evt);
+ }
+ }, this);
+ });
+ },
+
+ _findNearestEventManager: function(view, eventName) {
+ var manager = null;
+
+ while (view) {
+ manager = get(view, 'eventManager');
+ if (manager && manager[eventName]) { break; }
+
+ view = get(view, 'parentView');
+ }
+
+ return manager;
+ },
+
+ _dispatchEvent: function(object, evt, eventName, view) {
+ var result = true;
+
+ var handler = object[eventName];
+ if (Ember.typeOf(handler) === 'function') {
+ result = Ember.run(function() {
+ return handler.call(object, evt, view);
+ });
+ // Do not preventDefault in eventManagers.
+ evt.stopPropagation();
+ }
+ else {
+ result = this._bubbleEvent(view, evt, eventName);
+ }
+
+ return result;
+ },
+
+ _bubbleEvent: function(view, evt, eventName) {
+ return Ember.run(function bubbleEvent() {
+ return view.handleEvent(eventName, evt);
+ });
+ },
+
+ destroy: function() {
+ var rootElement = get(this, 'rootElement');
+ Ember.$(rootElement).off('.ember', '**').removeClass('ember-application');
+ return this._super();
+ }
+});
+
+})();
+
+
+
+(function() {
+/**
+@module ember
+@submodule ember-views
+*/
+
+// Add a new named queue for rendering views that happens
+// after bindings have synced, and a queue for scheduling actions
+// that that should occur after view rendering.
+var queues = Ember.run.queues,
+ indexOf = Ember.ArrayPolyfills.indexOf;
+queues.splice(indexOf.call(queues, 'actions')+1, 0, 'render', 'afterRender');
+
+})();
+
+
+
+(function() {
+/**
+@module ember
+@submodule ember-views
+*/
+
+var get = Ember.get, set = Ember.set;
+
+// Original class declaration and documentation in runtime/lib/controllers/controller.js
+// NOTE: It may be possible with YUIDoc to combine docs in two locations
+
+/**
+Additional methods for the ControllerMixin
+
+@class ControllerMixin
+@namespace Ember
+*/
+Ember.ControllerMixin.reopen({
+ target: null,
+ namespace: null,
+ view: null,
+ container: null,
+ _childContainers: null,
+
+ init: function() {
+ this._super();
+ set(this, '_childContainers', {});
+ },
+
+ _modelDidChange: Ember.observer('model', function() {
+ var containers = get(this, '_childContainers');
+
+ for (var prop in containers) {
+ if (!containers.hasOwnProperty(prop)) { continue; }
+ containers[prop].destroy();
+ }
+
+ set(this, '_childContainers', {});
+ })
+});
+
+})();
+
+
+
+(function() {
+
+})();
+
+
+
+(function() {
+var states = {};
+
+/**
+@module ember
+@submodule ember-views
+*/
+
+var get = Ember.get, set = Ember.set;
+var guidFor = Ember.guidFor;
+var a_forEach = Ember.EnumerableUtils.forEach;
+var a_addObject = Ember.EnumerableUtils.addObject;
+var meta = Ember.meta;
+
+var childViewsProperty = Ember.computed(function() {
+ var childViews = this._childViews, ret = Ember.A(), view = this;
+
+ a_forEach(childViews, function(view) {
+ var currentChildViews;
+ if (view.isVirtual) {
+ if (currentChildViews = get(view, 'childViews')) {
+ ret.pushObjects(currentChildViews);
+ }
+ } else {
+ ret.push(view);
+ }
+ });
+
+ ret.replace = function (idx, removedCount, addedViews) {
+ if (view instanceof Ember.ContainerView) {
+ Ember.deprecate("Manipulating an Ember.ContainerView through its childViews property is deprecated. Please use the ContainerView instance itself as an Ember.MutableArray.");
+ return view.replace(idx, removedCount, addedViews);
+ }
+ throw new Ember.Error("childViews is immutable");
+ };
+
+ return ret;
+});
+
+Ember.warn("The VIEW_PRESERVES_CONTEXT flag has been removed and the functionality can no longer be disabled.", Ember.ENV.VIEW_PRESERVES_CONTEXT !== false);
+
+/**
+ Global hash of shared templates. This will automatically be populated
+ by the build tools so that you can store your Handlebars templates in
+ separate files that get loaded into JavaScript at buildtime.
+
+ @property TEMPLATES
+ @for Ember
+ @type Hash
+*/
+Ember.TEMPLATES = {};
+
+/**
+ `Ember.CoreView` is an abstract class that exists to give view-like behavior
+ to both Ember's main view class `Ember.View` and other classes like
+ `Ember._SimpleMetamorphView` that don't need the fully functionaltiy of
+ `Ember.View`.
+
+ Unless you have specific needs for `CoreView`, you will use `Ember.View`
+ in your applications.
+
+ @class CoreView
+ @namespace Ember
+ @extends Ember.Object
+ @uses Ember.Evented
+ @uses Ember.ActionHandler
+*/
+
+Ember.CoreView = Ember.Object.extend(Ember.Evented, Ember.ActionHandler, {
+ isView: true,
+
+ states: states,
+
+ init: function() {
+ this._super();
+ this.transitionTo('preRender');
+ },
+
+ /**
+ If the view is currently inserted into the DOM of a parent view, this
+ property will point to the parent of the view.
+
+ @property parentView
+ @type Ember.View
+ @default null
+ */
+ parentView: Ember.computed(function() {
+ var parent = this._parentView;
+
+ if (parent && parent.isVirtual) {
+ return get(parent, 'parentView');
+ } else {
+ return parent;
+ }
+ }).property('_parentView'),
+
+ state: null,
+
+ _parentView: null,
+
+ // return the current view, not including virtual views
+ concreteView: Ember.computed(function() {
+ if (!this.isVirtual) { return this; }
+ else { return get(this, 'parentView'); }
+ }).property('parentView'),
+
+ instrumentName: 'core_view',
+
+ instrumentDetails: function(hash) {
+ hash.object = this.toString();
+ },
+
+ /**
+ Invoked by the view system when this view needs to produce an HTML
+ representation. This method will create a new render buffer, if needed,
+ then apply any default attributes, such as class names and visibility.
+ Finally, the `render()` method is invoked, which is responsible for
+ doing the bulk of the rendering.
+
+ You should not need to override this method; instead, implement the
+ `template` property, or if you need more control, override the `render`
+ method.
+
+ @method renderToBuffer
+ @param {Ember.RenderBuffer} buffer the render buffer. If no buffer is
+ passed, a default buffer, using the current view's `tagName`, will
+ be used.
+ @private
+ */
+ renderToBuffer: function(parentBuffer, bufferOperation) {
+ var name = 'render.' + this.instrumentName,
+ details = {};
+
+ this.instrumentDetails(details);
+
+ return Ember.instrument(name, details, function instrumentRenderToBuffer() {
+ return this._renderToBuffer(parentBuffer, bufferOperation);
+ }, this);
+ },
+
+ _renderToBuffer: function(parentBuffer, bufferOperation) {
+ // If this is the top-most view, start a new buffer. Otherwise,
+ // create a new buffer relative to the original using the
+ // provided buffer operation (for example, `insertAfter` will
+ // insert a new buffer after the "parent buffer").
+ var tagName = this.tagName;
+
+ if (tagName === null || tagName === undefined) {
+ tagName = 'div';
+ }
+
+ var buffer = this.buffer = parentBuffer && parentBuffer.begin(tagName) || Ember.RenderBuffer(tagName);
+ this.transitionTo('inBuffer', false);
+
+ this.beforeRender(buffer);
+ this.render(buffer);
+ this.afterRender(buffer);
+
+ return buffer;
+ },
+
+ /**
+ Override the default event firing from `Ember.Evented` to
+ also call methods with the given name.
+
+ @method trigger
+ @param name {String}
+ @private
+ */
+ trigger: function(name) {
+ this._super.apply(this, arguments);
+ var method = this[name];
+ if (method) {
+ var args = [], i, l;
+ for (i = 1, l = arguments.length; i < l; i++) {
+ args.push(arguments[i]);
+ }
+ return method.apply(this, args);
+ }
+ },
+
+ deprecatedSendHandles: function(actionName) {
+ return !!this[actionName];
+ },
+
+ deprecatedSend: function(actionName) {
+ var args = [].slice.call(arguments, 1);
+ Ember.assert('' + this + " has the action " + actionName + " but it is not a function", typeof this[actionName] === 'function');
+ Ember.deprecate('Action handlers implemented directly on views are deprecated in favor of action handlers on an `actions` object ( action: `' + actionName + '` on ' + this + ')', false);
+ this[actionName].apply(this, args);
+ return;
+ },
+
+ has: function(name) {
+ return Ember.typeOf(this[name]) === 'function' || this._super(name);
+ },
+
+ destroy: function() {
+ var parent = this._parentView;
+
+ if (!this._super()) { return; }
+
+ // destroy the element -- this will avoid each child view destroying
+ // the element over and over again...
+ if (!this.removedFromDOM) { this.destroyElement(); }
+
+ // remove from parent if found. Don't call removeFromParent,
+ // as removeFromParent will try to remove the element from
+ // the DOM again.
+ if (parent) { parent.removeChild(this); }
+
+ this.transitionTo('destroying', false);
+
+ return this;
+ },
+
+ clearRenderedChildren: Ember.K,
+ triggerRecursively: Ember.K,
+ invokeRecursively: Ember.K,
+ transitionTo: Ember.K,
+ destroyElement: Ember.K
+});
+
+var ViewCollection = Ember._ViewCollection = function(initialViews) {
+ var views = this.views = initialViews || [];
+ this.length = views.length;
+};
+
+ViewCollection.prototype = {
+ length: 0,
+
+ trigger: function(eventName) {
+ var views = this.views, view;
+ for (var i = 0, l = views.length; i < l; i++) {
+ view = views[i];
+ if (view.trigger) { view.trigger(eventName); }
+ }
+ },
+
+ triggerRecursively: function(eventName) {
+ var views = this.views;
+ for (var i = 0, l = views.length; i < l; i++) {
+ views[i].triggerRecursively(eventName);
+ }
+ },
+
+ invokeRecursively: function(fn) {
+ var views = this.views, view;
+
+ for (var i = 0, l = views.length; i < l; i++) {
+ view = views[i];
+ fn(view);
+ }
+ },
+
+ transitionTo: function(state, children) {
+ var views = this.views;
+ for (var i = 0, l = views.length; i < l; i++) {
+ views[i].transitionTo(state, children);
+ }
+ },
+
+ push: function() {
+ this.length += arguments.length;
+ var views = this.views;
+ return views.push.apply(views, arguments);
+ },
+
+ objectAt: function(idx) {
+ return this.views[idx];
+ },
+
+ forEach: function(callback) {
+ var views = this.views;
+ return a_forEach(views, callback);
+ },
+
+ clear: function() {
+ this.length = 0;
+ this.views.length = 0;
+ }
+};
+
+var EMPTY_ARRAY = [];
+
+/**
+ `Ember.View` is the class in Ember responsible for encapsulating templates of
+ HTML content, combining templates with data to render as sections of a page's
+ DOM, and registering and responding to user-initiated events.
+
+ ## HTML Tag
+
+ The default HTML tag name used for a view's DOM representation is `div`. This
+ can be customized by setting the `tagName` property. The following view
+ class:
+
+ ```javascript
+ ParagraphView = Ember.View.extend({
+ tagName: 'em'
+ });
+ ```
+
+ Would result in instances with the following HTML:
+
+ ```html
+
+ ```
+
+ ## HTML `class` Attribute
+
+ The HTML `class` attribute of a view's tag can be set by providing a
+ `classNames` property that is set to an array of strings:
+
+ ```javascript
+ MyView = Ember.View.extend({
+ classNames: ['my-class', 'my-other-class']
+ });
+ ```
+
+ Will result in view instances with an HTML representation of:
+
+ ```html
+
+ ```
+
+ `class` attribute values can also be set by providing a `classNameBindings`
+ property set to an array of properties names for the view. The return value
+ of these properties will be added as part of the value for the view's `class`
+ attribute. These properties can be computed properties:
+
+ ```javascript
+ MyView = Ember.View.extend({
+ classNameBindings: ['propertyA', 'propertyB'],
+ propertyA: 'from-a',
+ propertyB: function() {
+ if (someLogic) { return 'from-b'; }
+ }.property()
+ });
+ ```
+
+ Will result in view instances with an HTML representation of:
+
+ ```html
+
+ ```
+
+ If the value of a class name binding returns a boolean the property name
+ itself will be used as the class name if the property is true. The class name
+ will not be added if the value is `false` or `undefined`.
+
+ ```javascript
+ MyView = Ember.View.extend({
+ classNameBindings: ['hovered'],
+ hovered: true
+ });
+ ```
+
+ Will result in view instances with an HTML representation of:
+
+ ```html
+
+ ```
+
+ When using boolean class name bindings you can supply a string value other
+ than the property name for use as the `class` HTML attribute by appending the
+ preferred value after a ":" character when defining the binding:
+
+ ```javascript
+ MyView = Ember.View.extend({
+ classNameBindings: ['awesome:so-very-cool'],
+ awesome: true
+ });
+ ```
+
+ Will result in view instances with an HTML representation of:
+
+ ```html
+
+ ```
+
+ Boolean value class name bindings whose property names are in a
+ camelCase-style format will be converted to a dasherized format:
+
+ ```javascript
+ MyView = Ember.View.extend({
+ classNameBindings: ['isUrgent'],
+ isUrgent: true
+ });
+ ```
+
+ Will result in view instances with an HTML representation of:
+
+ ```html
+
+ ```
+
+ Class name bindings can also refer to object values that are found by
+ traversing a path relative to the view itself:
+
+ ```javascript
+ MyView = Ember.View.extend({
+ classNameBindings: ['messages.empty']
+ messages: Ember.Object.create({
+ empty: true
+ })
+ });
+ ```
+
+ Will result in view instances with an HTML representation of:
+
+ ```html
+
+ ```
+
+ If you want to add a class name for a property which evaluates to true and
+ and a different class name if it evaluates to false, you can pass a binding
+ like this:
+
+ ```javascript
+ // Applies 'enabled' class when isEnabled is true and 'disabled' when isEnabled is false
+ Ember.View.extend({
+ classNameBindings: ['isEnabled:enabled:disabled']
+ isEnabled: true
+ });
+ ```
+
+ Will result in view instances with an HTML representation of:
+
+ ```html
+
+ ```
+
+ When isEnabled is `false`, the resulting HTML reprensentation looks like
+ this:
+
+ ```html
+
+ ```
+
+ This syntax offers the convenience to add a class if a property is `false`:
+
+ ```javascript
+ // Applies no class when isEnabled is true and class 'disabled' when isEnabled is false
+ Ember.View.extend({
+ classNameBindings: ['isEnabled::disabled']
+ isEnabled: true
+ });
+ ```
+
+ Will result in view instances with an HTML representation of:
+
+ ```html
+
+ ```
+
+ When the `isEnabled` property on the view is set to `false`, it will result
+ in view instances with an HTML representation of:
+
+ ```html
+
+ ```
+
+ Updates to the the value of a class name binding will result in automatic
+ update of the HTML `class` attribute in the view's rendered HTML
+ representation. If the value becomes `false` or `undefined` the class name
+ will be removed.
+
+ Both `classNames` and `classNameBindings` are concatenated properties. See
+ [Ember.Object](/api/classes/Ember.Object.html) documentation for more
+ information about concatenated properties.
+
+ ## HTML Attributes
+
+ The HTML attribute section of a view's tag can be set by providing an
+ `attributeBindings` property set to an array of property names on the view.
+ The return value of these properties will be used as the value of the view's
+ HTML associated attribute:
+
+ ```javascript
+ AnchorView = Ember.View.extend({
+ tagName: 'a',
+ attributeBindings: ['href'],
+ href: 'http://google.com'
+ });
+ ```
+
+ Will result in view instances with an HTML representation of:
+
+ ```html
+
+ ```
+
+ If the return value of an `attributeBindings` monitored property is a boolean
+ the property will follow HTML's pattern of repeating the attribute's name as
+ its value:
+
+ ```javascript
+ MyTextInput = Ember.View.extend({
+ tagName: 'input',
+ attributeBindings: ['disabled'],
+ disabled: true
+ });
+ ```
+
+ Will result in view instances with an HTML representation of:
+
+ ```html
+
+ ```
+
+ `attributeBindings` can refer to computed properties:
+
+ ```javascript
+ MyTextInput = Ember.View.extend({
+ tagName: 'input',
+ attributeBindings: ['disabled'],
+ disabled: function() {
+ if (someLogic) {
+ return true;
+ } else {
+ return false;
+ }
+ }.property()
+ });
+ ```
+
+ Updates to the the property of an attribute binding will result in automatic
+ update of the HTML attribute in the view's rendered HTML representation.
+
+ `attributeBindings` is a concatenated property. See [Ember.Object](/api/classes/Ember.Object.html)
+ documentation for more information about concatenated properties.
+
+ ## Templates
+
+ The HTML contents of a view's rendered representation are determined by its
+ template. Templates can be any function that accepts an optional context
+ parameter and returns a string of HTML that will be inserted within the
+ view's tag. Most typically in Ember this function will be a compiled
+ `Ember.Handlebars` template.
+
+ ```javascript
+ AView = Ember.View.extend({
+ template: Ember.Handlebars.compile('I am the template')
+ });
+ ```
+
+ Will result in view instances with an HTML representation of:
+
+ ```html
+
I am the template
+ ```
+
+ Within an Ember application is more common to define a Handlebars templates as
+ part of a page:
+
+ ```html
+
+ ```
+
+ And associate it by name using a view's `templateName` property:
+
+ ```javascript
+ AView = Ember.View.extend({
+ templateName: 'some-template'
+ });
+ ```
+
+ If you have nested resources, your Handlebars template will look like this:
+
+ ```html
+
+ ```
+
+ And `templateName` property:
+
+ ```javascript
+ AView = Ember.View.extend({
+ templateName: 'posts/new'
+ });
+ ```
+
+ Using a value for `templateName` that does not have a Handlebars template
+ with a matching `data-template-name` attribute will throw an error.
+
+ For views classes that may have a template later defined (e.g. as the block
+ portion of a `{{view}}` Handlebars helper call in another template or in
+ a subclass), you can provide a `defaultTemplate` property set to compiled
+ template function. If a template is not later provided for the view instance
+ the `defaultTemplate` value will be used:
+
+ ```javascript
+ AView = Ember.View.extend({
+ defaultTemplate: Ember.Handlebars.compile('I was the default'),
+ template: null,
+ templateName: null
+ });
+ ```
+
+ Will result in instances with an HTML representation of:
+
+ ```html
+
I was the default
+ ```
+
+ If a `template` or `templateName` is provided it will take precedence over
+ `defaultTemplate`:
+
+ ```javascript
+ AView = Ember.View.extend({
+ defaultTemplate: Ember.Handlebars.compile('I was the default')
+ });
+
+ aView = AView.create({
+ template: Ember.Handlebars.compile('I was the template, not default')
+ });
+ ```
+
+ Will result in the following HTML representation when rendered:
+
+ ```html
+
I was the template, not default
+ ```
+
+ ## View Context
+
+ The default context of the compiled template is the view's controller:
+
+ ```javascript
+ AView = Ember.View.extend({
+ template: Ember.Handlebars.compile('Hello {{excitedGreeting}}')
+ });
+
+ aController = Ember.Object.create({
+ firstName: 'Barry',
+ excitedGreeting: function() {
+ return this.get("content.firstName") + "!!!"
+ }.property()
+ });
+
+ aView = AView.create({
+ controller: aController,
+ });
+ ```
+
+ Will result in an HTML representation of:
+
+ ```html
+
Hello Barry!!!
+ ```
+
+ A context can also be explicitly supplied through the view's `context`
+ property. If the view has neither `context` nor `controller` properties, the
+ `parentView`'s context will be used.
+
+ ## Layouts
+
+ Views can have a secondary template that wraps their main template. Like
+ primary templates, layouts can be any function that accepts an optional
+ context parameter and returns a string of HTML that will be inserted inside
+ view's tag. Views whose HTML element is self closing (e.g. ``)
+ cannot have a layout and this property will be ignored.
+
+ Most typically in Ember a layout will be a compiled `Ember.Handlebars`
+ template.
+
+ A view's layout can be set directly with the `layout` property or reference
+ an existing Handlebars template by name with the `layoutName` property.
+
+ A template used as a layout must contain a single use of the Handlebars
+ `{{yield}}` helper. The HTML contents of a view's rendered `template` will be
+ inserted at this location:
+
+ ```javascript
+ AViewWithLayout = Ember.View.extend({
+ layout: Ember.Handlebars.compile("
{{yield}}
")
+ template: Ember.Handlebars.compile("I got wrapped"),
+ });
+ ```
+
+ Will result in view instances with an HTML representation of:
+
+ ```html
+
+
+ I got wrapped
+
+
+ ```
+
+ See [Ember.Handlebars.helpers.yield](/api/classes/Ember.Handlebars.helpers.html#method_yield)
+ for more information.
+
+ ## Responding to Browser Events
+
+ Views can respond to user-initiated events in one of three ways: method
+ implementation, through an event manager, and through `{{action}}` helper use
+ in their template or layout.
+
+ ### Method Implementation
+
+ Views can respond to user-initiated events by implementing a method that
+ matches the event name. A `jQuery.Event` object will be passed as the
+ argument to this method.
+
+ ```javascript
+ AView = Ember.View.extend({
+ click: function(event) {
+ // will be called when when an instance's
+ // rendered element is clicked
+ }
+ });
+ ```
+
+ ### Event Managers
+
+ Views can define an object as their `eventManager` property. This object can
+ then implement methods that match the desired event names. Matching events
+ that occur on the view's rendered HTML or the rendered HTML of any of its DOM
+ descendants will trigger this method. A `jQuery.Event` object will be passed
+ as the first argument to the method and an `Ember.View` object as the
+ second. The `Ember.View` will be the view whose rendered HTML was interacted
+ with. This may be the view with the `eventManager` property or one of its
+ descendent views.
+
+ ```javascript
+ AView = Ember.View.extend({
+ eventManager: Ember.Object.create({
+ doubleClick: function(event, view) {
+ // will be called when when an instance's
+ // rendered element or any rendering
+ // of this views's descendent
+ // elements is clicked
+ }
+ })
+ });
+ ```
+
+ An event defined for an event manager takes precedence over events of the
+ same name handled through methods on the view.
+
+ ```javascript
+ AView = Ember.View.extend({
+ mouseEnter: function(event) {
+ // will never trigger.
+ },
+ eventManager: Ember.Object.create({
+ mouseEnter: function(event, view) {
+ // takes precedence over AView#mouseEnter
+ }
+ })
+ });
+ ```
+
+ Similarly a view's event manager will take precedence for events of any views
+ rendered as a descendent. A method name that matches an event name will not
+ be called if the view instance was rendered inside the HTML representation of
+ a view that has an `eventManager` property defined that handles events of the
+ name. Events not handled by the event manager will still trigger method calls
+ on the descendent.
+
+ ```javascript
+ OuterView = Ember.View.extend({
+ template: Ember.Handlebars.compile("outer {{#view InnerView}}inner{{/view}} outer"),
+ eventManager: Ember.Object.create({
+ mouseEnter: function(event, view) {
+ // view might be instance of either
+ // OuterView or InnerView depending on
+ // where on the page the user interaction occured
+ }
+ })
+ });
+
+ InnerView = Ember.View.extend({
+ click: function(event) {
+ // will be called if rendered inside
+ // an OuterView because OuterView's
+ // eventManager doesn't handle click events
+ },
+ mouseEnter: function(event) {
+ // will never be called if rendered inside
+ // an OuterView.
+ }
+ });
+ ```
+
+ ### Handlebars `{{action}}` Helper
+
+ See [Handlebars.helpers.action](/api/classes/Ember.Handlebars.helpers.html#method_action).
+
+ ### Event Names
+
+ All of the event handling approaches described above respond to the same set
+ of events. The names of the built-in events are listed below. (The hash of
+ built-in events exists in `Ember.EventDispatcher`.) Additional, custom events
+ can be registered by using `Ember.Application.customEvents`.
+
+ Touch events:
+
+ * `touchStart`
+ * `touchMove`
+ * `touchEnd`
+ * `touchCancel`
+
+ Keyboard events
+
+ * `keyDown`
+ * `keyUp`
+ * `keyPress`
+
+ Mouse events
+
+ * `mouseDown`
+ * `mouseUp`
+ * `contextMenu`
+ * `click`
+ * `doubleClick`
+ * `mouseMove`
+ * `focusIn`
+ * `focusOut`
+ * `mouseEnter`
+ * `mouseLeave`
+
+ Form events:
+
+ * `submit`
+ * `change`
+ * `focusIn`
+ * `focusOut`
+ * `input`
+
+ HTML5 drag and drop events:
+
+ * `dragStart`
+ * `drag`
+ * `dragEnter`
+ * `dragLeave`
+ * `drop`
+ * `dragEnd`
+
+ ## Handlebars `{{view}}` Helper
+
+ Other `Ember.View` instances can be included as part of a view's template by
+ using the `{{view}}` Handlebars helper. See [Ember.Handlebars.helpers.view](/api/classes/Ember.Handlebars.helpers.html#method_view)
+ for additional information.
+
+ @class View
+ @namespace Ember
+ @extends Ember.CoreView
+*/
+Ember.View = Ember.CoreView.extend({
+
+ concatenatedProperties: ['classNames', 'classNameBindings', 'attributeBindings'],
+
+ /**
+ @property isView
+ @type Boolean
+ @default true
+ @static
+ */
+ isView: true,
+
+ // ..........................................................
+ // TEMPLATE SUPPORT
+ //
+
+ /**
+ The name of the template to lookup if no template is provided.
+
+ By default `Ember.View` will lookup a template with this name in
+ `Ember.TEMPLATES` (a shared global object).
+
+ @property templateName
+ @type String
+ @default null
+ */
+ templateName: null,
+
+ /**
+ The name of the layout to lookup if no layout is provided.
+
+ By default `Ember.View` will lookup a template with this name in
+ `Ember.TEMPLATES` (a shared global object).
+
+ @property layoutName
+ @type String
+ @default null
+ */
+ layoutName: null,
+
+ /**
+ The template used to render the view. This should be a function that
+ accepts an optional context parameter and returns a string of HTML that
+ will be inserted into the DOM relative to its parent view.
+
+ In general, you should set the `templateName` property instead of setting
+ the template yourself.
+
+ @property template
+ @type Function
+ */
+ template: Ember.computed(function(key, value) {
+ if (value !== undefined) { return value; }
+
+ var templateName = get(this, 'templateName'),
+ template = this.templateForName(templateName, 'template');
+
+ Ember.assert("You specified the templateName " + templateName + " for " + this + ", but it did not exist.", !templateName || template);
+
+ return template || get(this, 'defaultTemplate');
+ }).property('templateName'),
+
+ /**
+ The controller managing this view. If this property is set, it will be
+ made available for use by the template.
+
+ @property controller
+ @type Object
+ */
+ controller: Ember.computed(function(key) {
+ var parentView = get(this, '_parentView');
+ return parentView ? get(parentView, 'controller') : null;
+ }).property('_parentView'),
+
+ /**
+ A view may contain a layout. A layout is a regular template but
+ supersedes the `template` property during rendering. It is the
+ responsibility of the layout template to retrieve the `template`
+ property from the view (or alternatively, call `Handlebars.helpers.yield`,
+ `{{yield}}`) to render it in the correct location.
+
+ This is useful for a view that has a shared wrapper, but which delegates
+ the rendering of the contents of the wrapper to the `template` property
+ on a subclass.
+
+ @property layout
+ @type Function
+ */
+ layout: Ember.computed(function(key) {
+ var layoutName = get(this, 'layoutName'),
+ layout = this.templateForName(layoutName, 'layout');
+
+ Ember.assert("You specified the layoutName " + layoutName + " for " + this + ", but it did not exist.", !layoutName || layout);
+
+ return layout || get(this, 'defaultLayout');
+ }).property('layoutName'),
+
+ _yield: function(context, options) {
+ var template = get(this, 'template');
+ if (template) { template(context, options); }
+ },
+
+ templateForName: function(name, type) {
+ if (!name) { return; }
+ Ember.assert("templateNames are not allowed to contain periods: "+name, name.indexOf('.') === -1);
+
+ // the defaultContainer is deprecated
+ var container = this.container || (Ember.Container && Ember.Container.defaultContainer);
+ return container && container.lookup('template:' + name);
+ },
+
+ /**
+ The object from which templates should access properties.
+
+ This object will be passed to the template function each time the render
+ method is called, but it is up to the individual function to decide what
+ to do with it.
+
+ By default, this will be the view's controller.
+
+ @property context
+ @type Object
+ */
+ context: Ember.computed(function(key, value) {
+ if (arguments.length === 2) {
+ set(this, '_context', value);
+ return value;
+ } else {
+ return get(this, '_context');
+ }
+ }).volatile(),
+
+ /**
+ Private copy of the view's template context. This can be set directly
+ by Handlebars without triggering the observer that causes the view
+ to be re-rendered.
+
+ The context of a view is looked up as follows:
+
+ 1. Supplied context (usually by Handlebars)
+ 2. Specified controller
+ 3. `parentView`'s context (for a child of a ContainerView)
+
+ The code in Handlebars that overrides the `_context` property first
+ checks to see whether the view has a specified controller. This is
+ something of a hack and should be revisited.
+
+ @property _context
+ @private
+ */
+ _context: Ember.computed(function(key) {
+ var parentView, controller;
+
+ if (controller = get(this, 'controller')) {
+ return controller;
+ }
+
+ parentView = this._parentView;
+ if (parentView) {
+ return get(parentView, '_context');
+ }
+
+ return null;
+ }),
+
+ /**
+ If a value that affects template rendering changes, the view should be
+ re-rendered to reflect the new value.
+
+ @method _contextDidChange
+ @private
+ */
+ _contextDidChange: Ember.observer('context', function() {
+ this.rerender();
+ }),
+
+ /**
+ If `false`, the view will appear hidden in DOM.
+
+ @property isVisible
+ @type Boolean
+ @default null
+ */
+ isVisible: true,
+
+ /**
+ Array of child views. You should never edit this array directly.
+ Instead, use `appendChild` and `removeFromParent`.
+
+ @property childViews
+ @type Array
+ @default []
+ @private
+ */
+ childViews: childViewsProperty,
+
+ _childViews: EMPTY_ARRAY,
+
+ // When it's a virtual view, we need to notify the parent that their
+ // childViews will change.
+ _childViewsWillChange: Ember.beforeObserver('childViews', function() {
+ if (this.isVirtual) {
+ var parentView = get(this, 'parentView');
+ if (parentView) { Ember.propertyWillChange(parentView, 'childViews'); }
+ }
+ }),
+
+ // When it's a virtual view, we need to notify the parent that their
+ // childViews did change.
+ _childViewsDidChange: Ember.observer('childViews', function() {
+ if (this.isVirtual) {
+ var parentView = get(this, 'parentView');
+ if (parentView) { Ember.propertyDidChange(parentView, 'childViews'); }
+ }
+ }),
+
+ /**
+ Return the nearest ancestor that is an instance of the provided
+ class.
+
+ @property nearestInstanceOf
+ @param {Class} klass Subclass of Ember.View (or Ember.View itself)
+ @return Ember.View
+ @deprecated
+ */
+ nearestInstanceOf: function(klass) {
+ Ember.deprecate("nearestInstanceOf is deprecated and will be removed from future releases. Use nearestOfType.");
+ var view = get(this, 'parentView');
+
+ while (view) {
+ if (view instanceof klass) { return view; }
+ view = get(view, 'parentView');
+ }
+ },
+
+ /**
+ Return the nearest ancestor that is an instance of the provided
+ class or mixin.
+
+ @property nearestOfType
+ @param {Class,Mixin} klass Subclass of Ember.View (or Ember.View itself),
+ or an instance of Ember.Mixin.
+ @return Ember.View
+ */
+ nearestOfType: function(klass) {
+ var view = get(this, 'parentView'),
+ isOfType = klass instanceof Ember.Mixin ?
+ function(view) { return klass.detect(view); } :
+ function(view) { return klass.detect(view.constructor); };
+
+ while (view) {
+ if (isOfType(view)) { return view; }
+ view = get(view, 'parentView');
+ }
+ },
+
+ /**
+ Return the nearest ancestor that has a given property.
+
+ @function nearestWithProperty
+ @param {String} property A property name
+ @return Ember.View
+ */
+ nearestWithProperty: function(property) {
+ var view = get(this, 'parentView');
+
+ while (view) {
+ if (property in view) { return view; }
+ view = get(view, 'parentView');
+ }
+ },
+
+ /**
+ Return the nearest ancestor whose parent is an instance of
+ `klass`.
+
+ @method nearestChildOf
+ @param {Class} klass Subclass of Ember.View (or Ember.View itself)
+ @return Ember.View
+ */
+ nearestChildOf: function(klass) {
+ var view = get(this, 'parentView');
+
+ while (view) {
+ if (get(view, 'parentView') instanceof klass) { return view; }
+ view = get(view, 'parentView');
+ }
+ },
+
+ /**
+ When the parent view changes, recursively invalidate `controller`
+
+ @method _parentViewDidChange
+ @private
+ */
+ _parentViewDidChange: Ember.observer('_parentView', function() {
+ if (this.isDestroying) { return; }
+
+ this.trigger('parentViewDidChange');
+
+ if (get(this, 'parentView.controller') && !get(this, 'controller')) {
+ this.notifyPropertyChange('controller');
+ }
+ }),
+
+ _controllerDidChange: Ember.observer('controller', function() {
+ if (this.isDestroying) { return; }
+
+ this.rerender();
+
+ this.forEachChildView(function(view) {
+ view.propertyDidChange('controller');
+ });
+ }),
+
+ cloneKeywords: function() {
+ var templateData = get(this, 'templateData');
+
+ var keywords = templateData ? Ember.copy(templateData.keywords) : {};
+ set(keywords, 'view', get(this, 'concreteView'));
+ set(keywords, '_view', this);
+ set(keywords, 'controller', get(this, 'controller'));
+
+ return keywords;
+ },
+
+ /**
+ Called on your view when it should push strings of HTML into a
+ `Ember.RenderBuffer`. Most users will want to override the `template`
+ or `templateName` properties instead of this method.
+
+ By default, `Ember.View` will look for a function in the `template`
+ property and invoke it with the value of `context`. The value of
+ `context` will be the view's controller unless you override it.
+
+ @method render
+ @param {Ember.RenderBuffer} buffer The render buffer
+ */
+ render: function(buffer) {
+ // If this view has a layout, it is the responsibility of the
+ // the layout to render the view's template. Otherwise, render the template
+ // directly.
+ var template = get(this, 'layout') || get(this, 'template');
+
+ if (template) {
+ var context = get(this, 'context');
+ var keywords = this.cloneKeywords();
+ var output;
+
+ var data = {
+ view: this,
+ buffer: buffer,
+ isRenderData: true,
+ keywords: keywords,
+ insideGroup: get(this, 'templateData.insideGroup')
+ };
+
+ // Invoke the template with the provided template context, which
+ // is the view's controller by default. A hash of data is also passed that provides
+ // the template with access to the view and render buffer.
+
+ Ember.assert('template must be a function. Did you mean to call Ember.Handlebars.compile("...") or specify templateName instead?', typeof template === 'function');
+ // The template should write directly to the render buffer instead
+ // of returning a string.
+ output = template(context, { data: data });
+
+ // If the template returned a string instead of writing to the buffer,
+ // push the string onto the buffer.
+ if (output !== undefined) { buffer.push(output); }
+ }
+ },
+
+ /**
+ Renders the view again. This will work regardless of whether the
+ view is already in the DOM or not. If the view is in the DOM, the
+ rendering process will be deferred to give bindings a chance
+ to synchronize.
+
+ If children were added during the rendering process using `appendChild`,
+ `rerender` will remove them, because they will be added again
+ if needed by the next `render`.
+
+ In general, if the display of your view changes, you should modify
+ the DOM element directly instead of manually calling `rerender`, which can
+ be slow.
+
+ @method rerender
+ */
+ rerender: function() {
+ return this.currentState.rerender(this);
+ },
+
+ clearRenderedChildren: function() {
+ var lengthBefore = this.lengthBeforeRender,
+ lengthAfter = this.lengthAfterRender;
+
+ // If there were child views created during the last call to render(),
+ // remove them under the assumption that they will be re-created when
+ // we re-render.
+
+ // VIEW-TODO: Unit test this path.
+ var childViews = this._childViews;
+ for (var i=lengthAfter-1; i>=lengthBefore; i--) {
+ if (childViews[i]) { childViews[i].destroy(); }
+ }
+ },
+
+ /**
+ Iterates over the view's `classNameBindings` array, inserts the value
+ of the specified property into the `classNames` array, then creates an
+ observer to update the view's element if the bound property ever changes
+ in the future.
+
+ @method _applyClassNameBindings
+ @private
+ */
+ _applyClassNameBindings: function(classBindings) {
+ var classNames = this.classNames,
+ elem, newClass, dasherizedClass;
+
+ // Loop through all of the configured bindings. These will be either
+ // property names ('isUrgent') or property paths relative to the view
+ // ('content.isUrgent')
+ a_forEach(classBindings, function(binding) {
+
+ Ember.assert("classNameBindings must not have spaces in them. Multiple class name bindings can be provided as elements of an array, e.g. ['foo', ':bar']", binding.indexOf(' ') === -1);
+
+ // Variable in which the old class value is saved. The observer function
+ // closes over this variable, so it knows which string to remove when
+ // the property changes.
+ var oldClass;
+ // Extract just the property name from bindings like 'foo:bar'
+ var parsedPath = Ember.View._parsePropertyPath(binding);
+
+ // Set up an observer on the context. If the property changes, toggle the
+ // class name.
+ var observer = function() {
+ // Get the current value of the property
+ newClass = this._classStringForProperty(binding);
+ elem = this.$();
+
+ // If we had previously added a class to the element, remove it.
+ if (oldClass) {
+ elem.removeClass(oldClass);
+ // Also remove from classNames so that if the view gets rerendered,
+ // the class doesn't get added back to the DOM.
+ classNames.removeObject(oldClass);
+ }
+
+ // If necessary, add a new class. Make sure we keep track of it so
+ // it can be removed in the future.
+ if (newClass) {
+ elem.addClass(newClass);
+ oldClass = newClass;
+ } else {
+ oldClass = null;
+ }
+ };
+
+ // Get the class name for the property at its current value
+ dasherizedClass = this._classStringForProperty(binding);
+
+ if (dasherizedClass) {
+ // Ensure that it gets into the classNames array
+ // so it is displayed when we render.
+ a_addObject(classNames, dasherizedClass);
+
+ // Save a reference to the class name so we can remove it
+ // if the observer fires. Remember that this variable has
+ // been closed over by the observer.
+ oldClass = dasherizedClass;
+ }
+
+ this.registerObserver(this, parsedPath.path, observer);
+ // Remove className so when the view is rerendered,
+ // the className is added based on binding reevaluation
+ this.one('willClearRender', function() {
+ if (oldClass) {
+ classNames.removeObject(oldClass);
+ oldClass = null;
+ }
+ });
+
+ }, this);
+ },
+
+ /**
+ Iterates through the view's attribute bindings, sets up observers for each,
+ then applies the current value of the attributes to the passed render buffer.
+
+ @method _applyAttributeBindings
+ @param {Ember.RenderBuffer} buffer
+ @private
+ */
+ _applyAttributeBindings: function(buffer, attributeBindings) {
+ var attributeValue, elem;
+
+ a_forEach(attributeBindings, function(binding) {
+ var split = binding.split(':'),
+ property = split[0],
+ attributeName = split[1] || property;
+
+ // Create an observer to add/remove/change the attribute if the
+ // JavaScript property changes.
+ var observer = function() {
+ elem = this.$();
+
+ attributeValue = get(this, property);
+
+ Ember.View.applyAttributeBindings(elem, attributeName, attributeValue);
+ };
+
+ this.registerObserver(this, property, observer);
+
+ // Determine the current value and add it to the render buffer
+ // if necessary.
+ attributeValue = get(this, property);
+ Ember.View.applyAttributeBindings(buffer, attributeName, attributeValue);
+ }, this);
+ },
+
+ /**
+ Given a property name, returns a dasherized version of that
+ property name if the property evaluates to a non-falsy value.
+
+ For example, if the view has property `isUrgent` that evaluates to true,
+ passing `isUrgent` to this method will return `"is-urgent"`.
+
+ @method _classStringForProperty
+ @param property
+ @private
+ */
+ _classStringForProperty: function(property) {
+ var parsedPath = Ember.View._parsePropertyPath(property);
+ var path = parsedPath.path;
+
+ var val = get(this, path);
+ if (val === undefined && Ember.isGlobalPath(path)) {
+ val = get(Ember.lookup, path);
+ }
+
+ return Ember.View._classStringForValue(path, val, parsedPath.className, parsedPath.falsyClassName);
+ },
+
+ // ..........................................................
+ // ELEMENT SUPPORT
+ //
+
+ /**
+ Returns the current DOM element for the view.
+
+ @property element
+ @type DOMElement
+ */
+ element: Ember.computed(function(key, value) {
+ if (value !== undefined) {
+ return this.currentState.setElement(this, value);
+ } else {
+ return this.currentState.getElement(this);
+ }
+ }).property('_parentView'),
+
+ /**
+ Returns a jQuery object for this view's element. If you pass in a selector
+ string, this method will return a jQuery object, using the current element
+ as its buffer.
+
+ For example, calling `view.$('li')` will return a jQuery object containing
+ all of the `li` elements inside the DOM element of this view.
+
+ @method $
+ @param {String} [selector] a jQuery-compatible selector string
+ @return {jQuery} the jQuery object for the DOM node
+ */
+ $: function(sel) {
+ return this.currentState.$(this, sel);
+ },
+
+ mutateChildViews: function(callback) {
+ var childViews = this._childViews,
+ idx = childViews.length,
+ view;
+
+ while(--idx >= 0) {
+ view = childViews[idx];
+ callback(this, view, idx);
+ }
+
+ return this;
+ },
+
+ forEachChildView: function(callback) {
+ var childViews = this._childViews;
+
+ if (!childViews) { return this; }
+
+ var len = childViews.length,
+ view, idx;
+
+ for (idx = 0; idx < len; idx++) {
+ view = childViews[idx];
+ callback(view);
+ }
+
+ return this;
+ },
+
+ /**
+ Appends the view's element to the specified parent element.
+
+ If the view does not have an HTML representation yet, `createElement()`
+ will be called automatically.
+
+ Note that this method just schedules the view to be appended; the DOM
+ element will not be appended to the given element until all bindings have
+ finished synchronizing.
+
+ This is not typically a function that you will need to call directly when
+ building your application. You might consider using `Ember.ContainerView`
+ instead. If you do need to use `appendTo`, be sure that the target element
+ you are providing is associated with an `Ember.Application` and does not
+ have an ancestor element that is associated with an Ember view.
+
+ @method appendTo
+ @param {String|DOMElement|jQuery} A selector, element, HTML string, or jQuery object
+ @return {Ember.View} receiver
+ */
+ appendTo: function(target) {
+ // Schedule the DOM element to be created and appended to the given
+ // element after bindings have synchronized.
+ this._insertElementLater(function() {
+ Ember.assert("You tried to append to (" + target + ") but that isn't in the DOM", Ember.$(target).length > 0);
+ Ember.assert("You cannot append to an existing Ember.View. Consider using Ember.ContainerView instead.", !Ember.$(target).is('.ember-view') && !Ember.$(target).parents().is('.ember-view'));
+ this.$().appendTo(target);
+ });
+
+ return this;
+ },
+
+ /**
+ Replaces the content of the specified parent element with this view's
+ element. If the view does not have an HTML representation yet,
+ `createElement()` will be called automatically.
+
+ Note that this method just schedules the view to be appended; the DOM
+ element will not be appended to the given element until all bindings have
+ finished synchronizing
+
+ @method replaceIn
+ @param {String|DOMElement|jQuery} target A selector, element, HTML string, or jQuery object
+ @return {Ember.View} received
+ */
+ replaceIn: function(target) {
+ Ember.assert("You tried to replace in (" + target + ") but that isn't in the DOM", Ember.$(target).length > 0);
+ Ember.assert("You cannot replace an existing Ember.View. Consider using Ember.ContainerView instead.", !Ember.$(target).is('.ember-view') && !Ember.$(target).parents().is('.ember-view'));
+
+ this._insertElementLater(function() {
+ Ember.$(target).empty();
+ this.$().appendTo(target);
+ });
+
+ return this;
+ },
+
+ /**
+ Schedules a DOM operation to occur during the next render phase. This
+ ensures that all bindings have finished synchronizing before the view is
+ rendered.
+
+ To use, pass a function that performs a DOM operation.
+
+ Before your function is called, this view and all child views will receive
+ the `willInsertElement` event. After your function is invoked, this view
+ and all of its child views will receive the `didInsertElement` event.
+
+ ```javascript
+ view._insertElementLater(function() {
+ this.createElement();
+ this.$().appendTo('body');
+ });
+ ```
+
+ @method _insertElementLater
+ @param {Function} fn the function that inserts the element into the DOM
+ @private
+ */
+ _insertElementLater: function(fn) {
+ this._scheduledInsert = Ember.run.scheduleOnce('render', this, '_insertElement', fn);
+ },
+
+ _insertElement: function (fn) {
+ this._scheduledInsert = null;
+ this.currentState.insertElement(this, fn);
+ },
+
+ /**
+ Appends the view's element to the document body. If the view does
+ not have an HTML representation yet, `createElement()` will be called
+ automatically.
+
+ If your application uses the `rootElement` property, you must append
+ the view within that element. Rendering views outside of the `rootElement`
+ is not supported.
+
+ Note that this method just schedules the view to be appended; the DOM
+ element will not be appended to the document body until all bindings have
+ finished synchronizing.
+
+ @method append
+ @return {Ember.View} receiver
+ */
+ append: function() {
+ return this.appendTo(document.body);
+ },
+
+ /**
+ Removes the view's element from the element to which it is attached.
+
+ @method remove
+ @return {Ember.View} receiver
+ */
+ remove: function() {
+ // What we should really do here is wait until the end of the run loop
+ // to determine if the element has been re-appended to a different
+ // element.
+ // In the interim, we will just re-render if that happens. It is more
+ // important than elements get garbage collected.
+ if (!this.removedFromDOM) { this.destroyElement(); }
+ this.invokeRecursively(function(view) {
+ if (view.clearRenderedChildren) { view.clearRenderedChildren(); }
+ });
+ },
+
+ elementId: null,
+
+ /**
+ Attempts to discover the element in the parent element. The default
+ implementation looks for an element with an ID of `elementId` (or the
+ view's guid if `elementId` is null). You can override this method to
+ provide your own form of lookup. For example, if you want to discover your
+ element using a CSS class name instead of an ID.
+
+ @method findElementInParentElement
+ @param {DOMElement} parentElement The parent's DOM element
+ @return {DOMElement} The discovered element
+ */
+ findElementInParentElement: function(parentElem) {
+ var id = "#" + this.elementId;
+ return Ember.$(id)[0] || Ember.$(id, parentElem)[0];
+ },
+
+ /**
+ Creates a DOM representation of the view and all of its
+ child views by recursively calling the `render()` method.
+
+ After the element has been created, `didInsertElement` will
+ be called on this view and all of its child views.
+
+ @method createElement
+ @return {Ember.View} receiver
+ */
+ createElement: function() {
+ if (get(this, 'element')) { return this; }
+
+ var buffer = this.renderToBuffer();
+ set(this, 'element', buffer.element());
+
+ return this;
+ },
+
+ /**
+ Called when a view is going to insert an element into the DOM.
+
+ @event willInsertElement
+ */
+ willInsertElement: Ember.K,
+
+ /**
+ Called when the element of the view has been inserted into the DOM
+ or after the view was re-rendered. Override this function to do any
+ set up that requires an element in the document body.
+
+ @event didInsertElement
+ */
+ didInsertElement: Ember.K,
+
+ /**
+ Called when the view is about to rerender, but before anything has
+ been torn down. This is a good opportunity to tear down any manual
+ observers you have installed based on the DOM state
+
+ @event willClearRender
+ */
+ willClearRender: Ember.K,
+
+ /**
+ Run this callback on the current view (unless includeSelf is false) and recursively on child views.
+
+ @method invokeRecursively
+ @param fn {Function}
+ @param includeSelf {Boolean} Includes itself if true.
+ @private
+ */
+ invokeRecursively: function(fn, includeSelf) {
+ var childViews = (includeSelf === false) ? this._childViews : [this];
+ var currentViews, view, currentChildViews;
+
+ while (childViews.length) {
+ currentViews = childViews.slice();
+ childViews = [];
+
+ for (var i=0, l=currentViews.length; i` tag for views.
+
+ @property tagName
+ @type String
+ @default null
+ */
+
+ // We leave this null by default so we can tell the difference between
+ // the default case and a user-specified tag.
+ tagName: null,
+
+ /**
+ The WAI-ARIA role of the control represented by this view. For example, a
+ button may have a role of type 'button', or a pane may have a role of
+ type 'alertdialog'. This property is used by assistive software to help
+ visually challenged users navigate rich web applications.
+
+ The full list of valid WAI-ARIA roles is available at:
+ [http://www.w3.org/TR/wai-aria/roles#roles_categorization](http://www.w3.org/TR/wai-aria/roles#roles_categorization)
+
+ @property ariaRole
+ @type String
+ @default null
+ */
+ ariaRole: null,
+
+ /**
+ Standard CSS class names to apply to the view's outer element. This
+ property automatically inherits any class names defined by the view's
+ superclasses as well.
+
+ @property classNames
+ @type Array
+ @default ['ember-view']
+ */
+ classNames: ['ember-view'],
+
+ /**
+ A list of properties of the view to apply as class names. If the property
+ is a string value, the value of that string will be applied as a class
+ name.
+
+ ```javascript
+ // Applies the 'high' class to the view element
+ Ember.View.extend({
+ classNameBindings: ['priority']
+ priority: 'high'
+ });
+ ```
+
+ If the value of the property is a Boolean, the name of that property is
+ added as a dasherized class name.
+
+ ```javascript
+ // Applies the 'is-urgent' class to the view element
+ Ember.View.extend({
+ classNameBindings: ['isUrgent']
+ isUrgent: true
+ });
+ ```
+
+ If you would prefer to use a custom value instead of the dasherized
+ property name, you can pass a binding like this:
+
+ ```javascript
+ // Applies the 'urgent' class to the view element
+ Ember.View.extend({
+ classNameBindings: ['isUrgent:urgent']
+ isUrgent: true
+ });
+ ```
+
+ This list of properties is inherited from the view's superclasses as well.
+
+ @property classNameBindings
+ @type Array
+ @default []
+ */
+ classNameBindings: EMPTY_ARRAY,
+
+ /**
+ A list of properties of the view to apply as attributes. If the property is
+ a string value, the value of that string will be applied as the attribute.
+
+ ```javascript
+ // Applies the type attribute to the element
+ // with the value "button", like
+ Ember.View.extend({
+ attributeBindings: ['type'],
+ type: 'button'
+ });
+ ```
+
+ If the value of the property is a Boolean, the name of that property is
+ added as an attribute.
+
+ ```javascript
+ // Renders something like
+ Ember.View.extend({
+ attributeBindings: ['enabled'],
+ enabled: true
+ });
+ ```
+
+ @property attributeBindings
+ */
+ attributeBindings: EMPTY_ARRAY,
+
+ // .......................................................
+ // CORE DISPLAY METHODS
+ //
+
+ /**
+ Setup a view, but do not finish waking it up.
+
+ * configure `childViews`
+ * register the view with the global views hash, which is used for event
+ dispatch
+
+ @method init
+ @private
+ */
+ init: function() {
+ this.elementId = this.elementId || guidFor(this);
+
+ this._super();
+
+ // setup child views. be sure to clone the child views array first
+ this._childViews = this._childViews.slice();
+
+ Ember.assert("Only arrays are allowed for 'classNameBindings'", Ember.typeOf(this.classNameBindings) === 'array');
+ this.classNameBindings = Ember.A(this.classNameBindings.slice());
+
+ Ember.assert("Only arrays are allowed for 'classNames'", Ember.typeOf(this.classNames) === 'array');
+ this.classNames = Ember.A(this.classNames.slice());
+ },
+
+ appendChild: function(view, options) {
+ return this.currentState.appendChild(this, view, options);
+ },
+
+ /**
+ Removes the child view from the parent view.
+
+ @method removeChild
+ @param {Ember.View} view
+ @return {Ember.View} receiver
+ */
+ removeChild: function(view) {
+ // If we're destroying, the entire subtree will be
+ // freed, and the DOM will be handled separately,
+ // so no need to mess with childViews.
+ if (this.isDestroying) { return; }
+
+ // update parent node
+ set(view, '_parentView', null);
+
+ // remove view from childViews array.
+ var childViews = this._childViews;
+
+ Ember.EnumerableUtils.removeObject(childViews, view);
+
+ this.propertyDidChange('childViews'); // HUH?! what happened to will change?
+
+ return this;
+ },
+
+ /**
+ Removes all children from the `parentView`.
+
+ @method removeAllChildren
+ @return {Ember.View} receiver
+ */
+ removeAllChildren: function() {
+ return this.mutateChildViews(function(parentView, view) {
+ parentView.removeChild(view);
+ });
+ },
+
+ destroyAllChildren: function() {
+ return this.mutateChildViews(function(parentView, view) {
+ view.destroy();
+ });
+ },
+
+ /**
+ Removes the view from its `parentView`, if one is found. Otherwise
+ does nothing.
+
+ @method removeFromParent
+ @return {Ember.View} receiver
+ */
+ removeFromParent: function() {
+ var parent = this._parentView;
+
+ // Remove DOM element from parent
+ this.remove();
+
+ if (parent) { parent.removeChild(this); }
+ return this;
+ },
+
+ /**
+ You must call `destroy` on a view to destroy the view (and all of its
+ child views). This will remove the view from any parent node, then make
+ sure that the DOM element managed by the view can be released by the
+ memory manager.
+
+ @method destroy
+ */
+ destroy: function() {
+ var childViews = this._childViews,
+ // get parentView before calling super because it'll be destroyed
+ nonVirtualParentView = get(this, 'parentView'),
+ viewName = this.viewName,
+ childLen, i;
+
+ if (!this._super()) { return; }
+
+ childLen = childViews.length;
+ for (i=childLen-1; i>=0; i--) {
+ childViews[i].removedFromDOM = true;
+ }
+
+ // remove from non-virtual parent view if viewName was specified
+ if (viewName && nonVirtualParentView) {
+ nonVirtualParentView.set(viewName, null);
+ }
+
+ childLen = childViews.length;
+ for (i=childLen-1; i>=0; i--) {
+ childViews[i].destroy();
+ }
+
+ return this;
+ },
+
+ /**
+ Instantiates a view to be added to the childViews array during view
+ initialization. You generally will not call this method directly unless
+ you are overriding `createChildViews()`. Note that this method will
+ automatically configure the correct settings on the new view instance to
+ act as a child of the parent.
+
+ @method createChildView
+ @param {Class|String} viewClass
+ @param {Hash} [attrs] Attributes to add
+ @return {Ember.View} new instance
+ */
+ createChildView: function(view, attrs) {
+ if (!view) {
+ throw new TypeError("createChildViews first argument must exist");
+ }
+
+ if (view.isView && view._parentView === this && view.container === this.container) {
+ return view;
+ }
+
+ attrs = attrs || {};
+ attrs._parentView = this;
+
+ if (Ember.CoreView.detect(view)) {
+ attrs.templateData = attrs.templateData || get(this, 'templateData');
+
+ attrs.container = this.container;
+ view = view.create(attrs);
+
+ // don't set the property on a virtual view, as they are invisible to
+ // consumers of the view API
+ if (view.viewName) {
+ set(get(this, 'concreteView'), view.viewName, view);
+ }
+ } else if ('string' === typeof view) {
+ var fullName = 'view:' + view;
+ var View = this.container.lookupFactory(fullName);
+
+ Ember.assert("Could not find view: '" + fullName + "'", !!View);
+
+ attrs.templateData = get(this, 'templateData');
+ view = View.create(attrs);
+ } else {
+ Ember.assert('You must pass instance or subclass of View', view.isView);
+ attrs.container = this.container;
+
+ if (!get(view, 'templateData')) {
+ attrs.templateData = get(this, 'templateData');
+ }
+
+ Ember.setProperties(view, attrs);
+
+ }
+
+ return view;
+ },
+
+ becameVisible: Ember.K,
+ becameHidden: Ember.K,
+
+ /**
+ When the view's `isVisible` property changes, toggle the visibility
+ element of the actual DOM element.
+
+ @method _isVisibleDidChange
+ @private
+ */
+ _isVisibleDidChange: Ember.observer('isVisible', function() {
+ var $el = this.$();
+ if (!$el) { return; }
+
+ var isVisible = get(this, 'isVisible');
+
+ $el.toggle(isVisible);
+
+ if (this._isAncestorHidden()) { return; }
+
+ if (isVisible) {
+ this._notifyBecameVisible();
+ } else {
+ this._notifyBecameHidden();
+ }
+ }),
+
+ _notifyBecameVisible: function() {
+ this.trigger('becameVisible');
+
+ this.forEachChildView(function(view) {
+ var isVisible = get(view, 'isVisible');
+
+ if (isVisible || isVisible === null) {
+ view._notifyBecameVisible();
+ }
+ });
+ },
+
+ _notifyBecameHidden: function() {
+ this.trigger('becameHidden');
+ this.forEachChildView(function(view) {
+ var isVisible = get(view, 'isVisible');
+
+ if (isVisible || isVisible === null) {
+ view._notifyBecameHidden();
+ }
+ });
+ },
+
+ _isAncestorHidden: function() {
+ var parent = get(this, 'parentView');
+
+ while (parent) {
+ if (get(parent, 'isVisible') === false) { return true; }
+
+ parent = get(parent, 'parentView');
+ }
+
+ return false;
+ },
+
+ clearBuffer: function() {
+ this.invokeRecursively(function(view) {
+ view.buffer = null;
+ });
+ },
+
+ transitionTo: function(state, children) {
+ var priorState = this.currentState,
+ currentState = this.currentState = this.states[state];
+ this.state = state;
+
+ if (priorState && priorState.exit) { priorState.exit(this); }
+ if (currentState.enter) { currentState.enter(this); }
+ if (state === 'inDOM') { delete Ember.meta(this).cache.element; }
+
+ if (children !== false) {
+ this.forEachChildView(function(view) {
+ view.transitionTo(state);
+ });
+ }
+ },
+
+ // .......................................................
+ // EVENT HANDLING
+ //
+
+ /**
+ Handle events from `Ember.EventDispatcher`
+
+ @method handleEvent
+ @param eventName {String}
+ @param evt {Event}
+ @private
+ */
+ handleEvent: function(eventName, evt) {
+ return this.currentState.handleEvent(this, eventName, evt);
+ },
+
+ registerObserver: function(root, path, target, observer) {
+ if (!observer && 'function' === typeof target) {
+ observer = target;
+ target = null;
+ }
+
+ if (!root || typeof root !== 'object') {
+ return;
+ }
+
+ var view = this,
+ stateCheckedObserver = function() {
+ view.currentState.invokeObserver(this, observer);
+ },
+ scheduledObserver = function() {
+ Ember.run.scheduleOnce('render', this, stateCheckedObserver);
+ };
+
+ Ember.addObserver(root, path, target, scheduledObserver);
+
+ this.one('willClearRender', function() {
+ Ember.removeObserver(root, path, target, scheduledObserver);
+ });
+ }
+
+});
+
+/*
+ Describe how the specified actions should behave in the various
+ states that a view can exist in. Possible states:
+
+ * preRender: when a view is first instantiated, and after its
+ element was destroyed, it is in the preRender state
+ * inBuffer: once a view has been rendered, but before it has
+ been inserted into the DOM, it is in the inBuffer state
+ * hasElement: the DOM representation of the view is created,
+ and is ready to be inserted
+ * inDOM: once a view has been inserted into the DOM it is in
+ the inDOM state. A view spends the vast majority of its
+ existence in this state.
+ * destroyed: once a view has been destroyed (using the destroy
+ method), it is in this state. No further actions can be invoked
+ on a destroyed view.
+*/
+
+ // in the destroyed state, everything is illegal
+
+ // before rendering has begun, all legal manipulations are noops.
+
+ // inside the buffer, legal manipulations are done on the buffer
+
+ // once the view has been inserted into the DOM, legal manipulations
+ // are done on the DOM element.
+
+function notifyMutationListeners() {
+ Ember.run.once(Ember.View, 'notifyMutationListeners');
+}
+
+var DOMManager = {
+ prepend: function(view, html) {
+ view.$().prepend(html);
+ notifyMutationListeners();
+ },
+
+ after: function(view, html) {
+ view.$().after(html);
+ notifyMutationListeners();
+ },
+
+ html: function(view, html) {
+ view.$().html(html);
+ notifyMutationListeners();
+ },
+
+ replace: function(view) {
+ var element = get(view, 'element');
+
+ set(view, 'element', null);
+
+ view._insertElementLater(function() {
+ Ember.$(element).replaceWith(get(view, 'element'));
+ notifyMutationListeners();
+ });
+ },
+
+ remove: function(view) {
+ view.$().remove();
+ notifyMutationListeners();
+ },
+
+ empty: function(view) {
+ view.$().empty();
+ notifyMutationListeners();
+ }
+};
+
+Ember.View.reopen({
+ domManager: DOMManager
+});
+
+Ember.View.reopenClass({
+
+ /**
+ Parse a path and return an object which holds the parsed properties.
+
+ For example a path like "content.isEnabled:enabled:disabled" will return the
+ following object:
+
+ ```javascript
+ {
+ path: "content.isEnabled",
+ className: "enabled",
+ falsyClassName: "disabled",
+ classNames: ":enabled:disabled"
+ }
+ ```
+
+ @method _parsePropertyPath
+ @static
+ @private
+ */
+ _parsePropertyPath: function(path) {
+ var split = path.split(':'),
+ propertyPath = split[0],
+ classNames = "",
+ className,
+ falsyClassName;
+
+ // check if the property is defined as prop:class or prop:trueClass:falseClass
+ if (split.length > 1) {
+ className = split[1];
+ if (split.length === 3) { falsyClassName = split[2]; }
+
+ classNames = ':' + className;
+ if (falsyClassName) { classNames += ":" + falsyClassName; }
+ }
+
+ return {
+ path: propertyPath,
+ classNames: classNames,
+ className: (className === '') ? undefined : className,
+ falsyClassName: falsyClassName
+ };
+ },
+
+ /**
+ Get the class name for a given value, based on the path, optional
+ `className` and optional `falsyClassName`.
+
+ - if a `className` or `falsyClassName` has been specified:
+ - if the value is truthy and `className` has been specified,
+ `className` is returned
+ - if the value is falsy and `falsyClassName` has been specified,
+ `falsyClassName` is returned
+ - otherwise `null` is returned
+ - if the value is `true`, the dasherized last part of the supplied path
+ is returned
+ - if the value is not `false`, `undefined` or `null`, the `value`
+ is returned
+ - if none of the above rules apply, `null` is returned
+
+ @method _classStringForValue
+ @param path
+ @param val
+ @param className
+ @param falsyClassName
+ @static
+ @private
+ */
+ _classStringForValue: function(path, val, className, falsyClassName) {
+ // When using the colon syntax, evaluate the truthiness or falsiness
+ // of the value to determine which className to return
+ if (className || falsyClassName) {
+ if (className && !!val) {
+ return className;
+
+ } else if (falsyClassName && !val) {
+ return falsyClassName;
+
+ } else {
+ return null;
+ }
+
+ // If value is a Boolean and true, return the dasherized property
+ // name.
+ } else if (val === true) {
+ // Normalize property path to be suitable for use
+ // as a class name. For exaple, content.foo.barBaz
+ // becomes bar-baz.
+ var parts = path.split('.');
+ return Ember.String.dasherize(parts[parts.length-1]);
+
+ // If the value is not false, undefined, or null, return the current
+ // value of the property.
+ } else if (val !== false && val != null) {
+ return val;
+
+ // Nothing to display. Return null so that the old class is removed
+ // but no new class is added.
+ } else {
+ return null;
+ }
+ }
+});
+
+var mutation = Ember.Object.extend(Ember.Evented).create();
+
+Ember.View.addMutationListener = function(callback) {
+ mutation.on('change', callback);
+};
+
+Ember.View.removeMutationListener = function(callback) {
+ mutation.off('change', callback);
+};
+
+Ember.View.notifyMutationListeners = function() {
+ mutation.trigger('change');
+};
+
+/**
+ Global views hash
+
+ @property views
+ @static
+ @type Hash
+*/
+Ember.View.views = {};
+
+// If someone overrides the child views computed property when
+// defining their class, we want to be able to process the user's
+// supplied childViews and then restore the original computed property
+// at view initialization time. This happens in Ember.ContainerView's init
+// method.
+Ember.View.childViewsProperty = childViewsProperty;
+
+Ember.View.applyAttributeBindings = function(elem, name, value) {
+ var type = Ember.typeOf(value);
+
+ // if this changes, also change the logic in ember-handlebars/lib/helpers/binding.js
+ if (name !== 'value' && (type === 'string' || (type === 'number' && !isNaN(value)))) {
+ if (value !== elem.attr(name)) {
+ elem.attr(name, value);
+ }
+ } else if (name === 'value' || type === 'boolean') {
+ if (Ember.isNone(value) || value === false) {
+ // `null`, `undefined` or `false` should remove attribute
+ elem.removeAttr(name);
+ elem.prop(name, '');
+ } else if (value !== elem.prop(name)) {
+ // value should always be properties
+ elem.prop(name, value);
+ }
+ } else if (!value) {
+ elem.removeAttr(name);
+ }
+};
+
+Ember.View.states = states;
+
+})();
+
+
+
+(function() {
+/**
+@module ember
+@submodule ember-views
+*/
+
+var get = Ember.get, set = Ember.set;
+
+Ember.View.states._default = {
+ // appendChild is only legal while rendering the buffer.
+ appendChild: function() {
+ throw "You can't use appendChild outside of the rendering process";
+ },
+
+ $: function() {
+ return undefined;
+ },
+
+ getElement: function() {
+ return null;
+ },
+
+ // Handle events from `Ember.EventDispatcher`
+ handleEvent: function() {
+ return true; // continue event propagation
+ },
+
+ destroyElement: function(view) {
+ set(view, 'element', null);
+ if (view._scheduledInsert) {
+ Ember.run.cancel(view._scheduledInsert);
+ view._scheduledInsert = null;
+ }
+ return view;
+ },
+
+ renderToBufferIfNeeded: function () {
+ return false;
+ },
+
+ rerender: Ember.K,
+ invokeObserver: Ember.K
+};
+
+})();
+
+
+
+(function() {
+/**
+@module ember
+@submodule ember-views
+*/
+
+var preRender = Ember.View.states.preRender = Ember.create(Ember.View.states._default);
+
+Ember.merge(preRender, {
+ // a view leaves the preRender state once its element has been
+ // created (createElement).
+ insertElement: function(view, fn) {
+ view.createElement();
+ var viewCollection = view.viewHierarchyCollection();
+
+ viewCollection.trigger('willInsertElement');
+
+ fn.call(view);
+
+ // We transition to `inDOM` if the element exists in the DOM
+ var element = view.get('element');
+ while (element = element.parentNode) {
+ if (element === document) {
+ viewCollection.transitionTo('inDOM', false);
+ viewCollection.trigger('didInsertElement');
+ }
+ }
+
+ },
+
+ renderToBufferIfNeeded: function(view, buffer) {
+ view.renderToBuffer(buffer);
+ return true;
+ },
+
+ empty: Ember.K,
+
+ setElement: function(view, value) {
+ if (value !== null) {
+ view.transitionTo('hasElement');
+ }
+ return value;
+ }
+});
+
+})();
+
+
+
+(function() {
+/**
+@module ember
+@submodule ember-views
+*/
+
+var get = Ember.get, set = Ember.set;
+
+var inBuffer = Ember.View.states.inBuffer = Ember.create(Ember.View.states._default);
+
+Ember.merge(inBuffer, {
+ $: function(view, sel) {
+ // if we don't have an element yet, someone calling this.$() is
+ // trying to update an element that isn't in the DOM. Instead,
+ // rerender the view to allow the render method to reflect the
+ // changes.
+ view.rerender();
+ return Ember.$();
+ },
+
+ // when a view is rendered in a buffer, rerendering it simply
+ // replaces the existing buffer with a new one
+ rerender: function(view) {
+ throw new Ember.Error("Something you did caused a view to re-render after it rendered but before it was inserted into the DOM.");
+ },
+
+ // when a view is rendered in a buffer, appending a child
+ // view will render that view and append the resulting
+ // buffer into its buffer.
+ appendChild: function(view, childView, options) {
+ var buffer = view.buffer, _childViews = view._childViews;
+
+ childView = view.createChildView(childView, options);
+ if (!_childViews.length) { _childViews = view._childViews = _childViews.slice(); }
+ _childViews.push(childView);
+
+ childView.renderToBuffer(buffer);
+
+ view.propertyDidChange('childViews');
+
+ return childView;
+ },
+
+ // when a view is rendered in a buffer, destroying the
+ // element will simply destroy the buffer and put the
+ // state back into the preRender state.
+ destroyElement: function(view) {
+ view.clearBuffer();
+ var viewCollection = view._notifyWillDestroyElement();
+ viewCollection.transitionTo('preRender', false);
+
+ return view;
+ },
+
+ empty: function() {
+ Ember.assert("Emptying a view in the inBuffer state is not allowed and " +
+ "should not happen under normal circumstances. Most likely " +
+ "there is a bug in your application. This may be due to " +
+ "excessive property change notifications.");
+ },
+
+ renderToBufferIfNeeded: function (view, buffer) {
+ return false;
+ },
+
+ // It should be impossible for a rendered view to be scheduled for
+ // insertion.
+ insertElement: function() {
+ throw "You can't insert an element that has already been rendered";
+ },
+
+ setElement: function(view, value) {
+ if (value === null) {
+ view.transitionTo('preRender');
+ } else {
+ view.clearBuffer();
+ view.transitionTo('hasElement');
+ }
+
+ return value;
+ },
+
+ invokeObserver: function(target, observer) {
+ observer.call(target);
+ }
+});
+
+
+})();
+
+
+
+(function() {
+/**
+@module ember
+@submodule ember-views
+*/
+
+var get = Ember.get, set = Ember.set;
+
+var hasElement = Ember.View.states.hasElement = Ember.create(Ember.View.states._default);
+
+Ember.merge(hasElement, {
+ $: function(view, sel) {
+ var elem = get(view, 'element');
+ return sel ? Ember.$(sel, elem) : Ember.$(elem);
+ },
+
+ getElement: function(view) {
+ var parent = get(view, 'parentView');
+ if (parent) { parent = get(parent, 'element'); }
+ if (parent) { return view.findElementInParentElement(parent); }
+ return Ember.$("#" + get(view, 'elementId'))[0];
+ },
+
+ setElement: function(view, value) {
+ if (value === null) {
+ view.transitionTo('preRender');
+ } else {
+ throw "You cannot set an element to a non-null value when the element is already in the DOM.";
+ }
+
+ return value;
+ },
+
+ // once the view has been inserted into the DOM, rerendering is
+ // deferred to allow bindings to synchronize.
+ rerender: function(view) {
+ view.triggerRecursively('willClearRender');
+
+ view.clearRenderedChildren();
+
+ view.domManager.replace(view);
+ return view;
+ },
+
+ // once the view is already in the DOM, destroying it removes it
+ // from the DOM, nukes its element, and puts it back into the
+ // preRender state if inDOM.
+
+ destroyElement: function(view) {
+ view._notifyWillDestroyElement();
+ view.domManager.remove(view);
+ set(view, 'element', null);
+ if (view._scheduledInsert) {
+ Ember.run.cancel(view._scheduledInsert);
+ view._scheduledInsert = null;
+ }
+ return view;
+ },
+
+ empty: function(view) {
+ var _childViews = view._childViews, len, idx;
+ if (_childViews) {
+ len = _childViews.length;
+ for (idx = 0; idx < len; idx++) {
+ _childViews[idx]._notifyWillDestroyElement();
+ }
+ }
+ view.domManager.empty(view);
+ },
+
+ // Handle events from `Ember.EventDispatcher`
+ handleEvent: function(view, eventName, evt) {
+ if (view.has(eventName)) {
+ // Handler should be able to re-dispatch events, so we don't
+ // preventDefault or stopPropagation.
+ return view.trigger(eventName, evt);
+ } else {
+ return true; // continue event propagation
+ }
+ },
+
+ invokeObserver: function(target, observer) {
+ observer.call(target);
+ }
+});
+})();
+
+
+
+(function() {
+/**
+@module ember
+@submodule ember-views
+*/
+
+var hasElement = Ember.View.states.hasElement;
+var inDOM = Ember.View.states.inDOM = Ember.create(hasElement);
+
+Ember.merge(inDOM, {
+ enter: function(view) {
+ // Register the view for event handling. This hash is used by
+ // Ember.EventDispatcher to dispatch incoming events.
+ if (!view.isVirtual) {
+ Ember.assert("Attempted to register a view with an id already in use: "+view.elementId, !Ember.View.views[view.elementId]);
+ Ember.View.views[view.elementId] = view;
+ }
+
+ view.addBeforeObserver('elementId', function() {
+ throw new Ember.Error("Changing a view's elementId after creation is not allowed");
+ });
+ },
+
+ exit: function(view) {
+ if (!this.isVirtual) delete Ember.View.views[view.elementId];
+ },
+
+ insertElement: function(view, fn) {
+ throw "You can't insert an element into the DOM that has already been inserted";
+ }
+});
+
+})();
+
+
+
+(function() {
+/**
+@module ember
+@submodule ember-views
+*/
+
+var destroyingError = "You can't call %@ on a view being destroyed", fmt = Ember.String.fmt;
+
+var destroying = Ember.View.states.destroying = Ember.create(Ember.View.states._default);
+
+Ember.merge(destroying, {
+ appendChild: function() {
+ throw fmt(destroyingError, ['appendChild']);
+ },
+ rerender: function() {
+ throw fmt(destroyingError, ['rerender']);
+ },
+ destroyElement: function() {
+ throw fmt(destroyingError, ['destroyElement']);
+ },
+ empty: function() {
+ throw fmt(destroyingError, ['empty']);
+ },
+
+ setElement: function() {
+ throw fmt(destroyingError, ["set('element', ...)"]);
+ },
+
+ renderToBufferIfNeeded: function() {
+ return false;
+ },
+
+ // Since element insertion is scheduled, don't do anything if
+ // the view has been destroyed between scheduling and execution
+ insertElement: Ember.K
+});
+
+
+})();
+
+
+
+(function() {
+Ember.View.cloneStates = function(from) {
+ var into = {};
+
+ into._default = {};
+ into.preRender = Ember.create(into._default);
+ into.destroying = Ember.create(into._default);
+ into.inBuffer = Ember.create(into._default);
+ into.hasElement = Ember.create(into._default);
+ into.inDOM = Ember.create(into.hasElement);
+
+ for (var stateName in from) {
+ if (!from.hasOwnProperty(stateName)) { continue; }
+ Ember.merge(into[stateName], from[stateName]);
+ }
+
+ return into;
+};
+
+})();
+
+
+
+(function() {
+var states = Ember.View.cloneStates(Ember.View.states);
+
+/**
+@module ember
+@submodule ember-views
+*/
+
+var get = Ember.get, set = Ember.set;
+var forEach = Ember.EnumerableUtils.forEach;
+var ViewCollection = Ember._ViewCollection;
+
+/**
+ A `ContainerView` is an `Ember.View` subclass that implements `Ember.MutableArray`
+ allowing programmatic management of its child views.
+
+ ## Setting Initial Child Views
+
+ The initial array of child views can be set in one of two ways. You can
+ provide a `childViews` property at creation time that contains instance of
+ `Ember.View`:
+
+ ```javascript
+ aContainer = Ember.ContainerView.create({
+ childViews: [Ember.View.create(), Ember.View.create()]
+ });
+ ```
+
+ You can also provide a list of property names whose values are instances of
+ `Ember.View`:
+
+ ```javascript
+ aContainer = Ember.ContainerView.create({
+ childViews: ['aView', 'bView', 'cView'],
+ aView: Ember.View.create(),
+ bView: Ember.View.create(),
+ cView: Ember.View.create()
+ });
+ ```
+
+ The two strategies can be combined:
+
+ ```javascript
+ aContainer = Ember.ContainerView.create({
+ childViews: ['aView', Ember.View.create()],
+ aView: Ember.View.create()
+ });
+ ```
+
+ Each child view's rendering will be inserted into the container's rendered
+ HTML in the same order as its position in the `childViews` property.
+
+ ## Adding and Removing Child Views
+
+ The container view implements `Ember.MutableArray` allowing programmatic management of its child views.
+
+ To remove a view, pass that view into a `removeObject` call on the container view.
+
+ Given an empty `` the following code
+
+ ```javascript
+ aContainer = Ember.ContainerView.create({
+ classNames: ['the-container'],
+ childViews: ['aView', 'bView'],
+ aView: Ember.View.create({
+ template: Ember.Handlebars.compile("A")
+ }),
+ bView: Ember.View.create({
+ template: Ember.Handlebars.compile("B")
+ })
+ });
+
+ aContainer.appendTo('body');
+ ```
+
+ Results in the HTML
+
+ ```html
+
+
A
+
B
+
+ ```
+
+ Removing a view
+
+ ```javascript
+ aContainer.toArray(); // [aContainer.aView, aContainer.bView]
+ aContainer.removeObject(aContainer.get('bView'));
+ aContainer.toArray(); // [aContainer.aView]
+ ```
+
+ Will result in the following HTML
+
+ ```html
+
+
A
+
+ ```
+
+ Similarly, adding a child view is accomplished by adding `Ember.View` instances to the
+ container view.
+
+ Given an empty `` the following code
+
+ ```javascript
+ aContainer = Ember.ContainerView.create({
+ classNames: ['the-container'],
+ childViews: ['aView', 'bView'],
+ aView: Ember.View.create({
+ template: Ember.Handlebars.compile("A")
+ }),
+ bView: Ember.View.create({
+ template: Ember.Handlebars.compile("B")
+ })
+ });
+
+ aContainer.appendTo('body');
+ ```
+
+ Results in the HTML
+
+ ```html
+
+
A
+
B
+
+ ```
+
+ Adding a view
+
+ ```javascript
+ AnotherViewClass = Ember.View.extend({
+ template: Ember.Handlebars.compile("Another view")
+ });
+
+ aContainer.toArray(); // [aContainer.aView, aContainer.bView]
+ aContainer.pushObject(AnotherViewClass.create());
+ aContainer.toArray(); // [aContainer.aView, aContainer.bView, ]
+ ```
+
+ Will result in the following HTML
+
+ ```html
+
+
A
+
B
+
Another view
+
+ ```
+
+ ## Templates and Layout
+
+ A `template`, `templateName`, `defaultTemplate`, `layout`, `layoutName` or
+ `defaultLayout` property on a container view will not result in the template
+ or layout being rendered. The HTML contents of a `Ember.ContainerView`'s DOM
+ representation will only be the rendered HTML of its child views.
+
+ @class ContainerView
+ @namespace Ember
+ @extends Ember.View
+*/
+Ember.ContainerView = Ember.View.extend(Ember.MutableArray, {
+ states: states,
+
+ init: function() {
+ this._super();
+
+ var childViews = get(this, 'childViews');
+
+ // redefine view's childViews property that was obliterated
+ Ember.defineProperty(this, 'childViews', Ember.View.childViewsProperty);
+
+ var _childViews = this._childViews;
+
+ forEach(childViews, function(viewName, idx) {
+ var view;
+
+ if ('string' === typeof viewName) {
+ view = get(this, viewName);
+ view = this.createChildView(view);
+ set(this, viewName, view);
+ } else {
+ view = this.createChildView(viewName);
+ }
+
+ _childViews[idx] = view;
+ }, this);
+
+ var currentView = get(this, 'currentView');
+ if (currentView) {
+ if (!_childViews.length) { _childViews = this._childViews = this._childViews.slice(); }
+ _childViews.push(this.createChildView(currentView));
+ }
+ },
+
+ replace: function(idx, removedCount, addedViews) {
+ var addedCount = addedViews ? get(addedViews, 'length') : 0;
+ var self = this;
+ Ember.assert("You can't add a child to a container that is already a child of another view", Ember.A(addedViews).every(function(item) { return !get(item, '_parentView') || get(item, '_parentView') === self; }));
+
+ this.arrayContentWillChange(idx, removedCount, addedCount);
+ this.childViewsWillChange(this._childViews, idx, removedCount);
+
+ if (addedCount === 0) {
+ this._childViews.splice(idx, removedCount) ;
+ } else {
+ var args = [idx, removedCount].concat(addedViews);
+ if (addedViews.length && !this._childViews.length) { this._childViews = this._childViews.slice(); }
+ this._childViews.splice.apply(this._childViews, args);
+ }
+
+ this.arrayContentDidChange(idx, removedCount, addedCount);
+ this.childViewsDidChange(this._childViews, idx, removedCount, addedCount);
+
+ return this;
+ },
+
+ objectAt: function(idx) {
+ return this._childViews[idx];
+ },
+
+ length: Ember.computed(function () {
+ return this._childViews.length;
+ }).volatile(),
+
+ /**
+ Instructs each child view to render to the passed render buffer.
+
+ @private
+ @method render
+ @param {Ember.RenderBuffer} buffer the buffer to render to
+ */
+ render: function(buffer) {
+ this.forEachChildView(function(view) {
+ view.renderToBuffer(buffer);
+ });
+ },
+
+ instrumentName: 'container',
+
+ /**
+ When a child view is removed, destroy its element so that
+ it is removed from the DOM.
+
+ The array observer that triggers this action is set up in the
+ `renderToBuffer` method.
+
+ @private
+ @method childViewsWillChange
+ @param {Ember.Array} views the child views array before mutation
+ @param {Number} start the start position of the mutation
+ @param {Number} removed the number of child views removed
+ **/
+ childViewsWillChange: function(views, start, removed) {
+ this.propertyWillChange('childViews');
+
+ if (removed > 0) {
+ var changedViews = views.slice(start, start+removed);
+ // transition to preRender before clearing parentView
+ this.currentState.childViewsWillChange(this, views, start, removed);
+ this.initializeViews(changedViews, null, null);
+ }
+ },
+
+ removeChild: function(child) {
+ this.removeObject(child);
+ return this;
+ },
+
+ /**
+ When a child view is added, make sure the DOM gets updated appropriately.
+
+ If the view has already rendered an element, we tell the child view to
+ create an element and insert it into the DOM. If the enclosing container
+ view has already written to a buffer, but not yet converted that buffer
+ into an element, we insert the string representation of the child into the
+ appropriate place in the buffer.
+
+ @private
+ @method childViewsDidChange
+ @param {Ember.Array} views the array of child views afte the mutation has occurred
+ @param {Number} start the start position of the mutation
+ @param {Number} removed the number of child views removed
+ @param {Number} the number of child views added
+ */
+ childViewsDidChange: function(views, start, removed, added) {
+ if (added > 0) {
+ var changedViews = views.slice(start, start+added);
+ this.initializeViews(changedViews, this, get(this, 'templateData'));
+ this.currentState.childViewsDidChange(this, views, start, added);
+ }
+ this.propertyDidChange('childViews');
+ },
+
+ initializeViews: function(views, parentView, templateData) {
+ forEach(views, function(view) {
+ set(view, '_parentView', parentView);
+
+ if (!view.container && parentView) {
+ set(view, 'container', parentView.container);
+ }
+
+ if (!get(view, 'templateData')) {
+ set(view, 'templateData', templateData);
+ }
+ });
+ },
+
+ currentView: null,
+
+ _currentViewWillChange: Ember.beforeObserver('currentView', function() {
+ var currentView = get(this, 'currentView');
+ if (currentView) {
+ currentView.destroy();
+ }
+ }),
+
+ _currentViewDidChange: Ember.observer('currentView', function() {
+ var currentView = get(this, 'currentView');
+ if (currentView) {
+ Ember.assert("You tried to set a current view that already has a parent. Make sure you don't have multiple outlets in the same view.", !get(currentView, '_parentView'));
+ this.pushObject(currentView);
+ }
+ }),
+
+ _ensureChildrenAreInDOM: function () {
+ this.currentState.ensureChildrenAreInDOM(this);
+ }
+});
+
+Ember.merge(states._default, {
+ childViewsWillChange: Ember.K,
+ childViewsDidChange: Ember.K,
+ ensureChildrenAreInDOM: Ember.K
+});
+
+Ember.merge(states.inBuffer, {
+ childViewsDidChange: function(parentView, views, start, added) {
+ throw new Ember.Error('You cannot modify child views while in the inBuffer state');
+ }
+});
+
+Ember.merge(states.hasElement, {
+ childViewsWillChange: function(view, views, start, removed) {
+ for (var i=start; i` and the following code:
+
+ ```javascript
+ someItemsView = Ember.CollectionView.create({
+ classNames: ['a-collection'],
+ content: ['A','B','C'],
+ itemViewClass: Ember.View.extend({
+ template: Ember.Handlebars.compile("the letter: {{view.content}}")
+ })
+ });
+
+ someItemsView.appendTo('body');
+ ```
+
+ Will result in the following HTML structure
+
+ ```html
+
+
the letter: A
+
the letter: B
+
the letter: C
+
+ ```
+
+ ## Automatic matching of parent/child tagNames
+
+ Setting the `tagName` property of a `CollectionView` to any of
+ "ul", "ol", "table", "thead", "tbody", "tfoot", "tr", or "select" will result
+ in the item views receiving an appropriately matched `tagName` property.
+
+ Given an empty `` and the following code:
+
+ ```javascript
+ anUnorderedListView = Ember.CollectionView.create({
+ tagName: 'ul',
+ content: ['A','B','C'],
+ itemViewClass: Ember.View.extend({
+ template: Ember.Handlebars.compile("the letter: {{view.content}}")
+ })
+ });
+
+ anUnorderedListView.appendTo('body');
+ ```
+
+ Will result in the following HTML structure
+
+ ```html
+
+
the letter: A
+
the letter: B
+
the letter: C
+
+ ```
+
+ Additional `tagName` pairs can be provided by adding to
+ `Ember.CollectionView.CONTAINER_MAP `
+
+ ```javascript
+ Ember.CollectionView.CONTAINER_MAP['article'] = 'section'
+ ```
+
+ ## Programmatic creation of child views
+
+ For cases where additional customization beyond the use of a single
+ `itemViewClass` or `tagName` matching is required CollectionView's
+ `createChildView` method can be overidden:
+
+ ```javascript
+ CustomCollectionView = Ember.CollectionView.extend({
+ createChildView: function(viewClass, attrs) {
+ if (attrs.content.kind == 'album') {
+ viewClass = App.AlbumView;
+ } else {
+ viewClass = App.SongView;
+ }
+ return this._super(viewClass, attrs);
+ }
+ });
+ ```
+
+ ## Empty View
+
+ You can provide an `Ember.View` subclass to the `Ember.CollectionView`
+ instance as its `emptyView` property. If the `content` property of a
+ `CollectionView` is set to `null` or an empty array, an instance of this view
+ will be the `CollectionView`s only child.
+
+ ```javascript
+ aListWithNothing = Ember.CollectionView.create({
+ classNames: ['nothing']
+ content: null,
+ emptyView: Ember.View.extend({
+ template: Ember.Handlebars.compile("The collection is empty")
+ })
+ });
+
+ aListWithNothing.appendTo('body');
+ ```
+
+ Will result in the following HTML structure
+
+ ```html
+
+
+ The collection is empty
+
+
+ ```
+
+ ## Adding and Removing items
+
+ The `childViews` property of a `CollectionView` should not be directly
+ manipulated. Instead, add, remove, replace items from its `content` property.
+ This will trigger appropriate changes to its rendered HTML.
+
+
+ @class CollectionView
+ @namespace Ember
+ @extends Ember.ContainerView
+ @since Ember 0.9
+*/
+Ember.CollectionView = Ember.ContainerView.extend({
+
+ /**
+ A list of items to be displayed by the `Ember.CollectionView`.
+
+ @property content
+ @type Ember.Array
+ @default null
+ */
+ content: null,
+
+ /**
+ This provides metadata about what kind of empty view class this
+ collection would like if it is being instantiated from another
+ system (like Handlebars)
+
+ @private
+ @property emptyViewClass
+ */
+ emptyViewClass: Ember.View,
+
+ /**
+ An optional view to display if content is set to an empty array.
+
+ @property emptyView
+ @type Ember.View
+ @default null
+ */
+ emptyView: null,
+
+ /**
+ @property itemViewClass
+ @type Ember.View
+ @default Ember.View
+ */
+ itemViewClass: Ember.View,
+
+ /**
+ Setup a CollectionView
+
+ @method init
+ */
+ init: function() {
+ var ret = this._super();
+ this._contentDidChange();
+ return ret;
+ },
+
+ /**
+ Invoked when the content property is about to change. Notifies observers that the
+ entire array content will change.
+
+ @private
+ @method _contentWillChange
+ */
+ _contentWillChange: Ember.beforeObserver('content', function() {
+ var content = this.get('content');
+
+ if (content) { content.removeArrayObserver(this); }
+ var len = content ? get(content, 'length') : 0;
+ this.arrayWillChange(content, 0, len);
+ }),
+
+ /**
+ Check to make sure that the content has changed, and if so,
+ update the children directly. This is always scheduled
+ asynchronously, to allow the element to be created before
+ bindings have synchronized and vice versa.
+
+ @private
+ @method _contentDidChange
+ */
+ _contentDidChange: Ember.observer('content', function() {
+ var content = get(this, 'content');
+
+ if (content) {
+ this._assertArrayLike(content);
+ content.addArrayObserver(this);
+ }
+
+ var len = content ? get(content, 'length') : 0;
+ this.arrayDidChange(content, 0, null, len);
+ }),
+
+ /**
+ Ensure that the content implements Ember.Array
+
+ @private
+ @method _assertArrayLike
+ */
+ _assertArrayLike: function(content) {
+ Ember.assert(fmt("an Ember.CollectionView's content must implement Ember.Array. You passed %@", [content]), Ember.Array.detect(content));
+ },
+
+ /**
+ Removes the content and content observers.
+
+ @method destroy
+ */
+ destroy: function() {
+ if (!this._super()) { return; }
+
+ var content = get(this, 'content');
+ if (content) { content.removeArrayObserver(this); }
+
+ if (this._createdEmptyView) {
+ this._createdEmptyView.destroy();
+ }
+
+ return this;
+ },
+
+ /**
+ Called when a mutation to the underlying content array will occur.
+
+ This method will remove any views that are no longer in the underlying
+ content array.
+
+ Invokes whenever the content array itself will change.
+
+ @method arrayWillChange
+ @param {Array} content the managed collection of objects
+ @param {Number} start the index at which the changes will occurr
+ @param {Number} removed number of object to be removed from content
+ */
+ arrayWillChange: function(content, start, removedCount) {
+ // If the contents were empty before and this template collection has an
+ // empty view remove it now.
+ var emptyView = get(this, 'emptyView');
+ if (emptyView && emptyView instanceof Ember.View) {
+ emptyView.removeFromParent();
+ }
+
+ // Loop through child views that correspond with the removed items.
+ // Note that we loop from the end of the array to the beginning because
+ // we are mutating it as we go.
+ var childViews = this._childViews, childView, idx, len;
+
+ len = this._childViews.length;
+
+ var removingAll = removedCount === len;
+
+ if (removingAll) {
+ this.currentState.empty(this);
+ this.invokeRecursively(function(view) {
+ view.removedFromDOM = true;
+ }, false);
+ }
+
+ for (idx = start + removedCount - 1; idx >= start; idx--) {
+ childView = childViews[idx];
+ childView.destroy();
+ }
+ },
+
+ /**
+ Called when a mutation to the underlying content array occurs.
+
+ This method will replay that mutation against the views that compose the
+ `Ember.CollectionView`, ensuring that the view reflects the model.
+
+ This array observer is added in `contentDidChange`.
+
+ @method arrayDidChange
+ @param {Array} content the managed collection of objects
+ @param {Number} start the index at which the changes occurred
+ @param {Number} removed number of object removed from content
+ @param {Number} added number of object added to content
+ */
+ arrayDidChange: function(content, start, removed, added) {
+ var addedViews = [], view, item, idx, len, itemViewClass,
+ emptyView;
+
+ len = content ? get(content, 'length') : 0;
+
+ if (len) {
+ itemViewClass = get(this, 'itemViewClass');
+
+ if ('string' === typeof itemViewClass) {
+ itemViewClass = get(itemViewClass) || itemViewClass;
+ }
+
+ Ember.assert(fmt("itemViewClass must be a subclass of Ember.View, not %@", [itemViewClass]), 'string' === typeof itemViewClass || Ember.View.detect(itemViewClass));
+
+ for (idx = start; idx < start+added; idx++) {
+ item = content.objectAt(idx);
+
+ view = this.createChildView(itemViewClass, {
+ content: item,
+ contentIndex: idx
+ });
+
+ addedViews.push(view);
+ }
+ } else {
+ emptyView = get(this, 'emptyView');
+
+ if (!emptyView) { return; }
+
+ if ('string' === typeof emptyView) {
+ emptyView = get(emptyView) || emptyView;
+ }
+
+ emptyView = this.createChildView(emptyView);
+ addedViews.push(emptyView);
+ set(this, 'emptyView', emptyView);
+
+ if (Ember.CoreView.detect(emptyView)) {
+ this._createdEmptyView = emptyView;
+ }
+ }
+
+ this.replace(start, 0, addedViews);
+ },
+
+ /**
+ Instantiates a view to be added to the childViews array during view
+ initialization. You generally will not call this method directly unless
+ you are overriding `createChildViews()`. Note that this method will
+ automatically configure the correct settings on the new view instance to
+ act as a child of the parent.
+
+ The tag name for the view will be set to the tagName of the viewClass
+ passed in.
+
+ @method createChildView
+ @param {Class} viewClass
+ @param {Hash} [attrs] Attributes to add
+ @return {Ember.View} new instance
+ */
+ createChildView: function(view, attrs) {
+ view = this._super(view, attrs);
+
+ var itemTagName = get(view, 'tagName');
+
+ if (itemTagName === null || itemTagName === undefined) {
+ itemTagName = Ember.CollectionView.CONTAINER_MAP[get(this, 'tagName')];
+ set(view, 'tagName', itemTagName);
+ }
+
+ return view;
+ }
+});
+
+/**
+ A map of parent tags to their default child tags. You can add
+ additional parent tags if you want collection views that use
+ a particular parent tag to default to a child tag.
+
+ @property CONTAINER_MAP
+ @type Hash
+ @static
+ @final
+*/
+Ember.CollectionView.CONTAINER_MAP = {
+ ul: 'li',
+ ol: 'li',
+ table: 'tr',
+ thead: 'tr',
+ tbody: 'tr',
+ tfoot: 'tr',
+ tr: 'td',
+ select: 'option'
+};
+
+})();
+
+
+
+(function() {
+/**
+ The ComponentTemplateDeprecation mixin is used to provide a useful
+ deprecation warning when using either `template` or `templateName` with
+ a component. The `template` and `templateName` properties specified at
+ extend time are moved to `layout` and `layoutName` respectively.
+
+ `Ember.ComponentTemplateDeprecation` is used internally by Ember in
+ `Ember.Component`.
+
+ @class ComponentTemplateDeprecation
+ @namespace Ember
+*/
+Ember.ComponentTemplateDeprecation = Ember.Mixin.create({
+ /**
+ @private
+
+ Moves `templateName` to `layoutName` and `template` to `layout` at extend
+ time if a layout is not also specified.
+
+ Note that this currently modifies the mixin themselves, which is technically
+ dubious but is practically of little consequence. This may change in the
+ future.
+
+ @method willMergeMixin
+ */
+ willMergeMixin: function(props) {
+ // must call _super here to ensure that the ActionHandler
+ // mixin is setup properly (moves actions -> _actions)
+ //
+ // Calling super is only OK here since we KNOW that
+ // there is another Mixin loaded first.
+ this._super.apply(this, arguments);
+
+ var deprecatedProperty, replacementProperty,
+ layoutSpecified = (props.layoutName || props.layout);
+
+ if (props.templateName && !layoutSpecified) {
+ deprecatedProperty = 'templateName';
+ replacementProperty = 'layoutName';
+
+ props.layoutName = props.templateName;
+ delete props['templateName'];
+ }
+
+ if (props.template && !layoutSpecified) {
+ deprecatedProperty = 'template';
+ replacementProperty = 'layout';
+
+ props.layout = props.template;
+ delete props['template'];
+ }
+
+ if (deprecatedProperty) {
+ Ember.deprecate('Do not specify ' + deprecatedProperty + ' on a Component, use ' + replacementProperty + ' instead.', false);
+ }
+ }
+});
+
+
+})();
+
+
+
+(function() {
+var get = Ember.get, set = Ember.set, isNone = Ember.isNone,
+ a_slice = Array.prototype.slice;
+
+
+/**
+@module ember
+@submodule ember-views
+*/
+
+/**
+ An `Ember.Component` is a view that is completely
+ isolated. Property access in its templates go
+ to the view object and actions are targeted at
+ the view object. There is no access to the
+ surrounding context or outer controller; all
+ contextual information must be passed in.
+
+ The easiest way to create an `Ember.Component` is via
+ a template. If you name a template
+ `components/my-foo`, you will be able to use
+ `{{my-foo}}` in other templates, which will make
+ an instance of the isolated component.
+
+ ```handlebars
+ {{app-profile person=currentUser}}
+ ```
+
+ ```handlebars
+
+
{{person.title}}
+
+
{{person.signature}}
+ ```
+
+ You can use `yield` inside a template to
+ include the **contents** of any block attached to
+ the component. The block will be executed in the
+ context of the surrounding context or outer controller:
+
+ ```handlebars
+ {{#app-profile person=currentUser}}
+
Admin mode
+ {{! Executed in the controller's context. }}
+ {{/app-profile}}
+ ```
+
+ ```handlebars
+
+
{{person.title}}
+ {{! Executed in the components context. }}
+ {{yield}} {{! block contents }}
+ ```
+
+ If you want to customize the component, in order to
+ handle events or actions, you implement a subclass
+ of `Ember.Component` named after the name of the
+ component. Note that `Component` needs to be appended to the name of
+ your subclass like `AppProfileComponent`.
+
+ For example, you could implement the action
+ `hello` for the `app-profile` component:
+
+ ```javascript
+ App.AppProfileComponent = Ember.Component.extend({
+ actions: {
+ hello: function(name) {
+ console.log("Hello", name);
+ }
+ }
+ });
+ ```
+
+ And then use it in the component's template:
+
+ ```handlebars
+
+
+
";
+ * div.firstChild.firstChild.tagName //=> ""
+ *
+ * If our script markers are inside such a node, we need to find that
+ * node and use *it* as the marker.
+ */
+ var realNode = function(start) {
+ while (start.parentNode.tagName === "") {
+ start = start.parentNode;
+ }
+
+ return start;
+ };
+
+ /*
+ * When automatically adding a tbody, Internet Explorer inserts the
+ * tbody immediately before the first
. Other browsers create it
+ * before the first node, no matter what.
+ *
+ * This means the the following code:
+ *
+ * div = document.createElement("div");
+ * div.innerHTML = "
hi
+ *
+ * Generates the following DOM in IE:
+ *
+ * + div
+ * + table
+ * - script id='first'
+ * + tbody
+ * + tr
+ * + td
+ * - "hi"
+ * - script id='last'
+ *
+ * Which means that the two script tags, even though they were
+ * inserted at the same point in the hierarchy in the original
+ * HTML, now have different parents.
+ *
+ * This code reparents the first script tag by making it the tbody's
+ * first child.
+ *
+ */
+ var fixParentage = function(start, end) {
+ if (start.parentNode !== end.parentNode) {
+ end.parentNode.insertBefore(start, end.parentNode.firstChild);
+ }
+ };
+
+ htmlFunc = function(html, outerToo) {
+ // get the real starting node. see realNode for details.
+ var start = realNode(document.getElementById(this.start));
+ var end = document.getElementById(this.end);
+ var parentNode = end.parentNode;
+ var node, nextSibling, last;
+
+ // make sure that the start and end nodes share the same
+ // parent. If not, fix it.
+ fixParentage(start, end);
+
+ // remove all of the nodes after the starting placeholder and
+ // before the ending placeholder.
+ node = start.nextSibling;
+ while (node) {
+ nextSibling = node.nextSibling;
+ last = node === end;
+
+ // if this is the last node, and we want to remove it as well,
+ // set the `end` node to the next sibling. This is because
+ // for the rest of the function, we insert the new nodes
+ // before the end (note that insertBefore(node, null) is
+ // the same as appendChild(node)).
+ //
+ // if we do not want to remove it, just break.
+ if (last) {
+ if (outerToo) { end = node.nextSibling; } else { break; }
+ }
+
+ node.parentNode.removeChild(node);
+
+ // if this is the last node and we didn't break before
+ // (because we wanted to remove the outer nodes), break
+ // now.
+ if (last) { break; }
+
+ node = nextSibling;
+ }
+
+ // get the first node for the HTML string, even in cases like
+ // tables and lists where a simple innerHTML on a div would
+ // swallow some of the content.
+ node = firstNodeFor(start.parentNode, html);
+
+ if (outerToo) {
+ start.parentNode.removeChild(start);
+ }
+
+ // copy the nodes for the HTML between the starting and ending
+ // placeholder.
+ while (node) {
+ nextSibling = node.nextSibling;
+ parentNode.insertBefore(node, end);
+ node = nextSibling;
+ }
+ };
+
+ // remove the nodes in the DOM representing this metamorph.
+ //
+ // this includes the starting and ending placeholders.
+ removeFunc = function() {
+ var start = realNode(document.getElementById(this.start));
+ var end = document.getElementById(this.end);
+
+ this.html('');
+ start.parentNode.removeChild(start);
+ end.parentNode.removeChild(end);
+ };
+
+ appendToFunc = function(parentNode) {
+ var node = firstNodeFor(parentNode, this.outerHTML());
+ var nextSibling;
+
+ while (node) {
+ nextSibling = node.nextSibling;
+ parentNode.appendChild(node);
+ node = nextSibling;
+ }
+ };
+
+ afterFunc = function(html) {
+ // get the real starting node. see realNode for details.
+ var end = document.getElementById(this.end);
+ var insertBefore = end.nextSibling;
+ var parentNode = end.parentNode;
+ var nextSibling;
+ var node;
+
+ // get the first node for the HTML string, even in cases like
+ // tables and lists where a simple innerHTML on a div would
+ // swallow some of the content.
+ node = firstNodeFor(parentNode, html);
+
+ // copy the nodes for the HTML between the starting and ending
+ // placeholder.
+ while (node) {
+ nextSibling = node.nextSibling;
+ parentNode.insertBefore(node, insertBefore);
+ node = nextSibling;
+ }
+ };
+
+ prependFunc = function(html) {
+ var start = document.getElementById(this.start);
+ var parentNode = start.parentNode;
+ var nextSibling;
+ var node;
+
+ node = firstNodeFor(parentNode, html);
+ var insertBefore = start.nextSibling;
+
+ while (node) {
+ nextSibling = node.nextSibling;
+ parentNode.insertBefore(node, insertBefore);
+ node = nextSibling;
+ }
+ };
+ }
+
+ Metamorph.prototype.html = function(html) {
+ this.checkRemoved();
+ if (html === undefined) { return this.innerHTML; }
+
+ htmlFunc.call(this, html);
+
+ this.innerHTML = html;
+ };
+
+ Metamorph.prototype.replaceWith = function(html) {
+ this.checkRemoved();
+ htmlFunc.call(this, html, true);
+ };
+
+ Metamorph.prototype.remove = removeFunc;
+ Metamorph.prototype.outerHTML = outerHTMLFunc;
+ Metamorph.prototype.appendTo = appendToFunc;
+ Metamorph.prototype.after = afterFunc;
+ Metamorph.prototype.prepend = prependFunc;
+ Metamorph.prototype.startTag = startTagFunc;
+ Metamorph.prototype.endTag = endTagFunc;
+
+ Metamorph.prototype.isRemoved = function() {
+ var before = document.getElementById(this.start);
+ var after = document.getElementById(this.end);
+
+ return !before || !after;
+ };
+
+ Metamorph.prototype.checkRemoved = function() {
+ if (this.isRemoved()) {
+ throw new Error("Cannot perform operations on a Metamorph that is not in the DOM.");
+ }
+ };
+
+ return Metamorph;
+ });
+
+})();
+
+(function() {
+/**
+@module ember
+@submodule ember-handlebars-compiler
+*/
+
+// Eliminate dependency on any Ember to simplify precompilation workflow
+var objectCreate = Object.create || function(parent) {
+ function F() {}
+ F.prototype = parent;
+ return new F();
+};
+
+var Handlebars = (Ember.imports && Ember.imports.Handlebars) || (this && this.Handlebars);
+if (!Handlebars && typeof require === 'function') {
+ Handlebars = require('handlebars');
+}
+
+Ember.assert("Ember Handlebars requires Handlebars version 1.0 or 1.1. Include " +
+ "a SCRIPT tag in the HTML HEAD linking to the Handlebars file " +
+ "before you link to Ember.", Handlebars);
+
+Ember.assert("Ember Handlebars requires Handlebars version 1.0 or 1.1, " +
+ "COMPILER_REVISION expected: 4, got: " + Handlebars.COMPILER_REVISION +
+ " - Please note: Builds of master may have other COMPILER_REVISION values.",
+ Handlebars.COMPILER_REVISION === 4);
+
+/**
+ Prepares the Handlebars templating library for use inside Ember's view
+ system.
+
+ The `Ember.Handlebars` object is the standard Handlebars library, extended to
+ use Ember's `get()` method instead of direct property access, which allows
+ computed properties to be used inside templates.
+
+ To create an `Ember.Handlebars` template, call `Ember.Handlebars.compile()`.
+ This will return a function that can be used by `Ember.View` for rendering.
+
+ @class Handlebars
+ @namespace Ember
+*/
+Ember.Handlebars = objectCreate(Handlebars);
+
+/**
+ Register a bound helper or custom view helper.
+
+ ## Simple bound helper example
+
+ ```javascript
+ Ember.Handlebars.helper('capitalize', function(value) {
+ return value.toUpperCase();
+ });
+ ```
+
+ The above bound helper can be used inside of templates as follows:
+
+ ```handlebars
+ {{capitalize name}}
+ ```
+
+ In this case, when the `name` property of the template's context changes,
+ the rendered value of the helper will update to reflect this change.
+
+ For more examples of bound helpers, see documentation for
+ `Ember.Handlebars.registerBoundHelper`.
+
+ ## Custom view helper example
+
+ Assuming a view subclass named `App.CalendarView` were defined, a helper
+ for rendering instances of this view could be registered as follows:
+
+ ```javascript
+ Ember.Handlebars.helper('calendar', App.CalendarView):
+ ```
+
+ The above bound helper can be used inside of templates as follows:
+
+ ```handlebars
+ {{calendar}}
+ ```
+
+ Which is functionally equivalent to:
+
+ ```handlebars
+ {{view App.CalendarView}}
+ ```
+
+ Options in the helper will be passed to the view in exactly the same
+ manner as with the `view` helper.
+
+ @method helper
+ @for Ember.Handlebars
+ @param {String} name
+ @param {Function|Ember.View} function or view class constructor
+ @param {String} dependentKeys*
+*/
+Ember.Handlebars.helper = function(name, value) {
+ Ember.assert("You tried to register a component named '" + name + "', but component names must include a '-'", !Ember.Component.detect(value) || name.match(/-/));
+
+ if (Ember.View.detect(value)) {
+ Ember.Handlebars.registerHelper(name, Ember.Handlebars.makeViewHelper(value));
+ } else {
+ Ember.Handlebars.registerBoundHelper.apply(null, arguments);
+ }
+};
+
+/**
+ Returns a helper function that renders the provided ViewClass.
+
+ Used internally by Ember.Handlebars.helper and other methods
+ involving helper/component registration.
+
+ @private
+ @method helper
+ @for Ember.Handlebars
+ @param {Function} ViewClass view class constructor
+*/
+Ember.Handlebars.makeViewHelper = function(ViewClass) {
+ return function(options) {
+ Ember.assert("You can only pass attributes (such as name=value) not bare values to a helper for a View found in '" + ViewClass.toString() + "'", arguments.length < 2);
+ return Ember.Handlebars.helpers.view.call(this, ViewClass, options);
+ };
+};
+
+/**
+@class helpers
+@namespace Ember.Handlebars
+*/
+Ember.Handlebars.helpers = objectCreate(Handlebars.helpers);
+
+/**
+ Override the the opcode compiler and JavaScript compiler for Handlebars.
+
+ @class Compiler
+ @namespace Ember.Handlebars
+ @private
+ @constructor
+*/
+Ember.Handlebars.Compiler = function() {};
+
+// Handlebars.Compiler doesn't exist in runtime-only
+if (Handlebars.Compiler) {
+ Ember.Handlebars.Compiler.prototype = objectCreate(Handlebars.Compiler.prototype);
+}
+
+Ember.Handlebars.Compiler.prototype.compiler = Ember.Handlebars.Compiler;
+
+/**
+ @class JavaScriptCompiler
+ @namespace Ember.Handlebars
+ @private
+ @constructor
+*/
+Ember.Handlebars.JavaScriptCompiler = function() {};
+
+// Handlebars.JavaScriptCompiler doesn't exist in runtime-only
+if (Handlebars.JavaScriptCompiler) {
+ Ember.Handlebars.JavaScriptCompiler.prototype = objectCreate(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 "''";
+};
+
+/**
+ Override the default buffer for Ember Handlebars. By default, Handlebars
+ creates an empty String at the beginning of each invocation and appends to
+ it. Ember's Handlebars overrides this to append to a single shared buffer.
+
+ @private
+ @method appendToBuffer
+ @param string {String}
+*/
+Ember.Handlebars.JavaScriptCompiler.prototype.appendToBuffer = function(string) {
+ return "data.buffer.push("+string+");";
+};
+
+// Hacks ahead:
+// Handlebars presently has a bug where the `blockHelperMissing` hook
+// doesn't get passed the name of the missing helper name, but rather
+// gets passed the value of that missing helper evaluated on the current
+// context, which is most likely `undefined` and totally useless.
+//
+// So we alter the compiled template function to pass the name of the helper
+// instead, as expected.
+//
+// This can go away once the following is closed:
+// https://github.com/wycats/handlebars.js/issues/634
+
+var DOT_LOOKUP_REGEX = /helpers\.(.*?)\)/,
+ BRACKET_STRING_LOOKUP_REGEX = /helpers\['(.*?)'/,
+ INVOCATION_SPLITTING_REGEX = /(.*blockHelperMissing\.call\(.*)(stack[0-9]+)(,.*)/;
+
+Ember.Handlebars.JavaScriptCompiler.stringifyLastBlockHelperMissingInvocation = function(source) {
+ var helperInvocation = source[source.length - 1],
+ helperName = (DOT_LOOKUP_REGEX.exec(helperInvocation) || BRACKET_STRING_LOOKUP_REGEX.exec(helperInvocation))[1],
+ matches = INVOCATION_SPLITTING_REGEX.exec(helperInvocation);
+
+ source[source.length - 1] = matches[1] + "'" + helperName + "'" + matches[3];
+}
+var stringifyBlockHelperMissing = Ember.Handlebars.JavaScriptCompiler.stringifyLastBlockHelperMissingInvocation;
+
+var originalBlockValue = Ember.Handlebars.JavaScriptCompiler.prototype.blockValue;
+Ember.Handlebars.JavaScriptCompiler.prototype.blockValue = function() {
+ originalBlockValue.apply(this, arguments);
+ stringifyBlockHelperMissing(this.source);
+};
+
+var originalAmbiguousBlockValue = Ember.Handlebars.JavaScriptCompiler.prototype.ambiguousBlockValue;
+Ember.Handlebars.JavaScriptCompiler.prototype.ambiguousBlockValue = function() {
+ originalAmbiguousBlockValue.apply(this, arguments);
+ stringifyBlockHelperMissing(this.source);
+};
+
+var prefix = "ember" + (+new Date()), incr = 1;
+
+/**
+ Rewrite simple mustaches from `{{foo}}` to `{{bind "foo"}}`. This means that
+ all simple mustaches in Ember's Handlebars will also set up an observer to
+ keep the DOM up to date when the underlying property changes.
+
+ @private
+ @method mustache
+ @for Ember.Handlebars.Compiler
+ @param mustache
+*/
+Ember.Handlebars.Compiler.prototype.mustache = function(mustache) {
+ if (mustache.isHelper && mustache.id.string === 'control') {
+ mustache.hash = mustache.hash || new Handlebars.AST.HashNode([]);
+ mustache.hash.pairs.push(["controlID", new Handlebars.AST.StringNode(prefix + incr++)]);
+ } else if (mustache.params.length || mustache.hash) {
+ // no changes required
+ } else {
+ var id = new Handlebars.AST.IdNode([{ part: '_triageMustache' }]);
+
+ // Update the mustache node to include a hash value indicating whether the original node
+ // was escaped. This will allow us to properly escape values when the underlying value
+ // changes and we need to re-render the value.
+ if (!mustache.escaped) {
+ mustache.hash = mustache.hash || new Handlebars.AST.HashNode([]);
+ mustache.hash.pairs.push(["unescaped", new Handlebars.AST.StringNode("true")]);
+ }
+ mustache = new Handlebars.AST.MustacheNode([id].concat([mustache.id]), mustache.hash, !mustache.escaped);
+ }
+
+ return Handlebars.Compiler.prototype.mustache.call(this, mustache);
+};
+
+/**
+ Used for precompilation of Ember Handlebars templates. This will not be used
+ during normal app execution.
+
+ @method precompile
+ @for Ember.Handlebars
+ @static
+ @param {String} string The template to precompile
+*/
+Ember.Handlebars.precompile = function(string) {
+ var ast = Handlebars.parse(string);
+
+ var options = {
+ knownHelpers: {
+ action: true,
+ unbound: true,
+ 'bind-attr': true,
+ template: true,
+ view: true,
+ _triageMustache: true
+ },
+ data: true,
+ stringParams: true
+ };
+
+ var environment = new Ember.Handlebars.Compiler().compile(ast, options);
+ return new Ember.Handlebars.JavaScriptCompiler().compile(environment, options, undefined, true);
+};
+
+// We don't support this for Handlebars runtime-only
+if (Handlebars.compile) {
+ /**
+ The entry point for Ember Handlebars. This replaces the default
+ `Handlebars.compile` and turns on template-local data and String
+ parameters.
+
+ @method compile
+ @for Ember.Handlebars
+ @static
+ @param {String} string The template to compile
+ @return {Function}
+ */
+ Ember.Handlebars.compile = function(string) {
+ var ast = Handlebars.parse(string);
+ var options = { data: true, stringParams: true };
+ var environment = new Ember.Handlebars.Compiler().compile(ast, options);
+ var templateSpec = new Ember.Handlebars.JavaScriptCompiler().compile(environment, options, undefined, true);
+
+ var template = Ember.Handlebars.template(templateSpec);
+ template.isMethod = false; //Make sure we don't wrap templates with ._super
+
+ return template;
+ };
+}
+
+
+})();
+
+(function() {
+var slice = Array.prototype.slice,
+ originalTemplate = Ember.Handlebars.template;
+
+/**
+ If a path starts with a reserved keyword, returns the root
+ that should be used.
+
+ @private
+ @method normalizePath
+ @for Ember
+ @param root {Object}
+ @param path {String}
+ @param data {Hash}
+*/
+var normalizePath = Ember.Handlebars.normalizePath = function(root, path, data) {
+ var keywords = (data && data.keywords) || {},
+ keyword, isKeyword;
+
+ // Get the first segment of the path. For example, if the
+ // path is "foo.bar.baz", returns "foo".
+ keyword = path.split('.', 1)[0];
+
+ // Test to see if the first path is a keyword that has been
+ // passed along in the view's data hash. If so, we will treat
+ // that object as the new root.
+ if (keywords.hasOwnProperty(keyword)) {
+ // Look up the value in the template's data hash.
+ root = keywords[keyword];
+ isKeyword = true;
+
+ // Handle cases where the entire path is the reserved
+ // word. In that case, return the object itself.
+ if (path === keyword) {
+ path = '';
+ } else {
+ // Strip the keyword from the path and look up
+ // the remainder from the newly found root.
+ path = path.substr(keyword.length+1);
+ }
+ }
+
+ return { root: root, path: path, isKeyword: isKeyword };
+};
+
+
+/**
+ Lookup both on root and on window. If the path starts with
+ a keyword, the corresponding object will be looked up in the
+ template's data hash and used to resolve the path.
+
+ @method get
+ @for Ember.Handlebars
+ @param {Object} root The object to look up the property on
+ @param {String} path The path to be lookedup
+ @param {Object} options The template's option hash
+*/
+var handlebarsGet = Ember.Handlebars.get = function(root, path, options) {
+ var data = options && options.data,
+ normalizedPath = normalizePath(root, path, data),
+ value;
+
+
+ root = normalizedPath.root;
+ path = normalizedPath.path;
+
+ value = Ember.get(root, path);
+
+ if (value === undefined && root !== Ember.lookup && Ember.isGlobalPath(path)) {
+ value = Ember.get(Ember.lookup, path);
+ }
+
+
+ return value;
+};
+
+/**
+ This method uses `Ember.Handlebars.get` to lookup a value, then ensures
+ that the value is escaped properly.
+
+ If `unescaped` is a truthy value then the escaping will not be performed.
+
+ @method getEscaped
+ @for Ember.Handlebars
+ @param {Object} root The object to look up the property on
+ @param {String} path The path to be lookedup
+ @param {Object} options The template's option hash
+*/
+Ember.Handlebars.getEscaped = function(root, path, options) {
+ var result = handlebarsGet(root, path, options);
+
+ if (result === null || result === undefined) {
+ result = "";
+ } else if (!(result instanceof Handlebars.SafeString)) {
+ result = String(result);
+ }
+ if (!options.hash.unescaped){
+ result = Handlebars.Utils.escapeExpression(result);
+ }
+
+ return result;
+};
+
+Ember.Handlebars.resolveParams = function(context, params, options) {
+ var resolvedParams = [], types = options.types, param, type;
+
+ for (var i=0, l=params.length; isomeString')
+ ```
+
+ @method htmlSafe
+ @for Ember.String
+ @static
+ @return {Handlebars.SafeString} a string that will not be html escaped by Handlebars
+*/
+Ember.String.htmlSafe = function(str) {
+ return new Handlebars.SafeString(str);
+};
+
+var htmlSafe = Ember.String.htmlSafe;
+
+if (Ember.EXTEND_PROTOTYPES === true || Ember.EXTEND_PROTOTYPES.String) {
+
+ /**
+ Mark a string as being safe for unescaped output with Handlebars.
+
+ ```javascript
+ '
someString
'.htmlSafe()
+ ```
+
+ See [Ember.String.htmlSafe](/api/classes/Ember.String.html#method_htmlSafe).
+
+ @method htmlSafe
+ @for String
+ @return {Handlebars.SafeString} a string that will not be html escaped by Handlebars
+ */
+ String.prototype.htmlSafe = function() {
+ return htmlSafe(this);
+ };
+}
+
+})();
+
+
+
+(function() {
+Ember.Handlebars.resolvePaths = function(options) {
+ var ret = [],
+ contexts = options.contexts,
+ roots = options.roots,
+ data = options.data;
+
+ for (var i=0, l=contexts.length; i{{user.name}}
+
+
+
{{user.role.label}}
+ {{user.role.id}}
+
+
{{user.role.description}}
+
+ ```
+
+ `{{with}}` can be our best friend in these cases,
+ instead of writing `user.role.*` over and over, we use `{{#with user.role}}`.
+ Now the context within the `{{#with}} .. {{/with}}` block is `user.role` so you can do the following:
+
+ ```handlebars
+
{{user.name}}
+
+
+ {{#with user.role}}
+
{{label}}
+ {{id}}
+
+
{{description}}
+ {{/with}}
+
+ ```
+
+ ### `as` operator
+
+ This operator aliases the scope to a new name. It's helpful for semantic clarity and to retain
+ default scope or to reference from another `{{with}}` block.
+
+ ```handlebars
+ // posts might not be
+ {{#with user.posts as blogPosts}}
+
+ There are {{blogPosts.length}} blog posts written by {{user.name}}.
+
+
+ {{#each post in blogPosts}}
+
{{post.title}}
+ {{/each}}
+ {{/with}}
+ ```
+
+ Without the `as` operator, it would be impossible to reference `user.name` in the example above.
+
+ ### `controller` option
+
+ Adding `controller='something'` instructs the `{{with}}` helper to create and use an instance of
+ the specified controller with the new context as its content.
+
+ This is very similar to using an `itemController` option with the `{{each}}` helper.
+
+ ```handlebars
+ {{#with users.posts controller='userBlogPosts'}}
+ {{!- The current context is wrapped in our controller instance }}
+ {{/with}}
+ ```
+
+ In the above example, the template provided to the `{{with}}` block is now wrapped in the
+ `userBlogPost` controller, which provides a very elegant way to decorate the context with custom
+ functions/properties.
+
+ @method with
+ @for Ember.Handlebars.helpers
+ @param {Function} context
+ @param {Hash} options
+ @return {String} HTML string
+*/
+EmberHandlebars.registerHelper('with', function withHelper(context, options) {
+ if (arguments.length === 4) {
+ var keywordName, path, rootPath, normalized, contextPath;
+
+ Ember.assert("If you pass more than one argument to the with helper, it must be in the form #with foo as bar", arguments[1] === "as");
+ options = arguments[3];
+ keywordName = arguments[2];
+ path = arguments[0];
+
+ Ember.assert("You must pass a block to the with helper", options.fn && options.fn !== Handlebars.VM.noop);
+
+ var localizedOptions = o_create(options);
+ localizedOptions.data = o_create(options.data);
+ localizedOptions.data.keywords = o_create(options.data.keywords || {});
+
+ if (Ember.isGlobalPath(path)) {
+ contextPath = path;
+ } else {
+ normalized = normalizePath(this, path, options.data);
+ path = normalized.path;
+ rootPath = normalized.root;
+
+ // This is a workaround for the fact that you cannot bind separate objects
+ // together. When we implement that functionality, we should use it here.
+ var contextKey = Ember.$.expando + Ember.guidFor(rootPath);
+ localizedOptions.data.keywords[contextKey] = rootPath;
+ // if the path is '' ("this"), just bind directly to the current context
+ contextPath = path ? contextKey + '.' + path : contextKey;
+ }
+
+ Ember.bind(localizedOptions.data.keywords, keywordName, contextPath);
+
+ return bind.call(this, path, localizedOptions, true, exists);
+ } else {
+ Ember.assert("You must pass exactly one argument to the with helper", arguments.length === 2);
+ Ember.assert("You must pass a block to the with helper", options.fn && options.fn !== Handlebars.VM.noop);
+ return helpers.bind.call(options.contexts[0], context, options);
+ }
+});
+
+
+/**
+ See [boundIf](/api/classes/Ember.Handlebars.helpers.html#method_boundIf)
+ and [unboundIf](/api/classes/Ember.Handlebars.helpers.html#method_unboundIf)
+
+ @method if
+ @for Ember.Handlebars.helpers
+ @param {Function} context
+ @param {Hash} options
+ @return {String} HTML string
+*/
+EmberHandlebars.registerHelper('if', function ifHelper(context, options) {
+ Ember.assert("You must pass exactly one argument to the if helper", arguments.length === 2);
+ Ember.assert("You must pass a block to the if helper", options.fn && options.fn !== Handlebars.VM.noop);
+ if (options.data.isUnbound) {
+ return helpers.unboundIf.call(options.contexts[0], context, options);
+ } else {
+ return helpers.boundIf.call(options.contexts[0], context, options);
+ }
+});
+
+/**
+ @method unless
+ @for Ember.Handlebars.helpers
+ @param {Function} context
+ @param {Hash} options
+ @return {String} HTML string
+*/
+EmberHandlebars.registerHelper('unless', function unlessHelper(context, options) {
+ Ember.assert("You must pass exactly one argument to the unless helper", arguments.length === 2);
+ Ember.assert("You must pass a block to the unless helper", options.fn && options.fn !== Handlebars.VM.noop);
+
+ var fn = options.fn, inverse = options.inverse;
+
+ options.fn = inverse;
+ options.inverse = fn;
+
+ if (options.data.isUnbound) {
+ return helpers.unboundIf.call(options.contexts[0], context, options);
+ } else {
+ return helpers.boundIf.call(options.contexts[0], context, options);
+ }
+});
+
+/**
+ `bind-attr` allows you to create a binding between DOM element attributes and
+ Ember objects. For example:
+
+ ```handlebars
+
+ ```
+
+ The above handlebars template will fill the ``'s `src` attribute will
+ the value of the property referenced with `"imageUrl"` and its `alt`
+ attribute with the value of the property referenced with `"imageTitle"`.
+
+ If the rendering context of this template is the following object:
+
+ ```javascript
+ {
+ imageUrl: 'http://lolcats.info/haz-a-funny',
+ imageTitle: 'A humorous image of a cat'
+ }
+ ```
+
+ The resulting HTML output will be:
+
+ ```html
+
+ ```
+
+ `bind-attr` cannot redeclare existing DOM element attributes. The use of `src`
+ in the following `bind-attr` example will be ignored and the hard coded value
+ of `src="/failwhale.gif"` will take precedence:
+
+ ```handlebars
+
+ ```
+
+ ### `bind-attr` and the `class` attribute
+
+ `bind-attr` supports a special syntax for handling a number of cases unique
+ to the `class` DOM element attribute. The `class` attribute combines
+ multiple discrete values into a single attribute as a space-delimited
+ list of strings. Each string can be:
+
+ * a string return value of an object's property.
+ * a boolean return value of an object's property
+ * a hard-coded value
+
+ A string return value works identically to other uses of `bind-attr`. The
+ return value of the property will become the value of the attribute. For
+ example, the following view and template:
+
+ ```javascript
+ AView = Ember.View.extend({
+ someProperty: function() {
+ return "aValue";
+ }.property()
+ })
+ ```
+
+ ```handlebars
+
+ ```
+
+ A boolean return value will insert a specified class name if the property
+ returns `true` and remove the class name if the property returns `false`.
+
+ A class name is provided via the syntax
+ `somePropertyName:class-name-if-true`.
+
+ ```javascript
+ AView = Ember.View.extend({
+ someBool: true
+ })
+ ```
+
+ ```handlebars
+
+ ```
+
+ Result in the following rendered output:
+
+ ```html
+
+ ```
+
+ An additional section of the binding can be provided if you want to
+ replace the existing class instead of removing it when the boolean
+ value changes:
+
+ ```handlebars
+
+ ```
+
+ A hard-coded value can be used by prepending `:` to the desired
+ class name: `:class-name-to-always-apply`.
+
+ ```handlebars
+
+ ```
+
+ Results in the following rendered output:
+
+ ```html
+
+ ```
+
+ All three strategies - string return value, boolean return value, and
+ hard-coded value – can be combined in a single declaration:
+
+ ```handlebars
+
+ ```
+
+ @method bind-attr
+ @for Ember.Handlebars.helpers
+ @param {Hash} options
+ @return {String} HTML string
+*/
+EmberHandlebars.registerHelper('bind-attr', function bindAttrHelper(options) {
+
+ var attrs = options.hash;
+
+ Ember.assert("You must specify at least one hash argument to bind-attr", !!Ember.keys(attrs).length);
+
+ var view = options.data.view;
+ var ret = [];
+ var ctx = this;
+
+ // Generate a unique id for this element. This will be added as a
+ // data attribute to the element so it can be looked up when
+ // the bound property changes.
+ var dataId = ++Ember.uuid;
+
+ // Handle classes differently, as we can bind multiple classes
+ var classBindings = attrs['class'];
+ if (classBindings != null) {
+ var classResults = EmberHandlebars.bindClasses(this, classBindings, view, dataId, options);
+
+ ret.push('class="' + Handlebars.Utils.escapeExpression(classResults.join(' ')) + '"');
+ delete attrs['class'];
+ }
+
+ var attrKeys = Ember.keys(attrs);
+
+ // For each attribute passed, create an observer and emit the
+ // current value of the property as an attribute.
+ forEach.call(attrKeys, function(attr) {
+ var path = attrs[attr],
+ normalized;
+
+ Ember.assert(fmt("You must provide an expression as the value of bound attribute. You specified: %@=%@", [attr, path]), typeof path === 'string');
+
+ normalized = normalizePath(ctx, path, options.data);
+
+ var value = (path === 'this') ? normalized.root : handlebarsGet(ctx, path, options),
+ type = Ember.typeOf(value);
+
+ Ember.assert(fmt("Attributes must be numbers, strings or booleans, not %@", [value]), value === null || value === undefined || type === 'number' || type === 'string' || type === 'boolean');
+
+ var observer, invoker;
+
+ observer = function observer() {
+ var result = handlebarsGet(ctx, path, options);
+
+ Ember.assert(fmt("Attributes must be numbers, strings or booleans, not %@", [result]),
+ result === null || result === undefined || typeof result === 'number' ||
+ typeof result === 'string' || typeof result === 'boolean');
+
+ var elem = view.$("[data-bindattr-" + dataId + "='" + dataId + "']");
+
+ // If we aren't able to find the element, it means the element
+ // to which we were bound has been removed from the view.
+ // In that case, we can assume the template has been re-rendered
+ // and we need to clean up the observer.
+ if (!elem || elem.length === 0) {
+ Ember.removeObserver(normalized.root, normalized.path, invoker);
+ return;
+ }
+
+ Ember.View.applyAttributeBindings(elem, attr, result);
+ };
+
+ // Add an observer to the view for when the property changes.
+ // When the observer fires, find the element using the
+ // unique data id and update the attribute to the new value.
+ // Note: don't add observer when path is 'this' or path
+ // is whole keyword e.g. {{#each x in list}} ... {{bind-attr attr="x"}}
+ if (path !== 'this' && !(normalized.isKeyword && normalized.path === '' )) {
+ view.registerObserver(normalized.root, normalized.path, observer);
+ }
+
+ // if this changes, also change the logic in ember-views/lib/views/view.js
+ if ((type === 'string' || (type === 'number' && !isNaN(value)))) {
+ ret.push(attr + '="' + Handlebars.Utils.escapeExpression(value) + '"');
+ } else if (value && type === 'boolean') {
+ // The developer controls the attr name, so it should always be safe
+ ret.push(attr + '="' + attr + '"');
+ }
+ }, this);
+
+ // Add the unique identifier
+ // NOTE: We use all lower-case since Firefox has problems with mixed case in SVG
+ ret.push('data-bindattr-' + dataId + '="' + dataId + '"');
+ return new EmberHandlebars.SafeString(ret.join(' '));
+});
+
+/**
+ See `bind-attr`
+
+ @method bindAttr
+ @for Ember.Handlebars.helpers
+ @deprecated
+ @param {Function} context
+ @param {Hash} options
+ @return {String} HTML string
+*/
+EmberHandlebars.registerHelper('bindAttr', function bindAttrHelper() {
+ Ember.warn("The 'bindAttr' view helper is deprecated in favor of 'bind-attr'");
+ return EmberHandlebars.helpers['bind-attr'].apply(this, arguments);
+});
+
+/**
+ Helper that, given a space-separated string of property paths and a context,
+ returns an array of class names. Calling this method also has the side
+ effect of setting up observers at those property paths, such that if they
+ change, the correct class name will be reapplied to the DOM element.
+
+ For example, if you pass the string "fooBar", it will first look up the
+ "fooBar" value of the context. If that value is true, it will add the
+ "foo-bar" class to the current element (i.e., the dasherized form of
+ "fooBar"). If the value is a string, it will add that string as the class.
+ Otherwise, it will not add any new class name.
+
+ @private
+ @method bindClasses
+ @for Ember.Handlebars
+ @param {Ember.Object} context The context from which to lookup properties
+ @param {String} classBindings A string, space-separated, of class bindings
+ to use
+ @param {Ember.View} view The view in which observers should look for the
+ element to update
+ @param {Srting} bindAttrId Optional bindAttr id used to lookup elements
+ @return {Array} An array of class names to add
+*/
+EmberHandlebars.bindClasses = function(context, classBindings, view, bindAttrId, options) {
+ var ret = [], newClass, value, elem;
+
+ // Helper method to retrieve the property from the context and
+ // determine which class string to return, based on whether it is
+ // a Boolean or not.
+ var classStringForPath = function(root, parsedPath, options) {
+ var val,
+ path = parsedPath.path;
+
+ if (path === 'this') {
+ val = root;
+ } else if (path === '') {
+ val = true;
+ } else {
+ val = handlebarsGet(root, path, options);
+ }
+
+ return Ember.View._classStringForValue(path, val, parsedPath.className, parsedPath.falsyClassName);
+ };
+
+ // For each property passed, loop through and setup
+ // an observer.
+ forEach.call(classBindings.split(' '), function(binding) {
+
+ // Variable in which the old class value is saved. The observer function
+ // closes over this variable, so it knows which string to remove when
+ // the property changes.
+ var oldClass;
+
+ var observer, invoker;
+
+ var parsedPath = Ember.View._parsePropertyPath(binding),
+ path = parsedPath.path,
+ pathRoot = context,
+ normalized;
+
+ if (path !== '' && path !== 'this') {
+ normalized = normalizePath(context, path, options.data);
+
+ pathRoot = normalized.root;
+ path = normalized.path;
+ }
+
+ // Set up an observer on the context. If the property changes, toggle the
+ // class name.
+ observer = function() {
+ // Get the current value of the property
+ newClass = classStringForPath(context, parsedPath, options);
+ elem = bindAttrId ? view.$("[data-bindattr-" + bindAttrId + "='" + bindAttrId + "']") : view.$();
+
+ // If we can't find the element anymore, a parent template has been
+ // re-rendered and we've been nuked. Remove the observer.
+ if (!elem || elem.length === 0) {
+ Ember.removeObserver(pathRoot, path, invoker);
+ } else {
+ // If we had previously added a class to the element, remove it.
+ if (oldClass) {
+ elem.removeClass(oldClass);
+ }
+
+ // If necessary, add a new class. Make sure we keep track of it so
+ // it can be removed in the future.
+ if (newClass) {
+ elem.addClass(newClass);
+ oldClass = newClass;
+ } else {
+ oldClass = null;
+ }
+ }
+ };
+
+ if (path !== '' && path !== 'this') {
+ view.registerObserver(pathRoot, path, observer);
+ }
+
+ // We've already setup the observer; now we just need to figure out the
+ // correct behavior right now on the first pass through.
+ value = classStringForPath(context, parsedPath, options);
+
+ if (value) {
+ ret.push(value);
+
+ // Make sure we save the current value so that it can be removed if the
+ // observer fires.
+ oldClass = value;
+ }
+ });
+
+ return ret;
+};
+
+
+})();
+
+
+
+(function() {
+/*globals Handlebars */
+
+// TODO: Don't require the entire module
+/**
+@module ember
+@submodule ember-handlebars
+*/
+
+var get = Ember.get, set = Ember.set;
+var EmberHandlebars = Ember.Handlebars;
+var LOWERCASE_A_Z = /^[a-z]/;
+var VIEW_PREFIX = /^view\./;
+
+function makeBindings(thisContext, options) {
+ var hash = options.hash,
+ hashType = options.hashTypes;
+
+ for (var prop in hash) {
+ if (hashType[prop] === 'ID') {
+
+ var value = hash[prop];
+
+ if (Ember.IS_BINDING.test(prop)) {
+ Ember.warn("You're attempting to render a view by passing " + prop + "=" + value + " to a view helper, but this syntax is ambiguous. You should either surround " + value + " in quotes or remove `Binding` from " + prop + ".");
+ } else {
+ hash[prop + 'Binding'] = value;
+ hashType[prop + 'Binding'] = 'STRING';
+ delete hash[prop];
+ delete hashType[prop];
+ }
+ }
+ }
+
+ if (hash.hasOwnProperty('idBinding')) {
+ // id can't be bound, so just perform one-time lookup.
+ hash.id = EmberHandlebars.get(thisContext, hash.idBinding, options);
+ hashType.id = 'STRING';
+ delete hash.idBinding;
+ delete hashType.idBinding;
+ }
+}
+
+EmberHandlebars.ViewHelper = Ember.Object.create({
+
+ propertiesFromHTMLOptions: function(options) {
+ var hash = options.hash, data = options.data;
+ var extensions = {},
+ classes = hash['class'],
+ dup = false;
+
+ if (hash.id) {
+ extensions.elementId = hash.id;
+ dup = true;
+ }
+
+ if (hash.tag) {
+ extensions.tagName = hash.tag;
+ dup = true;
+ }
+
+ if (classes) {
+ classes = classes.split(' ');
+ extensions.classNames = classes;
+ dup = true;
+ }
+
+ if (hash.classBinding) {
+ extensions.classNameBindings = hash.classBinding.split(' ');
+ dup = true;
+ }
+
+ if (hash.classNameBindings) {
+ if (extensions.classNameBindings === undefined) extensions.classNameBindings = [];
+ extensions.classNameBindings = extensions.classNameBindings.concat(hash.classNameBindings.split(' '));
+ dup = true;
+ }
+
+ if (hash.attributeBindings) {
+ Ember.assert("Setting 'attributeBindings' via Handlebars is not allowed. Please subclass Ember.View and set it there instead.");
+ extensions.attributeBindings = null;
+ dup = true;
+ }
+
+ if (dup) {
+ hash = Ember.$.extend({}, hash);
+ delete hash.id;
+ delete hash.tag;
+ delete hash['class'];
+ delete hash.classBinding;
+ }
+
+ // Set the proper context for all bindings passed to the helper. This applies to regular attribute bindings
+ // as well as class name bindings. If the bindings are local, make them relative to the current context
+ // instead of the view.
+ var path;
+
+ // Evaluate the context of regular attribute bindings:
+ for (var prop in hash) {
+ if (!hash.hasOwnProperty(prop)) { continue; }
+
+ // Test if the property ends in "Binding"
+ if (Ember.IS_BINDING.test(prop) && typeof hash[prop] === 'string') {
+ path = this.contextualizeBindingPath(hash[prop], data);
+ if (path) { hash[prop] = path; }
+ }
+ }
+
+ // Evaluate the context of class name bindings:
+ if (extensions.classNameBindings) {
+ for (var b in extensions.classNameBindings) {
+ var full = extensions.classNameBindings[b];
+ if (typeof full === 'string') {
+ // Contextualize the path of classNameBinding so this:
+ //
+ // classNameBinding="isGreen:green"
+ //
+ // is converted to this:
+ //
+ // classNameBinding="_parentView.context.isGreen:green"
+ var parsedPath = Ember.View._parsePropertyPath(full);
+ path = this.contextualizeBindingPath(parsedPath.path, data);
+ if (path) { extensions.classNameBindings[b] = path + parsedPath.classNames; }
+ }
+ }
+ }
+
+ return Ember.$.extend(hash, extensions);
+ },
+
+ // Transform bindings from the current context to a context that can be evaluated within the view.
+ // Returns null if the path shouldn't be changed.
+ //
+ // TODO: consider the addition of a prefix that would allow this method to return `path`.
+ contextualizeBindingPath: function(path, data) {
+ var normalized = Ember.Handlebars.normalizePath(null, path, data);
+ if (normalized.isKeyword) {
+ return 'templateData.keywords.' + path;
+ } else if (Ember.isGlobalPath(path)) {
+ return null;
+ } else if (path === 'this' || path === '') {
+ return '_parentView.context';
+ } else {
+ return '_parentView.context.' + path;
+ }
+ },
+
+ helper: function(thisContext, path, options) {
+ var data = options.data,
+ fn = options.fn,
+ newView;
+
+ makeBindings(thisContext, options);
+
+ if ('string' === typeof path) {
+
+ // TODO: this is a lame conditional, this should likely change
+ // but something along these lines will likely need to be added
+ // as deprecation warnings
+ //
+ if (options.types[0] === 'STRING' && LOWERCASE_A_Z.test(path) && !VIEW_PREFIX.test(path)) {
+ Ember.assert("View requires a container", !!data.view.container);
+ newView = data.view.container.lookupFactory('view:' + path);
+ } else {
+ newView = EmberHandlebars.get(thisContext, path, options);
+ }
+
+ Ember.assert("Unable to find view at path '" + path + "'", !!newView);
+ } else {
+ newView = path;
+ }
+
+ Ember.assert(Ember.String.fmt('You must pass a view to the #view helper, not %@ (%@)', [path, newView]), Ember.View.detect(newView) || Ember.View.detectInstance(newView));
+
+ var viewOptions = this.propertiesFromHTMLOptions(options, thisContext);
+ var currentView = data.view;
+ viewOptions.templateData = data;
+ var newViewProto = newView.proto ? newView.proto() : newView;
+
+ if (fn) {
+ Ember.assert("You cannot provide a template block if you also specified a templateName", !get(viewOptions, 'templateName') && !get(newViewProto, 'templateName'));
+ viewOptions.template = fn;
+ }
+
+ // We only want to override the `_context` computed property if there is
+ // no specified controller. See View#_context for more information.
+ if (!newViewProto.controller && !newViewProto.controllerBinding && !viewOptions.controller && !viewOptions.controllerBinding) {
+ viewOptions._context = thisContext;
+ }
+
+ currentView.appendChild(newView, viewOptions);
+ }
+});
+
+/**
+ `{{view}}` inserts a new instance of `Ember.View` into a template passing its
+ options to the `Ember.View`'s `create` method and using the supplied block as
+ the view's own template.
+
+ An empty `` and the following template:
+
+ ```handlebars
+ A span:
+ {{#view tagName="span"}}
+ hello.
+ {{/view}}
+ ```
+
+ Will result in HTML structure:
+
+ ```html
+
+
+
+
+ A span:
+
+ Hello.
+
+
+
+ ```
+
+ ### `parentView` setting
+
+ The `parentView` property of the new `Ember.View` instance created through
+ `{{view}}` will be set to the `Ember.View` instance of the template where
+ `{{view}}` was called.
+
+ ```javascript
+ aView = Ember.View.create({
+ template: Ember.Handlebars.compile("{{#view}} my parent: {{parentView.elementId}} {{/view}}")
+ });
+
+ aView.appendTo('body');
+ ```
+
+ Will result in HTML structure:
+
+ ```html
+
+
+ my parent: ember1
+
+
+ ```
+
+ ### Setting CSS id and class attributes
+
+ The HTML `id` attribute can be set on the `{{view}}`'s resulting element with
+ the `id` option. This option will _not_ be passed to `Ember.View.create`.
+
+ ```handlebars
+ {{#view tagName="span" id="a-custom-id"}}
+ hello.
+ {{/view}}
+ ```
+
+ Results in the following HTML structure:
+
+ ```html
+
+
+ hello.
+
+
+ ```
+
+ The HTML `class` attribute can be set on the `{{view}}`'s resulting element
+ with the `class` or `classNameBindings` options. The `class` option will
+ directly set the CSS `class` attribute and will not be passed to
+ `Ember.View.create`. `classNameBindings` will be passed to `create` and use
+ `Ember.View`'s class name binding functionality:
+
+ ```handlebars
+ {{#view tagName="span" class="a-custom-class"}}
+ hello.
+ {{/view}}
+ ```
+
+ Results in the following HTML structure:
+
+ ```html
+
+
+ hello.
+
+
+ ```
+
+ ### Supplying a different view class
+
+ `{{view}}` can take an optional first argument before its supplied options to
+ specify a path to a custom view class.
+
+ ```handlebars
+ {{#view "MyApp.CustomView"}}
+ hello.
+ {{/view}}
+ ```
+
+ The first argument can also be a relative path accessible from the current
+ context.
+
+ ```javascript
+ MyApp = Ember.Application.create({});
+ MyApp.OuterView = Ember.View.extend({
+ innerViewClass: Ember.View.extend({
+ classNames: ['a-custom-view-class-as-property']
+ }),
+ template: Ember.Handlebars.compile('{{#view "view.innerViewClass"}} hi {{/view}}')
+ });
+
+ MyApp.OuterView.create().appendTo('body');
+ ```
+
+ Will result in the following HTML:
+
+ ```html
+
+
+ hi
+
+
+ ```
+
+ ### Blockless use
+
+ If you supply a custom `Ember.View` subclass that specifies its own template
+ or provide a `templateName` option to `{{view}}` it can be used without
+ supplying a block. Attempts to use both a `templateName` option and supply a
+ block will throw an error.
+
+ ```handlebars
+ {{view "MyApp.ViewWithATemplateDefined"}}
+ ```
+
+ ### `viewName` property
+
+ You can supply a `viewName` option to `{{view}}`. The `Ember.View` instance
+ will be referenced as a property of its parent view by this name.
+
+ ```javascript
+ aView = Ember.View.create({
+ template: Ember.Handlebars.compile('{{#view viewName="aChildByName"}} hi {{/view}}')
+ });
+
+ aView.appendTo('body');
+ aView.get('aChildByName') // the instance of Ember.View created by {{view}} helper
+ ```
+
+ @method view
+ @for Ember.Handlebars.helpers
+ @param {String} path
+ @param {Hash} options
+ @return {String} HTML string
+*/
+EmberHandlebars.registerHelper('view', function viewHelper(path, options) {
+ Ember.assert("The view helper only takes a single argument", arguments.length <= 2);
+
+ // If no path is provided, treat path param as options.
+ if (path && path.data && path.data.isRenderData) {
+ options = path;
+ path = "Ember.View";
+ }
+
+ return EmberHandlebars.ViewHelper.helper(this, path, options);
+});
+
+
+})();
+
+
+
+(function() {
+// TODO: Don't require all of this module
+/**
+@module ember
+@submodule ember-handlebars
+*/
+
+var get = Ember.get, handlebarsGet = Ember.Handlebars.get, fmt = Ember.String.fmt;
+
+/**
+ `{{collection}}` is a `Ember.Handlebars` helper for adding instances of
+ `Ember.CollectionView` to a template. See [Ember.CollectionView](/api/classes/Ember.CollectionView.html)
+ for additional information on how a `CollectionView` functions.
+
+ `{{collection}}`'s primary use is as a block helper with a `contentBinding`
+ option pointing towards an `Ember.Array`-compatible object. An `Ember.View`
+ instance will be created for each item in its `content` property. Each view
+ will have its own `content` property set to the appropriate item in the
+ collection.
+
+ The provided block will be applied as the template for each item's view.
+
+ Given an empty `` the following template:
+
+ ```handlebars
+ {{#collection contentBinding="App.items"}}
+ Hi {{view.content.name}}
+ {{/collection}}
+ ```
+
+ And the following application code
+
+ ```javascript
+ App = Ember.Application.create()
+ App.items = [
+ Ember.Object.create({name: 'Dave'}),
+ Ember.Object.create({name: 'Mary'}),
+ Ember.Object.create({name: 'Sara'})
+ ]
+ ```
+
+ Will result in the HTML structure below
+
+ ```html
+
+
Hi Dave
+
Hi Mary
+
Hi Sara
+
+ ```
+
+ ### Blockless use in a collection
+
+ If you provide an `itemViewClass` option that has its own `template` you can
+ omit the block.
+
+ The following template:
+
+ ```handlebars
+ {{collection contentBinding="App.items" itemViewClass="App.AnItemView"}}
+ ```
+
+ And application code
+
+ ```javascript
+ App = Ember.Application.create();
+ App.items = [
+ Ember.Object.create({name: 'Dave'}),
+ Ember.Object.create({name: 'Mary'}),
+ Ember.Object.create({name: 'Sara'})
+ ];
+
+ App.AnItemView = Ember.View.extend({
+ template: Ember.Handlebars.compile("Greetings {{view.content.name}}")
+ });
+ ```
+
+ Will result in the HTML structure below
+
+ ```html
+
+
Greetings Dave
+
Greetings Mary
+
Greetings Sara
+
+ ```
+
+ ### Specifying a CollectionView subclass
+
+ By default the `{{collection}}` helper will create an instance of
+ `Ember.CollectionView`. You can supply a `Ember.CollectionView` subclass to
+ the helper by passing it as the first argument:
+
+ ```handlebars
+ {{#collection App.MyCustomCollectionClass contentBinding="App.items"}}
+ Hi {{view.content.name}}
+ {{/collection}}
+ ```
+
+ ### Forwarded `item.*`-named Options
+
+ As with the `{{view}}`, helper options passed to the `{{collection}}` will be
+ set on the resulting `Ember.CollectionView` as properties. Additionally,
+ options prefixed with `item` will be applied to the views rendered for each
+ item (note the camelcasing):
+
+ ```handlebars
+ {{#collection contentBinding="App.items"
+ itemTagName="p"
+ itemClassNames="greeting"}}
+ Howdy {{view.content.name}}
+ {{/collection}}
+ ```
+
+ Will result in the following HTML structure:
+
+ ```html
+
+
Howdy Dave
+
Howdy Mary
+
Howdy Sara
+
+ ```
+
+ @method collection
+ @for Ember.Handlebars.helpers
+ @param {String} path
+ @param {Hash} options
+ @return {String} HTML string
+ @deprecated Use `{{each}}` helper instead.
+*/
+Ember.Handlebars.registerHelper('collection', function collectionHelper(path, options) {
+ Ember.deprecate("Using the {{collection}} helper without specifying a class has been deprecated as the {{each}} helper now supports the same functionality.", path !== 'collection');
+
+ // If no path is provided, treat path param as options.
+ if (path && path.data && path.data.isRenderData) {
+ options = path;
+ path = undefined;
+ Ember.assert("You cannot pass more than one argument to the collection helper", arguments.length === 1);
+ } else {
+ Ember.assert("You cannot pass more than one argument to the collection helper", arguments.length === 2);
+ }
+
+ var fn = options.fn;
+ var data = options.data;
+ var inverse = options.inverse;
+ var view = options.data.view;
+
+
+ var controller, container;
+ // If passed a path string, convert that into an object.
+ // Otherwise, just default to the standard class.
+ var collectionClass;
+ if (path) {
+ controller = data.keywords.controller;
+ container = controller && controller.container;
+ collectionClass = handlebarsGet(this, path, options) || container.lookupFactory('view:' + path);
+ Ember.assert(fmt("%@ #collection: Could not find collection class %@", [data.view, path]), !!collectionClass);
+ }
+ else {
+ collectionClass = Ember.CollectionView;
+ }
+
+ var hash = options.hash, itemHash = {}, match;
+
+ // Extract item view class if provided else default to the standard class
+ var collectionPrototype = collectionClass.proto(),
+ itemViewClass;
+
+ if (hash.itemView) {
+ controller = data.keywords.controller;
+ Ember.assert('You specified an itemView, but the current context has no ' +
+ 'container to look the itemView up in. This probably means ' +
+ 'that you created a view manually, instead of through the ' +
+ 'container. Instead, use container.lookup("view:viewName"), ' +
+ 'which will properly instantiate your view.',
+ controller && controller.container);
+ container = controller.container;
+ itemViewClass = container.lookupFactory('view:' + hash.itemView);
+ Ember.assert('You specified the itemView ' + hash.itemView + ", but it was " +
+ "not found at " + container.describe("view:" + hash.itemView) +
+ " (and it was not registered in the container)", !!itemViewClass);
+ } else if (hash.itemViewClass) {
+ itemViewClass = handlebarsGet(collectionPrototype, hash.itemViewClass, options);
+ } else {
+ itemViewClass = collectionPrototype.itemViewClass;
+ }
+
+ Ember.assert(fmt("%@ #collection: Could not find itemViewClass %@", [data.view, itemViewClass]), !!itemViewClass);
+
+ delete hash.itemViewClass;
+ delete hash.itemView;
+
+ // Go through options passed to the {{collection}} helper and extract options
+ // that configure item views instead of the collection itself.
+ for (var prop in hash) {
+ if (hash.hasOwnProperty(prop)) {
+ match = prop.match(/^item(.)(.*)$/);
+
+ if (match && prop !== 'itemController') {
+ // Convert itemShouldFoo -> shouldFoo
+ itemHash[match[1].toLowerCase() + match[2]] = hash[prop];
+ // Delete from hash as this will end up getting passed to the
+ // {{view}} helper method.
+ delete hash[prop];
+ }
+ }
+ }
+
+ if (fn) {
+ itemHash.template = fn;
+ delete options.fn;
+ }
+
+ var emptyViewClass;
+ if (inverse && inverse !== Ember.Handlebars.VM.noop) {
+ emptyViewClass = get(collectionPrototype, 'emptyViewClass');
+ emptyViewClass = emptyViewClass.extend({
+ template: inverse,
+ tagName: itemHash.tagName
+ });
+ } else if (hash.emptyViewClass) {
+ emptyViewClass = handlebarsGet(this, hash.emptyViewClass, options);
+ }
+ if (emptyViewClass) { hash.emptyView = emptyViewClass; }
+
+ if (!hash.keyword) {
+ itemHash._context = Ember.computed.alias('content');
+ }
+
+ var viewOptions = Ember.Handlebars.ViewHelper.propertiesFromHTMLOptions({ data: data, hash: itemHash }, this);
+ hash.itemViewClass = itemViewClass.extend(viewOptions);
+
+ return Ember.Handlebars.helpers.view.call(this, collectionClass, options);
+});
+
+
+})();
+
+
+
+(function() {
+/*globals Handlebars */
+/**
+@module ember
+@submodule ember-handlebars
+*/
+
+var handlebarsGet = Ember.Handlebars.get;
+
+/**
+ `unbound` allows you to output a property without binding. *Important:* The
+ output will not be updated if the property changes. Use with caution.
+
+ ```handlebars
+
{{unbound somePropertyThatDoesntChange}}
+ ```
+
+ `unbound` can also be used in conjunction with a bound helper to
+ render it in its unbound form:
+
+ ```handlebars
+
+ ```
+
+ @method unbound
+ @for Ember.Handlebars.helpers
+ @param {String} property
+ @return {String} HTML string
+*/
+Ember.Handlebars.registerHelper('unbound', function unboundHelper(property, fn) {
+ var options = arguments[arguments.length - 1], helper, context, out;
+
+ if (arguments.length > 2) {
+ // Unbound helper call.
+ options.data.isUnbound = true;
+ helper = Ember.Handlebars.helpers[arguments[0]] || Ember.Handlebars.helpers.helperMissing;
+ out = helper.apply(this, Array.prototype.slice.call(arguments, 1));
+ delete options.data.isUnbound;
+ return out;
+ }
+
+ context = (fn.contexts && fn.contexts.length) ? fn.contexts[0] : this;
+ return handlebarsGet(context, property, fn);
+});
+
+})();
+
+
+
+(function() {
+/*jshint debug:true*/
+/**
+@module ember
+@submodule ember-handlebars
+*/
+
+var get = Ember.get, handlebarsGet = Ember.Handlebars.get, normalizePath = Ember.Handlebars.normalizePath;
+
+/**
+ `log` allows you to output the value of a variable in the current rendering
+ context.
+
+ ```handlebars
+ {{log myVariable}}
+ ```
+
+ @method log
+ @for Ember.Handlebars.helpers
+ @param {String} property
+*/
+Ember.Handlebars.registerHelper('log', function logHelper(property, options) {
+ var context = (options.contexts && options.contexts.length) ? options.contexts[0] : this,
+ normalized = normalizePath(context, property, options.data),
+ pathRoot = normalized.root,
+ path = normalized.path,
+ value = (path === 'this') ? pathRoot : handlebarsGet(pathRoot, path, options);
+ Ember.Logger.log(value);
+});
+
+/**
+ Execute the `debugger` statement in the current context.
+
+ ```handlebars
+ {{debugger}}
+ ```
+
+ Before invoking the `debugger` statement, there
+ are a few helpful variables defined in the
+ body of this helper that you can inspect while
+ debugging that describe how and where this
+ helper was invoked:
+
+ - templateContext: this is most likely a controller
+ from which this template looks up / displays properties
+ - typeOfTemplateContext: a string description of
+ what the templateContext is
+
+ For example, if you're wondering why a value `{{foo}}`
+ isn't rendering as expected within a template, you
+ could place a `{{debugger}}` statement, and when
+ the `debugger;` breakpoint is hit, you can inspect
+ `templateContext`, determine if it's the object you
+ expect, and/or evaluate expressions in the console
+ to perform property lookups on the `templateContext`:
+
+ ```
+ > templateContext.get('foo') // -> ""
+ ```
+
+ @method debugger
+ @for Ember.Handlebars.helpers
+ @param {String} property
+*/
+Ember.Handlebars.registerHelper('debugger', function debuggerHelper(options) {
+
+ // These are helpful values you can inspect while debugging.
+ var templateContext = this;
+ var typeOfTemplateContext = Ember.inspect(templateContext);
+
+ debugger;
+});
+
+
+
+})();
+
+
+
+(function() {
+/**
+@module ember
+@submodule ember-handlebars
+*/
+
+var get = Ember.get, set = Ember.set;
+var fmt = Ember.String.fmt;
+
+Ember.Handlebars.EachView = Ember.CollectionView.extend(Ember._Metamorph, {
+ init: function() {
+ var itemController = get(this, 'itemController');
+ var binding;
+
+ if (itemController) {
+ var controller = get(this, 'controller.container').lookupFactory('controller:array').create({
+ _isVirtual: true,
+ parentController: get(this, 'controller'),
+ itemController: itemController,
+ target: get(this, 'controller'),
+ _eachView: this
+ });
+
+ this.disableContentObservers(function() {
+ set(this, 'content', controller);
+ binding = new Ember.Binding('content', '_eachView.dataSource').oneWay();
+ binding.connect(controller);
+ });
+
+ set(this, '_arrayController', controller);
+ } else {
+ this.disableContentObservers(function() {
+ binding = new Ember.Binding('content', 'dataSource').oneWay();
+ binding.connect(this);
+ });
+ }
+
+ return this._super();
+ },
+
+ _assertArrayLike: function(content) {
+ Ember.assert(fmt("The value that #each loops over must be an Array. You " +
+ "passed %@, but it should have been an ArrayController",
+ [content.constructor]),
+ !Ember.ControllerMixin.detect(content) ||
+ (content && content.isGenerated) ||
+ content instanceof Ember.ArrayController);
+ Ember.assert(fmt("The value that #each loops over must be an Array. You passed %@", [(Ember.ControllerMixin.detect(content) && content.get('model') !== undefined) ? fmt("'%@' (wrapped in %@)", [content.get('model'), content]) : content]), Ember.Array.detect(content));
+ },
+
+ disableContentObservers: function(callback) {
+ Ember.removeBeforeObserver(this, 'content', null, '_contentWillChange');
+ Ember.removeObserver(this, 'content', null, '_contentDidChange');
+
+ callback.call(this);
+
+ Ember.addBeforeObserver(this, 'content', null, '_contentWillChange');
+ Ember.addObserver(this, 'content', null, '_contentDidChange');
+ },
+
+ itemViewClass: Ember._MetamorphView,
+ emptyViewClass: Ember._MetamorphView,
+
+ createChildView: function(view, attrs) {
+ view = this._super(view, attrs);
+
+ // At the moment, if a container view subclass wants
+ // to insert keywords, it is responsible for cloning
+ // the keywords hash. This will be fixed momentarily.
+ var keyword = get(this, 'keyword');
+ var content = get(view, 'content');
+
+ if (keyword) {
+ var data = get(view, 'templateData');
+
+ data = Ember.copy(data);
+ data.keywords = view.cloneKeywords();
+ set(view, 'templateData', data);
+
+ // In this case, we do not bind, because the `content` of
+ // a #each item cannot change.
+ data.keywords[keyword] = content;
+ }
+
+ // If {{#each}} is looping over an array of controllers,
+ // point each child view at their respective controller.
+ if (content && get(content, 'isController')) {
+ set(view, 'controller', content);
+ }
+
+ return view;
+ },
+
+ destroy: function() {
+ if (!this._super()) { return; }
+
+ var arrayController = get(this, '_arrayController');
+
+ if (arrayController) {
+ arrayController.destroy();
+ }
+
+ return this;
+ }
+});
+
+var GroupedEach = Ember.Handlebars.GroupedEach = function(context, path, options) {
+ var self = this,
+ normalized = Ember.Handlebars.normalizePath(context, path, options.data);
+
+ this.context = context;
+ this.path = path;
+ this.options = options;
+ this.template = options.fn;
+ this.containingView = options.data.view;
+ this.normalizedRoot = normalized.root;
+ this.normalizedPath = normalized.path;
+ this.content = this.lookupContent();
+
+ this.addContentObservers();
+ this.addArrayObservers();
+
+ this.containingView.on('willClearRender', function() {
+ self.destroy();
+ });
+};
+
+GroupedEach.prototype = {
+ contentWillChange: function() {
+ this.removeArrayObservers();
+ },
+
+ contentDidChange: function() {
+ this.content = this.lookupContent();
+ this.addArrayObservers();
+ this.rerenderContainingView();
+ },
+
+ contentArrayWillChange: Ember.K,
+
+ contentArrayDidChange: function() {
+ this.rerenderContainingView();
+ },
+
+ lookupContent: function() {
+ return Ember.Handlebars.get(this.normalizedRoot, this.normalizedPath, this.options);
+ },
+
+ addArrayObservers: function() {
+ if (!this.content) { return; }
+
+ this.content.addArrayObserver(this, {
+ willChange: 'contentArrayWillChange',
+ didChange: 'contentArrayDidChange'
+ });
+ },
+
+ removeArrayObservers: function() {
+ if (!this.content) { return; }
+
+ this.content.removeArrayObserver(this, {
+ willChange: 'contentArrayWillChange',
+ didChange: 'contentArrayDidChange'
+ });
+ },
+
+ addContentObservers: function() {
+ Ember.addBeforeObserver(this.normalizedRoot, this.normalizedPath, this, this.contentWillChange);
+ Ember.addObserver(this.normalizedRoot, this.normalizedPath, this, this.contentDidChange);
+ },
+
+ removeContentObservers: function() {
+ Ember.removeBeforeObserver(this.normalizedRoot, this.normalizedPath, this.contentWillChange);
+ Ember.removeObserver(this.normalizedRoot, this.normalizedPath, this.contentDidChange);
+ },
+
+ render: function() {
+ if (!this.content) { return; }
+
+ var content = this.content,
+ contentLength = get(content, 'length'),
+ data = this.options.data,
+ template = this.template;
+
+ data.insideEach = true;
+ for (var i = 0; i < contentLength; i++) {
+ template(content.objectAt(i), { data: data });
+ }
+ },
+
+ rerenderContainingView: function() {
+ var self = this;
+ Ember.run.scheduleOnce('render', this, function() {
+ // It's possible it's been destroyed after we enqueued a re-render call.
+ if (!self.destroyed) {
+ self.containingView.rerender();
+ }
+ });
+ },
+
+ destroy: function() {
+ this.removeContentObservers();
+ if (this.content) {
+ this.removeArrayObservers();
+ }
+ this.destroyed = true;
+ }
+};
+
+/**
+ The `{{#each}}` helper loops over elements in a collection, rendering its
+ block once for each item. It is an extension of the base Handlebars `{{#each}}`
+ helper:
+
+ ```javascript
+ Developers = [{name: 'Yehuda'},{name: 'Tom'}, {name: 'Paul'}];
+ ```
+
+ ```handlebars
+ {{#each Developers}}
+ {{name}}
+ {{/each}}
+ ```
+
+ `{{each}}` supports an alternative syntax with element naming:
+
+ ```handlebars
+ {{#each person in Developers}}
+ {{person.name}}
+ {{/each}}
+ ```
+
+ When looping over objects that do not have properties, `{{this}}` can be used
+ to render the object:
+
+ ```javascript
+ DeveloperNames = ['Yehuda', 'Tom', 'Paul']
+ ```
+
+ ```handlebars
+ {{#each DeveloperNames}}
+ {{this}}
+ {{/each}}
+ ```
+ ### {{else}} condition
+ `{{#each}}` can have a matching `{{else}}`. The contents of this block will render
+ if the collection is empty.
+
+ ```
+ {{#each person in Developers}}
+ {{person.name}}
+ {{else}}
+
Sorry, nobody is available for this task.
+ {{/each}}
+ ```
+ ### Specifying a View class for items
+ If you provide an `itemViewClass` option that references a view class
+ with its own `template` you can omit the block.
+
+ The following template:
+
+ ```handlebars
+ {{#view App.MyView }}
+ {{each view.items itemViewClass="App.AnItemView"}}
+ {{/view}}
+ ```
+
+ And application code
+
+ ```javascript
+ App = Ember.Application.create({
+ MyView: Ember.View.extend({
+ items: [
+ Ember.Object.create({name: 'Dave'}),
+ Ember.Object.create({name: 'Mary'}),
+ Ember.Object.create({name: 'Sara'})
+ ]
+ })
+ });
+
+ App.AnItemView = Ember.View.extend({
+ template: Ember.Handlebars.compile("Greetings {{name}}")
+ });
+ ```
+
+ Will result in the HTML structure below
+
+ ```html
+
+
Greetings Dave
+
Greetings Mary
+
Greetings Sara
+
+ ```
+
+ If an `itemViewClass` is defined on the helper, and therefore the helper is not
+ being used as a block, an `emptyViewClass` can also be provided optionally.
+ The `emptyViewClass` will match the behavior of the `{{else}}` condition
+ described above. That is, the `emptyViewClass` will render if the collection
+ is empty.
+
+ ### Representing each item with a Controller.
+ By default the controller lookup within an `{{#each}}` block will be
+ the controller of the template where the `{{#each}}` was used. If each
+ item needs to be presented by a custom controller you can provide a
+ `itemController` option which references a controller by lookup name.
+ Each item in the loop will be wrapped in an instance of this controller
+ and the item itself will be set to the `content` property of that controller.
+
+ This is useful in cases where properties of model objects need transformation
+ or synthesis for display:
+
+ ```javascript
+ App.DeveloperController = Ember.ObjectController.extend({
+ isAvailableForHire: function() {
+ return !this.get('content.isEmployed') && this.get('content.isSeekingWork');
+ }.property('isEmployed', 'isSeekingWork')
+ })
+ ```
+
+ ```handlebars
+ {{#each person in developers itemController="developer"}}
+ {{person.name}} {{#if person.isAvailableForHire}}Hire me!{{/if}}
+ {{/each}}
+ ```
+
+ Each itemController will receive a reference to the current controller as
+ a `parentController` property.
+
+ ### (Experimental) Grouped Each
+
+ When used in conjunction with the experimental [group helper](https://github.com/emberjs/group-helper),
+ you can inform Handlebars to re-render an entire group of items instead of
+ re-rendering them one at a time (in the event that they are changed en masse
+ or an item is added/removed).
+
+ ```handlebars
+ {{#group}}
+ {{#each people}}
+ {{firstName}} {{lastName}}
+ {{/each}}
+ {{/group}}
+ ```
+
+ This can be faster than the normal way that Handlebars re-renders items
+ in some cases.
+
+ If for some reason you have a group with more than one `#each`, you can make
+ one of the collections be updated in normal (non-grouped) fashion by setting
+ the option `groupedRows=true` (counter-intuitive, I know).
+
+ For example,
+
+ ```handlebars
+ {{dealershipName}}
+
+ {{#group}}
+ {{#each dealers}}
+ {{firstName}} {{lastName}}
+ {{/each}}
+
+ {{#each car in cars groupedRows=true}}
+ {{car.make}} {{car.model}} {{car.color}}
+ {{/each}}
+ {{/group}}
+ ```
+ Any change to `dealershipName` or the `dealers` collection will cause the
+ entire group to be re-rendered. However, changes to the `cars` collection
+ will be re-rendered individually (as normal).
+
+ Note that `group` behavior is also disabled by specifying an `itemViewClass`.
+
+ @method each
+ @for Ember.Handlebars.helpers
+ @param [name] {String} name for item (used with `in`)
+ @param [path] {String} path
+ @param [options] {Object} Handlebars key/value pairs of options
+ @param [options.itemViewClass] {String} a path to a view class used for each item
+ @param [options.itemController] {String} name of a controller to be created for each item
+ @param [options.groupedRows] {boolean} enable normal item-by-item rendering when inside a `#group` helper
+*/
+Ember.Handlebars.registerHelper('each', function eachHelper(path, options) {
+ if (arguments.length === 4) {
+ Ember.assert("If you pass more than one argument to the each helper, it must be in the form #each foo in bar", arguments[1] === "in");
+
+ var keywordName = arguments[0];
+
+ options = arguments[3];
+ path = arguments[2];
+ if (path === '') { path = "this"; }
+
+ options.hash.keyword = keywordName;
+ }
+
+ if (arguments.length === 1) {
+ options = path;
+ path = 'this';
+ }
+
+ options.hash.dataSourceBinding = path;
+ // Set up emptyView as a metamorph with no tag
+ //options.hash.emptyViewClass = Ember._MetamorphView;
+
+ if (options.data.insideGroup && !options.hash.groupedRows && !options.hash.itemViewClass) {
+ new Ember.Handlebars.GroupedEach(this, path, options).render();
+ } else {
+ return Ember.Handlebars.helpers.collection.call(this, 'Ember.Handlebars.EachView', options);
+ }
+});
+
+})();
+
+
+
+(function() {
+/**
+@module ember
+@submodule ember-handlebars
+*/
+
+/**
+ `template` allows you to render a template from inside another template.
+ This allows you to re-use the same template in multiple places. For example:
+
+ ```html
+
+ ```
+
+ ```html
+
+ ```
+
+ ```handlebars
+ {{#if isUser}}
+ {{template "user_info"}}
+ {{else}}
+ {{template "unlogged_user_info"}}
+ {{/if}}
+ ```
+
+ This helper looks for templates in the global `Ember.TEMPLATES` hash. If you
+ add `
+ ```
+
+ Take note that `"welcome"` is a string and not an object
+ reference.
+
+ @method loc
+ @for Ember.Handlebars.helpers
+ @param {String} str The string to format
+*/
+
+Ember.Handlebars.registerHelper('loc', function locHelper(str) {
+ return Ember.String.loc(str);
+});
+
+})();
+
+
+
+(function() {
+
+})();
+
+
+
+(function() {
+
+})();
+
+
+
+(function() {
+/**
+@module ember
+@submodule ember-handlebars
+*/
+
+var set = Ember.set, get = Ember.get;
+
+/**
+ The internal class used to create text inputs when the `{{input}}`
+ helper is used with `type` of `checkbox`.
+
+ See [handlebars.helpers.input](/api/classes/Ember.Handlebars.helpers.html#method_input) for usage details.
+
+ ## Direct manipulation of `checked`
+
+ The `checked` attribute of an `Ember.Checkbox` object should always be set
+ through the Ember object or by interacting with its rendered element
+ representation via the mouse, keyboard, or touch. Updating the value of the
+ checkbox via jQuery will result in the checked value of the object and its
+ element losing synchronization.
+
+ ## Layout and LayoutName properties
+
+ Because HTML `input` elements are self closing `layout` and `layoutName`
+ properties will not be applied. See [Ember.View](/api/classes/Ember.View.html)'s
+ layout section for more information.
+
+ @class Checkbox
+ @namespace Ember
+ @extends Ember.View
+*/
+Ember.Checkbox = Ember.View.extend({
+ classNames: ['ember-checkbox'],
+
+ tagName: 'input',
+
+ attributeBindings: ['type', 'checked', 'indeterminate', 'disabled', 'tabindex', 'name'],
+
+ type: "checkbox",
+ checked: false,
+ disabled: false,
+ indeterminate: false,
+
+ init: function() {
+ this._super();
+ this.on("change", this, this._updateElementValue);
+ },
+
+ didInsertElement: function() {
+ this._super();
+ this.get('element').indeterminate = !!this.get('indeterminate');
+ },
+
+ _updateElementValue: function() {
+ set(this, 'checked', this.$().prop('checked'));
+ }
+});
+
+})();
+
+
+
+(function() {
+/**
+@module ember
+@submodule ember-handlebars
+*/
+
+var get = Ember.get, set = Ember.set;
+
+/**
+ Shared mixin used by `Ember.TextField` and `Ember.TextArea`.
+
+ @class TextSupport
+ @namespace Ember
+ @private
+*/
+Ember.TextSupport = Ember.Mixin.create({
+ value: "",
+
+ attributeBindings: ['placeholder', 'disabled', 'maxlength', 'tabindex', 'readonly'],
+ placeholder: null,
+ disabled: false,
+ maxlength: null,
+
+ init: function() {
+ this._super();
+ this.on("focusOut", this, this._elementValueDidChange);
+ this.on("change", this, this._elementValueDidChange);
+ this.on("paste", this, this._elementValueDidChange);
+ this.on("cut", this, this._elementValueDidChange);
+ this.on("input", this, this._elementValueDidChange);
+ this.on("keyUp", this, this.interpretKeyEvents);
+ },
+
+ /**
+ The action to be sent when the user presses the return key.
+
+ This is similar to the `{{action}}` helper, but is fired when
+ the user presses the return key when editing a text field, and sends
+ the value of the field as the context.
+
+ @property action
+ @type String
+ @default null
+ */
+ action: null,
+
+ /**
+ The event that should send the action.
+
+ Options are:
+
+ * `enter`: the user pressed enter
+ * `keyPress`: the user pressed a key
+
+ @property onEvent
+ @type String
+ @default enter
+ */
+ onEvent: 'enter',
+
+ /**
+ Whether they `keyUp` event that triggers an `action` to be sent continues
+ propagating to other views.
+
+ By default, when the user presses the return key on their keyboard and
+ the text field has an `action` set, the action will be sent to the view's
+ controller and the key event will stop propagating.
+
+ If you would like parent views to receive the `keyUp` event even after an
+ action has been dispatched, set `bubbles` to true.
+
+ @property bubbles
+ @type Boolean
+ @default false
+ */
+ bubbles: false,
+
+ interpretKeyEvents: function(event) {
+ var map = Ember.TextSupport.KEY_EVENTS;
+ var method = map[event.keyCode];
+
+ this._elementValueDidChange();
+ if (method) { return this[method](event); }
+ },
+
+ _elementValueDidChange: function() {
+ set(this, 'value', this.$().val());
+ },
+
+ /**
+ The action to be sent when the user inserts a new line.
+
+ Called by the `Ember.TextSupport` mixin on keyUp if keycode matches 13.
+ Uses sendAction to send the `enter` action to the controller.
+
+ @method insertNewline
+ @param {Event} event
+ */
+ insertNewline: function(event) {
+ sendAction('enter', this, event);
+ sendAction('insert-newline', this, event);
+ },
+
+ /**
+ Called when the user hits escape.
+
+ Called by the `Ember.TextSupport` mixin on keyUp if keycode matches 27.
+ Uses sendAction to send the `escape-press` action to the controller.
+
+ @method cancel
+ @param {Event} event
+ */
+ cancel: function(event) {
+ sendAction('escape-press', this, event);
+ },
+
+ /**
+ Called when the text area is focused.
+
+ @method focusIn
+ @param {Event} event
+ */
+ focusIn: function(event) {
+ sendAction('focus-in', this, event);
+ },
+
+ /**
+ Called when the text area is blurred.
+
+ @method focusOut
+ @param {Event} event
+ */
+ focusOut: function(event) {
+ sendAction('focus-out', this, event);
+ },
+
+ /**
+ The action to be sent when the user presses a key. Enabled by setting
+ the `onEvent` property to `keyPress`.
+
+ Uses sendAction to send the `keyPress` action to the controller.
+
+ @method keyPress
+ @param {Event} event
+ */
+ keyPress: function(event) {
+ sendAction('key-press', this, event);
+ }
+
+});
+
+Ember.TextSupport.KEY_EVENTS = {
+ 13: 'insertNewline',
+ 27: 'cancel'
+};
+
+// In principle, this shouldn't be necessary, but the legacy
+// sectionAction semantics for TextField are different from
+// the component semantics so this method normalizes them.
+function sendAction(eventName, view, event) {
+ var action = get(view, eventName),
+ on = get(view, 'onEvent'),
+ value = get(view, 'value');
+
+ // back-compat support for keyPress as an event name even though
+ // it's also a method name that consumes the event (and therefore
+ // incompatible with sendAction semantics).
+ if (on === eventName || (on === 'keyPress' && eventName === 'key-press')) {
+ view.sendAction('action', value);
+ }
+
+ view.sendAction(eventName, value);
+
+ if (action || on === eventName) {
+ if(!get(view, 'bubbles')) {
+ event.stopPropagation();
+ }
+ }
+}
+
+})();
+
+
+
+(function() {
+/**
+@module ember
+@submodule ember-handlebars
+*/
+
+var get = Ember.get, set = Ember.set;
+
+/**
+
+ The internal class used to create text inputs when the `{{input}}`
+ helper is used with `type` of `text`.
+
+ See [Handlebars.helpers.input](/api/classes/Ember.Handlebars.helpers.html#method_input) for usage details.
+
+ ## Layout and LayoutName properties
+
+ Because HTML `input` elements are self closing `layout` and `layoutName`
+ properties will not be applied. See [Ember.View](/api/classes/Ember.View.html)'s
+ layout section for more information.
+
+ @class TextField
+ @namespace Ember
+ @extends Ember.Component
+ @uses Ember.TextSupport
+*/
+Ember.TextField = Ember.Component.extend(Ember.TextSupport, {
+
+ classNames: ['ember-text-field'],
+ tagName: "input",
+ attributeBindings: ['type', 'value', 'size', 'pattern', 'name', 'min', 'max'],
+
+ /**
+ The `value` attribute of the input element. As the user inputs text, this
+ property is updated live.
+
+ @property value
+ @type String
+ @default ""
+ */
+ value: "",
+
+ /**
+ The `type` attribute of the input element.
+
+ @property type
+ @type String
+ @default "text"
+ */
+ type: "text",
+
+ /**
+ The `size` of the text field in characters.
+
+ @property size
+ @type String
+ @default null
+ */
+ size: null,
+
+ /**
+ The `pattern` attribute of input element.
+
+ @property pattern
+ @type String
+ @default null
+ */
+ pattern: null,
+
+ /**
+ The `min` attribute of input element used with `type="number"` or `type="range"`.
+
+ @property min
+ @type String
+ @default null
+ */
+ min: null,
+
+ /**
+ The `max` attribute of input element used with `type="number"` or `type="range"`.
+
+ @property max
+ @type String
+ @default null
+ */
+ max: null
+});
+
+})();
+
+
+
+(function() {
+/**
+@module ember
+@submodule ember-handlebars
+*/
+
+var get = Ember.get, set = Ember.set;
+
+/**
+ The internal class used to create textarea element when the `{{textarea}}`
+ helper is used.
+
+ See [handlebars.helpers.textarea](/api/classes/Ember.Handlebars.helpers.html#method_textarea) for usage details.
+
+ ## Layout and LayoutName properties
+
+ Because HTML `textarea` elements do not contain inner HTML the `layout` and
+ `layoutName` properties will not be applied. See [Ember.View](/api/classes/Ember.View.html)'s
+ layout section for more information.
+
+ @class TextArea
+ @namespace Ember
+ @extends Ember.Component
+ @uses Ember.TextSupport
+*/
+Ember.TextArea = Ember.Component.extend(Ember.TextSupport, {
+ classNames: ['ember-text-area'],
+
+ tagName: "textarea",
+ attributeBindings: ['rows', 'cols', 'name'],
+ rows: null,
+ cols: null,
+
+ _updateElementValue: Ember.observer('value', function() {
+ // We do this check so cursor position doesn't get affected in IE
+ var value = get(this, 'value'),
+ $el = this.$();
+ if ($el && value !== $el.val()) {
+ $el.val(value);
+ }
+ }),
+
+ init: function() {
+ this._super();
+ this.on("didInsertElement", this, this._updateElementValue);
+ }
+
+});
+
+})();
+
+
+
+(function() {
+/*jshint eqeqeq:false */
+
+/**
+@module ember
+@submodule ember-handlebars
+*/
+
+var set = Ember.set,
+ get = Ember.get,
+ indexOf = Ember.EnumerableUtils.indexOf,
+ indexesOf = Ember.EnumerableUtils.indexesOf,
+ forEach = Ember.EnumerableUtils.forEach,
+ replace = Ember.EnumerableUtils.replace,
+ isArray = Ember.isArray,
+ precompileTemplate = Ember.Handlebars.compile;
+
+Ember.SelectOption = Ember.View.extend({
+ tagName: 'option',
+ attributeBindings: ['value', 'selected'],
+
+ defaultTemplate: function(context, options) {
+ options = { data: options.data, hash: {} };
+ Ember.Handlebars.helpers.bind.call(context, "view.label", options);
+ },
+
+ init: function() {
+ this.labelPathDidChange();
+ this.valuePathDidChange();
+
+ this._super();
+ },
+
+ selected: Ember.computed(function() {
+ var content = get(this, 'content'),
+ selection = get(this, 'parentView.selection');
+ if (get(this, 'parentView.multiple')) {
+ return selection && indexOf(selection, content.valueOf()) > -1;
+ } else {
+ // Primitives get passed through bindings as objects... since
+ // `new Number(4) !== 4`, we use `==` below
+ return content == selection;
+ }
+ }).property('content', 'parentView.selection'),
+
+ labelPathDidChange: Ember.observer('parentView.optionLabelPath', function() {
+ var labelPath = get(this, 'parentView.optionLabelPath');
+
+ if (!labelPath) { return; }
+
+ Ember.defineProperty(this, 'label', Ember.computed(function() {
+ return get(this, labelPath);
+ }).property(labelPath));
+ }),
+
+ valuePathDidChange: Ember.observer('parentView.optionValuePath', function() {
+ var valuePath = get(this, 'parentView.optionValuePath');
+
+ if (!valuePath) { return; }
+
+ Ember.defineProperty(this, 'value', Ember.computed(function() {
+ return get(this, valuePath);
+ }).property(valuePath));
+ })
+});
+
+Ember.SelectOptgroup = Ember.CollectionView.extend({
+ tagName: 'optgroup',
+ attributeBindings: ['label'],
+
+ selectionBinding: 'parentView.selection',
+ multipleBinding: 'parentView.multiple',
+ optionLabelPathBinding: 'parentView.optionLabelPath',
+ optionValuePathBinding: 'parentView.optionValuePath',
+
+ itemViewClassBinding: 'parentView.optionView'
+});
+
+/**
+ The `Ember.Select` view class renders a
+ [select](https://developer.mozilla.org/en/HTML/Element/select) HTML element,
+ allowing the user to choose from a list of options.
+
+ The text and `value` property of each `