diff --git a/assets/img/authors/charles.jpg b/assets/img/authors/charles.jpg new file mode 100644 index 00000000..6b42aaee Binary files /dev/null and b/assets/img/authors/charles.jpg differ diff --git a/assets/img/authors/elrick.jpg b/assets/img/authors/elrick.jpg new file mode 100644 index 00000000..592cc556 Binary files /dev/null and b/assets/img/authors/elrick.jpg differ diff --git a/assets/img/authors/jacob.jpg b/assets/img/authors/jacob.jpg new file mode 100644 index 00000000..35b54b73 Binary files /dev/null and b/assets/img/authors/jacob.jpg differ diff --git a/assets/img/authors/jeffrey.jpg b/assets/img/authors/jeffrey.jpg new file mode 100644 index 00000000..bc44cb64 Binary files /dev/null and b/assets/img/authors/jeffrey.jpg differ diff --git a/assets/img/authors/jorge.jpg b/assets/img/authors/jorge.jpg new file mode 100644 index 00000000..622216c4 Binary files /dev/null and b/assets/img/authors/jorge.jpg differ diff --git a/assets/img/authors/min.jpg b/assets/img/authors/min.jpg new file mode 100644 index 00000000..6376e9fb Binary files /dev/null and b/assets/img/authors/min.jpg differ diff --git a/assets/img/authors/paul.jpg b/assets/img/authors/paul.jpg new file mode 100644 index 00000000..cb57b220 Binary files /dev/null and b/assets/img/authors/paul.jpg differ diff --git a/assets/img/authors/rachelle.jpg b/assets/img/authors/rachelle.jpg new file mode 100644 index 00000000..f47a836a Binary files /dev/null and b/assets/img/authors/rachelle.jpg differ diff --git a/assets/img/authors/taras.jpg b/assets/img/authors/taras.jpg new file mode 100644 index 00000000..2cf3965b Binary files /dev/null and b/assets/img/authors/taras.jpg differ diff --git a/assets/prism-atom-one-dark.css b/assets/prism-atom-one-dark.css new file mode 100644 index 00000000..f0daf7ee --- /dev/null +++ b/assets/prism-atom-one-dark.css @@ -0,0 +1,492 @@ +/** + * Added to make line numbering and higlight selection work: + * https://github.com/timlrx/rehype-prism-plus#styling + */ +pre { + overflow-x: auto; +} + +/** + * Inspired by gatsby remark prism - https://www.gatsbyjs.com/plugins/gatsby-remark-prismjs/ + * 1. Make the element just wide enough to fit its content. + * 2. Always fill the visible space in .code-highlight. + */ +.code-highlight { + float: left; /* 1 */ + min-width: 100%; /* 2 */ +} + +.code-line { + display: block; + padding-left: 16px; + padding-right: 16px; + margin-left: -16px; + margin-right: -16px; + border-left: 4px solid rgba(0, 0, 0, 0); /* Set placeholder for highlight accent border color to transparent */ +} + +.code-line.inserted { + background-color: rgba(16, 185, 129, 0.2); /* Set inserted line (+) color */ +} + +.code-line.deleted { + background-color: rgba(239, 68, 68, 0.2); /* Set deleted line (-) color */ +} + +.highlight-line { + margin-left: -16px; + margin-right: -16px; + background-color: rgba(55, 65, 81, 0.5); /* Set highlight bg color */ + border-left: 4px solid rgb(59, 130, 246); /* Set highlight accent border color */ +} + +.line-number::before { + display: inline-block; + width: 1rem; + text-align: right; + margin-right: 16px; + margin-left: -8px; + color: rgb(156, 163, 175); /* Line number color */ + content: attr(line); +} + +/** + * One Dark theme for prism.js + * Based on Atom's One Dark theme: https://github.com/atom/atom/tree/master/packages/one-dark-syntax + */ + +/** + * One Dark colours (accurate as of commit 8ae45ca on 6 Sep 2018) + * From colors.less + * --mono-1: hsl(220, 14%, 71%); + * --mono-2: hsl(220, 9%, 55%); + * --mono-3: hsl(220, 10%, 40%); + * --hue-1: hsl(187, 47%, 55%); + * --hue-2: hsl(207, 82%, 66%); + * --hue-3: hsl(286, 60%, 67%); + * --hue-4: hsl(95, 38%, 62%); + * --hue-5: hsl(355, 65%, 65%); + * --hue-5-2: hsl(5, 48%, 51%); + * --hue-6: hsl(29, 54%, 61%); + * --hue-6-2: hsl(39, 67%, 69%); + * --syntax-fg: hsl(220, 14%, 71%); + * --syntax-bg: hsl(220, 13%, 18%); + * --syntax-gutter: hsl(220, 14%, 45%); + * --syntax-guide: hsla(220, 14%, 71%, 0.15); + * --syntax-accent: hsl(220, 100%, 66%); + * From syntax-variables.less + * --syntax-selection-color: hsl(220, 13%, 28%); + * --syntax-gutter-background-color-selected: hsl(220, 13%, 26%); + * --syntax-cursor-line: hsla(220, 100%, 80%, 0.04); + */ + +code[class*="language-"], +pre[class*="language-"] { + background: hsl(220, 13%, 18%); + color: hsl(220, 14%, 71%); + text-shadow: 0 1px rgba(0, 0, 0, 0.3); + font-family: "Fira Code", "Fira Mono", Menlo, Consolas, "DejaVu Sans Mono", monospace; + direction: ltr; + text-align: left; + white-space: pre; + word-spacing: normal; + word-break: normal; + line-height: 1.5; + -moz-tab-size: 2; + -o-tab-size: 2; + tab-size: 2; + -webkit-hyphens: none; + -moz-hyphens: none; + -ms-hyphens: none; + hyphens: none; +} + +/* Selection */ +code[class*="language-"]::-moz-selection, +code[class*="language-"] *::-moz-selection, +pre[class*="language-"] *::-moz-selection { + background: hsl(220, 13%, 28%); + color: inherit; + text-shadow: none; +} + +code[class*="language-"]::selection, +code[class*="language-"] *::selection, +pre[class*="language-"] *::selection { + background: hsl(220, 13%, 28%); + color: inherit; + text-shadow: none; +} + +/* Code blocks */ +pre[class*="language-"] { + padding: 1em; + margin: 0.5em 0; + overflow: auto; + border-radius: 0.3em; +} + +/* Inline code */ +:not(pre) > code[class*="language-"] { + padding: 0.2em 0.3em; + border-radius: 0.3em; + white-space: normal; +} + +/* Print */ +@media print { + code[class*="language-"], + pre[class*="language-"] { + text-shadow: none; + } +} + +.token.comment, +.token.prolog, +.token.cdata { + color: hsl(220, 10%, 40%); +} + +.token.doctype, +.token.punctuation, +.token.entity { + color: hsl(220, 14%, 71%); +} + +.token.attr-name, +.token.class-name, +.token.boolean, +.token.constant, +.token.number, +.token.atrule { + color: hsl(29, 54%, 61%); +} + +.token.keyword { + color: hsl(286, 60%, 67%); +} + +.token.property, +.token.tag, +.token.symbol, +.token.deleted, +.token.important { + color: hsl(355, 65%, 65%); +} + +.token.selector, +.token.string, +.token.char, +.token.builtin, +.token.inserted, +.token.regex, +.token.attr-value, +.token.attr-value > .token.punctuation { + color: hsl(95, 38%, 62%); +} + +.token.variable, +.token.operator, +.token.function { + color: hsl(207, 82%, 66%); +} + +.token.url { + color: hsl(187, 47%, 55%); +} + +/* HTML overrides */ +.token.attr-value > .token.punctuation.attr-equals, +.token.special-attr > .token.attr-value > .token.value.css { + color: hsl(220, 14%, 71%); +} + +/* CSS overrides */ +.language-css .token.selector { + color: hsl(355, 65%, 65%); +} + +.language-css .token.property { + color: hsl(220, 14%, 71%); +} + +.language-css .token.function, +.language-css .token.url > .token.function { + color: hsl(187, 47%, 55%); +} + +.language-css .token.url > .token.string.url { + color: hsl(95, 38%, 62%); +} + +.language-css .token.important, +.language-css .token.atrule .token.rule { + color: hsl(286, 60%, 67%); +} + +/* JS overrides */ +.language-javascript .token.operator { + color: hsl(286, 60%, 67%); +} + +.language-javascript .token.template-string > .token.interpolation > .token.interpolation-punctuation.punctuation { + color: hsl(5, 48%, 51%); +} + +/* JSON overrides */ +.language-json .token.operator { + color: hsl(220, 14%, 71%); +} + +.language-json .token.null.keyword { + color: hsl(29, 54%, 61%); +} + +/* MD overrides */ +.language-markdown .token.url, +.language-markdown .token.url > .token.operator, +.language-markdown .token.url-reference.url > .token.string { + color: hsl(220, 14%, 71%); +} + +.language-markdown .token.url > .token.content { + color: hsl(207, 82%, 66%); +} + +.language-markdown .token.url > .token.url, +.language-markdown .token.url-reference.url { + color: hsl(187, 47%, 55%); +} + +.language-markdown .token.blockquote.punctuation, +.language-markdown .token.hr.punctuation { + color: hsl(220, 10%, 40%); + font-style: italic; +} + +.language-markdown .token.code-snippet { + color: hsl(95, 38%, 62%); +} + +.language-markdown .token.bold .token.content { + color: hsl(29, 54%, 61%); +} + +.language-markdown .token.italic .token.content { + color: hsl(286, 60%, 67%); +} + +.language-markdown .token.strike .token.content, +.language-markdown .token.strike .token.punctuation, +.language-markdown .token.list.punctuation, +.language-markdown .token.title.important > .token.punctuation { + color: hsl(355, 65%, 65%); +} + +/* General */ +.token.bold { + font-weight: bold; +} + +.token.comment, +.token.italic { + font-style: italic; +} + +.token.entity { + cursor: help; +} + +.token.namespace { + opacity: 0.8; +} + +/* Plugin overrides */ +/* Selectors should have higher specificity than those in the plugins' default stylesheets */ + +/* Show Invisibles plugin overrides */ +.token.token.tab:not(:empty):before, +.token.token.cr:before, +.token.token.lf:before, +.token.token.space:before { + color: hsla(220, 14%, 71%, 0.15); + text-shadow: none; +} + +/* Toolbar plugin overrides */ +/* Space out all buttons and move them away from the right edge of the code block */ +div.code-toolbar > .toolbar.toolbar > .toolbar-item { + margin-right: 0.4em; +} + +/* Styling the buttons */ +div.code-toolbar > .toolbar.toolbar > .toolbar-item > button, +div.code-toolbar > .toolbar.toolbar > .toolbar-item > a, +div.code-toolbar > .toolbar.toolbar > .toolbar-item > span { + background: hsl(220, 13%, 26%); + color: hsl(220, 9%, 55%); + padding: 0.1em 0.4em; + border-radius: 0.3em; +} + +div.code-toolbar > .toolbar.toolbar > .toolbar-item > button:hover, +div.code-toolbar > .toolbar.toolbar > .toolbar-item > button:focus, +div.code-toolbar > .toolbar.toolbar > .toolbar-item > a:hover, +div.code-toolbar > .toolbar.toolbar > .toolbar-item > a:focus, +div.code-toolbar > .toolbar.toolbar > .toolbar-item > span:hover, +div.code-toolbar > .toolbar.toolbar > .toolbar-item > span:focus { + background: hsl(220, 13%, 28%); + color: hsl(220, 14%, 71%); +} + +/* Line Highlight plugin overrides */ +/* The highlighted line itself */ +.line-highlight.line-highlight { + background: hsla(220, 100%, 80%, 0.04); +} + +/* Default line numbers in Line Highlight plugin */ +.line-highlight.line-highlight:before, +.line-highlight.line-highlight[data-end]:after { + background: hsl(220, 13%, 26%); + color: hsl(220, 14%, 71%); + padding: 0.1em 0.6em; + border-radius: 0.3em; + box-shadow: 0 2px 0 0 rgba(0, 0, 0, 0.2); /* same as Toolbar plugin default */ +} + +/* Hovering over a linkable line number (in the gutter area) */ +/* Requires Line Numbers plugin as well */ +pre[id].linkable-line-numbers.linkable-line-numbers span.line-numbers-rows > span:hover:before { + background-color: hsla(220, 100%, 80%, 0.04); +} + +/* Line Numbers and Command Line plugins overrides */ +/* Line separating gutter from coding area */ +.line-numbers.line-numbers .line-numbers-rows, +.command-line .command-line-prompt { + border-right-color: hsla(220, 14%, 71%, 0.15); +} + +/* Stuff in the gutter */ +.line-numbers .line-numbers-rows > span:before, +.command-line .command-line-prompt > span:before { + color: hsl(220, 14%, 45%); +} + +/* Match Braces plugin overrides */ +/* Note: Outline colour is inherited from the braces */ +.rainbow-braces .token.token.punctuation.brace-level-1, +.rainbow-braces .token.token.punctuation.brace-level-5, +.rainbow-braces .token.token.punctuation.brace-level-9 { + color: hsl(355, 65%, 65%); +} + +.rainbow-braces .token.token.punctuation.brace-level-2, +.rainbow-braces .token.token.punctuation.brace-level-6, +.rainbow-braces .token.token.punctuation.brace-level-10 { + color: hsl(95, 38%, 62%); +} + +.rainbow-braces .token.token.punctuation.brace-level-3, +.rainbow-braces .token.token.punctuation.brace-level-7, +.rainbow-braces .token.token.punctuation.brace-level-11 { + color: hsl(207, 82%, 66%); +} + +.rainbow-braces .token.token.punctuation.brace-level-4, +.rainbow-braces .token.token.punctuation.brace-level-8, +.rainbow-braces .token.token.punctuation.brace-level-12 { + color: hsl(286, 60%, 67%); +} + +/* Diff Highlight plugin overrides */ +/* Taken from https://github.com/atom/github/blob/master/styles/variables.less */ +pre.diff-highlight > code .token.token.deleted:not(.prefix), +pre > code.diff-highlight .token.token.deleted:not(.prefix) { + background-color: hsla(353, 100%, 66%, 0.15); +} + +pre.diff-highlight > code .token.token.deleted:not(.prefix)::-moz-selection, +pre.diff-highlight > code .token.token.deleted:not(.prefix) *::-moz-selection, +pre > code.diff-highlight .token.token.deleted:not(.prefix)::-moz-selection, +pre > code.diff-highlight .token.token.deleted:not(.prefix) *::-moz-selection { + background-color: hsla(353, 95%, 66%, 0.25); +} + +pre.diff-highlight > code .token.token.deleted:not(.prefix)::selection, +pre.diff-highlight > code .token.token.deleted:not(.prefix) *::selection, +pre > code.diff-highlight .token.token.deleted:not(.prefix)::selection, +pre > code.diff-highlight .token.token.deleted:not(.prefix) *::selection { + background-color: hsla(353, 95%, 66%, 0.25); +} + +pre.diff-highlight > code .token.token.inserted:not(.prefix), +pre > code.diff-highlight .token.token.inserted:not(.prefix) { + background-color: hsla(137, 100%, 55%, 0.15); +} + +pre.diff-highlight > code .token.token.inserted:not(.prefix)::-moz-selection, +pre.diff-highlight > code .token.token.inserted:not(.prefix) *::-moz-selection, +pre > code.diff-highlight .token.token.inserted:not(.prefix)::-moz-selection, +pre > code.diff-highlight .token.token.inserted:not(.prefix) *::-moz-selection { + background-color: hsla(135, 73%, 55%, 0.25); +} + +pre.diff-highlight > code .token.token.inserted:not(.prefix)::selection, +pre.diff-highlight > code .token.token.inserted:not(.prefix) *::selection, +pre > code.diff-highlight .token.token.inserted:not(.prefix)::selection, +pre > code.diff-highlight .token.token.inserted:not(.prefix) *::selection { + background-color: hsla(135, 73%, 55%, 0.25); +} + +/* Previewers plugin overrides */ +/* Based on https://github.com/atom-community/atom-ide-datatip/blob/master/styles/atom-ide-datatips.less and https://github.com/atom/atom/blob/master/packages/one-dark-ui */ +/* Border around popup */ +.prism-previewer.prism-previewer:before, +.prism-previewer-gradient.prism-previewer-gradient div { + border-color: hsl(224, 13%, 17%); +} + +/* Angle and time should remain as circles and are hence not included */ +.prism-previewer-color.prism-previewer-color:before, +.prism-previewer-gradient.prism-previewer-gradient div, +.prism-previewer-easing.prism-previewer-easing:before { + border-radius: 0.3em; +} + +/* Triangles pointing to the code */ +.prism-previewer.prism-previewer:after { + border-top-color: hsl(224, 13%, 17%); +} + +.prism-previewer-flipped.prism-previewer-flipped.after { + border-bottom-color: hsl(224, 13%, 17%); +} + +/* Background colour within the popup */ +.prism-previewer-angle.prism-previewer-angle:before, +.prism-previewer-time.prism-previewer-time:before, +.prism-previewer-easing.prism-previewer-easing { + background: hsl(219, 13%, 22%); +} + +/* For angle, this is the positive area (eg. 90deg will display one quadrant in this colour) */ +/* For time, this is the alternate colour */ +.prism-previewer-angle.prism-previewer-angle circle, +.prism-previewer-time.prism-previewer-time circle { + stroke: hsl(220, 14%, 71%); + stroke-opacity: 1; +} + +/* Stroke colours of the handle, direction point, and vector itself */ +.prism-previewer-easing.prism-previewer-easing circle, +.prism-previewer-easing.prism-previewer-easing path, +.prism-previewer-easing.prism-previewer-easing line { + stroke: hsl(220, 14%, 71%); +} + +/* Fill colour of the handle */ +.prism-previewer-easing.prism-previewer-easing circle { + fill: transparent; +} diff --git a/blog/2006-07-08-learning-javascript-from-the-command-line/index.md b/blog/2006-07-08-learning-javascript-from-the-command-line/index.md new file mode 100644 index 00000000..de1af7a6 --- /dev/null +++ b/blog/2006-07-08-learning-javascript-from-the-command-line/index.md @@ -0,0 +1,62 @@ +--- +title: Learning Javascript from the Command Line +author: Charles Lowell +tags: + - javascript +--- + +

In the [Drunk and Retired Podcast](http://www.drunkandretired/podcast), [episode 59](http://www.drunkandretired.com/2006/07/08/drunkandretiredcom-podcast-episode-59-lightside-v-darkside-plus-learning-javascript-the-language-not-the-javascript-the-browser-scriptus/#comments) I spoke about learning your way around javascript the language independently from the browser, and how you can use the command line tools that come with the various javascript engines to interactively explore the javascript runtime.

+ +

When most folks think about javascript, they think about scripts that they embed into their web pages, but the truth is that it is a general-purpose programming language that has absolutely nothing to do with HTML. In fact, the javascript runtime is so orthogonal to other web browser functionality, that mozilla offers the javascript interpreter that it uses in Firefox and friends as a [completely separate download](http://www.mozilla.org/js/spidermonkey). It's available as both .deb or .rpm package, and just to show it: Here's the wonderful hello world program, as entered into the shell.

+ +``` +cowboyd@subzero:~$ js +js> alert('hello world') +1: ReferenceError: alert is not defined +js> +``` + +

OK, so I boobie-trapped that example in an attempt to beat the point I've been making to death. It's an error because `alert()` isn't actually part of javascript. In the context with which we're familiar(DHTML), it's a function that's defined by the browser. Of course, it just so happens that every browser implements `alert()` to behave in almost exactly the same way, but the function itself has nothing to do with the javascript core. Implementing our own version of alert is simple enough though.

+ +``` +js> var alert = function(message) { print(message)} +js> alert('hello world') +hello world +js> +``` + +

Personally, I love the command line because it let's you dig your fingers deep into the computer's brain and, by pushing its buttons directly, see what's going to work and what isn't. Every time I have a question about how the javascript interpreter is going to behave, I don't look up the spec, or write something into my programs that I'm not sure how it will work. Instead, I fire up my trusty interpreter to discover empirically how the system works. Need to know if a RegExp is going to match? Don't guess. Ask the interpreter.

+``` +js> "foo".match(/bar/) +null +js> "foo".match(/oo/) +oo +js> "foo".match(/oo$/) +oo +js> +``` +

Wonder what the built-in "constructor" property of an object is? The runtime can tell you. It's his business after all.

+ +``` +js> function A() {} +js> var a = new A() +js> var o = new Object() +js> a.constructor + +function A() { +} + +js> o.constructor + +function Object() { + [native code] +} + +js> o.constructor == Object +true +js> a.constructor == A +true +js> +``` + +

Sure, you could write an in-browser script to do all this and throw output at yourself in the form of alerts, but the beauty of the javascript command line is that you can collapse the whole edit-save-reload-alert scripting cycle into a single step; type in the next line and see what happens. It's that super-tight feedback which let's you learn that much faster.

diff --git a/blog/2006-07-15-live-stylesheets/index.md b/blog/2006-07-15-live-stylesheets/index.md new file mode 100644 index 00000000..cf7f76eb --- /dev/null +++ b/blog/2006-07-15-live-stylesheets/index.md @@ -0,0 +1,71 @@ +--- +title: Live Stylesheets +author: Charles Lowell +tags: + - dhtml + - javascript +--- + +

Even after working with DTML for over a year, I'm still constantly astounded by how dynamic it actually is. The most important thing to keep in the back of your head as a DHTML programmer is that the same mechanisms used by the browser to build an HTML document at load time are available to your in-page scripts. So, really, the static loading of your pages when you access a url is just the HTML parser invoking the same methods on the same objects that are available to your javascript code.

+ +

Take this static HTML code:

+ +```html + +
This is my div
+ + +``` + +

The same result can be achieved dynamically:

+```html + + + + + +``` + +

Nothing mind-blowing there; indeed, this technique is the cornerstone of DHTML. But it takes awhile to let it sink in to the point where using it is one of your first strategies to solving a problem. Specifically, you can use it not only to dynamically change layout-related HTML elements (span, div, ul, table, et al...), but also to create/modify behavioral elements(link, script, meta, etc...)

+ +

That said, I was recently trying to create a CSS stylesheet at runtime, and enable it so that its rules would be active in the page. At first, I was trying to use the W3C DOM CSS interface, the idea being to create a stylesheet object, add a bunch of rules to it corresponding to the rules that I wanted, and then put it into the stylesheets array. Unfortunately, doing this in a cross-platform way is borderline impossible. After pounding my head against that for awhile and getting nowhere, I figured, "why not just create the whole thing as a top-level html element, and let the browser just take it away from there?" The basic strategy is this: create a style DOM Element dynamically, add text content to it representing the actual stylesheet, and then pop it into the HTML DOM. With a few caveats, it works like a charm. The browser parses the text as CSS, and links in into the live cascade.

+ +

I mentioned caveats.. well, of course there are the obligatory cross-browser compatibility issues to be aware of:

+ + + +

All in all, they're nothing to worry about and do not stand in the way of the fundamental technique. I've created a demo page showing this code in action. As a nice side effect, it works as quick way of playing around with CSS properties and how they effect the styling of elements.

+ +> ⚠️ NOTE: The demo site has been lost to posterity, but we can assure you that it was really, really cool. +> We apologize for any inconvenience this may have caused. +> +> --The Management + +

Here is the source snippet implementing the technique I've described here:

+ +```javascript +var style = document.createElement('style') +style.setAttribute('type', 'text/css') + +var cssText = $('cssText').value +if (style.styleSheet) { //IE only + style.styleSheet.cssText = cssText +} else { + //for some reason this fails in IE. + var text = document.createTextNode(cssText) + style.appendChild(text) +} +``` diff --git a/blog/2006-08-04-taming-the-rhino-making-mozillas-javascript-command-line-a-little-less-brutish/index.md b/blog/2006-08-04-taming-the-rhino-making-mozillas-javascript-command-line-a-little-less-brutish/index.md new file mode 100644 index 00000000..5ecfb15e --- /dev/null +++ b/blog/2006-08-04-taming-the-rhino-making-mozillas-javascript-command-line-a-little-less-brutish/index.md @@ -0,0 +1,94 @@ +--- +title: Taming the Rhino +author: Charles Lowell +tags: + - javascript +--- + +

I recently [described](learning-javascript-from-the-command-line) how to use the one of the [freely available shells](http://www.mozilla.org/js/) as a great way to explore your javascript runtime. There are two implementations of the javascript interpreter sponsored by the Mozilla project, [Spider Monkey](http://www.mozilla.org/js/spidermonkey) an interpreter implemented in C, and [Rhino](http://www.mozilla.org/rhino/), an interpreter implemented in Java. With respect to the javascript runtime itself, these two implementations are almost identical, and so what works in one, will generally work in the other. They diverge, however, when it comes to embedding objects and functions that are implemented in a language other than javascript. Naturally, Spidermonkey is better suited for embedding objects implemented in C, while Rhino excels at embedding objects implemented in Java.

+ +

What exactly is "embedding" an object implemented in Java? If you don't know what this means already, I could try to describe it to you, but why not show it live and in the flesh with the Rhino command line? That's the exploratory technique that I find so valuable.

+ +``` + cowboyd@subzero:~$ java -classpath js.jar org.mozilla.javascript.tools.shell.Main + Rhino 1.5 release 5 2004 03 25 + js> var javaString = new java.lang.String("Hello World") + js> javaString.hashCode() + -862545276 + js> javaString.startsWith("Hello") + true + js> javaString.startsWith("World") + false + js> var jsString = new String("Hello World") + js> jsString.startsWith("Hello") + js: "", line 5: uncaught JavaScript exception: TypeError: startsWith is not a function. (; line 5) + js> +``` + +As you can see, javaString is a reference to an actual `java.lang.String` object, and has access to all the methods of that class, many of which are not contained in the native javascript `String`. + +Embedding goes two ways. Not only can you instantiate and use java objects from javascript, but you can also pass in javascript objects as parameters to java methods. You can even extend objects and implement interfaces in javascript! Once again, the command line shows this in action. In this example, we'll implement the `java.lang.Runnable` interface in javascript + +``` + cowboyd@subzero:~$ java -classpath js.jar org.mozilla.javascript.tools.shell.Main + Rhino 1.5 release 5 2004 03 25 + js> var impl = new Object() + js> impl.run = function() {print("Yeah that's right, you better run!")} + + function () { + print("Yeah that's right, you better run!"); + } + + js> var runnable = new java.lang.Runnable(impl) + js> var thread = new java.lang.Thread(runnable) + js> thread.run() + Yeah that's right, you better run! + js> +``` + +

This is all well and good, but that's quite a bit of code to get right the first time! Chances are, if you're just starting out with the Rhino command line, you're not going to have as much luck, especially if you're using it as a tool to learn javascript in the first place, and unfortunately, this is where Rhino comes up way short. Rather than have a forgiving [CLI](http://en.wikipedia.org/wiki/Command_line_interface), Rhino punishes you for every syntactic and semantic error that you make by not collecting command history. Even worse, hitting the up and down arrows result in bizarre character literals output directly to the prompt. You can't even correct a mistake that you've made in the current line you're typing without backspacing all the way to the error, and then re-typing from that point on.

+ +

In this example, I'd like to arrow-left so that I can correct my misspelling of "java.lang"

+ + cowboyd@subzero:~$ java -classpath js.jar org.mozilla.javascript.tools.shell.Main + Rhino 1.5 release 5 2004 03 25 + js> var r = new java.lng.Runnable(^[[D^[[D^[[D^[[D + +

Ugh! Or what happens if the shell didn't take my last command because it was slightly bogus? I'd like to retrieve the command with the up-arrow, edit it a little bit in-place and then try again because after all, it was only slightly bogus. Watch me try and recover from this minor syntax error...

+ +``` +cowboyd@subzero:~$ java -classpath /usr/share/java/js.jar org.mozilla.javascript.tools.shell.Main +Rhino 1.5 release 5 2004 03 25 +js> var f = function() {print("oops I forgot to close these parens"} +js: "", line 29: missing ) after argument list +js: var f = function() {print("oops I forgot to close these parens"} +js: ...............................................................^ +js: "", line 29: missing } after function body +js: var f = function() {print("oops I forgot to close these parens"} +js: ...............................................................^ +js: "", line 29: Compilation produced 2 syntax errors. +js> +js> //I know. Up-arrow to the rescue! +js> ^[[A^[[A^[[A^[[A^[[A^[[A //drat, foiled again! +``` + +

These problems are in particularly nasty contraposition to the technique of exploration via the shell which I advocate because the the cost for failure is so expensive. Indeed, what is so wonderful about most modern shells is that the cost for a syntax error is so small. For some reason, the implementors of the Rhino CLI decided to implement their shell with the typical functionality circa 1962.

+ +

It's not a problem for me though, thanks to one of my favorite unsung java libraries, [JLine](http://jline.sourceforge.net/). JLine hits a super sweet spot in that it takes somewhere around 0 effort to add loads of standard functionality to your command line interfaces. It seems that no one in the java world bothers with a decent CLI; tragic in my opinion, but probably because it's considered well-understood, non-trivial and therefore tedious. With JLine, building that CLI comes at around 0 cost. What's really cool about JLine is that a program doesn't even need to be written with it. It can transparently intercept the console input for any java program and seamlessly splice on any and all functionality you'd expect from a hot shell: in-place editing, command history, you name it. In a word: perfect for a beast like Rhino.

+ +``` +cowboyd@subzero:~$ java -classpath js.jar:jline.jar jline.ConsoleRunner org.mozilla.javascript.tools.shell.Main +Rhino 1.5 release 5 2004 03 25 +js> prnt("oops let me try that again") +js: "", line 1: uncaught JavaScript exception: ReferenceError: "prnt" is not defined. (; line 1) +js> print("oops let me try that again") +oops let me try that again +js> //trust me, that was easy. Just like it should have been in the first place. +js> +``` + +

JLine truly is a healing salve for your chafing CLI woes. Did I mention that it's cross-platform?

+ +

While the barrier to entry is extremely low, the path to upgrade is but a mild upward slope. If you do end up wanting nice extras such as TAB-completion or custom key bindings, it has a simple configuration mechanism, and [Java API](http://jline.sourceforge.net/apidocs/index.html) to make the pain of writing custom code as minimal as possible. But that's another bedtime story altogether.

+ +

I hope you enjoy JLine with Rhino, or with any other impolite java command lines you may use!

diff --git a/blog/2007-06-09-the-chicken-and-the-vine-trans-global-pair-programming/index.md b/blog/2007-06-09-the-chicken-and-the-vine-trans-global-pair-programming/index.md new file mode 100644 index 00000000..84d0d491 --- /dev/null +++ b/blog/2007-06-09-the-chicken-and-the-vine-trans-global-pair-programming/index.md @@ -0,0 +1,29 @@ +--- +title: Trans-Global Pair Programming +author: Charles Lowell +tags: + - proskillz +--- + +

The Frontside Software is a three person company with "offices" in Michigan, Finland, Massachusetts, and New Jersey. We're don't see each other every day, and we're rarely in the same room, but we still do a significant portion of our development work in pairs. Despite many other competing setups, we still do this with the not-so-new, not-so-exciting, yet extremely flexible and reliable [VNC](http://www.realvnc.com/what.html) combined with a voip product like [skype](http://skype.com).

+ +

How it works

+

One of us (the driver) runs a vnc server which transmits everything that is rendered on his display to one or more vnc clients (passengers) being run by the other half of the pair. That way, the client can see everything that goes on while the driver is coding, including his code editor, his web browser, his terminal windows, etc... Meanwhile, you've got real-time audio so that you can talk about the work you're doing as you're doing it.

+ +

Is there something better?

+

Not yet. We've seen some new collaborative coding tools like [SubEthaEdit](http://www.codingmonkeys.de/subethaedit/) and [Gobby](https://gobby.0x539.de/trac/) come down with some very slick features. Specifically, the updating and syncing of editor state between the two machines is very fast, and effectively coordinates multiple people editing the same document, with as few clashes as possible. The way in which they do this is impressive, but after having given it several abortive attempts as a real solution for remote pair programming, we went back to good old VNC. Here's why:

+
    +
  1. Lack of editing features: The collaborative editors of today are good at one thing: editing text collaboratively. The problem is that when you're pair-programming, you're not editing text, you're editing code, and code is only a simple sequence of text to a computer. There are lots of editors these days that leverage the semantically rich structure of the documents on which they operate like TextMate, Emacs, Eclipse.... everybody has a favorite, and because your collaborative editor is not your favorite, that means it sucks ;-)
  2. +
  3. Lack of environment: Of course, there are a scant few collaborative editing plug-ins for existing IDEs which would seem to address this problem, but adding on another layer, development is about more than just coding. It's about browsing documentation, running servers, invoking build scripts from the command line, and about a million other tiny tasks. In effect, your actual IDE is not just one application, it's your whole computer, and if the only thing being shared is a single app, then it cuts your pair out of a lot of important context. With VNC, everybody sees what's going on all the time. They can see not only the code, but also the running program.
  4. +
  5. Editing the same document at the same time isn't really helpful anyway: If you've done much pair-programming, then you realize that the real value doesn't come from having two sets of fingers on the keyboard at the same time. In fact quite the opposite: Both participants are following the thread of development, but one of them is freed entirely from the act of coding so that they can think about high-level architectural issues, or lookup api docs, or google for resources --all in parallel. Having both people pounding on the keyboard actually hinders this dynamic.
  6. +
  7. VNC is cross platform: This is a biggie. There are vnc clients and servers for Windows, OSX, and Linux (we code on all three), and they all interoperate with each other. That's a pretty hard feature to top, especially for single-platform apps like SubEthaEdit and Gobby which run on OSX and Linux respectively.
  8. +
+ +

About the only drawback to pair programming with VNC is that there can be some fairly significant lag (between 1-4 seconds) depending on network load on either end, but even so, when it comes to actually developing collaboratively, VNC and Skype are our daily tools of choice.

+ +

VNC Clients/servers

+
    +
  • OSX client: [Chicken of the VNC](https://sourceforge.net/projects/cotvnc/) +
  • OSX server: [Vine Server](http://www.redstonesoftware.com/products/vine/server/vineosx/)
  • +
  • Windows/Linux client/server: [http://www.tightvnc.com/download.html](http://www.tightvnc.com/download.html)
  • +
diff --git a/blog/2007-06-11-when-using-mod-rewrite-remember-that-the-version-counts/index.md b/blog/2007-06-11-when-using-mod-rewrite-remember-that-the-version-counts/index.md new file mode 100644 index 00000000..4918ddd6 --- /dev/null +++ b/blog/2007-06-11-when-using-mod-rewrite-remember-that-the-version-counts/index.md @@ -0,0 +1,25 @@ +--- +title: "mod_rewrite: Remeber that the version counts" +author: Charles Lowell +tags: + - tips +--- + +Pro Tip: Which regular expressions work depends on the version of Apache/mod_rewrite that you're using. I recently tested the following rewrite rule on Apache2 + +``` +RewriteRule ^archives/(\d+).html http://www.thefrontside.net/map2new.php?$1 [R] +``` + + +I wanted to match files like archives/000532.html. + +But when it came time to deploy, it didn't work. Turns out the environment I was deploying to was using a different version of Apache. That's bad practice of course, but aside from that, the little gotcha was that the regular expression interpreter was different in different versions of Apache, and in this case, its behavior differed not only from the newer version but also from the behavior of most regexp engine's out there. Specifically, the "digit" literal `\d` is not understood by older versions (which interpret it as a literal "d"). + +Instead I had to use \[0-9\] + +``` +RewriteRule ^archives/([0-9]+).html http://www.thefrontside.net/map2new.php?$1 [R] +``` + +This is a very specific nugget, but hopefully it will save someone a headache down the road. Just remember, if your mod_rewrite regexp isn't working, check the specific version of the engine, and make sure your regexp is one that it will understand. diff --git a/blog/2008-06-05-patiently-waiting-for-javafx-script/index.md b/blog/2008-06-05-patiently-waiting-for-javafx-script/index.md new file mode 100644 index 00000000..db07e00e --- /dev/null +++ b/blog/2008-06-05-patiently-waiting-for-javafx-script/index.md @@ -0,0 +1,37 @@ +--- +title: Patiently waiting for JavaFX +author: Charles Lowell +tags: + - java + - jafafx +--- + +About a year ago, SUN announced a "new" platform for developing embeddable applications on the web, and they called this platform JavaFX. In reality, this new platform seems to me less of an innovation and more of a rehabilitation of the applet infrastructure which they allowed to languish over the last decade. + +That's not a criticism, it's a compliment. I'm happy that they're going to give applets another go. Heck, I'm even glad they're calling it JavaFX too. "Applet" always sounded too effete anyway. In any case I've always like the applet model, and wished it were a more viable option for web development. Given the success of Flex, which also uses the applet model, other people must feel this way too. + +But I digress. + +As far as I can tell (I've only been able to get my hands on some demos and a little open source compiler) JavaFX involves upgrading some of the more boorish aspects of the java browser plugin. To name a few: faster download and initialization of application and JRE code, tighter integration and communication with the rest of the DOM, modern video and audio codecs.... All those are necessary upgrades to ensure the viability of the platform, but they can hardly be called innovative + +That said, one new and curious aspect of JavaFX is that it will (for the most part) not be written in Java. Instead, SUN is promoting a completely new and aptly named scripting language: JavaFX script. + +Of all the new features it was the specification of this language that impressed me most. I'm not sure how this little gem of a language managed to come out of SUN, but it's absolutely loaded with modern language features to make programming in it clear and concise. + +I'm talking about +
    +
  • closures
  • +
  • pure functions
  • +
  • lazy evaluation
  • +
  • list comprehensions (that put ruby's to shame)
  • +
  • optional declarative syntax
  • +
  • rolled in query language for searching and modifying data structures
  • +
  • data binding supported at the language level
  • +
  • tons of stuff I'm sure I don't even know about....
  • +
+ +Overall it seems like JavaFX script is like 4 separate languages existing in harmony in one neat package, and as I read more and more about it, I kept thinking to myself "This is not just some standard UI scripting language with a little sugar and a few extra hooks for writing GUIs. This is positively avante-garde. + +I like the idea that SUN can challenge their developers to think in new ways, but I can also imagine a backlash, or more mildly put, an general aversion in the developer community, since this is not any old language research project, but the centerpiece in SUN's product in the battle over the browser VM. + +So where did this decision come from to go with such a departure from the norm when it came to defining JavaFX script? I like it alot, but I worry that SUN is going to chicken out, and go with something a little less sexy but a little more digestible (at least in the short term) to its current developers as well as the developers of competing platforms like Adobe Flex or Microsoft Silverlight. diff --git a/blog/2008-06-25-please-stop-using-global-variables-in-ruby/index.md b/blog/2008-06-25-please-stop-using-global-variables-in-ruby/index.md new file mode 100644 index 00000000..527af338 --- /dev/null +++ b/blog/2008-06-25-please-stop-using-global-variables-in-ruby/index.md @@ -0,0 +1,73 @@ +--- +title: Please Stop Using Global Variables in Ruby +author: Charles Lowell +tags: + - ruby + +--- + +I couldn't sleep this morning. I woke up around 4:30 AM thinking about how code is organized in ruby. I'm sure I will look back on my life with disbelief and a touch of shame at such behavior, but that is (hopefully) decades away. + +The problem turning around in my head which eventually caused me to wake was my own inability to come to terms with a prevalent pattern in the ruby community: the global variable. Swirling in my semi-conscious mind was a motley mixture of bewilderment, self-doubt, scorn and a touch of righteous indignation. + +On the one hand, there must be something I'm missing. One of the reasons I love ruby-space is that it tends to be filled with talented programmers who work the language to produce code that is powerful while at the same time being easy to understand and use. Surely, their collective conscious is a better guide than my own experience and intuition. I worry that is primarily my own fuddy-duddity that prevents me from accepting global variables as good practice. + +To clarify, when I speak about global variables, I'm talking primarily about using global class objects as the repositories for methods and state. There are plenty examples out there of this: +```ruby +# active record +Post.find(:all) + +# amazon s3 +S3Object.store('me.jpg', open('headshot.jpg'), 'photos') + +# paypal-business gem +Paypal.capture(params) +``` + +In each of the preceding examples, there is implicit state stored on the class object. In active record, the database connection information, in S3Object, the AWS access key id and secret key, and finally, in Paypal, the paypal server url and business account name. Because the class object is global, the state on it is effectively global. And that is what sets my spidey sense to tingling. + +Not only is this style subject to the all the arguments for why [global variables are bad](http://c2.com/cgi/wiki?GlobalVariablesAreBad), but it just flat-out makes the API's themselves less useful. Here's an example from an actual project. + +I had a system which needed to talk to two different SQS queues which were contained in two separate amazon web services accounts. Unfortunately, the standard SQS api uses class variables to store configuration information, and then a set of class methods to access the queue services, so I was forced to overwrite the global configuration parameters every time I wanted to access the queue. Luckily, the application was not multi threaded, or I would have been stuck rolling my own. + +I don't see why: + +```ruby +SQS.access_key_id = 'ACCESS_ID' +SQS.secret_access_key = 'SECRET' +q = SQS.get_queue 'MyQueue' +``` + +is any better than: + +```ruby +@sqs = SQS.new :access_key_id => 'ACCESS_ID', :secret_access_key => 'SECRET' +q = @sqs.get_queue 'MyQueue' +``` + +(IMHO it's uglier) and in the second, hypothetical API, you can painlessly, and in a thread-safe manner talk to multiple queues on multiple accounts. + +```ruby +@one = SQS.new :access_key_id => 'AccountOne', :secret_access_key => "AccountOneSecret" +@two = SQS.new :access_key_id => 'AccountTwo', :secret_access_key => "AccountTwoSecret" +``` + +But there's a deeper principle at stake here than allowing multiple instances. It's about giving power to the programmer, and letting them control the API, and not the other way around. + +It seems to me that when you're designing an API, you want the programmer(to the greatest extent possible) be able to create their own little world over which they have complete control. The should be able to spin up as many instances of your code and use it in ways that perhaps you hadn't thought of without them having to worry that one world will interfere with another world they've created. + +If you've absolutely got to go global, it's very little effort to wrap a static/global interface around a single default instance, but not the other way around. Given the similarity in effort, it seems like embedability is the way to go. + +Global variables surely have their place in some applications, and I'm not opposed to them on purely ideological grounds, but ruby already has a construct for global variables... it's called the '$' sigil. + +In your environment.rb, or wherever you can just put: + +```ruby +#environment.rb +$sqs = SQS.new :access_key_id => 'AccountOne', :secret_access_key => "AccountOneSecret" + +#somewhere else +q = $sqs.get_queue "MyQueue" +``` + +My suspicion is that Ruby on Rails shares a large portion of the blame for the evolution of this style what with ActiveRecord.establish_connection() and friends. As the highest profile ruby project it's only natural that people will copy its conventions --for better or for worse. Still, I wish people in the future would break from this particular precedent and if they go global, to do it properly. Give the poor class objects a break! diff --git a/blog/2008-12-16-in-search-of-a-better-autogrowing-textfield/index.md b/blog/2008-12-16-in-search-of-a-better-autogrowing-textfield/index.md new file mode 100644 index 00000000..0e37ec0d --- /dev/null +++ b/blog/2008-12-16-in-search-of-a-better-autogrowing-textfield/index.md @@ -0,0 +1,24 @@ +--- +title: In Search of a Better Autogrowing Textfield +author: Charles Lowell +tags: + - dhtml +--- + +Autogrowing textfields are a nice piece of flash for any web application. They conserve precious screen real-estate, but grow to a height just big enough to accomodate the text the user actually typed without resorting to unsightly things scrollbars. + +There are a number of solutions out there, but since I'm using jQuery on my current project, the two that I evaluated were Growfield and Autogrow. + +While those two plugins often did what they were supposed to do, they also had tons of quirks and edge cases that prompted me give it a go myself. Specifically, I found that depending on the browser and operating system I would encounter: + +* eye-jarring jitter wherein the textfield would expand and contract my 1 or 2 pixels with every keystroke +* resizing of the textfield to 0 pixels, when there was no text in the box +* intermittent loss of focus on some browsers + +But most importantly, it seemed that more often than not, the text did not actually grow correctly in any of the real-world environments that I tried in my production application. + +As I discovered, the main reason for this was that the technique used was to fill a hidden DIV element with the text from the textfield, and then use the height of that DIV to determine how tall the textfield should be. That works great except for one nasty detail: most browsers use native widgets for text inputs, and those native widgets have their own word-wrap and layout code, so even things like font, line-height and letter spacing being equal, you can still get different word-wrap behavior from OS to OS. + +In an attempt to side-step all of these issues, I thought "what if instead of using a DIV element to calculate the height, I could use a native textfield?" After all, if native textfields use a different word-wrap and layout algorithms, can't I just try and harness those algorithms, whatever they might be? At first this seems perfectly circular i.e.: I don't know what the ideal height of a textarea should be, so I'll use the height of a texarea to find out. Of course there's a twist, and the wonderful little property that makes it all possible is the scrollHeight. + +lines, not heights. diff --git a/blog/2009-06-29-rye-repeat-yourself-enough/index.md b/blog/2009-06-29-rye-repeat-yourself-enough/index.md new file mode 100644 index 00000000..3dfc9633 --- /dev/null +++ b/blog/2009-06-29-rye-repeat-yourself-enough/index.md @@ -0,0 +1,27 @@ +--- +title: "RYE: Repeat Yourself Enough" +author: Charles Lowell +tags: + - proskillz +--- + + +Lately, I've been practicing the exact opposite of the DRY principle. Yup, you read that right: I repeat myself constantly, and in as many different contexts... including code, database schemas, test-plans and even documentation. The reason: So I won't have to repeat myself. It's not that I don't buy into the DRY principle, or that I'm somehow skeptical about the value its proper application yields. I just find it difficult to achieve in practice. + +According to the Pragmatic Programmers who coined the term: + +DRY says that every piece of system knowledge should have one authoritative, unambiguous representation. Every piece of knowledge in the development of something should have a single representation. A system's knowledge is far broader than just its code. It refers to database schemas, test plans, the build system, even documentation. + +Essentially it is an empirical measure against which to gauge the proper level of abstraction in your system. If you have not made the right abstractions, then you will be found to be repeating yourself, whereas if you *have* made the right abstractions, then you won't. This is because abstractions *directly encode* the system knowledge that DRY talks about no less than 3 times in its definition. + +The benefits of good abstractions are many and well known, and I'm not going to go into them here (there's always the internet for that), but what I've taken particular heed of lately is that abstraction is a double-edged sword that should not be wielded lightly. Get it right, and the previously intractable dissolves to simplicity in an instant... but get it wrong, and a snarl of complexity gridlocks your code base just as fast. + +You see, I wasn't telling the whole truth when I said before that abstractions directly encode system knowledge. It would be more correct to say: *Good* abstractions directly encode system knowledge. *Bad* abstractions directly encode *system ignorance.* + +The problem is that the DRY principle, while still very valid and useful, doesn't help us here because it assumes system knowledge as its input. But really valuable system knowledge isn't just lying around, it must be carefully constructed with significant amounts of research and analysis. That is to say, it must be learned. And what better way is there to learn something than by doing it over and over again until you know it inside and out? What better way is there than trial and error to really *get at the physics* of a phenomenon until you can use it to easily analyze it in all its forms? The fact is, you've got to repeat yourself over and over enough times until you can say with confidence: *This I know to be true.* + +You can think of it like doing your math homework, or being like Daniel-San: waxing Mr Miyagi's car again and again until you know karate by instinct. In fact, I'm convinced that there's no other way to generate *real* knowledge. + +Ironically, I repeat myself a lot these days because I hate repeating myself. I loathe it with an almost pathological passion, and sometimes (more often then I'd like to admit) it plays to my disadvantage. In my obsessive/compulsive rush to not repeat myself I'll introduce a bad abstraction based on flawed or incomplete system knowledge, and worse still, I won't know that I took the wrong fork in the road until it's way late in the game and it's hard to find the way back home. + +That's why I'm playing it cool lately and making sure that I repeat myself enough to *perfect* the system knowledge required to keep myself DRY. diff --git a/blog/2010-10-25-accessing-javascript-objects-from-ruby/index.md b/blog/2010-10-25-accessing-javascript-objects-from-ruby/index.md new file mode 100644 index 00000000..1518bb6a --- /dev/null +++ b/blog/2010-10-25-accessing-javascript-objects-from-ruby/index.md @@ -0,0 +1,226 @@ +--- +title: Accessing Javascript Objects from Ruby +author: Charles Lowell +tags: + - javascript + - ruby + - therubyracer +--- + +Awhile back, I wrote a post on how to [access Ruby objects from inside your JavaScript enviroment](../2010-06-30-accessing-ruby-objects-from-V8) when using [The Ruby Racer](http://github.com/cowboyd/therubyracer). It showed just some of the many ways that you can access Ruby state and call Ruby code from within JavaScript. However, the story doesn't end there. The Ruby Racer is, after all, a two way bridge, and I thought it would be useful to document some of the ways in which you can access your JavaScript environment from within Ruby. + +Let's start with the `Context#eval` method, which is used to execute JavaScript code from Ruby. Its the most intuitive way, +and what gets used most often for examples. + +Given a string, it compiles it as JavaScript source and then executes it inside the context. Nothing +crazy there: + +```ruby +V8::Context.new do |cxt| + cxt.eval('1 + 1') #=> 2 + cxt.eval('foo = {bar: "bar", baz: "baz"}') + cxt.eval('foo.bar') #=> "bar" + cxt.eval('foo.baz') #=> "baz" + cxt.eval('new Object()') #=> [object Object] +end +``` + +Among other things, the context captures what functions and objects are defined in the global scope +whenever anything gets `eval()`'d. So, for example, the `Object` constructor used in the last line is stored +in the context. + +As a tool for embedding however, `eval()` is the most blunt and (often) inefficient method available. +For this reason [The Ruby Racer](http://github.com/cowboyd/therubyracer), sports an extensive Ruby API for +interacting with JavaScript objects which includes accessing properties, calling functions and methods, as well +as creating new instances. In fact, I rarely use `eval()` _at all_ to manipulate a JavaScript context +except for loading source files into the interpreter. + +The Ruby API consists of `V8::Object` and all of its subclasses: + +```ruby +V8::Context.new do |cxt| + cxt.eval('new Object()').class #=> V8::Object + cxt.eval('(function() {})').class #=> V8::Function + cxt.eval('new Array()).class #=> V8::Array +end +``` + +## Objects + +The most fundamental operations in dealing with JavaScript objects are the getting and setting of values. +Coincidentally, this is also a fundamental concept in Ruby, so it comes as no great surprise that we can re-use +those constructs in Ruby that deal with property access to mirror those same operations on their JavaScript +counterparts: the `[]()`/`[]=()` and `foo()`/`foo=()` methods: + +given the following context: + +```ruby +cxt = V8::Context.new +order = cxt.eval('order = {eggs: "over-easy"}') +``` + +we can read the `eggs` property of our order in several differnt ways. +Via hash access (note that both string and symbol are acceptable as key values): + +```ruby +order['eggs'] #=> "over-easy" +order[:eggs] #=> "over-easy" +``` + +Values can be set with the hash-style access as well, and changes made from Ruby +will be reflected accordingly on the JavaScript side + +```ruby +order['eggs'] = "sunny side up" +cxt.eval('order.eggs') #=> "sunny side up" +``` + +For property names that are also valid Ruby method names, you can access them just +like you would with Ruby properties declared with `attr_reader` or `attr_accessor`. Again, +changes made to the object in this way will be reflected in JavaScript: + +```ruby +order.eggs #=> "sunny side up" +order.eggs = "scrambled" +cxt.eval('order.eggs') #=> "scrambled" +``` + +In the cases where the property name is not a valid Ruby method name, hash-style access is mandatory: + +```ruby +order['Extra $%#@! Mayonaise!'] = true +``` + +`V8::Object` also includes `Enumerable` and allows you to access all properties of a given object. + +```ruby +order.each do |key, value| + puts "#{key} -> #{value}" +end + +#outputs: +eggs -> scrambled +Extra $%#@! Mayonaise! -> true +``` + +As a convenience, `V8::Context` delegates the hash access functions to the JavaScript object which serves +as its global scope. + +```ruby +order == cxt['order'] #=> true +order == cxt.scope['order'] #=> true +``` +## Arrays + +Not much to see here, but before you move along: A `V8::Array` is like every other `V8::Object` except it has a `length` property, and +it enumerates over the items stored at indices instead of the key, value pairs of its properties. + +```ruby +array = cxt.eval('["green", "red", "golden"]') +array.length #=> 3 +array.map {|color| "#{color} slumbers"} #=> ['green slumbers', 'red slumbers', 'golden slumbers'] +``` + +## Functions + +Consider the classic "Circle" example from every object oriented playbook you'll ever read. In JavaScript, +the way to implement the circle "class" is with a constructor function. + +```ruby +circle = cxt.eval<<-JS + function Circle(radius) { + this.radius = radius + this.area = function() { + return this.radius * this.radius * Math.PI + } + this.circumference = function() { + return 2 * Math.PI * this.radius + } + } + new Circle(5) +JS +``` + +Now that we have the `Circle` constructor defined, and we have a reference to an instance of it in Ruby, +we can call its methods just as though it were a normal Ruby object. Of course, only we know +that under the covers the implementation is actually in JavaScript: + +```ruby +circle.class #=> V8::Object +circle.radius #=> 5 +circle.area() #=> 25Π +circle.circumference() #=> 10Π +``` + +In JavaScript, methods are just object properties that happen to be functions. Therefore, you can get +a reference to the actual function value just as you could from JavaScript simply by accessing the property +by name. The resulting value is an instance of `V8::Function`. + +```ruby +area = circle['area'] +circumference = circle['circumference'] +area.class #=> V8::Function +circumference.class #=> V8::Function +``` + +Once you have a reference to a `V8::Function`, there are two ways to call the underlying JavaScript code +(Actually there are three, but the third will be covered in the next section). These are the `call()` and +`methodcall()` methods. To understand the difference between these two methods, it helps to understand how +JavaScript functions themselves are invoked. In the event, there is the option to pass an object +which will serve as the implicit invocant or `this` value. If no `this` is provided, the function will +use the global scope in its place. Take for example the following JavaScript + +```ruby +var circle = new Circle(5) +var area = circle.area //=> [object Function] +//no invocant, corresponds to ruby call() +area() //=> NaN, there is no global 'radius'. +//call with invocant, corresponds ruby methodcall() +area.apply(circle) //=> 25Π +``` + +The same code in Ruby: + +```ruby +area.class #=> V8::Function +area.call() #=> NaN +area.methodcall(circle) #=> 25Π +area = circle['area'] +``` + +Because of this mechanism, JavaScript method invocation is much more flexible than Ruby in the sense that +virtually _any_ object can be used as the invocant of _any_ function provided it has the requisite properties +to satisfy the function's requirements: + +```ruby +area.methodcall(:radius => 5) #=> 25Π +other_circle = OpenStruct.new +other_circle.radius = 10 +area.methodcall(other_circle) #=> 100Π +``` + +A very powerful construct indeed. + +## Constructors + +But wait, there's more! Any JavaScript function can be either invoked normally, or, combined with the `new` keyword, +as a _constructor_. It's what we used in the original eval() block to create the instance of `Circle` whose methods we were messing +about with. + +That was not quite necessary since we can invoke functions as constructors from Ruby too. This is done with the `new` +method of `V8::Function`. + +```ruby +Circle = cxt['Circle'] +circle2 = Circle.new(3) +circle2.radius #=> 3 +circle2.area #=> 9Π +circle2.circumference #=> 6Π +``` + +Interestingly, when used as a constructor, a JavaScript function is almost completely indistinguishable from a Ruby class. +This apparent duality between classes and functions is behind the decision to [represent Ruby classes reflected into JavaScript as functions](../2010-06-30-accessing-ruby-objects-from-V8#classes) + +The driving goal in all of these cases is the ability to author intuitive, tightly bound JavaScript, which necessarily doesn't +involve evaluating strings to get what you want done. In closing, if there is something missing from this API you think would +be useful, I would love to [hear about it](http://github.com/cowboyd/therubyracer/issues) diff --git a/blog/2010-11-14-we-must-come-together-to-honor-the-command-line/index.md b/blog/2010-11-14-we-must-come-together-to-honor-the-command-line/index.md new file mode 100644 index 00000000..0b36044c --- /dev/null +++ b/blog/2010-11-14-we-must-come-together-to-honor-the-command-line/index.md @@ -0,0 +1,31 @@ +--- +title: We must come together to honor the command line +author: Charles Lowell +tags: + - ruby +--- + +I reap no joy from writing command line interfaces in Ruby, and about 5 minutes before leaving RubyConf this year I realized that I am not alone. + +It's not angst I feel so much as a general _"meh"_: There's a fairly decent out-of-the-box way to do it in `OptionParser`, but the minute you want to introduce commands is when the muddling begins. Sure you can see +your way through it, but it's never robust and you always get smacked in the ass by the edge cases that +inevitably come along. + +## It ain't for lack of trying + +My own abortive attempts not withstanding, I've tried in the last year at least 3 different libraries for parsing CLI options in my applications and gems. These are +[gli](http://github.com/davetron5000), [thor](http://github.com/wycats/thor) and [optitron](http://github.com/joshbuddy/optitron). I have known and loved each of these libraries in its own way, but ultimately all of them left me feeling unsatisfied. I won't go into my specific dissatisfactions with each here, but suffice it to say that my malaise was shared. The general consensus of the +cohort at the bar was that despite the recent innovations and improvements in the space, we'd really have just been better off with +"`OptionParser` plus commands." + +## But we should do better + +Better off for sure, but is that still what we want: to just get by? + +No. we need a way to write command lines that feels like judo: deploying minimal force to effortlessly sling mountains of code. + +I say we can have our cake and eat it too. I say our command lines can be easy. I say our command lines can be sexy. I say our command lines can be so awesome that they crap rainbows and have herds of unicorns come thundering out of their asses. + +That's why I'm starting the [optimal](http://github.com/cowboyd/optimal) project. It may lead to nothing. It might even lead eventually to some code... that's not the point right now though. The point is to reach out to everyone who feels strongly about this and ask them to contribute ideas and requirements in order to build a consensus on the next generation of CLI builder that everybody can be proud of. + +It certainly will not happen overnight, but for the betterment of all, it's high time to find the true successor to OptionParser: the one the community sees eventually integrated into the standard library. diff --git a/blog/2011-01-06-wrapping-each-hudson-distribution-in-its-own-rubygem/index.md b/blog/2011-01-06-wrapping-each-hudson-distribution-in-its-own-rubygem/index.md new file mode 100644 index 00000000..14d43088 --- /dev/null +++ b/blog/2011-01-06-wrapping-each-hudson-distribution-in-its-own-rubygem/index.md @@ -0,0 +1,121 @@ +--- +title: Wrapping each Hudson distribution in its own RubyGem +author: Charles Lowell +tags: + - ruby + - hudson + - java + - jenkins +--- + +> ___Update___: As of version `1.396`, this is applicable to the [Jenkins](http://jenkins-ci.org) project. +> `1.395` will be the last version of the hudson-war gem + +As you may or may not know, we here at The FrontSide have been working since +late last year to create a proper ruby environment for extending hudson. This +is no mean feat because it implies not only the ability to use nothing but ruby +_code_ to implement hudson plugins, but just as importantly, the ability to use +nothing but ruby _tools_ to develop them. That means no javac, no +[jar files](http://en.wikipedia.org/wiki/Jar_file), no [war files](http://en.wikipedia.org/wiki/WAR_%28Sun_file_format%29), and for the love of all that is holy: [absolutely, +positively, no maven](http://kent.spillner.org/blog/work/2009/11/14/java-build-tools.html). In short, the only folks you'll need to know to get in +the door with Hudson are your pals Ruby, Rake and Bundler. + +Of course, all those jars and wars and perhaps some class generation will be +there behind the scenes. After all, we'll still need to compile java stubs that +depend on core Hudson classes. We'll still need to package your plugin along +with its ruby code and dependencies in a jar file, and we'll still need to take +care of all the blah, blah, MANIFEST, blah, META-INF, WEB-INF, blah, blah, blah... + +## The War + +If we're going to do the compilation against Hudson core from within ruby and +Rake instead of maven, then we're going to need a reliable way to get at the +core hudson classes so that we can use them in our compilation classpath. Not +only that, but as part of the development process, you're going to want to test +your plugins against a fully functioning hudson server. Luckily, both of these +are contained in the full hudson distribution that you download as a single +war file. Wouldn't it be nice if you had a simple way to get at a specific +version of one of those wars and then get at all those java goodies contained +therein? + +## The Gem + +To my thinking, this sounds like a job for: RubyGems! That's why we're introducing +the new [hudson-war](https://rubygems.org/gems/hudson-war) gem, which does nothing except wrap a single hudson +distribution so that it can be managed coherently just like any other ruby +component. Want the latest warfile? + +``` +$ gem install hudson-war +``` + +Or perhaps you need hudson 1.386? That's fine, hudson-war versions are the same +as the hudson distributions they wrap. + +``` +$ gem install hudson-war --version 1.386 +``` + +It's not just a war file either. In keeping with the best traditions of OOP +data comes bundled with behavior. The gem comes with a ruby API and a CLI baked +in to help us do all the nifty things we might want to do with a war file. +For starters, you might want to know where it is. + +``` +$ cowboyd$ hudson.war location +/Users/cowboyd/.rvm/gems/ruby-1.8.7-p174/gems/hudson-war-1.386/lib/hudson/hudson.war +``` + +If you want to unpack it to a particular location then + +``` +$ hudson.war unpack tmp +jar xvf /Users/cowboyd/.rvm/gems/ruby-1.8.7-p174/gems/hudson-war-1.386/lib/hudson/hudson.war -C tmp/ +``` + +You can run a test server with it + +``` +$ hudson.war server --daemon +Forking into background to run as a daemon. +Use --logfile to redirect output to a file +$ hudson.war server --kill +``` + +Beyond the basics, let's say you want to do something interesting like compile +some java code against the hudson core. You're gonna need a classpath, and you +can get it with: + +``` +$ hudson.war classpath +/Users/cowboyd/.hudson/wars/1.386/WEB-INF/lib/hudson-core-1.386.jar +``` + +You can even compile java code with it directly. Here we compile a trivial +class that has a dependency on hudson. + +``` +$ cat > ImportsHudson.java +import hudson.model.Hudson; +public class ImportsHudson { + public static void main(String args[]) { + System.out.printf("The Hudson Model object's class is %s\n", Hudson.class.getName()); + } +} +$ hudson.war javac ImportsHudson.java +$ java -cp .:`hudson.war classpath` ImportsHudson +The Hudson Model object's class is hudson.model.Hudson +``` + +It's like RVM for hudson. Wrangling java has never been so fun! + +## The War Gemmer + +Befitting a continuous integration project, the Hudson team releases a new +version at least every two weeks, and sometimes even sooner. It can be a bitch +to keep up with, so I've set up a +[wargemmer hudson](http://github.com/cowboyd/hudson-wargemmer) task to +periodically poll the update center to see if there is a new release. If there +is, it'll gem it right up and push it to rubygems. No fuss no muss. + +Let the war begin! diff --git a/blog/2011-05-12-what-it-take-to-bring-ruby-to-jenkins/index.md b/blog/2011-05-12-what-it-take-to-bring-ruby-to-jenkins/index.md new file mode 100644 index 00000000..554d17bc --- /dev/null +++ b/blog/2011-05-12-what-it-take-to-bring-ruby-to-jenkins/index.md @@ -0,0 +1,82 @@ +--- +title: What it takes to bring Ruby to Jenkins +author: Charles Lowell +tags: + - java + - ruby + - jenkins +--- + +"Jenkins Ruby Plugins" are at an important, yet fragile stage of their life. Over the past several months, we have made +tremendous progress towards making extending Jenkins with nothing but Ruby a blissful reality. However, at this moment, +they exist only as a collection of hacks, or, to frame it more kindly, "proofs of concept" all buried quite deeply in a +[terribly named repo on github](http://github.com/cowboyd/fog.hpi). + + +To name a few of them, we are now able to + +* automatically discover Ruby plugins and register them +* work with Jenkins internals like BuildWrapper, RootActions, etc.. via a native Ruby-like API +* persist mixed hierarchies of Java and Ruby objects transparently as Jenkins configuration +* use ERB to template views, while still having access to the standard UI helpers +* extend the Jenkins REST API from Ruby + +This is by no means an exhaustive list, but I want to stress just how far we've come before I talk about where it is that +we need to go. + +While the work done so far represents some phenomenal accomplishments, in the end, they are only technical accomplishments which do not together create a coherent developer experience. So now, for this endeavor to succeed, we must focus our attention on making that *entire experience*, from project start to release after release, as frictionless as possible. + +To that end, I propose the following high level goal for the Jenkins Ruby Plugins project: + +>To provide the facility to create, develop and release extensions for Jenkins with nothing but knowledge of the language, tools and best practices of the Ruby community. + +This is, I think, the most important ideal to aspire to when claiming to provide a Ruby development experience. Unless it feels like the real McCoy, then you aren't really developing Ruby at all, you're just scripting Java with JRuby. You won't fool anybody, and there will be far less enthusiasm and adoption. + +That said, several things follow logically from this thesis, the first of which is: + +### Absolutely, Positively, No Maven! + +This is not an ideological stance on the value of Maven as a wretched pile of toad sick software development tool. Rather, it is an explicit acknowledgement that it is not a Ruby tool, it doesn't fit with the community's style, and if you're using it +you might not be doing Java, but you *certainly* aren't doing Ruby. Instead, we should turn to the Ruby tools that +occupy this same space: Rake for scripting the build, and Bundler for dependency management. + +### Ruby-Like Project Structure + +If we aren't going to use Maven, then why should we be constrained by the project directory structure that it imposes. `src/main/resources/what?` Nope. Just one glance at Jenkins Ruby Plugin's layout in an editor or file system browser +should put a Rubyist's mind right at ease. You see that? There's your `Gemfile` just +where you left him. And oh, your pals `lib/` and `spec/` are there too. Let's get right to work! + +### Managing The Project Life-Cycle + +From inception to release, you should be able to manage all aspects of your Jenkins plugin with Ruby. There must be single +commands (or rake tasks) to + +* conjure a new plugin into existence +* generate model/view boilerplate +* run a server with the plugin loaded +* package, push, tag and release a plugin to update-center + +### Testing + +The importance of testing in the Ruby community cannot be overstated. The Ruby Tools need to provide a clear path to +testing the plugin at both the unit and functional levels. + +### Documentation + +The "Ruby-like" API that plugin development provides is similar, but not equivalent to its native Java equivalent. +It is critical that this API be well documented, so that developers can understand it, and do not find themselves constantly +referring to the JavaDoc, which they may very well not understand (it being Java), and might be misleading given the natural +asymmetries of the two APIs. + +## Heed the Call! + +There are plenty of people who would love to see this project get done, but don't know where to start. I know this because I was one of them. The upshot is that now, as I've just described, there is plenty of work to be done that doesn't require any knowledge of Java and very little knowledge of Jenkins internals. That means that *you* can contribute in a meaningful way! + +Next week, I'll be publishing some specifications in the form of Cucumber scenarios for some of the features up above that we can discuss and then hopefully start implementing. + +But the most important thing that you can do is to join in on the conversation! + +[irc](irc://freenode.net/jenkins), and [email](http://groups.google.com/group/jenkinsrb) are just a few of the ways to reach out! + +> Update. I have posted the first scenario: [creating a plugin](https://github.com/cowboyd/jenkins.rb/blob/ruby-plugin-development/features/plugins/create-new-plugin.feature). +> The first bounty is officially posted. diff --git a/blog/2011-06-13-therubyracer-isnt-threadsafe-yet/index.md b/blog/2011-06-13-therubyracer-isnt-threadsafe-yet/index.md new file mode 100644 index 00000000..9a7dd1fd --- /dev/null +++ b/blog/2011-06-13-therubyracer-isnt-threadsafe-yet/index.md @@ -0,0 +1,64 @@ +--- +title: The Ruby Racer isn't threadsafe... yet. +author: Charles Lowell +tags: + - javascript + - ruby + - therubyracer +--- + +> +> In which I explain problems unique to running The Ruby Racer in a multithreaded environment, +> which users are affected by these problems, and what is to be done for them. +> +> UPDATE: This issue has been resolved with [version 0.9.1](http://rubygems.org/gems/therubyracer/versions/0.9.1). +> You should be able to stop reading here, update your Gemfile, and have a great day. That said, please don't +> let me discourage you from continuing down the page. It's riveting. + +There have been a number of crashes reported in [several](https://github.com/cowboyd/therubyracer/issues/79) [different](https://github.com/rails/rails/issues/1667) [places](http://redmine.ruby-lang.org/issues/4821) for which [The Ruby Racer](http://github.com/cowboyd/therubyracer) is the culprit. For the most part, these are people running an Rails 3.1 application in a multithreaded webserver such as WEBrick, but as we'll see, it could be anybody that is using The Ruby Racer in a mulithreaded environment. In order to speak to these and future issues +in one place, as well as prevent the spread of any misinformation or impressions, I wanted to very quickly explain what exactly is causing these crashes, who is affected by them and where, and finally how we're going to make them go away. + +## Background + +When an instance of V8 starts running, it associates all of its global state with the currently running native OS thread. By global state, I mean things like heap storage, stacks, callbacks... everything that it needs to make JavaScript happen. Every time you `eval()` a script or you allocate an object with `new`, it retrieves that state from the executing thread to perform that operation. + +In a single threaded application, that's the end of the story. This is because every V8 operation happens from the same thread as the rest of your code, so the V8 state is always right there ready at all times. If you're running your app in a rack server like Passenger or Unicorn that uses a multi-process model to achieve concurrency, you'll never see this crash because you don't use threads. Also, if you're running on MRI 1.8.7 which uses green threads, you will also not be affected. While you may have more than one Ruby thread in your application, you still only have one *native* OS thread which is what V8 will use. + +The problem comes in when you are using Ruby 1.9 in a multi-threaded environment and you access V8 from more than one thread. This is because 1.9 uses native OS threads under the covers for its own threading API. When The Ruby Racer starts up V8, it uses whatever thread happens to be running as the home for its runtime data. Then, when you try and do some JavaScript operation from another thread, V8 can't find its state, freaks out and crashes your entire process. + +Luckily, V8 does give us some help here. It provides a [locker API](http://bespin.cz/~ondras/html/classv8_1_1Unlocker.html) as a way to deal with threading issues. It acts as both mutex on a V8 instance while at the same time providing a facility to access it from different threads. +We can wrap calls that touch V8 with a `Locker` in order to prevent these crashes. e.g. + + V8::C::Locker() do + #... code that calls V8 + end + +That sounds simple enough: just never access V8 without a lock and you're good. In fact, [ExecJS does this](https://github.com/sstephenson/execjs/blob/b67a563ab4c6e26dc468e948d523456d531463f9/lib/execjs/ruby_racer_runtime.rb#L70-86) whenever it invokes its therubyracer runtime, and it works... almost. + +## I would have gotten away with it too if it hadn't been for you pesky GCs + +If ExecJS locks V8, then why am I still crashing, and at seemingly random intervals? + +The problem comes when we need to run garbage collection. The awesome thing about The Ruby Racer is that it +lets you hold on to V8 objects from Ruby. It does this by using a proxy --a ruby object that implements all its behavior by delegating to an underlying JavaScript object. As long as Ruby holds a reference to that proxy, the proxy will in turn hold a reference to its underlying V8 object. However, when Ruby no longer holds a reference to the proxy and the proxy is garbage collected, we need to tell V8 that we aren't holding a reference to the underlying object anymore. And, as you may have guessed, in order to remove references to V8 objects, you need to hold what? Yep, the V8 lock. To operate properly, GC also needs to lock V8. Sadly, it does not, which explains why crashes continue to happen at random places. + +You might be asking yourself, "why not just lock V8 in your GC routines and have done with it?" The answer +is that it would be a terribly dangerous and irresponsible thing to do. + +Suppose that thread `A` has V8 locked and is doing some fancy JavaScript stuff. At some point, we do a context switch over to thread `B` which is not doing anything even remotely JavaScriptful like loading ActiveRecords from the database or something. Thread `B` is chugging threw lots of memory, and all of a sudden triggers GC. There are some V8 objects that need cleanup due to our JavaScript activities over in thread `A`, so it tries to lock V8, but uh-oh, thread `A` already holds the V8 lock, so GC may have to wait forever until it becomes available. Deadlock! + +The answer is that instead of immediately releasing v8 objects inside the GC thread, we need to equeue them somewhere where they can be released. This probably means starting a thread per V8 instance to consume this reference queue and release them in the context of a V8 lock that isn't contending for any other resources. +While the answer is not trivial, I don't expect it to be that complex. + +## What to do? What to do? + +In the mean time, there is a [workaround](https://gist.github.com/1011718) for locking V8 with each request, which will ensure that GC running in that request will have already acquired the V8 lock. Bear in mind that this is not a silver bullet, and you still could encounter crashes and deadlocks, but they should be very few and far between. If, as is the case with most people, you are just using a multithreaded server in your development environment but deploy to multi-process model, then this should be sufficient. If on the other hand you run multi-threaded in production, then you should definitely *not* be locking V8 in your middleware since this will effectively synchronize every request. Not a good idea. + +No matter which category you fall into, I expect that we'll have a beta fix by the end of this week, and if all goes well, a patch release after another. This may seem like a long time for such a small thing as coordinating locking with GC, but my experience tells me that threading is not a thing best done by humans, and so there will +inevitable stumbles and cursing. + +Also, I'm keen to make sure that any solution is both invisible to you the developer, as well as compatible with other Ruby versions and implementations. This deserves some thought. + +## What? you're still here? + +Alright, time to stop talking and get started with the repairs. I just wanted to take a moment to let you know what the problem was, why it was happening, how're you're affected, what you can do now, and when you can expect a more permanent fix. diff --git a/blog/2011-09-13-ruby-for-jenkins-goes-pre-alpha/index.md b/blog/2011-09-13-ruby-for-jenkins-goes-pre-alpha/index.md new file mode 100644 index 00000000..e5e3428c --- /dev/null +++ b/blog/2011-09-13-ruby-for-jenkins-goes-pre-alpha/index.md @@ -0,0 +1,32 @@ +--- +title: Ruby for Jenkins Goes Pre-Alpha +author: Charles Lowell +tags: + - java + - ruby +--- + + +To quote Dave Bowman, "something wonderful happened" last week during the weekly [Jenkins-Ruby hack session][1]. + +We were able to boot a [plugin written in pure Ruby][2] into a Jenkins server just by executing a single command from the command line (rake server). Furthermore, this command did not involve a compilation step, and the plugin that it booted did not bare a trace of evidence that it was to be loaded into a server written in Java. + +That's right. Using nothing but good 'ol Bundler and Rake, we hoisted a pure Ruby plugin into the Jenkins runtime. +And I can tell you that after hacking on this for almost a year: it felt... so.... good.... man. + +It's not time to declare victory yet, but at least the first two of [our major goals][3] have been realized, and that is definitely a cause to celebrate. + +Practically, this means that rubyists can check out the source and get hacking without needing any knowledge of +Java or any Java toolchain. That's why I think it's safe to officially declare the project to be in +a ***pre-alpha*** state. + +It isn't yet for the faint of heart, and you're sure to find many things confusing and +difficult. But at least those who know only Ruby can contribute bug reports, identify pain points, and contribute +to the creative process. + +It's 8:20am, but it still feels like Miller time. + + +[1]: http://wiki.jenkins-ci.org/display/JENKINS/Jenkins+plugin+development+in+Ruby "Jenkins Ruby Hacking" +[2]: https://github.com/cowboyd/jenkins-prototype-ruby-plugin "Prototype Ruby Plugin" +[3]: ../2011-05-12-what-it-take-to-bring-ruby-to-jenkins "What it takes to bring Ruby to Jenkins" diff --git a/blog/2012-01-16-implementing-a-jenkins-extension-point-with-native-api-in-ruby/index.md b/blog/2012-01-16-implementing-a-jenkins-extension-point-with-native-api-in-ruby/index.md new file mode 100644 index 00000000..e9ed005f --- /dev/null +++ b/blog/2012-01-16-implementing-a-jenkins-extension-point-with-native-api-in-ruby/index.md @@ -0,0 +1,173 @@ +--- +title: Implementing a Jenkins Extension Point with the Native Java API inside a Ruby Plugin +author: Charles Lowell +tags: + - jenkins + - ruby + - java +image: jenkins-ruby.png +--- + +> In which I elaborate why the idomatic Ruby API is sometimes not enough, +> and describe a method to harness the full power of the underlying +> Jenkins API while still happily coding your extension in Ruby + +## The Ruby API + +One of the primary goals of the [effort to bring Ruby to Jenkins][1] +is to enable developers to extend Jenkins in a way that looks and +feels like a normal Ruby environment. This means using rake, bundler +ERB, and plain old Ruby objects to roll your plugin. + +For example, the [native BuildStep][2] class is made available through +the [Ruby BuildStep][3]. They are similar to be sure, but the Ruby +object is much less complicated, and actually bears no relation to its +Java equivalent inside the Java hiearchy of inheritance. + +Exposing this simplicity is a worthy goal, but to do so requires +careful consideration of each Jenkins Java API and how to provide its +functionality in the Ruby way --no mean feat given the breadth of the +Jenkins. + +Sadly, it means that these Ruby-friendly APIs must necessarily lag +behind their Java counterparts. + +This can be a serious source of frustration for those looking to +dive into Jenkins Ruby development right now. Initial excitement is +quickly dulled when you find out that the extension point that you +wanted to implement is unavailable from Ruby land. You might get +discouraged and feel like you might as well be coding your plugin +with Java. + +Well don't lose heart! I'd like to share with you a super easy way to +write your extension points in Ruby even when there isn't a friendly +wrapper. We'll actually implement a very handy extension point called +`RunListener` within a Ruby plugin even though it is not part of the +official Ruby API. We'll do it by scripting the Java class directly +with JRuby's Java integration feature, and then register it directly +with the plugin runtime. + +## The Extension Point + +We'll be working with the Jenkins [RunListener][4] interface. This is +a wonderful extension point that allows you to receive callbacks +at each point during the actual running of a build. There's currently +no nice ruby API for it, but we won't let that stop us. + +First, let's create a new plugin called _my-listener_. We'll do this +with the `jpi new` command. + +```ruby +legolas:Jenkins cowboyd$ jpi new my-listener +create my-listener/Gemfile +create my-listener/my-listener.pluginspec +``` + +> Fun fact: 'jpi' is an acronym for (J)enkins (P)lug-(I)n. You can +> install the tool with rubygems: `gem install jpi` + +Next, we'll cd into our new plugin and create our listener class +inside the models/ directory. Jenkins will automatically evaluate +everything in this directory on plugin initialization. + +```ruby +legolas:Jenkins cowboyd$ cd my-listener/ +legolas:my-listener cowboyd$ mkdir models +legolas:my-listener cowboyd$ touch models/my_listener.rb +``` + +Our ultimate goal here is to implement a `RunListener`, so let's +go ahead and start our class definition inside that file. + +```ruby +class MyListener < Java.hudson.model.listeners.RunListener + def initialize() + super(Java.hudson.model.Run.java_class) + end +end +``` + +There's a couple key takeaways here. First, notice that we use +JRuby integration to extend the class +`hudson.model.listeners.RunListener` directly. Second, and this is +a gotcha anytime you extend a Java class: you _must_ invoke one of +the Java super constructors if it does not have a default +constructor. I can't tell you how many times I've been bitten by this. + +In our case, the `RunListener` class filters which jobs +it will provide callbacks for by class. By providing a more specific +class to the constructor, you limit the scope of jobs you'll receive +to subclasses of that class. For our purposes, we cast a pretty wide +net by selecting all builds via the `AbstractBuild` Java class. + +> Pro Tip: when you're implementing a native Java API, it really +> helps to have the javadoc open in one window so that you +> can view the documentation and crib from the source + +Now that we've got our class defined, let's implement some methods! +We'll add callbacks for when a build is started and when it's +completed. + +```ruby +class MyListener < Java.hudson.model.listeners.RunListener + def initialize() + super(Java.hudson.model.AbstractBuild.java_class) + end + def onStarted(run, listener) + listener.getLogger().println("onStarted(#{run})") + end + def onCompleted(run, listener) + listener.getLogger().println("onCompleted(#{run})") + end +end + +Jenkins.plugin.register_extension MyListener.new +``` + +And finally, on the last line, we actually inform Jenkins about the +existence of our new Listener with the call to +`Jenkins.plugin.register_extension` + +And that's about it. We can start up our test server with our `jpi` +tool to see our listener in action. + +``` +legolas:my-listener cowboyd$ jpi server +Listening for transport dt_socket at address: 8000 +Running from: /Users/cowboyd/.rvm/gems/jruby-1.6.5/gems/jenkins-war-1.446/lib/jenkins/jenkins.war +... +Jan 16, 2012 12:46:15 AM ruby.RubyRuntimePlugin start +INFO: Injecting JRuby into XStream +Loading /Users/cowboyd/Projects/Jenkins/my-listener/models/my_listener.rb +INFO: Prepared all plugins +... +INFO: Jenkins is fully up and running +``` + +To view the output, create a freestyle build called HelloWorld that +doesn't have any build steps at all, build it and view the console +output. You should see something like this: + +``` +Started by user anonymous +onStarted(#) +onCompleted(#) +Finished: SUCCESS +``` + +## The Sweet Reality + +Even though we were dealing more directly with Java, we were +still able to use all of the simplicity that comes with developing +plugins in Ruby. Furthermore, even though our extension point was just +scripted Java, it doesn't mean that inside its methods it can't call +as much pure Ruby code as its heart desires. + +I hope that if you're considering writing your next (or your first!) +Jenkins plugin in Ruby, you'll feel confident that you can always +fall back to the native APIs at any point. + +[1]: http://blog.thefrontside.net/2011/05/12/what-it-take-to-bring-ruby-to-jenkins +[2]: https://github.com/jenkinsci/jenkins/blob/master/core/src/main/java/hudson/tasks/BuildStep.java +[3]: https://github.com/jenkinsci/jenkins-plugin-runtime.rb/blob/master/lib/jenkins/tasks/build_step.rb +[4]: https://github.com/jenkinsci/jenkins/blob/master/core/src/main/java/hudson/model/listeners/RunListener.java diff --git a/blog/2012-01-16-implementing-a-jenkins-extension-point-with-native-api-in-ruby/jenkins-ruby.png b/blog/2012-01-16-implementing-a-jenkins-extension-point-with-native-api-in-ruby/jenkins-ruby.png new file mode 100644 index 00000000..0e0c8a79 Binary files /dev/null and b/blog/2012-01-16-implementing-a-jenkins-extension-point-with-native-api-in-ruby/jenkins-ruby.png differ diff --git a/blog/2012-12-04-therubyracer-rides-again/index.md b/blog/2012-12-04-therubyracer-rides-again/index.md new file mode 100644 index 00000000..cdc73080 --- /dev/null +++ b/blog/2012-12-04-therubyracer-rides-again/index.md @@ -0,0 +1,160 @@ +--- +title: The Ruby Racer Rides Again +author: Charles Lowell +tags: + - javascript + - ruby + - therubyracer +--- + +## It began during RailsConf. + +My fingers were itching to code, so between sessions I started +tinkering with some of the more fanciful enhancements to +[The Ruby Racer][1] I'd been contemplating as well as wrestling with +a number of long-standing bugs. But what started out as a small +refactoring slowly but relentlessly gained momentum until things +were completely out of control. + +By all accounts my hands were steady and the work proceeded in an +orderly fashion. Instead, it was *I* who was out of control. +Personally and psychologically I was helpless to stop myself as line +after line, class after class unravelled before me --each one more +rapidly than before. Soon a critical mass was achieved and boom! +Nuclear. A from-scratch rewrite was underway. + +Yes it was a rewrite, but by the time I was aware of it, I was too +deeply invested to turn back. + +It's a tichy thing, is a rewrite. Instead of the careful, iterative +approach that serves as law in our profession, you drop everything +and sprint towards the cliff in the hope that you can lunge across the chasm in +in one leap. Once you're in the air there's no going back, and the +possibility is as thrilling as it is real that, like Icarus, you'll +hurtle into the abyss... another unsung casualty of hubris. + +The prize of course is a better library that is more stable, has more +features and is easier to build and install, so if a rewrite it had to be, +I did the only thing I could do yield myself to it utterly. + +I'm happy to say that it's mostly there there and that, despite some outstanding +flaws, I'll be releasing the next version shortly. But more on that later. +First, what's new? + +## Stability + +For certain applications therubyracer has been plagued by crashes and memory +leaks that have limited its usefulness to a much smaller domain than +necessary: specifically, low-traffic applications that only instantiate a +single JavaScript context. These problems stemmed from within the library but +also from within Ruby itself. + +The Ruby Racer relies heavily on weak references in order to maintain +referential integrity between Ruby object and the JavaScript objects they +represent (and vice-versa). Sadly, the [WeakRef implementation on 1.9 is +completely broken][2]. This is unfortunate because functioning weak +references are sometimes a necessary component for building correct +programs on platforms with garbage collection. Luckily [there are +workarounds][3] which have been deployed while we wait for 2.0. + +But that's not the whole story. It would be a shameful misrepresentatio +to lay the bulk of the blame on Ruby's door. Distributed garbage collection +is hard, and The Ruby Racer engaged in all manners of evil hackery and dark +GC foo in order to prevent cycles of garbage from spanning the Ruby and +JavaScript VMs. Sadly, this did not work in the general case, and furthermore, +was not portable to other Ruby implementations (or even Ruby versions). + +I've come to realize that to solve these memory problems in the general case +would require each host Ruby platform, as well as V8, to expose a uniform +interface to its garbage collector so that The Ruby Racer can traverse its +object graph in order to detect cycles of garbage. In other words "not +bloody likely." + +As a fallback, I've decided to throw up my hands and eschew GC histrionics in +favor of more sane memory management in the C extension combined with an explicit +teardown mechanism for cases where cycles of garbage do occur. e.g. + +```ruby +context = V8::Context.new +context['cycle'] = context #oh no, a vicious cycle! + +context.dispose() # Gordion knot is cleaved, sir! +``` + +In exchange, we get a kinder, gentler racer that works on MRI as well as +Rubinius, and which you'll be able to rely on in your production processes. + +## Resource Constraints + +Speaking of production, the upcoming release contains a much more +comprehensive coverage of underlying V8 functionality than every before. + +Among other things, you can query the V8 vm for heap usage by young generation, +old generation as well as absolute heap size including executable memory. Not +only that, but you can also place hard caps on each of these numbers. That way +you can contain memory usage and keep your processes under control; even in a +resource constrained environment like Heroku. + +## A (hopefully) kinder build + +One of the sorest pain-points with The Ruby Racer was the way it built. Prior to +0.11, it had a hard dependency on the [libv8 rubygem][2] in order to provide the +actual V8 library. In retrospect, this was a bad idea. + +If you were unable to build the v8 contained in the libv8 gem, then +you were completely hosed. You could not use therubyracer at all. Or, if you +wanted to use a custom v8 patched for security or with tweaked startup data, +then you were out of luck. Finally, it raised an unnecessary barrier +to distributing therubyracer as a binary gem (which I would like to do more of). +If you have a binary version, then why do you need to have libv8 at all? + +For these reasons, the dependency on libv8 has been converted to a soft one. If +a compatible version is detected in your rubygems, then it will compile and link +against it. However, if for whatever reason, you do *not* want to use the libv8 +gem to build, then that is now your prerogative. The Ruby Racer will search +your system for v8 just like any other library. + +The only difference is that if you want to use the libv8 gem, then you will have +to explicitly require a compatible version in your Gemfile. + +## More bugs, but not on Rubinius + +There are still some outstanding issues on MRI that emerge under a heavy stress of +rapid object allocation and deallocation. As far as I can tell they are related to +to the weakref implementation, wherein MRI gets confused and starts handing back +references to completely unrelated objects. I say "as far as I can tell" because the +subtleties involved lie beyond the frontier of my understanding of MRI internals. +Given my current workload, it is unlikely I will have time to make the necessary +investment in understanding required to completely quash these problems. + +Despite this, I'm releasing the new version anyhow in order to support Rubinius more +fully and because I think that even on MRI it will solve more problems than it introduces. + +For starters, 0.11 is very stable on Rubinius and that platform is long overdue +for a version of therubyracer that doesn't suck. The Rubinius core team, and [@dbussink][5] +in particular, have been very patient and helpful in helping me resolve all the lingering +crashes both big and small... even going to the trouble of adding checks and diagnostics +to the garbage collector just for my sake. They were even willing and often able to +diagnose problems I was having on MRI in order to make the library for everybody. +If anything, I feel that they deserve a functioning JavaScript environment and it wouldn't +be fair for me to withhold it from them. + +On the MRI front, I still think that closing some of the loopholes in the memory management, +the enhanced extensibility, and better build make it worth releasing anyway. I've been +recommending people to upgrade to the beta for almost 3 months and it is almost always the +right decision. Given that I have neither the time nor the inclination to support what I now +consider to be the legacy release, I think it's high time to either shit or get off the +proverbial pot. + +If you, or anybody you know has a deep knowledge of MRI internals and would be willing to help +me address these issues as well as others that may arise, I would love to work with you to +knock 'em down. + +So this is why I'm releasing version 0.11.0 of The Ruby Racer today. Of course, I may +come to quickly regret it, but that's what `gem yank` is for right? + +[1]: https://github.com/cowboyd/therubyracer +[2]: http://bugs.ruby-lang.org/issues/show/4168 +[3]: https://github.com/bdurand/ref +[4]: https://github.com/cowboyd/libv8 +[5]: https://twitter.com/dbussink diff --git a/blog/2013-09-21-ember-on-rails-realtalk/index.md b/blog/2013-09-21-ember-on-rails-realtalk/index.md new file mode 100644 index 00000000..d65dcaab --- /dev/null +++ b/blog/2013-09-21-ember-on-rails-realtalk/index.md @@ -0,0 +1,20 @@ +--- +title: "Ember on Rails: #REALTALK" +author: Brandon Hays +tags: + - talks + - videos + - ember + - rails +image: realtalk.png +--- + +If you work in Rails and have ever wondered about Ember.js, you should know that Ember and Rails go together like Nutella and pretzels. (Which is to say, quite well indeed.) + +Get an inside look of the experience of going from having never tried Ember to shipping a production application in it. What makes Ember a good match for certain types of applications? + +This talk is intended to help relative newcomers jump in and start seeing the kind of crazy-ambitious applications you can build when you've got Ember.js in your toolset. + + + +**Note: The audio is rough, but gets a little better as the talk progresses. Sorry about that.** diff --git a/blog/2013-09-21-ember-on-rails-realtalk/realtalk.png b/blog/2013-09-21-ember-on-rails-realtalk/realtalk.png new file mode 100644 index 00000000..643adf86 Binary files /dev/null and b/blog/2013-09-21-ember-on-rails-realtalk/realtalk.png differ diff --git a/blog/2014-02-24-ember-and-the-future-of-the-web/basecamp-marked-up.png b/blog/2014-02-24-ember-and-the-future-of-the-web/basecamp-marked-up.png new file mode 100644 index 00000000..5294f753 Binary files /dev/null and b/blog/2014-02-24-ember-and-the-future-of-the-web/basecamp-marked-up.png differ diff --git a/blog/2014-02-24-ember-and-the-future-of-the-web/challenge-accepted-l.png b/blog/2014-02-24-ember-and-the-future-of-the-web/challenge-accepted-l.png new file mode 100644 index 00000000..91652a51 Binary files /dev/null and b/blog/2014-02-24-ember-and-the-future-of-the-web/challenge-accepted-l.png differ diff --git a/blog/2014-02-24-ember-and-the-future-of-the-web/index.md b/blog/2014-02-24-ember-and-the-future-of-the-web/index.md new file mode 100644 index 00000000..787f0640 --- /dev/null +++ b/blog/2014-02-24-ember-and-the-future-of-the-web/index.md @@ -0,0 +1,216 @@ +--- +title: Ember and the future of the web +author: Brandon Hays +tags: + - ember + - platforms + - prognostication +image: peabody.jpg +--- + +If you know me, you know it's easy to get me talking about [Ember.js](http://emberjs.com). A year ago, I was deeply skeptical of developing client-side applications. Now, after spending a year shipping production applications in Ember, I believe that client-side apps represent the same generational leap that database-backed apps with Rails offered circa 2006. + +Despite the title of this article, I can't actually predict the future, but I can bet on it. And Charles and I are betting The Frontside's future on the fact that Ember provides a foundation for creating amazing, next-generation user experiences for the web. + +## What server-side MVC does well (and doesn't) + +Server-side MVC frameworks rely on the classic Apple philosophy of optimizing for the 80% use case. These frameworks excel at taking a database, exposing CRUD (Create, Read, Update, Destroy) actions via HTTP, and building form-based UIs to work with these records. CRUD-oriented frameworks make it easy to build applications like blogs and contact management software. + +What server-side MVC frameworks are not particularly well-suited to are user-driven, stateful UIs like you'd see in desktop apps like Word, Excel, or iTunes. + +Rails, as a mature framework, has evolved a set of standard solutions to these types of problems. Generally, they involve minimizing the amount of JavaScript needed and placing as much of the burden on the server as possible. + +## Server-side workarounds + +I've built these many times "the Rails way", and I can viscerally feel myself cross the boundaries of the things Rails does well. I might start by creating a template called `create.js.erb` to be triggered from `new.html.erb`. + +```javascript +$('#tag_form').html('<%=j render("form") %>'); +$('#tags').html('<%=j render(@el.all_tags) %>'); +initSelect2($("#tags")); +``` + +But what's actually happening here is not simple (and has [security implications](http://homakov.blogspot.com/2013/05/do-not-use-rjs-like-techniques.html) that bear keeping in mind): + +[![](rjs-flow.png)](http://homakov.blogspot.com/2013/05/do-not-use-rjs-like-techniques.html) +*(Credit: [Egor Homakov's blog](http://homakov.blogspot.com))* + +And that's a greatly simplified example. The fact is that if you're building stateful client-side interactions, you're already managing a lot of JavaScript, but your code is probably scattered across jQuery plugins, server-side rendered JavaScript, and custom JS in your assets. + +These patterns quickly become difficult to maintain, and the pain encourages developers to steer clear of complex interactions and focus on the Rails happy path. + +And this isn't necessarily a bad thing. We love Rails for making those tradeoffs on our behalf, so we can focus on the 80% case. + +But I have two cautions about this kind of pragmatism: + +1. Trying to stretch the 80% solution into the other 20% almost always results in pain. +2. Keep your eye on the 20%, because it may just be the next 80% use case. + +## Two cases for using Ember + +Ember, to me, indicates a seismic shift in the way web applications are created. And not because Ember itself is so great (although I do like it a lot). + +**Rather, it's that Ember makes it *fun* to splash around in the other 20% and build next-generation applications.** + +I'll make two cases for Ember and then explain how I see it as a representation of the web of the future. +### Case 1: MVC/MVC is a feature, not a bug + +I'm a classic pragmatist. I like to learn things with an eye toward solving problems. So the concept of an MVC framework on top of an MVC framework struck me as odious. But since then, I've come to see it as incredibly freeing. + +I never realized how much of my thinking was shaped by the limitations of the tools I used until exploring something with a totally different set of capabilities and constraints. + +Let's look at the original Rails app, Basecamp: + +![Basecamp](basecamp-marked-up.png) + +Yes, the screenshot is outdated. This is not meant as a knock against Basecamp, which has been since rewritten, but rather an example of how most of us write our Rails apps. **Server-side MVC frameworks lay a yellow brick road right to that interface.** It's certainly representative of the majority of *my* vanilla Rails applications. + +A few months ago, we started using the chat service Slack. I feel comfortable saying that it is a good example of web applications in 2014: it is so deeply interactive and so native-feeling that it nearly made me choke on my Cheerios. + +See if you can spot a direct mapping to database rows in Slack: + +![Slack](slack-marked-up.png) + +I sure couldn't pin it down. In fact, after downloading the standalone Slack app for OS X, I was sure that it was a native app until I right-clicked and saw "inspect element" in the menu. It's not an Ember app, but I can easily see how it would be done. + +**Client-side applications basically require you to saw your applications in half.** Handle your persistence layer however you want, just expose APIs to create, update, fetch, or delete records (a task to which Rails is particularly well-suited). + +The other half of your application is now 100% tailored to the user interface you want to build. You find yourself using the language and workflow of your users, rather than wiggling around within the constraints of your database tables and columns. + +All of this is wonderful, but represents any client-side MVC, rather than being unique to Ember. + +### Case 2: You Need Models, You Just Might Not Know it Yet + +Ember has a lot of strong suits, but one of the strongest is its rock-solid model layer. There aren't many hard UI problems that can't be made much simpler by using models to back your interactions. + +Back to my personal journey: After a while, I abandoned RJS and started using plain JavaScript and jQuery to manage complex interactions. + +My new AJAX-y workflow went like this: + + 1. Let the server render stuff to the page + 2. Set up a controller to handle AJAX requests from the UI + 3. Manipulate the DOM using a jQuery plugin + 4. Traverse the DOM again to see what changed + 5. Serialize changes from the DOM and send via AJAX + 6. The server returns an HTML string that is stuffed back into the DOM + 7. Repeat steps 3-6 + 8. Spend days troubleshooting inconsistent UI results + +I'll spare you the gory details, but I've written a ton of code that looks like this: + +```javascript +// ZOMG remember to clean up row position or everything breaks +var renumberRows = function(){ + $("#sortable .content_item:visible").each(function(index){ + $(this).find(".row_num").html(index + 1); + }); +}; +``` + +Not only is this style not simple, it's quite difficult to test. My application is flying blind on steps 3-5, which, as it turns out, is the absolute core of the user experience. + +With Ember, these DOM manipulations are *side effects* of working with models. A user doesn't know it, but they're pushing actual data around, toggling boolean attributes, and moving full-blown objects between lists. + +Here's an Ember computed property to illustrate: + +```javascript +// Model, you worry about position and I'll go get a soda +sortedRows: function() { + this.get("rows").sortBy("position"); +}.property("rows") +``` + +I can't show actual comparison code to the `renumberRows()` function because in Ember, *there is none*. Any changes rendered on the screen are the automatic result of changes to model data. With Ember's router, even your URL is rooted in this model data. + +Think about that for a second. Imagine never writing another line of jQuery code that second-guesses or cleans up inconsistencies from your DOM. + +Ember's object model, its system of observers and computed properties, and the way these are bound to the DOM are second to none. Its model layer isn't bolted on or mixed in, it's the foundation of the framework itself, and is absolutely rock solid. + +## Looking out for the next 80% + +The book [The Innovator's Dilemma](http://www.amazon.com/Innovators-Dilemma-Technologies-Management-Innovation-ebook/dp/B00E257S86/) is a fantastic read, and, I believe, applicable to the state of MVC frameworks. + +The same pragmatism that allows us to get things done can actually become toxic after a significant degree of success. Almost by default, people tend to try and extract the maximum value out of existing solutions. They don't notice as the ground shifts beneath them, and are often surprised to find that their old, reliable solution has been disrupted. + +By nearly all accounts, when Rails entered the scene it acted as this disruptive force. As anyone trying to bring Rails to work projects before 2010 will tell you, it was a tough sell, often being shouted down for not being "enterprise grade". + +To me, the most powerful feature of Rails was that it took things that were excruciatingly painful to build and made them *fun*. It took user interactions that were previously reserved for large teams of experienced developers and put that power in the hands of small, nimble teams and newer developers. + +Ten years after its inception, Rails now represents the 80% use case for web applications, with the 20% use case invovling highly interactive, app-like experiences. + +## The new table stakes + +A decade ago, my water delivery company could get away with a static site with their phone number on it if you wanted to sign up or schedule a delivery. + +Now, I'd take my business elsewhere if I couldn't manage my delivery schedule online. The stakes simply went up. My expectations as a user went up. + +I'll contend that we're on the cusp of a new "80% expectation": the majority of people using our software on the web are now becoming accustomed to richer, more app-like experiences. The stakes are being raised. + + +## Ember's bet on the future + +Many of the benefits above are just about making it easier to do *the same things* we did before. But that's not Ember's bet. Ember's gamble is that when armed with this set of tools, you'll attempt increasingly ambitious projects. + +#### Browser as an App Platform +I can't speak for the team that created Ember, but I can say from my vantage point that it looks like Ember is betting that the browser will act as an application runtime, not unlike the platforms for iOS or Android, and that the web is becoming the world's largest and most open app marketplace. + +#### Interchangeable, Reusable Components + +[Web Components](http://css-tricks.com/modular-future-web-components/) are an important upcoming change to the way web developers work, and Ember's take on them is impressive. When you start using Ember Components, it feels like the jQuery UI of the future: You get reusable drop-in functionality that's totally customizable, and backed by Ember's model system. + +Ember Components are worth the price of admission alone, and we do drop them in as standalone pieces of interactivity into some of our server-side apps when it isn't feasible to create a full Ember application. + +## Ember in the real world + +Its rather audacious goals are why Ember is improving so quickly, and the things people are building with it are sometimes mind-boggling. + +Here are just a few of the cool things I've seen come out of the Ember community lately: + + - [NBC News](http://nbcnews.com): NBC's new interface is more interactive exhibit than static news website + - [Huboard](http://huboard.com): A lovely kanban board built on top of GitHub issues + - [Discourse](http://discourse.org): No less audacious a goal than to replace every PHP forum on the internet + - [Ember Beats](http://emberbeats.gavinjoyce.com/): Super fun drum machine webapp + - [Bustle](http://bustle.com): A women-focused news and opinion site + - [Vine](https://vine.co/feed): Twitter's 6-second video site + +The limitations of the Web have historically been in its underlying technologies, but with advances like HTML5, CSS3, and tools like Ember, our biggest limitation on the web is quickly becoming our own imaginations. + +## Challenges + +#### Challenge 1: The learning curve +Learning Ember was a challenge for me. I started pretty early and the framework was famous at the time for frequent, major API changes and sparse documentation. + +Those have both largely been addressed with [great documentation](http://emberjs.com/guides/) and things like [Code School](https://www.codeschool.com/courses/warming-up-with-emberjs), and I see newcomers picking things up about 5 times faster than I did. (Here's hoping that speaks more about the state of Ember than it does my learning abilities.) + +That said, it is a big library and can still feel still quite challenging to learn, and may actually be *more* difficult for programmers who are skilled at server-side MVC and must "un-learn" the way their framework handled working with the front end. + +But once you grasp a few core concepts, you'll start to "get" Ember, and we'll outline those in another post. + +#### Challenge 2: JS solution fragmentation +Tools for languages like Ruby are now mature, stable platforms, while JavaScript, by comparison, can feel like the wild west. + +If you want RSpec as your test framework in Rails, it's generally one or two lines of code to make the change. Choosing and setting up your test setup in JavaScript is still confusing and often fraught with issues. + +The good news is that people are working to solve this problem as well. Ember App Kit is one of the most ambitious projects I've seen in this space. It's young but it seems to aim to make choosing and setting up all the ancillary stuff as simple as it is in server-side frameworks like Rails. + +## So why Ember, specifically? + +Ember, like any tool or framework, doesn't offer the perfect solution to every problem. But recently, whenever I've built an application without it, I've wished I had it to manage the complexity that lurks behind nearly every front-end feature request. + +Between Ember's bulletproof object model and thoroughly-thought-out routing system, stuff that would have looked like a sheer cliff face before now looks like a playground to me. + +It's the epitome of a disruptive technology: Ember allows me to do things that I wouldn't have even thought to attempt before, because the degree of difficulty was so high my brain just closed the door on the option. (Paul Graham calls this [schlep blindness](http://paulgraham.com/schlep.html).) It begs to be given a harder class of user experience problem, which is fun, rewarding, and in line with our purpose at The Frontside to push UX on the web forward. + +Lastly, I tend to vote with community. I think Ruby and Rails has emerged as one of the best communities in any category, not just programming, and I feel similarly about the fast-growing, yet tight-knit Ember.js community. + +## What if I'm wrong? + +What if my crystal ball is broken, and users stay content with web applications as they are? + +I can't deny that I really do believe we're looking at the future of web applications, but none of this future-talk matters too much. The simple point is this: **Ember makes it fun to build things that are awesome.** + +And if you're going to do something, why not do something awesome? + +Ember may not be for every problem or every developer, but if you'll give it a shot on your next project with high front-end ambitions, I promise you'll start feeling less intimidated and more like this: + +![](challenge-accepted-l.png) diff --git a/blog/2014-02-24-ember-and-the-future-of-the-web/peabody.jpg b/blog/2014-02-24-ember-and-the-future-of-the-web/peabody.jpg new file mode 100644 index 00000000..926f1bf6 Binary files /dev/null and b/blog/2014-02-24-ember-and-the-future-of-the-web/peabody.jpg differ diff --git a/blog/2014-02-24-ember-and-the-future-of-the-web/rjs-flow.png b/blog/2014-02-24-ember-and-the-future-of-the-web/rjs-flow.png new file mode 100644 index 00000000..7d3e2a42 Binary files /dev/null and b/blog/2014-02-24-ember-and-the-future-of-the-web/rjs-flow.png differ diff --git a/blog/2014-02-24-ember-and-the-future-of-the-web/slack-marked-up.png b/blog/2014-02-24-ember-and-the-future-of-the-web/slack-marked-up.png new file mode 100644 index 00000000..6060e395 Binary files /dev/null and b/blog/2014-02-24-ember-and-the-future-of-the-web/slack-marked-up.png differ diff --git a/blog/2014-05-16-programming-in-the-wild-west/experiment-1.png b/blog/2014-05-16-programming-in-the-wild-west/experiment-1.png new file mode 100644 index 00000000..663714b4 Binary files /dev/null and b/blog/2014-05-16-programming-in-the-wild-west/experiment-1.png differ diff --git a/blog/2014-05-16-programming-in-the-wild-west/experiment-2.png b/blog/2014-05-16-programming-in-the-wild-west/experiment-2.png new file mode 100644 index 00000000..f72806f7 Binary files /dev/null and b/blog/2014-05-16-programming-in-the-wild-west/experiment-2.png differ diff --git a/blog/2014-05-16-programming-in-the-wild-west/experiment-3.png b/blog/2014-05-16-programming-in-the-wild-west/experiment-3.png new file mode 100644 index 00000000..c2dbfb5b Binary files /dev/null and b/blog/2014-05-16-programming-in-the-wild-west/experiment-3.png differ diff --git a/blog/2014-05-16-programming-in-the-wild-west/experiment-4.png b/blog/2014-05-16-programming-in-the-wild-west/experiment-4.png new file mode 100644 index 00000000..da6b8dac Binary files /dev/null and b/blog/2014-05-16-programming-in-the-wild-west/experiment-4.png differ diff --git a/blog/2014-05-16-programming-in-the-wild-west/index.md b/blog/2014-05-16-programming-in-the-wild-west/index.md new file mode 100644 index 00000000..9d69d4a0 --- /dev/null +++ b/blog/2014-05-16-programming-in-the-wild-west/index.md @@ -0,0 +1,165 @@ +--- +title: Programming in the Wild West +author: Brandon Hays +tags: + - community + - javascript + - tdd +image: thumbnail.png +--- + +Much like revisiting a neighborhood from my youth, when I reflect on my scant few years in software I'm astonished at how much has changed. + +This might have also struck you at some point. Perhaps you were even a pioneer in your community's wild west days, huddling in log cabins of your own construction, only to look around now and see yoga studios and gourmet cupcake shops. + +The pattern of how and why this happens is actually related to the way the "red-green-refactor" loop in test-first development helps match our work to our brains. But more importantly, it's reflective of how we can adapt and help our communities as they mature. + +### First, a journey into the human brain + +Studies are revealing that during complex tasks, each of us switches between **"open mode"**, primarily associated with free-form experimentation, and **"closed mode"**, associated with focused execution. + +Sarah Mei spoke about this last year at LoneStarRuby, and compared the TDD cycle to moving between these modes. *(Starting at 12:20)* + + + +The general idea is that at different times, **people are better at thinking about the big picture, or zooming in on the details, but not both simultaneously.** + +This sounds like a simple concept, even obvious, but we ignore it so much that when we actually put the idea into practice, it can be life-altering. + +### Test-first and open & closed modes + +![The TDD cycle](experiment-1.png) + +First, I should clarify that this is representative of how I've learned to define and practice test-first development, and others may have different definitions or methods. + +For me, this pattern has helped me leverage the strengths of open/closed modes in my brain, because **it compartmentalizes times for experimentation and then execution.** Much of my own "open mode" experimentation happens via a practice that I don't often hear discussed in TDD circles: spiking code. + +The "spike" is where you throw away best practices, testing, and most forms of discipline in favor of discovery. You get out the machete and hack a meandering path from Point A to Point B. + +**Spiking is the act of figuring out what question you need to be asking.** The "red" step is asking the question, then the rest of the TDD process is about answering it. To me, the spiking phase is an essential part of the "open mode" quest to ask the right questions. + +*The only rule of the spike is that it must be thrown away.* You now know where Point B is, and the second trip through using discipline and techniques like TDD will reveal a much straighter path. + +But it's an extremely bitter pill: **throwing away functioning code is one of the most ego-shattering, counterintuitive things I do as a developer.** However, I've learned it's typically faster to subjugate my ego and just delete the code. + +The spike clears the "fog of war", shedding light on real-life obstacles in my initial design. Their concrete result is a set of high-level integration tests, from which I can drive out a new implementation in a more direct, point-A-to-point-B way, without having to machete-hack through the jungle. + +### How this relates to software communities + +Software communities, unsurprisingly, behave much like the humans who comprise them. + +Yehuda Katz gave a landmark talk at RailsConf, inspiring this post in the first place. He talks about when it's time to move from *experimentation* mode to *shared solutions* mode in your community. *(Starting at 13:25)* + + + +The talk covers several topics, but the main point to me was that software communities have trouble toggling between experimentation ("open") mode and shared solutions ("closed") mode. + +To me, this looks a lot like the test-first cycle above: + +![The community cycle](experiment-2.png) + +**Early in a community's maturity, it may be time for a bazaar of different solutions** while everyone is simply learning the right questions to ask. What does an authentication solution look like? What about persistence? Security? Code sharing? Scalability? Deployment? + +Once a community settles on those few key questions and sees some possible solutions to each, the time comes to get behind existing solutions and improve them. Often, these early projects aren't extremely pleasant to use or well-factored, but can serve to clear that "fog of war" inherent to a new domain. But once the fog is cleared, one or two solutions to a given problem usually gain traction within a community. + +### "Worse is Better" + +When it feels like a community is rallying around a shared set of abstractions, it may be time to delclare it a platform and move from experimentation mode to reaching an agreement. Examples of this in the past include the merging of rival Ruby frameworks Merb and Rails, or the Promises/A+ spec in JavaScript. + +In order for this to work, people in a community must be willing to put their energy and focus behind a set of solutions that they don't see as ideal. Maybe they even think they suck. Yehuda used the phrase ["worse is better"](http://www.jwz.org/doc/worse-is-better.html) to describe this. The concept is generally that **after the time of experimentation, a community is better served by embracing a comparatively crummy solution and improving it.** + +There's friction when this change starts happening. Some people feel left behind by the community they helped build. Others see the flaws in existing solutions and want to take their best shot at it, though the time for experimentation has passed. Many may feel that no shared solution could possibly approximate their unique needs. + +The good news is that after agreeing on a solution, even if it's a sub-optimal, you can improve the new shared solution, and without much fanfare, there's a new platform for everyone to build upon. + +### Platforms and scaffolding + +![Platforms and scaffolding](experiment-3.png) + +Each new layer adds a level of abstraction, so **you need to have confidence in the layers beneath you.** In software projects, this confidence comes from good tests. In communities, the confidence tends to come from seeing a project in broad use. + +We don't spend a ton of time wondering if UNIX is going to crumble beneath us when we're building web applications on top of it, or that the V8 engine in Chrome is just going to stop evaluating JavaScript. + +At some point, we declare these to be stable platforms and simply build on top of them without constantly running down to the basement to make sure the furnace didn't explode. + +### Programming in the Wild West + +When I was first learning to program, I was taught a term for people who refuse to leave the "spike" phase of programming. They're called "cowboy coders", often disparagingly. Many of my favorite programmers work in this style, so I use the term hoping it implies no judgment on my part. + +Communities have an equivalent type, the "pioneer" who prefers to live in the experimentation phase. Pioneers would rather invent a persistence library or templating engine than spend their day building apps in an existing framework. + +**When you are in the Wild West, you are, by nature of your presence, a unique and special snowflake.** You're the pioneer. You could be the first doctor, the first newspaper editor, or the first blacksmith. All the problems you encounter are by definition novel, and you must survive by your own ingenuity. + +Pioneers have much to offer in the "wild west" phase of a developer community. There are few tools, no consensus, and little infrastructure, so there's ample opportunity to contribute in a significant way. + +The Rust community seems like a good example of a current-day wild west, leaving many questions yet to be asked, much less answered. + +### The taming of the West + +But after a while, the gold rush has passed, and people have set up Costcos and Pilates studios. The experimentation phase is over, and people have decided on a platform. + +Programmers using Rails might remember this starting around Rails 3.0. After that, the big questions were answered, and a few solutions emerged that have evolved into what are essentially just [two technology stacks](http://words.steveklabnik.com/rails-has-two-default-stacks). + +The problem occurs when remaining pioneers still want to build a hospital, but there are now plenty of decent hospitals. The community has matured to where offering a lot of alternative solutions start doing more harm than good, creating confusion and decision fatigue. + +How do you know a community is reaching the end of the experimentation phase? + + - You see articles saying "please no more `x technology` testing libraries/package managers/persistence frameworks" + - Consultants specializing in `x technology` proliferate + - Both traditional and hacker schools start teaching `x technology` + - You see job postings at Fortune 500 companies for `x technology` + - `x technology` is on the cover of magazines targeted at CEO types + +### The bitter pill + +Remember the bitter pill in TDD, throwing away the spike to embrace discipline and move into closed mode? + +As a software community's experimentation phase wraps up and moves into shared solutions, developers have a bitter, ego-shattering pill to swallow, as well. **It's the admission that you are not a special snowflake.** + +If you want to continue working in the community you helped grow, you suddenly have to contend with the fact that many other people moved in and share your same set of problems. It can be brutal to face the idea that a community stands to get greater benefit if you now pour your efforts into shared solutions than into a bespoke, one-of-a-kind tool. + +### Go West! + +Some people have an insatiable desire to pioneer. They are never satisfied with the state of things and want to see the best solution prevail. As they see fences erected and freeways built, they can feel frustrated, as if their freedom is being taken away. + +A Rails developer may be tempted to create a better persistence framework, but the community has largely agreed on ActiveRecord and is actively working to make it better. + +So what's a pioneering spirit to do? That's the tough part. You can pioneer in ever-smaller niches within your community, swallow the bitter pill of not being a unique snowflake and work on the "worse" solutions, or head west in search of a less mature community in need of doctors and blacksmiths. + +You may even find it rewarding to play around with all three options. And the best part is, **there is an infinite frontier for programmers.** You can always go west, there is always something new to explore if your desire is to chase the sunset into the unknown. + +### Understanding open/closed modes is good for your software + +I am insatiably curious as to why things work, and I'm so glad that Sarah touched on exactly *why* TDD works in her talk. + +I started as a "cowboy coder" and resisted testing for a long time, particularly TDD. It feels restrictive to have to compartmentalize. I already know how to make something work. But it doesn't take me much time to realize how expensive it is to try and do both modes at once, the complexity catches up with me, and I end up wishing I could throw the code in the garbage instead of maintain it. + +**In TDD, you're dividing the incredibly complex task of writing software along the brain's natural modes.** In a designated period of "open mode" thinking, you start to ask the right questions, giving you a concrete result: failing test cases. + +You then have a natural break into closed-mode, solution-oriented work, and back again. You let the tests act as a sort of bookmark for your thinking as you toggle between modes, keeping you in context and making you more productive. + +Let's look again at the circular diagrams above. From that perspective, they look positively Sisyphean and don't tell the whole story. In reality, they are the means by which these towering platforms are built, but you have to view the circle from a third dimension to see it: + +![The TDD cycle, from the side](experiment-4.png) + +With good tests, every trip around the loop provides more capabilities on top of a trustworthy codebase. You can keep building without fear of your codebase collapsing under the weight of its own complexity. + +### Understanding open/closed modes is good for communities + +In Yehuda's talk, he plays a couple of videos of Steve Jobs talking about using frameworks to provide a solid platform, so you can build your 5 stories starting on the 25th floor, rather than the 5th floor. + +In open source software communities, we can't simply buy into these platforms, we have to agree on them. This happens in the same open/closed cycle: we discover questions by experimenting in open mode, then diving into the closed-mode work of converging on a solution and improving it. + +**The benefit is an exponential gain in productivity because we aren't exhausting ourselves** by keeping 25 floors of scaffolding from collapsing, and instead focusing only on the few flights that comprise our software's unique needs. + +At each turn of the loop, a community is establishing a platform for the next go-round. Open source has done a phenomenal job of this over the last 30 years, and the cumulative effect is simply staggering. + +### The pain of moving into "closed mode" + +When Yehuda outlined the difficulty in moving from "experimentation" mode to "shared solution" mode, I felt as if a gong went off in my head, and I suddenly understood much of the friction in the maturing JavaScript community. + +I'd contend that we're reaching the end of the JavaScript community's experimentation phase, particularly as it pertains to building client-side applications. We're starting to see people setting up Pilates studios and Costcos. It's entirely possible we'll be talking about ES6 as a watershed moment, the way Rails developers sometimes do about the shift to Rails 3. + +This can be rough on the pioneers who got us here. It can also feel constrictive to those who want to build a better version of an existing solution. It's not easy for me, either. I want great tools, not mediocre ones. But if we want the benefits of building on top of platforms instead of our own rickety scaffolding, **our responsibility is to embrace imperfect solutions as they get traction, and then improve them.** + +I'm glad to have the opportunity to participate in the JavaScript community as it starts to converge on "platform-level" solutions. If we'll work together and embrace that we're not "unique and special snowflakes", we can focus on the few floors that actually make our software unique and valuable to other people, so the next generation of developers can start building on the 1000th floor. diff --git a/blog/2014-05-16-programming-in-the-wild-west/thumbnail.png b/blog/2014-05-16-programming-in-the-wild-west/thumbnail.png new file mode 100644 index 00000000..839a36ac Binary files /dev/null and b/blog/2014-05-16-programming-in-the-wild-west/thumbnail.png differ diff --git a/blog/2014-09-21-reactive-modeling-with-ember/color.js b/blog/2014-09-21-reactive-modeling-with-ember/color.js new file mode 100644 index 00000000..0589b37a --- /dev/null +++ b/blog/2014-09-21-reactive-modeling-with-ember/color.js @@ -0,0 +1,120 @@ +(function() { + var attrReader = Ember.computed.readOnly + var Color = Ember.Object.extend({ + rgb: Ember.computed.readOnly('rgbValue.rgb'), + r: Ember.computed.readOnly('rgb.r'), + g: Ember.computed.readOnly('rgb.g'), + b: Ember.computed.readOnly('rgb.b'), + + rgbValue: function() { + return this.get('hslValue.rgbValue') + }.property('hslValue'), + + hsl: Ember.computed.readOnly('hslValue.hsl'), + h: Ember.computed.readOnly('hsl.h'), + s: Ember.computed.readOnly('hsl.s'), + l: Ember.computed.readOnly('hsl.l'), + + hslValue: function() { + return this.get('rgbValue.hslValue') + }.property('rgbValue'), + }) + + var rgbSpace = {} + var hslSpace = {} + Color.reopenClass({ + fromRGB: function(r,g,b) { + var values = null + if (Ember.typeOf(r) == "number") { + values = {r: r, g: g, b: b} + } else { + values = r || {} + } + var value = RGBValue.create(roundRGB(values)) + var key = JSON.stringify(value.get('rgb')) + return rgbSpace[key] || (rgbSpace[key] = Color.create({rgbValue: value})) + }, + fromHSL: function(h,s,l) { + var values = null + if (Ember.typeOf(h) == "number") { + values = {h: h, s: s, l: l} + } else { + values = h || {} + } + var value = HSLValue.create(roundHSL(values)) + var key = JSON.stringify(value.get('hsl')) + return hslSpace[key] || (hslSpace[key] = Color.create({hslValue: value})) + } + }) + + Color.OR = Ember.Object.extend({ + output: function() { + return this.get('a') || this.get('b') + }.property('a', 'b'), + }) + + this.Color = Color + var RGBValue = Ember.Object.extend({ + r: 0, g: 0, b: 0, + + rgb: function() { + return this.getProperties('r','g','b') + }.property('r','g','b'), + + hslValue: function() { + var rgb = Ember.copy(this.get('rgb')) + return HSLValue.create(roundHSL(tinycolor(rgb).toHsl())) + }.property('rgb') + }) + + + var HSLValue = Ember.Object.extend({ + h: 0, s: 0.5, l: 0, + + hsl: function() { + return this.getProperties('h','s','l') + }.property('h','s','l'), + + rgbValue: function() { + var hsl = Ember.copy(this.get('hsl')) + return RGBValue.create(roundRGB(tinycolor(hsl).toRgb())) + }.property('hsl'), + }) + + function rgbCoord(value) { + return Math.min(Math.max(Math.round(parseFloat(value) || 0), 0), 255) + } + + function hCoord(value) { + return new Number(((parseFloat(value) || 0) % 360).toFixed(2)).valueOf() + } + + function SLCoord(value) { + return new Number(Math.min(Math.max(0,(parseFloat(value) || 0)), 1).toFixed(3)).valueOf() + } + + function roundRGB(values) { + return { + r: rgbCoord(values.r), + g: rgbCoord(values.g), + b: rgbCoord(values.b) + }; + } + + function roundHSL(values) { + return { + h: hCoord(values.h), + s: SLCoord(values.s), + l: SLCoord(values.l) + }; + } + this.RGBColor = Ember.Object.extend({ + colorBinding: Ember.Binding.oneWay('colorValue'), + colorValue: Ember.computed('r', 'g', 'b', function() { + return Color.fromRGB(this.get('r'), this.get('g'), this.get('b')); + }), + rBinding: Ember.Binding.oneWay('color.r'), + gBinding: Ember.Binding.oneWay('color.g'), + bBinding: Ember.Binding.oneWay('color.b') + }); +}).call(this); diff --git a/blog/2014-09-21-reactive-modeling-with-ember/ember-1.5.1.min.js b/blog/2014-09-21-reactive-modeling-with-ember/ember-1.5.1.min.js new file mode 100644 index 00000000..e530e2ab --- /dev/null +++ b/blog/2014-09-21-reactive-modeling-with-ember/ember-1.5.1.min.js @@ -0,0 +1,19 @@ +/*! + * @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.5.1 + */ +!function(){var e,t,r,n;!function(){var i={},o={};e=function(e,t,r){i[e]={deps:t,callback:r}},n=r=t=function(e){function r(t){if("."!==t.charAt(0))return t;for(var r=t.split("/"),n=e.split("/").slice(0,-1),i=0,o=r.length;o>i;i++){var a=r[i];if(".."===a)n.pop();else{if("."===a)continue;n.push(a)}}return n.join("/")}if(n._eak_seen=i,o[e])return o[e];if(o[e]={},!i[e])throw new Error("Could not find module "+e);for(var a,s=i[e],u=s.deps,l=s.callback,c=[],h=0,m=u.length;m>h;h++)"exports"===u[h]?c.push(a={}):c.push(t(r(u[h])));var p=l.apply(this,c);return o[e]=a||p}}(),function(){"undefined"==typeof Ember&&(Ember={});{var e=(Ember.imports=Ember.imports||this,Ember.exports=Ember.exports||this);Ember.lookup=Ember.lookup||this}e.Em=e.Ember=Em=Ember,Ember.isNamespace=!0,Ember.toString=function(){return"Ember"},Ember.VERSION="1.5.1",Ember.ENV||(Ember.ENV="undefined"!=typeof EmberENV?EmberENV:"undefined"!=typeof ENV?ENV:{}),Ember.config=Ember.config||{},"undefined"==typeof Ember.ENV.DISABLE_RANGE_API&&(Ember.ENV.DISABLE_RANGE_API=!0),"undefined"==typeof MetamorphENV&&(e.MetamorphENV={}),MetamorphENV.DISABLE_RANGE_API=Ember.ENV.DISABLE_RANGE_API,Ember.FEATURES=Ember.ENV.FEATURES||{},Ember.FEATURES.isEnabled=function(e){var t=Ember.FEATURES[e];return Ember.ENV.ENABLE_ALL_FEATURES?!0:t===!0||t===!1||void 0===t?t:Ember.ENV.ENABLE_OPTIONAL_FEATURES?!0:!1},Ember.EXTEND_PROTOTYPES=Ember.ENV.EXTEND_PROTOTYPES,"undefined"==typeof Ember.EXTEND_PROTOTYPES&&(Ember.EXTEND_PROTOTYPES=!0),Ember.LOG_STACKTRACE_ON_DEPRECATION=Ember.ENV.LOG_STACKTRACE_ON_DEPRECATION!==!1,Ember.SHIM_ES5=Ember.ENV.SHIM_ES5===!1?!1:Ember.EXTEND_PROTOTYPES,Ember.LOG_VERSION=Ember.ENV.LOG_VERSION===!1?!1:!0,Ember.K=function(){return this},"undefined"==typeof Ember.assert&&(Ember.assert=Ember.K),"undefined"==typeof Ember.warn&&(Ember.warn=Ember.K),"undefined"==typeof Ember.debug&&(Ember.debug=Ember.K),"undefined"==typeof Ember.runInDebug&&(Ember.runInDebug=Ember.K),"undefined"==typeof Ember.deprecate&&(Ember.deprecate=Ember.K),"undefined"==typeof Ember.deprecateFunc&&(Ember.deprecateFunc=function(e,t){return t}),Ember.uuid=0,Ember.merge=function(e,t){for(var r in t)t.hasOwnProperty(r)&&(e[r]=t[r]);return e},Ember.isNone=function(e){return null===e||void 0===e},Ember.none=Ember.deprecateFunc("Ember.none is deprecated. Please use Ember.isNone instead.",Ember.isNone),Ember.isEmpty=function(e){return Ember.isNone(e)||0===e.length&&"function"!=typeof e||"object"==typeof e&&0===Ember.get(e,"length")},Ember.empty=Ember.deprecateFunc("Ember.empty is deprecated. Please use Ember.isEmpty instead.",Ember.isEmpty),Ember.isBlank=function(e){return Ember.isEmpty(e)||"string"==typeof e&&null===e.match(/\S/)}}(),function(){var e=Ember.platform={};if(Ember.create=Object.create,Ember.create&&2!==Ember.create({a:1},{a:{value:2}}).a&&(Ember.create=null),!Ember.create||Ember.ENV.STUB_OBJECT_CREATE){var t=function(){};Ember.create=function(e,r){if(t.prototype=e,e=new t,r){t.prototype=e;for(var n in r)t.prototype[n]=r[n].value;e=new t}return t.prototype=null,e},Ember.create.isSimulated=!0}var r,n,i=Object.defineProperty;if(i)try{i({},"a",{get:function(){}})}catch(o){i=null}i&&(r=function(){var e={};return i(e,"a",{configurable:!0,enumerable:!0,get:function(){},set:function(){}}),i(e,"a",{configurable:!0,enumerable:!0,writable:!0,value:!0}),e.a===!0}(),n=function(){try{return i(document.createElement("div"),"definePropertyOnDOM",{}),!0}catch(e){}return!1}(),r?n||(i=function(e,t,r){var n;return n="object"==typeof Node?e instanceof Node:"object"==typeof e&&"number"==typeof e.nodeType&&"string"==typeof e.nodeName,n?e[t]=r.value:Object.defineProperty(e,t,r)}):i=null),e.defineProperty=i,e.hasPropertyAccessors=!0,e.defineProperty||(e.hasPropertyAccessors=!1,e.defineProperty=function(e,t,r){r.get||(e[t]=r.value)},e.defineProperty.isSimulated=!0),Ember.ENV.MANDATORY_SETTER&&!e.hasPropertyAccessors&&(Ember.ENV.MANDATORY_SETTER=!1)}(),function(){var e=function(e){return e&&Function.prototype.toString.call(e).indexOf("[native code]")>-1},t=e(Array.prototype.map)?Array.prototype.map:function(e){if(void 0===this||null===this)throw new TypeError;var t=Object(this),r=t.length>>>0;if("function"!=typeof e)throw new TypeError;for(var n=new Array(r),i=arguments[1],o=0;r>o;o++)o in t&&(n[o]=e.call(i,t[o],o,t));return n},r=e(Array.prototype.forEach)?Array.prototype.forEach:function(e){if(void 0===this||null===this)throw new TypeError;var t=Object(this),r=t.length>>>0;if("function"!=typeof e)throw new TypeError;for(var n=arguments[1],i=0;r>i;i++)i in t&&e.call(n,t[i],i,t)},n=e(Array.prototype.indexOf)?Array.prototype.indexOf:function(e,t){null===t||void 0===t?t=0:0>t&&(t=Math.max(0,this.length+t));for(var r=t,n=this.length;n>r;r++)if(this[r]===e)return r;return-1},i=e(Array.prototype.filter)?Array.prototype.filter:function(e,t){var r,n,i=[],o=this.length;for(r=0;o>r;r++)this.hasOwnProperty(r)&&(n=this[r],e.call(t,n,r,this)&&i.push(n));return i};Ember.ArrayPolyfills={map:t,forEach:r,filter:i,indexOf:n},Ember.SHIM_ES5&&(Array.prototype.map||(Array.prototype.map=t),Array.prototype.forEach||(Array.prototype.forEach=r),Array.prototype.filter||(Array.prototype.filter=i),Array.prototype.indexOf||(Array.prototype.indexOf=n))}(),function(){var e=["description","fileName","lineNumber","message","name","number","stack"];Ember.Error=function(){var t=Error.apply(this,arguments);Error.captureStackTrace&&Error.captureStackTrace(this,Ember.Error);for(var r=0;rs;s++){if(i=t[s],o=a[i]){if(o.__ember_source__!==e){if(!r)return void 0;o=a[i]=n(o),o.__ember_source__=e}}else{if(!r)return void 0;o=a[i]={__ember_source__:e}}a=o}return o},Ember.wrap=function(e,t){function r(){var r,n=this.__nextSuper;return this.__nextSuper=t,r=e.apply(this,arguments),this.__nextSuper=n,r}return r.wrappedFunction=e,r.__ember_observes__=e.__ember_observes__,r.__ember_observesBefore__=e.__ember_observesBefore__,r.__ember_listens__=e.__ember_listens__,r},Ember.isArray=function(e){return!e||e.setInterval?!1:Array.isArray&&Array.isArray(e)?!0:Ember.Array&&Ember.Array.detect(e)?!0:void 0!==e.length&&"object"==typeof e?!0:!1},Ember.makeArray=function(e){return null===e||void 0===e?[]:Ember.isArray(e)?e:[e]},Ember.canInvoke=t,Ember.tryInvoke=function(e,r,n){return t(e,r)?e[r].apply(e,n||[]):void 0};var f=function(){var e=0;try{try{}finally{throw e++,new Error("needsFinallyFixTest")}}catch(t){}return 1!==e}();Ember.tryFinally=f?function(e,t,r){var n,i,o;r=r||this;try{n=e.call(r)}finally{try{i=t.call(r)}catch(a){o=a}}if(o)throw o;return void 0===i?n:i}:function(e,t,r){var n,i;r=r||this;try{n=e.call(r)}finally{i=t.call(r)}return void 0===i?n:i},Ember.tryCatchFinally=f?function(e,t,r,n){var i,o,a;n=n||this;try{i=e.call(n)}catch(s){i=t.call(n,s)}finally{try{o=r.call(n)}catch(u){a=u}}if(a)throw a;return void 0===o?i:o}:function(e,t,r,n){var i,o;n=n||this;try{i=e.call(n)}catch(a){i=t.call(n,a)}finally{o=r.call(n)}return void 0===o?i:o};var d={},b="Boolean Number String Function Array Date RegExp Object".split(" ");Ember.ArrayPolyfills.forEach.call(b,function(e){d["[object "+e+"]"]=e.toLowerCase()});var v=Object.prototype.toString;Ember.typeOf=function(e){var t;return t=null===e||void 0===e?String(e):d[v.call(e)]||"object","function"===t?Ember.Object&&Ember.Object.detect(e)&&(t="class"):"object"===t&&(e instanceof Error?t="error":Ember.Object&&e instanceof Ember.Object?t="instance":e instanceof Date&&(t="date")),t},Ember.inspect=function(e){var t=Ember.typeOf(e);if("array"===t)return"["+e+"]";if("object"!==t)return e+"";var r,n=[];for(var i in e)if(e.hasOwnProperty(i)){if(r=e[i],"toString"===r)continue;"function"===Ember.typeOf(r)&&(r="function() { ... }"),n.push(i+": "+r)}return"{"+n.join(", ")+"}"}}(),function(){Ember.Instrumentation={};var e=[],t={},r=function(r){for(var n,i=[],o=0,a=e.length;a>o;o++)n=e[o],n.regex.test(r)&&i.push(n.object);return t[r]=i,i},n=function(){var e="undefined"!=typeof window?window.performance||{}:{},t=e.now||e.mozNow||e.webkitNow||e.msNow||e.oNow;return t?t.bind(e):function(){return+new Date}}();Ember.Instrumentation.instrument=function(e,i,o,a){function s(){for(f=0,d=m.length;d>f;f++)p=m[f],b[f]=p.before(e,n(),i);return o.call(a)}function u(e){i=i||{},i.exception=e}function l(){for(f=0,d=m.length;d>f;f++)p=m[f],p.after(e,n(),i,b[f]);Ember.STRUCTURED_PROFILE&&console.timeEnd(c)}var c,h,m=t[e];if(Ember.STRUCTURED_PROFILE&&(c=e+": "+i.object,console.time(c)),m||(m=r(e)),0===m.length)return h=o.call(a),Ember.STRUCTURED_PROFILE&&console.timeEnd(c),h;var p,f,d,b=[];return Ember.tryCatchFinally(s,u,l)},Ember.Instrumentation.subscribe=function(r,n){for(var i,o=r.split("."),a=[],s=0,u=o.length;u>s;s++)i=o[s],"*"===i?a.push("[^\\.]*"):a.push(i);a=a.join("\\."),a+="(\\..*)?";var l={pattern:r,regex:new RegExp("^"+a+"$"),object:n};return e.push(l),t={},l},Ember.Instrumentation.unsubscribe=function(r){for(var n,i=0,o=e.length;o>i;i++)e[i]===r&&(n=i);e.splice(n,1),t={}},Ember.Instrumentation.reset=function(){e=[],t={}},Ember.instrument=Ember.Instrumentation.instrument,Ember.subscribe=Ember.Instrumentation.subscribe}(),function(){var e,t,r,n,i;e=Array.prototype.map||Ember.ArrayPolyfills.map,t=Array.prototype.forEach||Ember.ArrayPolyfills.forEach,r=Array.prototype.indexOf||Ember.ArrayPolyfills.indexOf,i=Array.prototype.filter||Ember.ArrayPolyfills.filter,n=Array.prototype.splice;var o=Ember.EnumerableUtils={map:function(t,r,n){return t.map?t.map.call(t,r,n):e.call(t,r,n)},forEach:function(e,r,n){return e.forEach?e.forEach.call(e,r,n):t.call(e,r,n)},filter:function(e,t,r){return e.filter?e.filter.call(e,t,r):i.call(e,t,r)},indexOf:function(e,t,n){return e.indexOf?e.indexOf.call(e,t,n):r.call(e,t,n)},indexesOf:function(e,t){return void 0===t?[]:o.map(t,function(t){return o.indexOf(e,t)})},addObject:function(e,t){var r=o.indexOf(e,t);-1===r&&e.push(t)},removeObject:function(e,t){var r=o.indexOf(e,t);-1!==r&&e.splice(r,1)},_replace:function(e,t,r,i){for(var o,a,s=[].concat(i),u=[],l=6e4,c=t,h=r;s.length;)a=h>l?l:h,0>=a&&(a=0),o=s.splice(0,l),o=[c,a].concat(o),c+=l,h-=a,u=u.concat(n.apply(e,o));return u},replace:function(e,t,r,n){return e.replace?e.replace(t,r,n):o._replace(e,t,r,n)},intersection:function(e,t){var r=[];return o.forEach(e,function(e){o.indexOf(t,e)>=0&&r.push(e)}),r}}}(),function(){var e,t=Ember.META_KEY,r=Ember.ENV.MANDATORY_SETTER,n=/^([A-Z$]|([0-9][A-Z$])).*[\.\*]/,i=/^this[\.\*]/,o=/^([^\.\*]+)/;e=function(e,n){if(""===n)return e;if(n||"string"!=typeof e||(n=e,e=null),null===e||-1!==n.indexOf("."))return s(e,n);var i,o=e[t],a=o&&o.descs[n];return a?a.get(e,n):(i=r&&o&&o.watching[n]>0?o.values[n]:e[n],void 0!==i||"object"!=typeof e||n in e||"function"!=typeof e.unknownProperty?i:e.unknownProperty(n))},Ember.config.overrideAccessors&&(Ember.get=e,Ember.config.overrideAccessors(),e=Ember.get);var a=Ember.normalizeTuple=function(t,r){var a,s=i.test(r),u=!s&&n.test(r);if((!t||u)&&(t=Ember.lookup),s&&(r=r.slice(5)),t===Ember.lookup&&(a=r.match(o)[0],t=e(t,a),r=r.slice(a.length+1)),!r||0===r.length)throw new Ember.Error("Path cannot be empty");return[t,r]},s=Ember._getPath=function(t,r){var n,o,s,u,l;if(null===t&&-1===r.indexOf("."))return e(Ember.lookup,r);for(n=i.test(r),(!t||n)&&(s=a(t,r),t=s[0],r=s[1],s.length=0),o=r.split("."),l=o.length,u=0;null!=t&&l>u;u++)if(t=e(t,o[u],!0),t&&t.isDestroyed)return void 0;return t};Ember.getWithDefault=function(t,r,n){var i=e(t,r);return void 0===i?n:i},Ember.get=e}(),function(){function e(e,t,r){for(var n=-1,i=e.length-3;i>=0;i-=3)if(t===e[i]&&r===e[i+1]){n=i;break}return n}function t(e,t){var r,n=p(e,!0);return n.listeners||(n.listeners={}),n.hasOwnProperty("listeners")||(n.listeners=m(n.listeners)),r=n.listeners[t],r&&!n.listeners.hasOwnProperty(t)?r=n.listeners[t]=n.listeners[t].slice():r||(r=n.listeners[t]=[]),r}function r(t,r,n){var i=t[f],o=i&&i.listeners&&i.listeners[r];if(o)for(var a=o.length-3;a>=0;a-=3){var s=o[a],u=o[a+1],l=o[a+2],c=e(n,s,u);-1===c&&n.push(s,u,l)}}function n(t,r,n){var i=t[f],o=i&&i.listeners&&i.listeners[r],a=[];if(o){for(var s=o.length-3;s>=0;s-=3){var u=o[s],l=o[s+1],c=o[s+2],h=e(n,u,l);-1===h&&(n.push(u,l,c),a.push(u,l,c))}return a}}function i(r,n,i,o,a){o||"function"!=typeof i||(o=i,i=null);var s=t(r,n),u=e(s,i,o),l=0;a&&(l|=b),-1===u&&(s.push(i,o,l),"function"==typeof r.didAddListener&&r.didAddListener(n,i,o))}function o(r,n,i,o){function a(i,o){var a=t(r,n),s=e(a,i,o);-1!==s&&(a.splice(s,3),"function"==typeof r.didRemoveListener&&r.didRemoveListener(n,i,o))}if(o||"function"!=typeof i||(o=i,i=null),o)a(i,o);else{var s=r[f],u=s&&s.listeners&&s.listeners[n];if(!u)return;for(var l=u.length-3;l>=0;l-=3)a(u[l],u[l+1])}}function a(r,n,i,o,a){function s(){return a.call(i)}function u(){-1!==c&&(l[c+2]&=~v)}o||"function"!=typeof i||(o=i,i=null);var l=t(r,n),c=e(l,i,o);return-1!==c&&(l[c+2]|=v),Ember.tryFinally(s,u)}function s(r,n,i,o,a){function s(){return a.call(i)}function u(){for(var e=0,t=p.length;t>e;e++){var r=p[e];f[e][r+2]&=~v}}o||"function"!=typeof i||(o=i,i=null);var l,c,h,m,p=[],f=[];for(h=0,m=n.length;m>h;h++){l=n[h],c=t(r,l);var d=e(c,i,o);-1!==d&&(c[d+2]|=v,p.push(d),f.push(c))}return Ember.tryFinally(s,u)}function u(e){var t=e[f].listeners,r=[];if(t)for(var n in t)t[n]&&r.push(n);return r}function l(e,t,r,n){if(e!==Ember&&"function"==typeof e.sendEvent&&e.sendEvent(t,r),!n){var i=e[f];n=i&&i.listeners&&i.listeners[t]}if(n){for(var a=n.length-3;a>=0;a-=3){var s=n[a],u=n[a+1],l=n[a+2];u&&(l&v||(l&b&&o(e,t,s,u),s||(s=e),"string"==typeof u&&(u=s[u]),r?u.apply(s,r):u.call(s)))}return!0}}function c(e,t){var r=e[f],n=r&&r.listeners&&r.listeners[t];return!(!n||!n.length)}function h(e,t){var r=[],n=e[f],i=n&&n.listeners&&n.listeners[t];if(!i)return r;for(var o=0,a=i.length;a>o;o+=3){var s=i[o],u=i[o+1];r.push([s,u])}return r}var m=Ember.create,p=Ember.meta,f=Ember.META_KEY,d=[].slice,b=1,v=2;Ember.on=function(){var e=d.call(arguments,-1)[0],t=d.call(arguments,0,-1);return e.__ember_listens__=t,e},Ember.addListener=i,Ember.removeListener=o,Ember._suspendListener=a,Ember._suspendListeners=s,Ember.sendEvent=l,Ember.hasListeners=c,Ember.watchedEvents=u,Ember.listenersFor=h,Ember.listenersDiff=n,Ember.listenersUnion=r}(),function(){var e=Ember.guidFor,t=Ember.sendEvent,r=Ember._ObserverSet=function(){this.clear()};r.prototype.add=function(t,r,n){var i,o=this.observerSet,a=this.observers,s=e(t),u=o[s];return u||(o[s]=u={}),i=u[r],void 0===i&&(i=a.push({sender:t,keyName:r,eventName:n,listeners:[]})-1,u[r]=i),a[i].listeners},r.prototype.flush=function(){var e,r,n,i,o=this.observers;for(this.clear(),e=0,r=o.length;r>e;++e)n=o[e],i=n.sender,i.isDestroying||i.isDestroyed||t(i,n.eventName,[i,n.keyName],n.listeners)},r.prototype.clear=function(){this.observerSet={},this.observers=[]}}(),function(){function e(e,t){var n=e[h],i=n&&n.watching[t]>0||"length"===t,a=n&&n.proto,s=n&&n.descs[t];i&&a!==e&&(s&&s.willChange&&s.willChange(e,t),r(e,t,n),o(e,t,n),l(e,t))}function t(e,t){var r=e[h],i=r&&r.watching[t]>0||"length"===t,o=r&&r.proto,s=r&&r.descs[t];o!==e&&(s&&s.didChange&&s.didChange(e,t),(i||"length"===t)&&(n(e,t,r),a(e,t,r,!1),c(e,t)))}function r(t,r,n){if(!t.isDestroying){var o=_,a=!o;a&&(o=_={}),i(e,t,r,o,n),a&&(_=null)}}function n(e,r,n){if(!e.isDestroying){var o=w,a=!o;a&&(o=w={}),i(t,e,r,o,n),a&&(w=null)}}function i(e,t,r,n,i){var o=m(t);if(n[o]||(n[o]={}),!n[o][r]){n[o][r]=!0;var a=i.deps;if(a=a&&a[r])for(var s in a){var u=i.descs[s];u&&u._suspended===t||e(t,s)}}}function o(t,r,n){if(n.hasOwnProperty("chainWatchers")&&n.chainWatchers[r]){var i,o,a=n.chainWatchers[r],s=[];for(i=0,o=a.length;o>i;i++)a[i].willChange(s);for(i=0,o=s.length;o>i;i+=2)e(s[i],s[i+1])}}function a(e,r,n,i){if(n&&n.hasOwnProperty("chainWatchers")&&n.chainWatchers[r]){var o,a,s=n.chainWatchers[r],u=i?null:[];for(o=0,a=s.length;a>o;o++)s[o].didChange(u);if(!i)for(o=0,a=u.length;a>o;o+=2)t(u[o],u[o+1])}}function s(){y++}function u(){y--,0>=y&&(E.clear(),g.flush())}function l(e,t){if(!e.isDestroying){var r,n,i=t+":before";y?(r=E.add(e,t,i),n=b(e,i,r),f(e,i,[e,t],n)):f(e,i,[e,t])}}function c(e,t){if(!e.isDestroying){var r,n=t+":change";y?(r=g.add(e,t,n),d(e,n,r)):f(e,n,[e,t])}}var h=Ember.META_KEY,m=Ember.guidFor,p=Ember.tryFinally,f=Ember.sendEvent,d=Ember.listenersUnion,b=Ember.listenersDiff,v=Ember._ObserverSet,E=new v,g=new v,y=0;Ember.propertyWillChange=e,Ember.propertyDidChange=t;var _,w;Ember.overrideChains=function(e,t,r){a(e,t,r,!0)},Ember.beginPropertyChanges=s,Ember.endPropertyChanges=u,Ember.changeProperties=function(e,t){s(),p(e,u,t)}}(),function(){function e(e,t,r,o){var a;if(a=t.slice(t.lastIndexOf(".")+1),t=t===a?a:t.slice(0,t.length-(a.length+1)),"this"!==t&&(e=n(e,t)),!a||0===a.length)throw new Ember.Error("Property set failed: You passed an empty path");if(!e){if(o)return;throw new Ember.Error('Property set failed: object in path "'+t+'" could not be found or was destroyed.')}return i(e,a,r)}var t=Ember.META_KEY,r=Ember.ENV.MANDATORY_SETTER,n=Ember._getPath,i=function(n,i,o,a){if("string"==typeof n&&(o=i,i=n,n=null),!n||-1!==i.indexOf("."))return e(n,i,o,a);var s,u,l=n[t],c=l&&l.descs[i];return c?c.set(n,i,o):(s="object"==typeof n&&!(i in n),s&&"function"==typeof n.setUnknownProperty?n.setUnknownProperty(i,o):l&&l.watching[i]>0?(u=r?l.values[i]:n[i],o!==u&&(Ember.propertyWillChange(n,i),r?(void 0!==u||i in n)&&n.propertyIsEnumerable(i)?l.values[i]=o:Ember.defineProperty(n,i,null,o):n[i]=o,Ember.propertyDidChange(n,i))):n[i]=o),o};Ember.config.overrideAccessors&&(Ember.set=i,Ember.config.overrideAccessors(),i=Ember.set),Ember.set=i,Ember.trySet=function(e,t,r){return i(e,t,r,!0)}}(),function(){var e=Ember.set,t=Ember.guidFor,r=Ember.ArrayPolyfills.indexOf,n=function(e){var t={};for(var r in e)e.hasOwnProperty(r)&&(t[r]=e[r]);return t},i=function(e,t){var r=e.keys.copy(),i=n(e.values);return t.keys=r,t.values=i,t.length=e.length,t},o=Ember.OrderedSet=function(){this.clear()};o.create=function(){return new o},o.prototype={clear:function(){this.presenceSet={},this.list=[]},add:function(e){var r=t(e),n=this.presenceSet,i=this.list;r in n||(n[r]=!0,i.push(e))},remove:function(e){var n=t(e),i=this.presenceSet,o=this.list;delete i[n];var a=r.call(o,e);a>-1&&o.splice(a,1)},isEmpty:function(){return 0===this.list.length},has:function(e){var r=t(e),n=this.presenceSet;return r in n},forEach:function(e,t){for(var r=this.toArray(),n=0,i=r.length;i>n;n++)e.call(t,r[n])},toArray:function(){return this.list.slice()},copy:function(){var e=new o;return e.presenceSet=n(this.presenceSet),e.list=this.toArray(),e}};var a=Ember.Map=function(){this.keys=Ember.OrderedSet.create(),this.values={}};a.create=function(){return new a},a.prototype={length:0,get:function(e){var r=this.values,n=t(e);return r[n]},set:function(r,n){var i=this.keys,o=this.values,a=t(r);i.add(r),o[a]=n,e(this,"length",i.list.length)},remove:function(r){var n=this.keys,i=this.values,o=t(r);return i.hasOwnProperty(o)?(n.remove(r),delete i[o],e(this,"length",n.list.length),!0):!1},has:function(e){var r=this.values,n=t(e);return r.hasOwnProperty(n)},forEach:function(e,r){var n=this.keys,i=this.values;n.forEach(function(n){var o=t(n);e.call(r,n,i[o])})},copy:function(){return i(this,new a)}};var s=Ember.MapWithDefault=function(e){a.call(this),this.defaultValue=e.defaultValue};s.create=function(e){return e?new s(e):new a},s.prototype=Ember.create(a.prototype),s.prototype.get=function(e){var t=this.has(e);if(t)return a.prototype.get.call(this,e);var r=this.defaultValue(e);return this.set(e,r),r},s.prototype.copy=function(){return i(this,new s({defaultValue:this.defaultValue}))}}(),function(){function e(e){var t,r;Ember.imports.console?t=Ember.imports.console:"undefined"!=typeof console&&(t=console);var n="object"==typeof t?t[e]:null;return n?"function"==typeof n.apply?(r=function(){n.apply(t,arguments)},r.displayName="console."+e,r):function(){var e=Array.prototype.join.call(arguments,", ");n(e)}:void 0}function t(e,t){if(!e)try{throw new Ember.Error("assertion failed: "+t)}catch(r){setTimeout(function(){throw r},0)}}Ember.Logger={log:e("log")||Ember.K,warn:e("warn")||Ember.K,error:e("error")||Ember.K,info:e("info")||Ember.K,debug:e("debug")||e("info")||Ember.K,assert:e("assert")||t}}(),function(){var e=Ember.META_KEY,t=Ember.meta,r=Ember.platform.defineProperty,n=Ember.ENV.MANDATORY_SETTER;Ember.Descriptor=function(){};var i=Ember.MANDATORY_SETTER_FUNCTION=function(){},o=Ember.DEFAULT_GETTER_FUNCTION=function(t){return function(){var r=this[e];return r&&r.values[t]}};if(Ember.defineProperty=function(e,s,u,l,c){var h,m,p,f;return c||(c=t(e)),h=c.descs,m=c.descs[s],p=c.watching[s]>0,m instanceof Ember.Descriptor&&m.teardown(e,s),u instanceof Ember.Descriptor?(f=u,h[s]=u,n&&p?r(e,s,{configurable:!0,enumerable:!0,writable:!0,value:void 0}):e[s]=void 0,Ember.FEATURES.isEnabled("composable-computed-properties")&&u.func&&u._dependentCPs&&a(e,u._dependentCPs,c)):(h[s]=void 0,null==u?(f=l,n&&p?(c.values[s]=l,r(e,s,{configurable:!0,enumerable:!0,set:i,get:o(s)})):e[s]=l):(f=u,r(e,s,u))),p&&Ember.overrideChains(e,s,c),e.didDefineProperty&&e.didDefineProperty(e,s,f),this},Ember.FEATURES.isEnabled("composable-computed-properties"))var a=function(e,t,r){for(var n,i,o=t.length,s=0;o>s;++s)n=t[s],i=n.implicitCPKey,Ember.defineProperty(e,i,n,void 0,r),n._dependentCPs&&a(e,n._dependentCPs,r)}}(),function(){var e=Ember.get;Ember.getProperties=function(t){var r={},n=arguments,i=1;2===arguments.length&&"array"===Ember.typeOf(arguments[1])&&(i=0,n=arguments[1]);for(var o=n.length;o>i;i++)r[n[i]]=e(t,n[i]);return r}}(),function(){var e=Ember.changeProperties,t=Ember.set;Ember.setProperties=function(r,n){return e(function(){for(var e in n)n.hasOwnProperty(e)&&t(r,e,n[e])}),r}}(),function(){var e=Ember.meta,t=Ember.typeOf,r=Ember.ENV.MANDATORY_SETTER,n=Ember.platform.defineProperty;Ember.watchKey=function(i,o,a){if("length"!==o||"array"!==t(i)){var s=a||e(i),u=s.watching;u[o]?u[o]=(u[o]||0)+1:(u[o]=1,"function"==typeof i.willWatchProperty&&i.willWatchProperty(o),r&&o in i&&(s.values[o]=i[o],n(i,o,{configurable:!0,enumerable:i.propertyIsEnumerable(o),set:Ember.MANDATORY_SETTER_FUNCTION,get:Ember.DEFAULT_GETTER_FUNCTION(o)})))}},Ember.unwatchKey=function(t,i,o){var a=o||e(t),s=a.watching;1===s[i]?(s[i]=0,"function"==typeof t.didUnwatchProperty&&t.didUnwatchProperty(i),r&&i in t&&n(t,i,{configurable:!0,enumerable:t.propertyIsEnumerable(i),set:function(e){n(t,i,{configurable:!0,writable:!0,enumerable:!0,value:e}),delete a.values[i]},get:Ember.DEFAULT_GETTER_FUNCTION(i)})):s[i]>1&&s[i]--}}(),function(){function e(e){return e.match(c)[0]}function t(e,t,r){if(e&&"object"==typeof e){var i=n(e),o=i.chainWatchers;i.hasOwnProperty("chainWatchers")||(o=i.chainWatchers={}),o[t]||(o[t]=[]),o[t].push(r),u(e,t,i)}}function r(e,t){if(!e)return void 0;var r=e[h];if(r&&r.proto===e)return void 0;if("@each"===t)return i(e,t);var n=r&&r.descs[t];return n&&n._cacheable?t in r.cache?r.cache[t]:void 0:i(e,t)}var n=Ember.meta,i=Ember.get,o=Ember.normalizeTuple,a=Ember.ArrayPolyfills.forEach,s=Ember.warn,u=Ember.watchKey,l=Ember.unwatchKey,c=/^([^\.\*]+)/,h=Ember.META_KEY,m=[];Ember.flushPendingChains=function(){if(0!==m.length){var e=m;m=[],a.call(e,function(e){e[0].add(e[1])}),s("Watching an undefined global, Ember expects watched globals to be setup by the time the run loop is flushed, check for typos",0===m.length)}};var p=Ember.removeChainWatcher=function(e,t,r){if(e&&"object"==typeof e){var n=e[h];if(!n||n.hasOwnProperty("chainWatchers")){var i=n&&n.chainWatchers;if(i&&i[t]){i=i[t];for(var o=0,a=i.length;a>o;o++)i[o]===r&&i.splice(o,1)}l(e,t,n)}}},f=Ember._ChainNode=function(e,r,n){this._parent=e,this._key=r,this._watching=void 0===n,this._value=n,this._paths={},this._watching&&(this._object=e.value(),this._object&&t(this._object,this._key,this)),this._parent&&"@each"===this._parent._key&&this.value()},d=f.prototype;d.value=function(){if(void 0===this._value&&this._watching){var e=this._parent.value();this._value=r(e,this._key)}return this._value},d.destroy=function(){if(this._watching){var e=this._object;e&&p(e,this._key,this),this._watching=!1}},d.copy=function(e){var t,r=new f(null,null,e),n=this._paths;for(t in n)n[t]<=0||r.add(t);return r},d.add=function(t){var r,n,i,a,s;if(s=this._paths,s[t]=(s[t]||0)+1,r=this.value(),n=o(r,t),n[0]&&n[0]===r)t=n[1],i=e(t),t=t.slice(i.length+1);else{if(!n[0])return m.push([this,t]),n.length=0,void 0;a=n[0],i=t.slice(0,0-(n[1].length+1)),t=n[1]}n.length=0,this.chain(i,t,a)},d.remove=function(t){var r,n,i,a,s;s=this._paths,s[t]>0&&s[t]--,r=this.value(),n=o(r,t),n[0]===r?(t=n[1],i=e(t),t=t.slice(i.length+1)):(a=n[0],i=t.slice(0,0-(n[1].length+1)),t=n[1]),n.length=0,this.unchain(i,t)},d.count=0,d.chain=function(t,r,n){var i,o=this._chains;o||(o=this._chains={}),i=o[t],i||(i=o[t]=new f(this,t,n)),i.count++,r&&r.length>0&&(t=e(r),r=r.slice(t.length+1),i.chain(t,r))},d.unchain=function(t,r){var n=this._chains,i=n[t];r&&r.length>1&&(t=e(r),r=r.slice(t.length+1),i.unchain(t,r)),i.count--,i.count<=0&&(delete n[i._key],i.destroy())},d.willChange=function(e){var t=this._chains;if(t)for(var r in t)t.hasOwnProperty(r)&&t[r].willChange(e);this._parent&&this._parent.chainWillChange(this,this._key,1,e)},d.chainWillChange=function(e,t,r,n){this._key&&(t=this._key+"."+t),this._parent?this._parent.chainWillChange(this,t,r+1,n):(r>1&&n.push(this.value(),t),t="this."+t,this._paths[t]>0&&n.push(this.value(),t))},d.chainDidChange=function(e,t,r,n){this._key&&(t=this._key+"."+t),this._parent?this._parent.chainDidChange(this,t,r+1,n):(r>1&&n.push(this.value(),t),t="this."+t,this._paths[t]>0&&n.push(this.value(),t))},d.didChange=function(e){if(this._watching){var r=this._parent.value();r!==this._object&&(p(this._object,this._key,this),this._object=r,t(r,this._key,this)),this._value=void 0,this._parent&&"@each"===this._parent._key&&this.value()}var n=this._chains;if(n)for(var i in n)n.hasOwnProperty(i)&&n[i].didChange(e);null!==e&&this._parent&&this._parent.chainDidChange(this,this._key,1,e)},Ember.finishChains=function(e){var t=e[h],r=t&&t.chains;r&&(r.value()!==e?n(e).chains=r=r.copy(e):r.didChange(null))}}(),function(){var e=Ember.EnumerableUtils.forEach,t=/^((?:[^\.]*\.)*)\{(.*)\}$/;Ember.expandProperties=function(r,n){var i,o,a;(i=t.exec(r))?(o=i[1],a=i[2],e(a.split(","),function(e){n(o+e)})):n(r)}}(),function(){function e(e,r){var i=r||t(e),o=i.chains;return o?o.value()!==e&&(o=i.chains=o.copy(e)):o=i.chains=new n(null,null,e),o}var t=Ember.meta,r=Ember.typeOf,n=Ember._ChainNode;Ember.watchPath=function(n,i,o){if("length"!==i||"array"!==r(n)){var a=o||t(n),s=a.watching;s[i]?s[i]=(s[i]||0)+1:(s[i]=1,e(n,a).add(i))}},Ember.unwatchPath=function(r,n,i){var o=i||t(r),a=o.watching;1===a[n]?(a[n]=0,e(r,o).remove(n)):a[n]>1&&a[n]--}}(),function(){function e(e){return"*"===e||!c.test(e)}var t=(Ember.meta,Ember.GUID_KEY),r=Ember.META_KEY,n=Ember.removeChainWatcher,i=Ember.watchKey,o=Ember.unwatchKey,a=Ember.watchPath,s=Ember.unwatchPath,u=Ember.typeOf,l=Ember.generateGuid,c=/[\.\*]/;Ember.watch=function(t,r,n){("length"!==r||"array"!==u(t))&&(e(r)?i(t,r,n):a(t,r,n))},Ember.isWatching=function(e,t){var n=e[r];return(n&&n.watching[t])>0},Ember.watch.flushPending=Ember.flushPendingChains,Ember.unwatch=function(t,r,n){("length"!==r||"array"!==u(t))&&(e(r)?o(t,r,n):s(t,r,n))},Ember.rewatch=function(e){var n=e[r],i=n&&n.chains;t in e&&!e.hasOwnProperty(t)&&l(e),i&&i.value()!==e&&(n.chains=i.copy(e))};var h=[];Ember.destroy=function(e){var t,i,o,a,s=e[r];if(s&&(e[r]=null,t=s.chains))for(h.push(t);h.length>0;){if(t=h.pop(),i=t._chains)for(o in i)i.hasOwnProperty(o)&&h.push(i[o]);t._watching&&(a=t._object,a&&n(a,t._key,t))}}}(),function(){function e(e,t){var r=e[t];return r?e.hasOwnProperty(t)||(r=e[t]=h(r)):r=e[t]={},r}function t(t){return e(t,"deps")}function r(r,n,i,o){var a,s,u,l,c,h=r._dependentKeys;if(h)for(a=t(o),s=0,u=h.length;u>s;s++)l=h[s],c=e(a,l),c[i]=(c[i]||0)+1,p(n,l,o)}function n(r,n,i,o){var a,s,u,l,c,h=r._dependentKeys;if(h)for(a=t(o),s=0,u=h.length;u>s;s++)l=h[s],c=e(a,l),c[i]=(c[i]||0)-1,f(n,l,o)}function i(e,t){this.func=e,Ember.FEATURES.isEnabled("composable-computed-properties")?P(this,t&&t.dependentKeys):this._dependentKeys=t&&t.dependentKeys,this._cacheable=t&&void 0!==t.cacheable?t.cacheable:!0,this._readOnly=t&&(void 0!==t.readOnly||!!t.readOnly)}function o(e){for(var t=0,r=e.length;r>t;t++)e[t].didChange(null)}function a(e,t){for(var r={},n=0;nr;r++)d(arguments[r],t);return Ember.FEATURES.isEnabled("composable-computed-properties")?P(this,e):this._dependentKeys=e,this},b.meta=function(e){return 0===arguments.length?this._meta||{}:(this._meta=e,this)},b.didChange=function(e,t){if(this._cacheable&&this._suspended!==e){var r=l(e);t in r.cache&&(delete r.cache[t],n(this,e,t,r))}},b.get=function(e,t){var n,i,a,s;if(this._cacheable){if(a=l(e),i=a.cache,t in i)return i[t];n=i[t]=this.func.call(e,t),s=a.chainWatchers&&a.chainWatchers[t],s&&o(s),r(this,e,t,a)}else n=this.func.call(e,t);return n},b.set=function(e,t,n){var i,o,a,s=this._cacheable,u=this.func,c=l(e,s),h=c.watching[t],m=this._suspended,p=!1,f=c.cache;if(this._readOnly)throw new Ember.Error('Cannot set read-only property "'+t+'" on object: '+Ember.inspect(e));this._suspended=e;try{if(s&&f.hasOwnProperty(t)&&(o=f[t],p=!0),i=u.wrappedFunction?u.wrappedFunction.length:u.length,3===i)a=u.call(e,t,n,o);else{if(2!==i)return Ember.defineProperty(e,t,null,o),Ember.set(e,t,n),void 0;a=u.call(e,t,n)}if(p&&o===a)return;h&&Ember.propertyWillChange(e,t),p&&delete f[t],s&&(p||r(this,e,t,c),f[t]=a),h&&Ember.propertyDidChange(e,t)}finally{this._suspended=m}return a},b.teardown=function(e,t){var r=l(e);return t in r.cache&&n(this,e,t,r),this._cacheable&&delete r.cache[t],null},Ember.computed=function(e){var t;if(arguments.length>1&&(t=c.call(arguments,0,-1),e=c.call(arguments,-1)[0]),"function"!=typeof e)throw new Ember.Error("Computed Property declared without a property function");var r=new i(e);return t&&r.property.apply(r,t),r},Ember.cacheFor=function(e,t){var r=e[m],n=r&&r.cache;return n&&t in n?n[t]:void 0};var v,E;if(Ember.FEATURES.isEnabled("composable-computed-properties")){var g=Ember.guidFor,y=Ember.EnumerableUtils.map,_=Ember.EnumerableUtils.filter,w=(Ember.typeOf,function(e){return[g(e)].concat(e._dependentKeys).join("_").replace(/\./g,"_DOT_") +}),C=function(e){return e instanceof Ember.ComputedProperty?w(e):e},O=function(e){return y(e,function(e){return C(e)})},A=function(e){return _(e,function(e){return e instanceof Ember.ComputedProperty})},P=function(e,t){t?(e._dependentKeys=O(t),e._dependentCPs=A(t)):e._dependentKeys=e._dependentCPs=[],e.implicitCPKey=w(e)};Ember.computed.normalizeDependentKey=C,Ember.computed.normalizeDependentKeys=O,v=function(e,t){Ember.computed[e]=function(e){var r=O(c.call(arguments));return Ember.computed(e,function(){return t.apply(this,r)})}}}Ember.FEATURES.isEnabled("composable-computed-properties")?E=function(e,t){Ember.computed[e]=function(){var e=c.call(arguments),r=O(e),n=Ember.computed(function(){return t.apply(this,[a(this,r)])});return n.property.apply(n,e)}}:(v=function(e,t){Ember.computed[e]=function(e){var r=c.call(arguments);return Ember.computed(e,function(){return t.apply(this,r)})}},E=function(e,t){Ember.computed[e]=function(){var e=c.call(arguments),r=Ember.computed(function(){return t.apply(this,[a(this,e)])});return r.property.apply(r,e)}}),Ember.FEATURES.isEnabled("composable-computed-properties")&&(Ember.computed.literal=function(e){return Ember.computed(function(){return e})}),v("empty",function(e){return Ember.isEmpty(s(this,e))}),v("notEmpty",function(e){return!Ember.isEmpty(s(this,e))}),v("none",function(e){return Ember.isNone(s(this,e))}),v("not",function(e){return!s(this,e)}),v("bool",function(e){return!!s(this,e)}),v("match",function(e,t){var r=s(this,e);return"string"==typeof r?t.test(r):!1}),v("equal",function(e,t){return s(this,e)===t}),v("gt",function(e,t){return s(this,e)>t}),v("gte",function(e,t){return s(this,e)>=t}),v("lt",function(e,t){return s(this,e)1?(u(this,e,r),r):s(this,e)})},Ember.computed.oneWay=function(e){return Ember.computed(e,function(){return s(this,e)})},Ember.computed.readOnly=function(e){return Ember.computed(e,function(){return s(this,e)}).readOnly()},Ember.computed.defaultTo=function(e){return Ember.computed(function(t,r,n){return 1===arguments.length?null!=n?n:s(this,e):null!=r?r:s(this,e)})}}(),function(){function e(e){return e+r}function t(e){return e+n}var r=":change",n=":before";Ember.addObserver=function(t,r,n,i){return Ember.addListener(t,e(r),n,i),Ember.watch(t,r),this},Ember.observersFor=function(t,r){return Ember.listenersFor(t,e(r))},Ember.removeObserver=function(t,r,n,i){return Ember.unwatch(t,r),Ember.removeListener(t,e(r),n,i),this},Ember.addBeforeObserver=function(e,r,n,i){return Ember.addListener(e,t(r),n,i),Ember.watch(e,r),this},Ember._suspendBeforeObserver=function(e,r,n,i,o){return Ember._suspendListener(e,t(r),n,i,o)},Ember._suspendObserver=function(t,r,n,i,o){return Ember._suspendListener(t,e(r),n,i,o)};var i=Ember.ArrayPolyfills.map;Ember._suspendBeforeObservers=function(e,r,n,o,a){var s=i.call(r,t);return Ember._suspendListeners(e,s,n,o,a)},Ember._suspendObservers=function(t,r,n,o,a){var s=i.call(r,e);return Ember._suspendListeners(t,s,n,o,a)},Ember.beforeObserversFor=function(e,r){return Ember.listenersFor(e,t(r))},Ember.removeBeforeObserver=function(e,r,n,i){return Ember.unwatch(e,r),Ember.removeListener(e,t(r),n,i),this}}(),function(){e("backburner/queue",["exports"],function(e){"use strict";function t(e,t,r){this.daq=e,this.name=t,this.options=r,this._queue=[]}t.prototype={daq:null,name:null,options:null,_queue:null,push:function(e,t,r,n){var i=this._queue;return i.push(e,t,r,n),{queue:this,target:e,method:t}},pushUnique:function(e,t,r,n){var i,o,a,s,u=this._queue;for(a=0,s=u.length;s>a;a+=4)if(i=u[a],o=u[a+1],i===e&&o===t)return u[a+2]=r,u[a+3]=n,{queue:this,target:e,method:t};return this._queue.push(e,t,r,n),{queue:this,target:e,method:t}},flush:function(){var e,t,r,n,i,o=this._queue,a=this.options,s=a&&a.before,u=a&&a.after,l=o.length;for(l&&s&&s(),i=0;l>i;i+=4)e=o[i],t=o[i+1],r=o[i+2],n=o[i+3],r&&r.length>0?t.apply(e,r):t.call(e);l&&u&&u(),o.length>l?(this._queue=o.slice(l),this.flush()):this._queue.length=0},cancel:function(e){var t,r,n,i,o=this._queue;for(n=0,i=o.length;i>n;n+=4)if(t=o[n],r=o[n+1],t===e.target&&r===e.method)return o.splice(n,4),!0;if(o=this._queueBeingFlushed)for(n=0,i=o.length;i>n;n+=4)if(t=o[n],r=o[n+1],t===e.target&&r===e.method)return o[n+1]=null,!0}},e.Queue=t}),e("backburner/deferred_action_queues",["backburner/queue","exports"],function(e,t){"use strict";function r(e,t){var r=this.queues={};this.queueNames=e=e||[];for(var n,o=0,a=e.length;a>o;o++)n=e[o],r[n]=new i(this,n,t[n])}function n(e,t){for(var r,n,i=0,o=t;o>=i;i++)if(r=e.queueNames[i],n=e.queues[r],n._queue.length)return i;return-1}var i=e.Queue;r.prototype={queueNames:null,queues:null,schedule:function(e,t,r,n,i,o){var a=this.queues,s=a[e];if(!s)throw new Error("You attempted to schedule an action in a queue ("+e+") that doesn't exist");return i?s.pushUnique(t,r,n,o):s.push(t,r,n,o)},flush:function(){for(var e,t,r,i,o=this.queues,a=this.queueNames,s=0,u=a.length;u>s;){e=a[s],t=o[e],r=t._queueBeingFlushed=t._queue.slice(),t._queue=[];var l,c,h,m,p=t.options,f=p&&p.before,d=p&&p.after,b=0,v=r.length;for(v&&f&&f();v>b;)l=r[b],c=r[b+1],h=r[b+2],m=r[b+3],"string"==typeof c&&(c=l[c]),c&&(h&&h.length>0?c.apply(l,h):c.call(l)),b+=4;t._queueBeingFlushed=null,v&&d&&d(),-1===(i=n(this,s))?s++:s=i}}},t.DeferredActionQueues=r}),e("backburner",["backburner/deferred_action_queues","exports"],function(e,t){"use strict";function r(e){return"number"==typeof e||g.test(e)}function n(e,t){this.queueNames=e,this.options=t||{},this.options.defaultQueue||(this.options.defaultQueue=e[0]),this.instanceStack=[]}function i(e){e.begin(),l=E.setTimeout(function(){l=null,e.end()})}function o(e,t,r){(!c||h>t)&&(c&&clearTimeout(c),c=E.setTimeout(function(){c=null,h=null,a(e)},r),h=t)}function a(e){var t,r,n,i,a=+new Date;e.run(function(){for(n=0,i=v.length;i>n&&(t=v[n],!(t>a));n+=2);for(r=v.splice(0,n),n=1,i=r.length;i>n;n+=2)e.schedule(e.options.defaultQueue,null,r[n])}),v.length&&o(e,v[0],v[0]-a)}function s(e,t){for(var r,n=-1,i=0,o=b.length;o>i;i++)if(r=b[i],r[0]===e&&r[1]===t){n=i;break}return n}function u(e,t){for(var r,n=-1,i=0,o=d.length;o>i;i++)if(r=d[i],r[0]===e&&r[1]===t){n=i;break}return n}var l,c,h,m=e.DeferredActionQueues,p=[].slice,f=[].pop,d=[],b=[],v=[],E=this,g=/\d+/;n.prototype={queueNames:null,options:null,currentInstance:null,instanceStack:null,begin:function(){var e=this.options&&this.options.onBegin,t=this.currentInstance;t&&this.instanceStack.push(t),this.currentInstance=new m(this.queueNames,this.options),e&&e(this.currentInstance,t)},end:function(){var e=this.options&&this.options.onEnd,t=this.currentInstance,r=null;try{t.flush()}finally{this.currentInstance=null,this.instanceStack.length&&(r=this.instanceStack.pop(),this.currentInstance=r),e&&e(t,r)}},run:function(e,t){var r;this.begin(),t||(t=e,e=null),"string"==typeof t&&(t=e[t]);var n=!1;try{r=arguments.length>2?t.apply(e,p.call(arguments,2)):t.call(e)}finally{n||(n=!0,this.end())}return r},defer:function(e,t,r){r||(r=t,t=null),"string"==typeof r&&(r=t[r]);var n=this.DEBUG?new Error:void 0,o=arguments.length>3?p.call(arguments,3):void 0;return this.currentInstance||i(this),this.currentInstance.schedule(e,t,r,o,!1,n)},deferOnce:function(e,t,r){r||(r=t,t=null),"string"==typeof r&&(r=t[r]);var n=this.DEBUG?new Error:void 0,o=arguments.length>3?p.call(arguments,3):void 0;return this.currentInstance||i(this),this.currentInstance.schedule(e,t,r,o,!0,n)},setTimeout:function(){function e(){t.apply(i,l)}var t,n,i,a,s,u,l=p.call(arguments),c=l.length,h=this;if(0!==c){if(1===c)t=l.shift(),n=0;else if(2===c)a=l[0],s=l[1],"function"==typeof s||"function"==typeof a[s]?(i=l.shift(),t=l.shift(),n=0):r(s)?(t=l.shift(),n=l.shift()):(t=l.shift(),n=0);else{var m=l[l.length-1];r(m)&&(n=l.pop()),a=l[0],u=l[1],"function"==typeof u||"string"==typeof u&&null!==a&&u in a?(i=l.shift(),t=l.shift()):t=l.shift()}var f=+new Date+parseInt(n,10);"string"==typeof t&&(t=i[t]);var d,b;for(d=0,b=v.length;b>d&&!(f-1?d[i]:(o=E.setTimeout(function(){l||a.run.apply(a,s);var r=u(e,t);r>-1&&d.splice(r,1)},r),l&&a.run.apply(a,s),n=[e,t,o],d.push(n),n)},debounce:function(e,t){var r,n,i,o,a=this,u=arguments,l=f.call(u);return"number"==typeof l||"string"==typeof l?(r=l,l=!1):r=f.call(u),r=parseInt(r,10),n=s(e,t),n>-1&&(i=b[n],b.splice(n,1),clearTimeout(i[2])),o=E.setTimeout(function(){l||a.run.apply(a,u);var r=s(e,t);r>-1&&b.splice(r,1)},r),l&&-1===n&&a.run.apply(a,u),i=[e,t,o],b.push(i),i},cancelTimers:function(){var e,t;for(e=0,t=d.length;t>e;e++)clearTimeout(d[e][2]);for(d=[],e=0,t=b.length;t>e;e++)clearTimeout(b[e][2]);b=[],c&&(clearTimeout(c),c=null),v=[],l&&(clearTimeout(l),l=null)},hasTimers:function(){return!!v.length||l},cancel:function(e){var t=typeof e;if(e&&"object"===t&&e.queue&&e.method)return e.queue.cancel(e);if("function"!==t)return"[object Array]"===Object.prototype.toString.call(e)?this._cancelItem(u,d,e)||this._cancelItem(s,b,e):void 0;for(var r=0,n=v.length;n>r;r+=2)if(v[r+1]===e)return v.splice(r,2),!0},_cancelItem:function(e,t,r){var n,i;return r.length<3?!1:(i=e(r[0],r[1]),i>-1&&(n=t[i],n[2]===r[2])?(t.splice(i,1),clearTimeout(r[2]),!0):!1)}},n.prototype.schedule=n.prototype.defer,n.prototype.scheduleOnce=n.prototype.deferOnce,n.prototype.later=n.prototype.setTimeout,t.Backburner=n})}(),function(){function e(e){try{return a.run.apply(a,e)}catch(t){Ember.onerror(t)}}function r(){!Ember.run.currentRunLoop}{var n=function(e){Ember.run.currentRunLoop=e},i=function(e,t){Ember.run.currentRunLoop=t},o=t("backburner").Backburner,a=new o(["sync","actions","destroy"],{sync:{before:Ember.beginPropertyChanges,after:Ember.endPropertyChanges},defaultQueue:"actions",onBegin:n,onEnd:i}),s=[].slice;[].concat}Ember.run=function(){return Ember.onerror?e(arguments):a.run.apply(a,arguments)},Ember.run.join=function(){if(!Ember.run.currentRunLoop)return Ember.run.apply(Ember.run,arguments);var e=s.call(arguments);e.unshift("actions"),Ember.run.schedule.apply(Ember.run,e)},Ember.run.bind=function(){var e=s.call(arguments);return function(){return Ember.run.join.apply(Ember.run,e.concat(s.call(arguments)))}},Ember.run.backburner=a;Ember.run;Ember.run.currentRunLoop=null,Ember.run.queues=a.queueNames,Ember.run.begin=function(){a.begin()},Ember.run.end=function(){a.end()},Ember.run.schedule=function(){r(),a.schedule.apply(a,arguments)},Ember.run.hasScheduledTimers=function(){return a.hasTimers()},Ember.run.cancelTimers=function(){a.cancelTimers()},Ember.run.sync=function(){a.currentInstance&&a.currentInstance.queues.sync.flush()},Ember.run.later=function(){return a.later.apply(a,arguments)},Ember.run.once=function(){r();var e=s.call(arguments);return e.unshift("actions"),a.scheduleOnce.apply(a,e)},Ember.run.scheduleOnce=function(){return r(),a.scheduleOnce.apply(a,arguments)},Ember.run.next=function(){var e=s.call(arguments);return e.push(1),a.later.apply(a,e)},Ember.run.cancel=function(e){return a.cancel(e)},Ember.run.debounce=function(){return a.debounce.apply(a,arguments)},Ember.run.throttle=function(){return a.throttle.apply(a,arguments)}}(),function(){function e(e,t){return r(o(t)?Ember.lookup:e,t)}function t(e,t){for(var r in t)t.hasOwnProperty(r)&&(e[r]=t[r])}Ember.LOG_BINDINGS=!1||!!Ember.ENV.LOG_BINDINGS;var r=Ember.get,n=(Ember.set,Ember.guidFor),i=/^([A-Z$]|([0-9][A-Z$]))/,o=Ember.isGlobalPath=function(e){return i.test(e)},a=function(e,t){this._direction="fwd",this._from=t,this._to=e,this._directionMap=Ember.Map.create()};a.prototype={copy:function(){var e=new a(this._to,this._from);return this._oneWay&&(e._oneWay=!0),e},from:function(e){return this._from=e,this},to:function(e){return this._to=e,this},oneWay:function(){return this._oneWay=!0,this},toString:function(){var e=this._oneWay?"[oneWay]":"";return"Ember.Binding<"+n(this)+">("+this._from+" -> "+this._to+")"+e},connect:function(t){var r=this._from,n=this._to;return Ember.trySet(t,n,e(t,r)),Ember.addObserver(t,r,this,this.fromDidChange),this._oneWay||Ember.addObserver(t,n,this,this.toDidChange),this._readyToSync=!0,this},disconnect:function(e){var t=!this._oneWay;return Ember.removeObserver(e,this._from,this,this.fromDidChange),t&&Ember.removeObserver(e,this._to,this,this.toDidChange),this._readyToSync=!1,this},fromDidChange:function(e){this._scheduleSync(e,"fwd")},toDidChange:function(e){this._scheduleSync(e,"back")},_scheduleSync:function(e,t){var r=this._directionMap,n=r.get(e);n||(Ember.run.schedule("sync",this,this._sync,e),r.set(e,t)),"back"===n&&"fwd"===t&&r.set(e,"fwd")},_sync:function(t){var n=Ember.LOG_BINDINGS;if(!t.isDestroyed&&this._readyToSync){var i=this._directionMap,o=i.get(t),a=this._from,s=this._to;if(i.remove(t),"fwd"===o){var u=e(t,this._from);n&&Ember.Logger.log(" ",this.toString(),"->",u,t),this._oneWay?Ember.trySet(t,s,u):Ember._suspendObserver(t,s,this,this.toDidChange,function(){Ember.trySet(t,s,u)})}else if("back"===o){var l=r(t,this._to);n&&Ember.Logger.log(" ",this.toString(),"<-",l,t),Ember._suspendObserver(t,a,this,this.fromDidChange,function(){Ember.trySet(Ember.isGlobalPath(a)?Ember.lookup:t,a,l)})}}}},t(a,{from:function(){var e=this,t=new e;return t.from.apply(t,arguments)},to:function(){var e=this,t=new e;return t.to.apply(t,arguments)},oneWay:function(e,t){var r=this,n=new r(null,e);return n.oneWay(t)}}),Ember.Binding=a,Ember.bind=function(e,t,r){return new Ember.Binding(t,r).connect(e)},Ember.oneWay=function(e,t,r){return new Ember.Binding(t,r).oneWay().connect(e)}}(),function(){function e(){var e,t=this.__nextSuper;return t&&(this.__nextSuper=null,e=t.apply(this,arguments),this.__nextSuper=t),e}function t(e){var t=N(e,!0),r=t.mixins;return r?t.hasOwnProperty("mixins")||(r=t.mixins=x(r)):r=t.mixins={},r}function r(e,t){return t&&t.length>0&&(e.mixins=O.call(t,function(e){if(e instanceof _)return e;var t=new _;return t.properties=e,t})),e}function n(e){return"function"==typeof e&&e.isMethod!==!1&&e!==Boolean&&e!==Object&&e!==Number&&e!==Array&&e!==Date&&e!==String}function i(e,t){var r;return t instanceof _?(r=V(t),e[r]?D:(e[r]=t,t.properties)):t}function o(e,t,r,n){var i;return i=r[e]||n[e],t[e]&&(i=i?i.concat(t[e]):t[e]),i}function a(e,t,r,n,i){var o;return void 0===n[t]&&(o=i[t]),o=o||e.descs[t],o&&o instanceof Ember.ComputedProperty?(r=x(r),r.func=Ember.wrap(r.func,o.func),r):r}function s(e,t,r,n,i){var o;return void 0===i[t]&&(o=n[t]),o=o||e[t],"function"!=typeof o?r:Ember.wrap(r,o)}function u(e,t,r,n){var i=n[t]||e[t];return i?"function"==typeof i.concat?i.concat(r):Ember.makeArray(i).concat(r):Ember.makeArray(r)}function l(t,r,i,o){var a=o[r]||t[r];if(!a)return i;var u=Ember.merge({},a),l=!1;for(var c in i)if(i.hasOwnProperty(c)){var h=i[c];n(h)?(l=!0,u[c]=s(t,c,h,a,{})):u[c]=h}return l&&(u._super=e),u}function c(e,t,r,i,o,c,h,m){if(r instanceof Ember.Descriptor){if(r===w&&o[t])return D;r.func&&(r=a(i,t,r,c,o)),o[t]=r,c[t]=void 0}else h&&A.call(h,t)>=0||"concatenatedProperties"===t||"mergedProperties"===t?r=u(e,t,r,c):m&&A.call(m,t)>=0?r=l(e,t,r,c):n(r)&&(r=s(e,t,r,c,o)),o[t]=void 0,c[t]=r}function h(e,t,r,n,a,s){function u(e){delete r[e],delete n[e]}for(var l,m,p,f,d,b,v=0,E=e.length;E>v;v++)if(l=e[v],m=i(t,l),m!==D)if(m){b=N(a),a.willMergeMixin&&a.willMergeMixin(m),f=o("concatenatedProperties",m,n,a),d=o("mergedProperties",m,n,a);for(p in m)m.hasOwnProperty(p)&&(s.push(p),c(a,p,m[p],b,r,n,f,d));m.hasOwnProperty("toString")&&(a.toString=m.toString)}else l.mixins&&(h(l.mixins,t,r,n,a,s),l._without&&P.call(l._without,u))}function m(e,t,r,n){if(M.test(t)){var i=n.bindings;i?n.hasOwnProperty("bindings")||(i=n.bindings=x(n.bindings)):i=n.bindings={},i[t]=r}}function p(e,t){var r,n,i,o=t.bindings;if(o){for(r in o)n=o[r],n&&(i=r.slice(0,-7),n instanceof Ember.Binding?(n=n.copy(),n.to(i)):n=new Ember.Binding(i,n),n.connect(e),e[r]=n);t.bindings={}}}function f(e,t){return p(e,t||N(e)),e}function d(e,t,r,n,i){var o,a=t.methodName;return n[a]||i[a]?(o=i[a],t=n[a]):r.descs[a]?(t=r.descs[a],o=void 0):(t=void 0,o=e[a]),{desc:t,value:o}}function b(e,t,r,n,i){var o=r[n];if(o)for(var a=0,s=o.length;s>a;a++)Ember[i](e,o[a],null,t)}function v(e,t,r){var n=e[t];"function"==typeof n&&(b(e,t,n,"__ember_observesBefore__","removeBeforeObserver"),b(e,t,n,"__ember_observes__","removeObserver"),b(e,t,n,"__ember_listens__","removeListener")),"function"==typeof r&&(b(e,t,r,"__ember_observesBefore__","addBeforeObserver"),b(e,t,r,"__ember_observes__","addObserver"),b(e,t,r,"__ember_listens__","addListener"))}function E(r,n,i){var o,a,s,u={},l={},c=N(r),p=[];r._super=e,h(n,t(r),u,l,r,p);for(var b=0,E=p.length;E>b;b++)if(o=p[b],"constructor"!==o&&l.hasOwnProperty(o)&&(s=u[o],a=l[o],s!==w)){for(;s&&s instanceof C;){var g=d(r,s,c,u,l);s=g.desc,a=g.value}(void 0!==s||void 0!==a)&&(v(r,o,a),m(r,o,a,c),S(r,o,s,a,c))}return i||f(r,c),r}function g(e,t,r){var n=V(e);if(r[n])return!1;if(r[n]=!0,e===t)return!0;for(var i=e.mixins,o=i?i.length:0;--o>=0;)if(g(i[o],t,r))return!0;return!1}function y(e,t,r){if(!r[V(t)])if(r[V(t)]=!0,t.properties){var n=t.properties;for(var i in n)n.hasOwnProperty(i)&&(e[i]=!0)}else t.mixins&&P.call(t.mixins,function(t){y(e,t,r)})}var _,w,C,O=Ember.ArrayPolyfills.map,A=Ember.ArrayPolyfills.indexOf,P=Ember.ArrayPolyfills.forEach,T=[].slice,x=Ember.create,S=Ember.defineProperty,V=Ember.guidFor,N=Ember.meta,I=Ember.META_KEY,R=Ember.expandProperties,D={},M=Ember.IS_BINDING=/^.+Binding$/;Ember.mixin=function(e){var t=T.call(arguments,1);return E(e,t,!1),e},Ember.Mixin=function(){return r(this,arguments)},_=Ember.Mixin,_.prototype={properties:null,mixins:null,ownerConstructor:null},_._apply=E,_.applyPartial=function(e){var t=T.call(arguments,1);return E(e,t,!0)},_.finishPartial=f,Ember.anyUnprocessedMixins=!1,_.create=function(){Ember.anyUnprocessedMixins=!0;var e=this;return r(new e,arguments)};var k=_.prototype;k.reopen=function(){var e,t;this.properties?(e=_.create(),e.properties=this.properties,delete this.properties,this.mixins=[e]):this.mixins||(this.mixins=[]);var r,n=arguments.length,i=this.mixins;for(r=0;n>r;r++)e=arguments[r],e instanceof _?i.push(e):(t=_.create(),t.properties=e,i.push(t));return this},k.apply=function(e){return E(e,[this],!1)},k.applyPartial=function(e){return E(e,[this],!0)},k.detect=function(e){if(!e)return!1;if(e instanceof _)return g(e,this,{});var t=e[I],r=t&&t.mixins;return r?!!r[V(this)]:!1},k.without=function(){var e=new _(this);return e._without=T.call(arguments),e},k.keys=function(){var e={},t={},r=[];y(e,this,t);for(var n in e)e.hasOwnProperty(n)&&r.push(n);return r},_.mixins=function(e){var t=e[I],r=t&&t.mixins,n=[];if(!r)return n;for(var i in r){var o=r[i];o.properties||n.push(o)}return n},w=new Ember.Descriptor,w.toString=function(){return"(Required Property)"},Ember.required=function(){return w},C=function(e){this.methodName=e},C.prototype=new Ember.Descriptor,Ember.aliasMethod=function(e){return new C(e)},Ember.observer=function(){var e,t=T.call(arguments,-1)[0],r=function(t){e.push(t)},n=T.call(arguments,0,-1);"function"!=typeof t&&(t=arguments[0],n=T.call(arguments,1)),e=[];for(var i=0;ie;e++){arguments[e]}return Ember.observer.apply(this,arguments)},Ember.beforeObserver=function(){var e,t=T.call(arguments,-1)[0],r=function(t){e.push(t)},n=T.call(arguments,0,-1);"function"!=typeof t&&(t=arguments[0],n=T.call(arguments,1)),e=[];for(var i=0;ir;r++)if(e[r]===t)return r;return-1},r=function(e){var t=e._promiseCallbacks;return t||(t=e._promiseCallbacks={}),t};e["default"]={mixin:function(e){return e.on=this.on,e.off=this.off,e.trigger=this.trigger,e._promiseCallbacks=void 0,e},on:function(e,n){var i,o=r(this);i=o[e],i||(i=o[e]=[]),-1===t(i,n)&&i.push(n)},off:function(e,n){var i,o,a=r(this);return n?(i=a[e],o=t(i,n),-1!==o&&i.splice(o,1),void 0):(a[e]=[],void 0)},trigger:function(e,t){var n,i,o=r(this);if(n=o[e])for(var a=0;at;t++)e[t]&&i.push(n[t]);return i})})}var o=e["default"],a=t["default"],s=r.isFunction,u=r.isArray;n["default"]=i}),e("rsvp/hash",["./promise","./utils","exports"],function(e,t,r){"use strict";var n=e["default"],i=t.isNonThenable,o=t.keysOf;r["default"]=function(e){return new n(function(t,r){function a(e){return function(r){c[e]=r,0===--m&&t(c)}}function s(e){m=0,r(e)}var u,l,c={},h=o(e),m=h.length;if(0===m)return t(c),void 0;for(var p=0;ps;s++)l.push(t(n[s]));return i(l,r)})}}),e("rsvp/node",["./promise","exports"],function(e,t){"use strict";function r(e,t){return function(r,n){r?t(r):arguments.length>2?e(i.call(arguments,1)):e(n)}}var n=e["default"],i=Array.prototype.slice;t["default"]=function(e,t){return function(){var o=i.call(arguments),a=this||t;return new n(function(t,i){n.all(o).then(function(n){try{n.push(r(t,i)),e.apply(a,n)}catch(o){i(o)}})})}}}),e("rsvp/promise",["./config","./events","./instrument","./utils","./promise/cast","./promise/all","./promise/race","./promise/resolve","./promise/reject","exports"],function(e,t,r,n,i,o,a,s,u,l){"use strict";function c(){}function h(e,t){if(!A(e))throw new TypeError("You must pass a resolver function as the first argument to the promise constructor");if(!(this instanceof h))throw new TypeError("Failed to construct 'Promise': Please use the 'new' operator, this object constructor cannot be called as a function.");this._id=R++,this._label=t,this._subscribers=[],w.instrument&&C("created",this),c!==e&&m(e,this)}function m(e,t){function r(e){v(t,e)}function n(e){g(t,e)}try{e(r,n)}catch(i){n(i)}}function p(e,t,r,n){var i=e._subscribers,o=i.length;i[o]=t,i[o+k]=r,i[o+j]=n}function f(e,t){var r,n,i=e._subscribers,o=e._detail;w.instrument&&C(t===k?"fulfilled":"rejected",e);for(var a=0;aa;a++){if(n=t[a],o=i(e,n.fullName),void 0===o)throw new Error("Attempting to inject an unknown injection: `"+n.fullName+"`");r[n.property]=o}return r}function u(e,t,r){var n=e._options.get(t);if(n&&void 0!==n[r])return n[r];var i=t.split(":")[0];return n=e._typeOptions.get(i),n?n[r]:void 0}function l(e,t){var r,n=t,i=e.resolve(n),o=e.factoryCache,a=t.split(":")[0];if(void 0!==i){if(o.has(t))return o.get(t);if(!i||"function"!=typeof i.extend||!Ember.MODEL_FACTORY_INJECTIONS&&"model"===a)return i;var s=c(e,t),u=h(e,t);return u._toString=e.makeToString(i,t),r=i.extend(s),r.reopenClass(u),o.set(t,r),r}}function c(e,t){var r=t.split(":"),n=r[0],i=[];return i=i.concat(e.typeInjections.get(n)||[]),i=i.concat(e.injections[t]||[]),i=s(e,i),i._debugContainerKey=t,i.container=e,i}function h(e,t){var r=t.split(":"),n=r[0],i=[];return i=i.concat(e.factoryTypeInjections.get(n)||[]),i=i.concat(e.factoryInjections[t]||[]),i=s(e,i),i._debugContainerKey=t,i}function m(e,t){var r=l(e,t);return u(e,t,"instantiate")===!1?r:r?"function"==typeof r.extend?r.create():r.create(c(e,t)):void 0}function p(e,t){e.cache.eachLocal(function(r,n){u(e,r,"instantiate")!==!1&&t(n) +})}function f(e){e.cache.eachLocal(function(t,r){u(e,t,"instantiate")!==!1&&r.destroy()}),e.cache.dict={}}function d(e,t,r,n){var i=e.get(t);i||(i=[],e.set(t,i)),i.push({property:r,fullName:n})}function b(e){if(!g.test(e))throw new TypeError("Invalid Fullname, expected: `type:name` got: "+e)}function v(e,t,r,n){var i=e[t]=e[t]||[];i.push({property:r,fullName:n})}var E=e["default"];r.prototype={parent:null,children:null,resolver:null,registry:null,cache:null,typeInjections:null,injections:null,_options:null,_typeOptions:null,child:function(){var e=new r(this);return this.children.push(e),e},set:function(e,t,r){e[t]=r},register:function(e,t,r){if(b(e),void 0===t)throw new TypeError("Attempting to register an unknown factory: `"+e+"`");var n=this.normalize(e);if(this.cache.has(n))throw new Error("Cannot re-register: `"+e+"`, as it has already been looked up.");this.registry.set(n,t),this._options.set(n,r||{})},unregister:function(e){b(e);var t=this.normalize(e);this.registry.remove(t),this.cache.remove(t),this.factoryCache.remove(t),this.resolveCache.remove(t),this._options.remove(t)},resolve:function(e){b(e);var t=this.normalize(e),r=this.resolveCache.get(t);if(r)return r;var n=this.resolver(t)||this.registry.get(t);return this.resolveCache.set(t,n),n},describe:function(e){return e},normalize:function(e){return e},makeToString:function(e){return e.toString()},lookup:function(e,t){return b(e),i(this,this.normalize(e),t)},lookupFactory:function(e){return b(e),l(this,this.normalize(e))},has:function(e){return b(e),n(this,this.normalize(e))},optionsForType:function(e,t){this.parent&&o("optionsForType"),this._typeOptions.set(e,t)},options:function(e,t){this.optionsForType(e,t)},typeInjection:function(e,t,r){b(r),this.parent&&o("typeInjection"),d(this.typeInjections,e,t,r)},injection:function(e,t,r){this.parent&&o("injection"),b(r);var n=this.normalize(r);if(-1===e.indexOf(":"))return this.typeInjection(e,t,n);b(e);var i=this.normalize(e);v(this.injections,i,t,n)},factoryTypeInjection:function(e,t,r){this.parent&&o("factoryTypeInjection"),d(this.factoryTypeInjections,e,t,this.normalize(r))},factoryInjection:function(e,t,r){this.parent&&o("injection");var n=this.normalize(e),i=this.normalize(r);return b(r),-1===e.indexOf(":")?this.factoryTypeInjection(n,t,i):(b(e),v(this.factoryInjections,n,t,i),void 0)},destroy:function(){for(var e=0,t=this.children.length;t>e;e++)this.children[e].destroy();this.children=[],p(this,function(e){e.destroy()}),this.parent=void 0,this.isDestroyed=!0},reset:function(){for(var e=0,t=this.children.length;t>e;e++)f(this.children[e]);f(this)}};var g=/^[^:]+.+:[^:]+$/;t["default"]=r}),e("container/inheriting_dict",["exports"],function(e){"use strict";function t(e){this.parent=e,this.dict={}}t.prototype={parent:null,dict:null,get:function(e){var t=this.dict;return t.hasOwnProperty(e)?t[e]:this.parent?this.parent.get(e):void 0},set:function(e,t){this.dict[e]=t},remove:function(e){delete this.dict[e]},has:function(e){var t=this.dict;return t.hasOwnProperty(e)?!0:this.parent?this.parent.has(e):!1},eachLocal:function(e,t){var r=this.dict;for(var n in r)r.hasOwnProperty(n)&&e.call(t,n,r[n])}},e["default"]=t}),e("container",["container/container","exports"],function(e,t){"use strict";Ember.MODEL_FACTORY_INJECTIONS=!1||!!Ember.ENV.MODEL_FACTORY_INJECTIONS;var r=e["default"];t["default"]=r})}(),function(){function e(r,n,i,o){var a,s,u;if("object"!=typeof r||null===r)return r;if(n&&(s=t(i,r))>=0)return o[s];if("array"===Ember.typeOf(r)){if(a=r.slice(),n)for(s=a.length;--s>=0;)a[s]=e(a[s],n,i,o)}else if(Ember.Copyable&&Ember.Copyable.detect(r))a=r.copy(n,i,o);else if(r instanceof Date)a=new Date(r.getTime());else{a={};for(u in r)r.hasOwnProperty(u)&&"__"!==u.substring(0,2)&&(a[u]=n?e(r[u],n,i,o):r[u])}return n&&(i.push(r),o.push(a)),a}var t=Ember.EnumerableUtils.indexOf;if(Ember.compare=function i(e,t){if(e===t)return 0;var r=Ember.typeOf(e),n=Ember.typeOf(t),o=Ember.Comparable;if(o){if("instance"===r&&o.detect(e.constructor))return e.constructor.compare(e,t);if("instance"===n&&o.detect(t.constructor))return 1-t.constructor.compare(t,e)}var a=Ember.ORDER_DEFINITION_MAPPING;if(!a){var s=Ember.ORDER_DEFINITION;a=Ember.ORDER_DEFINITION_MAPPING={};var u,l;for(u=0,l=s.length;l>u;++u)a[s[u]]=u;delete Ember.ORDER_DEFINITION}var c=a[r],h=a[n];if(h>c)return-1;if(c>h)return 1;switch(r){case"boolean":case"number":return t>e?-1:e>t?1:0;case"string":var m=e.localeCompare(t);return 0>m?-1:m>0?1:0;case"array":for(var p=e.length,f=t.length,d=Math.min(p,f),b=0,v=0;0===b&&d>v;)b=i(e[v],t[v]),v++;return 0!==b?b:f>p?-1:p>f?1:0;case"instance":return Ember.Comparable&&Ember.Comparable.detect(e)?e.compare(e,t):0;case"date":var E=e.getTime(),g=t.getTime();return g>E?-1:E>g?1:0;default:return 0}},Ember.copy=function(t,r){return"object"!=typeof t||null===t?t:Ember.Copyable&&Ember.Copyable.detect(t)?t.copy(r):e(t,r,r?[]:null,r?[]:null)},Ember.isEqual=function(e,t){return e&&"function"==typeof e.isEqual?e.isEqual(t):e===t},Ember.ORDER_DEFINITION=Ember.ENV.ORDER_DEFINITION||["undefined","null","boolean","number","string","array","object","instance","function","class","date"],Ember.keys=Object.keys,!Ember.keys||Ember.create.isSimulated){var r=["constructor","hasOwnProperty","isPrototypeOf","propertyIsEnumerable","valueOf","toLocaleString","toString"],n=function(e,r,n){"__"!==n.substring(0,2)&&"_super"!==n&&(t(r,n)>=0||e.hasOwnProperty(n)&&r.push(n))};Ember.keys=function(e){var t,i=[];for(t in e)n(e,i,t);for(var o=0,a=r.length;a>o;o++)t=r[o],n(e,i,t);return i}}}(),function(){var e=/[ _]/g,t={},r=/([a-z\d])([A-Z])/g,n=/(\-|_|\.|\s)+(.)?/g,i=/([a-z\d])([A-Z]+)/g,o=/\-|\s+/g;Ember.STRINGS={},Ember.String={fmt:function(e,t){var r=0;return e.replace(/%@([0-9]+)?/g,function(e,n){return n=n?parseInt(n,10)-1:r++,e=t[n],null===e?"(null)":void 0===e?"":Ember.inspect(e)})},loc:function(e,t){return e=Ember.STRINGS[e]||e,Ember.String.fmt(e,t)},w:function(e){return e.split(/\s+/)},decamelize:function(e){return e.replace(r,"$1_$2").toLowerCase()},dasherize:function(r){var n,i=t,o=i.hasOwnProperty(r);return o?i[r]:(n=Ember.String.decamelize(r).replace(e,"-"),i[r]=n,n)},camelize:function(e){return e.replace(n,function(e,t,r){return r?r.toUpperCase():""}).replace(/^([A-Z])/,function(e){return e.toLowerCase()})},classify:function(e){for(var t=e.split("."),r=[],n=0,i=t.length;i>n;n++){var o=Ember.String.camelize(t[n]);r.push(o.charAt(0).toUpperCase()+o.substr(1))}return r.join(".")},underscore:function(e){return e.replace(i,"$1_$2").replace(o,"_").toLowerCase()},capitalize:function(e){return e.charAt(0).toUpperCase()+e.substr(1)}}}(),function(){var e=Ember.String.fmt,t=Ember.String.w,r=Ember.String.loc,n=Ember.String.camelize,i=Ember.String.decamelize,o=Ember.String.dasherize,a=Ember.String.underscore,s=Ember.String.capitalize,u=Ember.String.classify;(Ember.EXTEND_PROTOTYPES===!0||Ember.EXTEND_PROTOTYPES.String)&&(String.prototype.fmt=function(){return e(this,arguments)},String.prototype.w=function(){return t(this)},String.prototype.loc=function(){return r(this,arguments)},String.prototype.camelize=function(){return n(this)},String.prototype.decamelize=function(){return i(this)},String.prototype.dasherize=function(){return o(this)},String.prototype.underscore=function(){return a(this)},String.prototype.classify=function(){return u(this)},String.prototype.capitalize=function(){return s(this)})}(),function(){var e=Ember.get,t=Ember.set,r=Array.prototype.slice,n=Ember.getProperties;Ember.Observable=Ember.Mixin.create({get:function(t){return e(this,t)},getProperties:function(){return n.apply(null,[this].concat(r.call(arguments)))},set:function(e,r){return t(this,e,r),this},setProperties:function(e){return Ember.setProperties(this,e)},beginPropertyChanges:function(){return Ember.beginPropertyChanges(),this},endPropertyChanges:function(){return Ember.endPropertyChanges(),this},propertyWillChange:function(e){return Ember.propertyWillChange(this,e),this},propertyDidChange:function(e){return Ember.propertyDidChange(this,e),this},notifyPropertyChange:function(e){return this.propertyWillChange(e),this.propertyDidChange(e),this},addBeforeObserver:function(e,t,r){Ember.addBeforeObserver(this,e,t,r)},addObserver:function(e,t,r){Ember.addObserver(this,e,t,r)},removeObserver:function(e,t,r){Ember.removeObserver(this,e,t,r)},hasObserverFor:function(e){return Ember.hasListeners(this,e+":change")},getWithDefault:function(e,t){return Ember.getWithDefault(this,e,t)},incrementProperty:function(r,n){return Ember.isNone(n)&&(n=1),t(this,r,(e(this,r)||0)+n),e(this,r)},decrementProperty:function(r,n){return Ember.isNone(n)&&(n=1),t(this,r,(e(this,r)||0)-n),e(this,r)},toggleProperty:function(r){return t(this,r,!e(this,r)),e(this,r)},cacheFor:function(e){return Ember.cacheFor(this,e)},observersForKey:function(e){return Ember.observersFor(this,e)}})}(),function(){function e(){var e,t,o=!1,a=function(){o||a.proto(),n(this,i,_),n(this,"__nextSuper",y);var u=s(this),l=u.proto;if(u.proto=this,e){var m=e;e=null,this.reopen.apply(this,m)}if(t){var p=t;t=null;for(var f=this.concatenatedProperties,d=0,v=p.length;v>d;d++){var w=p[d];if("object"!=typeof w&&void 0!==w)throw new Ember.Error("Ember.Object.create only accepts objects.");if(w)for(var C=Ember.keys(w),O=0,A=C.length;A>O;O++){var P=C[O];if(w.hasOwnProperty(P)){var T=w[P],x=Ember.IS_BINDING;if(x.test(P)){var S=u.bindings;S?u.hasOwnProperty("bindings")||(S=u.bindings=r(u.bindings)):S=u.bindings={},S[P]=T}var V=u.descs[P];if(f&&g(f,P)>=0){var N=this[P];T=N?"function"==typeof N.concat?N.concat(T):Ember.makeArray(N).concat(T):Ember.makeArray(T)}V?V.set(this,P,T):"function"!=typeof this.setUnknownProperty||P in this?E?Ember.defineProperty(this,P,null,T):this[P]=T:this.setUnknownProperty(P,T)}}}}b(this,u),this.init.apply(this,arguments),u.proto=l,c(this),h(this,"init")};return a.toString=f.prototype.toString,a.willReopen=function(){o&&(a.PrototypeMixin=f.create(a.PrototypeMixin)),o=!1},a._initMixins=function(t){e=t},a._initProperties=function(e){t=e},a.proto=function(){var e=a.superclass;return e&&e.proto(),o||(o=!0,a.PrototypeMixin.applyPartial(a.prototype),l(a.prototype)),this.prototype},a}function t(e){return function(){return e}}var r=(Ember.set,Ember.get,Ember.create),n=Ember.platform.defineProperty,i=Ember.GUID_KEY,o=Ember.guidFor,a=Ember.generateGuid,s=Ember.meta,u=Ember.META_KEY,l=Ember.rewatch,c=Ember.finishChains,h=Ember.sendEvent,m=Ember.destroy,p=Ember.run.schedule,f=Ember.Mixin,d=f._apply,b=f.finishPartial,v=f.prototype.reopen,E=Ember.ENV.MANDATORY_SETTER,g=Ember.EnumerableUtils.indexOf,y={configurable:!0,writable:!0,enumerable:!1,value:void 0},_={configurable:!0,writable:!0,enumerable:!1,value:null},w=e();w.toString=function(){return"Ember.CoreObject"},w.PrototypeMixin=f.create({reopen:function(){return d(this,arguments,!0),this},init:function(){},concatenatedProperties:null,isDestroyed:!1,isDestroying:!1,destroy:function(){return this.isDestroying?void 0:(this.isDestroying=!0,p("actions",this,this.willDestroy),p("destroy",this,this._scheduledDestroy),this)},willDestroy:Ember.K,_scheduledDestroy:function(){this.isDestroyed||(m(this),this.isDestroyed=!0)},bind:function(e,t){return t instanceof Ember.Binding||(t=Ember.Binding.from(t)),t.to(e).connect(this),t},toString:function(){var e="function"==typeof this.toStringExtension,r=e?":"+this.toStringExtension():"",n="<"+this.constructor.toString()+":"+o(this)+r+">";return this.toString=t(n),n}}),w.PrototypeMixin.ownerConstructor=w,Ember.config.overridePrototypeMixin&&Ember.config.overridePrototypeMixin(w.PrototypeMixin),w.__super__=null;var C=f.create({ClassMixin:Ember.required(),PrototypeMixin:Ember.required(),isClass:!0,isMethod:!1,extend:function(){var t,n=e();return n.ClassMixin=f.create(this.ClassMixin),n.PrototypeMixin=f.create(this.PrototypeMixin),n.ClassMixin.ownerConstructor=n,n.PrototypeMixin.ownerConstructor=n,v.apply(n.PrototypeMixin,arguments),n.superclass=this,n.__super__=this.prototype,t=n.prototype=r(this.prototype),t.constructor=n,a(t),s(t).proto=t,n.ClassMixin.apply(n),n},createWithMixins:function(){var e=this;return arguments.length>0&&this._initMixins(arguments),new e},create:function(){var e=this;return arguments.length>0&&this._initProperties(arguments),new e},reopen:function(){return this.willReopen(),v.apply(this.PrototypeMixin,arguments),this},reopenClass:function(){return v.apply(this.ClassMixin,arguments),d(this,arguments,!1),this},detect:function(e){if("function"!=typeof e)return!1;for(;e;){if(e===this)return!0;e=e.superclass}return!1},detectInstance:function(e){return e instanceof this},metaForProperty:function(e){var t=this.proto()[u],r=t&&t.descs[e];return r._meta||{}},eachComputedProperty:function(e,t){var r,n=this.proto(),i=s(n).descs,o={};for(var a in i)r=i[a],r instanceof Ember.ComputedProperty&&e.call(t||this,a,r._meta||o)}});C.ownerConstructor=w,Ember.config.overrideClassMixin&&Ember.config.overrideClassMixin(C),w.ClassMixin=C,C.apply(w),Ember.CoreObject=w}(),function(){Ember.Object=Ember.CoreObject.extend(Ember.Observable),Ember.Object.toString=function(){return"Ember.Object"}}(),function(){function e(t,r,i){var a=t.length;l[t.join(".")]=r;for(var s in r)if(c.call(r,s)){var u=r[s];if(t[a]=s,u&&u.toString===n)u.toString=o(t.join(".")),u[m]=t.join(".");else if(u&&u.isNamespace){if(i[h(u)])continue;i[h(u)]=!0,e(t,u,i)}}t.length=a}function t(){var e,t,r=Ember.Namespace,n=Ember.lookup;if(!r.PROCESSED)for(var i in n)if("parent"!==i&&"top"!==i&&"frameElement"!==i&&"webkitStorageInfo"!==i&&!("globalStorage"===i&&n.StorageList&&n.globalStorage instanceof n.StorageList||n.hasOwnProperty&&!n.hasOwnProperty(i))){try{e=Ember.lookup[i],t=e&&e.isNamespace}catch(o){continue}t&&(e[m]=i)}}function r(e){var t=e.superclass;return t?t[m]?t[m]:r(t):void 0}function n(){Ember.BOOTED||this[m]||i();var e;if(this[m])e=this[m];else if(this._toString)e=this._toString;else{var t=r(this);e=t?"(subclass of "+t+")":"(unknown mixin)",this.toString=o(e)}return e}function i(){var r=!u.PROCESSED,n=Ember.anyUnprocessedMixins;if(r&&(t(),u.PROCESSED=!0),r||n){for(var i,o=u.NAMESPACES,a=0,s=o.length;s>a;a++)i=o[a],e([i.toString()],i,{});Ember.anyUnprocessedMixins=!1}}function o(e){return function(){return e}}var a=Ember.get,s=Ember.ArrayPolyfills.indexOf,u=Ember.Namespace=Ember.Object.extend({isNamespace:!0,init:function(){Ember.Namespace.NAMESPACES.push(this),Ember.Namespace.PROCESSED=!1},toString:function(){var e=a(this,"name");return e?e:(t(),this[Ember.GUID_KEY+"_name"])},nameClasses:function(){e([this.toString()],this,{})},destroy:function(){var e=Ember.Namespace.NAMESPACES;Ember.lookup[this.toString()]=void 0,delete Ember.Namespace.NAMESPACES_BY_ID[this.toString()],e.splice(s.call(e,this),1),this._super()}});u.reopenClass({NAMESPACES:[Ember],NAMESPACES_BY_ID:{},PROCESSED:!1,processAll:i,byName:function(e){return Ember.BOOTED||i(),l[e]}});var l=u.NAMESPACES_BY_ID,c={}.hasOwnProperty,h=Ember.guidFor,m=Ember.NAME_KEY=Ember.GUID_KEY+"_name";Ember.Mixin.prototype.toString=n}(),function(){function e(e,t){var r=t.slice(8);r in this||u(this,r)}function t(e,t){var r=t.slice(8);r in this||l(this,r)}var r=Ember.get,n=Ember.set,i=(Ember.String.fmt,Ember.addBeforeObserver),o=Ember.addObserver,a=Ember.removeBeforeObserver,s=Ember.removeObserver,u=Ember.propertyWillChange,l=Ember.propertyDidChange,c=Ember.meta,h=Ember.defineProperty;Ember.ObjectProxy=Ember.Object.extend({content:null,_contentDidChange:Ember.observer("content",function(){}),isTruthy:Ember.computed.bool("content"),_debugContainerKey:null,willWatchProperty:function(r){var n="content."+r;i(this,n,null,e),o(this,n,null,t)},didUnwatchProperty:function(r){var n="content."+r;a(this,n,null,e),s(this,n,null,t)},unknownProperty:function(e){var t=r(this,"content");return t?r(t,e):void 0},setUnknownProperty:function(e,t){var i=c(this);if(i.proto===this)return h(this,e,null,t),t;var o=r(this,"content");return n(o,e,t)}})}(),function(){function e(){return 0===s.length?{}:s.pop()}function t(e){return s.push(e),null}function r(e,t){function r(r){var o=n(r,e);return i?t===o:!!o}var i=2===arguments.length;return r}var n=Ember.get,i=Ember.set,o=Array.prototype.slice,a=Ember.EnumerableUtils.indexOf,s=[];Ember.Enumerable=Ember.Mixin.create({nextObject:Ember.required(Function),firstObject:Ember.computed(function(){if(0===n(this,"length"))return void 0;var r,i=e();return r=this.nextObject(0,null,i),t(i),r}).property("[]"),lastObject:Ember.computed(function(){var r=n(this,"length");if(0===r)return void 0;var i,o=e(),a=0,s=null;do s=i,i=this.nextObject(a++,s,o);while(void 0!==i);return t(o),s}).property("[]"),contains:function(e){return void 0!==this.find(function(t){return t===e})},forEach:function(r,i){if("function"!=typeof r)throw new TypeError;var o=n(this,"length"),a=null,s=e();void 0===i&&(i=null);for(var u=0;o>u;u++){var l=this.nextObject(u,a,s);r.call(i,l,u,this),a=l}return a=null,s=t(s),this},getEach:function(e){return this.mapBy(e)},setEach:function(e,t){return this.forEach(function(r){i(r,e,t)})},map:function(e,t){var r=Ember.A();return this.forEach(function(n,i,o){r[i]=e.call(t,n,i,o)}),r},mapBy:function(e){return this.map(function(t){return n(t,e)})},mapProperty:Ember.aliasMethod("mapBy"),filter:function(e,t){var r=Ember.A();return this.forEach(function(n,i,o){e.call(t,n,i,o)&&r.push(n)}),r},reject:function(e,t){return this.filter(function(){return!e.apply(t,arguments)})},filterBy:function(){return this.filter(r.apply(this,arguments))},filterProperty:Ember.aliasMethod("filterBy"),rejectBy:function(e,t){var r=function(r){return n(r,e)===t},i=function(t){return!!n(t,e)},o=2===arguments.length?r:i;return this.reject(o)},rejectProperty:Ember.aliasMethod("rejectBy"),find:function(r,i){var o=n(this,"length");void 0===i&&(i=null);for(var a,s,u=null,l=!1,c=e(),h=0;o>h&&!l;h++)a=this.nextObject(h,u,c),(l=r.call(i,a,h,this))&&(s=a),u=a;return a=u=null,c=t(c),s},findBy:function(){return this.find(r.apply(this,arguments))},findProperty:Ember.aliasMethod("findBy"),every:function(e,t){return!this.find(function(r,n,i){return!e.call(t,r,n,i)})},everyBy:Ember.aliasMethod("isEvery"),everyProperty:Ember.aliasMethod("isEvery"),isEvery:function(){return this.every(r.apply(this,arguments))},any:function(r,i){var o,a,s=n(this,"length"),u=e(),l=!1,c=null;for(void 0===i&&(i=null),a=0;s>a&&!l;a++)o=this.nextObject(a,c,u),l=r.call(i,o,a,this),c=o;return o=c=null,u=t(u),l},some:Ember.aliasMethod("any"),isAny:function(){return this.any(r.apply(this,arguments))},anyBy:Ember.aliasMethod("isAny"),someProperty:Ember.aliasMethod("isAny"),reduce:function(e,t,r){if("function"!=typeof e)throw new TypeError;var n=t;return this.forEach(function(t,i){n=e(n,t,i,this,r)},this),n},invoke:function(e){var t,r=Ember.A();return arguments.length>1&&(t=o.call(arguments,1)),this.forEach(function(n,i){var o=n&&n[e];"function"==typeof o&&(r[i]=t?o.apply(n,t):n[e]())},this),r},toArray:function(){var e=Ember.A();return this.forEach(function(t,r){e[r]=t}),e},compact:function(){return this.filter(function(e){return null!=e})},without:function(e){if(!this.contains(e))return this;var t=Ember.A();return this.forEach(function(r){r!==e&&(t[t.length]=r)}),t},uniq:function(){var e=Ember.A();return this.forEach(function(t){a(e,t)<0&&e.push(t)}),e},"[]":Ember.computed(function(){return this}),addEnumerableObserver:function(e,t){var r=t&&t.willChange||"enumerableWillChange",i=t&&t.didChange||"enumerableDidChange",o=n(this,"hasEnumerableObservers");return o||Ember.propertyWillChange(this,"hasEnumerableObservers"),Ember.addListener(this,"@enumerable:before",e,r),Ember.addListener(this,"@enumerable:change",e,i),o||Ember.propertyDidChange(this,"hasEnumerableObservers"),this},removeEnumerableObserver:function(e,t){var r=t&&t.willChange||"enumerableWillChange",i=t&&t.didChange||"enumerableDidChange",o=n(this,"hasEnumerableObservers");return o&&Ember.propertyWillChange(this,"hasEnumerableObservers"),Ember.removeListener(this,"@enumerable:before",e,r),Ember.removeListener(this,"@enumerable:change",e,i),o&&Ember.propertyDidChange(this,"hasEnumerableObservers"),this},hasEnumerableObservers:Ember.computed(function(){return Ember.hasListeners(this,"@enumerable:change")||Ember.hasListeners(this,"@enumerable:before")}),enumerableContentWillChange:function(e,t){var r,i,o;return r="number"==typeof e?e:e?n(e,"length"):e=-1,i="number"==typeof t?t:t?n(t,"length"):t=-1,o=0>i||0>r||i-r!==0,-1===e&&(e=null),-1===t&&(t=null),Ember.propertyWillChange(this,"[]"),o&&Ember.propertyWillChange(this,"length"),Ember.sendEvent(this,"@enumerable:before",[this,e,t]),this},enumerableContentDidChange:function(e,t){var r,i,o;return r="number"==typeof e?e:e?n(e,"length"):e=-1,i="number"==typeof t?t:t?n(t,"length"):t=-1,o=0>i||0>r||i-r!==0,-1===e&&(e=null),-1===t&&(t=null),Ember.sendEvent(this,"@enumerable:change",[this,e,t]),o&&Ember.propertyDidChange(this,"length"),Ember.propertyDidChange(this,"[]"),this},sortBy:function(){var e=arguments;return this.toArray().sort(function(t,r){for(var i=0;it||t>=e(this,"length")?void 0:e(this,t)},objectsAt:function(e){var t=this;return r(e,function(e){return t.objectAt(e)})},nextObject:function(e){return this.objectAt(e)},"[]":Ember.computed(function(t,r){return void 0!==r&&this.replace(0,e(this,"length"),r),this}),firstObject:Ember.computed(function(){return this.objectAt(0)}),lastObject:Ember.computed(function(){return this.objectAt(e(this,"length")-1)}),contains:function(e){return this.indexOf(e)>=0},slice:function(r,n){var i=Ember.A(),o=e(this,"length");for(t(r)&&(r=0),(t(n)||n>o)&&(n=o),0>r&&(r=o+r),0>n&&(n=o+n);n>r;)i[i.length]=this.objectAt(r++);return i},indexOf:function(t,r){var n,i=e(this,"length");for(void 0===r&&(r=0),0>r&&(r+=i),n=r;i>n;n++)if(this.objectAt(n)===t)return n;return-1},lastIndexOf:function(t,r){var n,i=e(this,"length");for((void 0===r||r>=i)&&(r=i-1),0>r&&(r+=i),n=r;n>=0;n--)if(this.objectAt(n)===t)return n;return-1},addArrayObserver:function(t,r){var n=r&&r.willChange||"arrayWillChange",i=r&&r.didChange||"arrayDidChange",o=e(this,"hasArrayObservers");return o||Ember.propertyWillChange(this,"hasArrayObservers"),Ember.addListener(this,"@array:before",t,n),Ember.addListener(this,"@array:change",t,i),o||Ember.propertyDidChange(this,"hasArrayObservers"),this},removeArrayObserver:function(t,r){var n=r&&r.willChange||"arrayWillChange",i=r&&r.didChange||"arrayDidChange",o=e(this,"hasArrayObservers");return o&&Ember.propertyWillChange(this,"hasArrayObservers"),Ember.removeListener(this,"@array:before",t,n),Ember.removeListener(this,"@array:change",t,i),o&&Ember.propertyDidChange(this,"hasArrayObservers"),this},hasArrayObservers:Ember.computed(function(){return Ember.hasListeners(this,"@array:change")||Ember.hasListeners(this,"@array:before")}),arrayContentWillChange:function(t,r,n){void 0===t?(t=0,r=n=-1):(void 0===r&&(r=-1),void 0===n&&(n=-1)),Ember.isWatching(this,"@each")&&e(this,"@each"),Ember.sendEvent(this,"@array:before",[this,t,r,n]);var i,o;if(t>=0&&r>=0&&e(this,"hasEnumerableObservers")){i=[],o=t+r;for(var a=t;o>a;a++)i.push(this.objectAt(a))}else i=r;return this.enumerableContentWillChange(i,n),this},arrayContentDidChange:function(t,r,i){void 0===t?(t=0,r=i=-1):(void 0===r&&(r=-1),void 0===i&&(i=-1));var o,a;if(t>=0&&i>=0&&e(this,"hasEnumerableObservers")){o=[],a=t+i;for(var s=t;a>s;s++)o.push(this.objectAt(s))}else o=i;this.enumerableContentDidChange(r,o),Ember.sendEvent(this,"@array:change",[this,t,r,i]);var u=e(this,"length"),l=n(this,"firstObject"),c=n(this,"lastObject");return this.objectAt(0)!==l&&(Ember.propertyWillChange(this,"firstObject"),Ember.propertyDidChange(this,"firstObject")),this.objectAt(u-1)!==c&&(Ember.propertyWillChange(this,"lastObject"),Ember.propertyDidChange(this,"lastObject")),this},"@each":Ember.computed(function(){return this.__each||(this.__each=new Ember.EachProxy(this)),this.__each})})}(),function(){function e(e,t){return"@this"===t?e:m(e,t)}function t(e,t,r){this.callbacks=e,this.cp=t,this.instanceMeta=r,this.dependentKeysByGuid={},this.trackedArraysByGuid={},this.suspended=!1,this.changedItems={}}function r(e,t,r){this.dependentArray=e,this.index=t,this.item=e.objectAt(t),this.trackedArray=r,this.beforeObserver=null,this.observer=null,this.destroyed=!1}function n(e,t,r){return 0>e?Math.max(0,t+e):t>e?e:Math.min(t-r,e)}function i(e,t,r){return Math.min(r,t-e)}function o(e,t,r,n,i,o){var a={arrayChanged:e,index:r,item:t,propertyName:n,property:i};return o&&(a.previousValues=o),a}function a(e,t,r,n,i){O(e,function(a,s){i.setValue(t.addedItem.call(this,i.getValue(),a,o(e,a,s,n,r),i.sugarMeta))},this)}function s(e,t){{var r;e._callbacks()}e._hasInstanceMeta(this,t)?(r=e._instanceMeta(this,t),r.setValue(e.resetValue(r.getValue()))):r=e._instanceMeta(this,t),e.options.initialize&&e.options.initialize.call(this,r.getValue(),{property:e,propertyName:t},r.sugarMeta)}function u(t,r){if(T.test(r))return!1;var n=e(t,r);return Ember.Array.detect(n)}function l(e,t,r){this.context=e,this.propertyName=t,this.cache=f(e).cache,this.dependentArrays={},this.sugarMeta={},this.initialValue=r}function c(t){var r=this;this.options=t,this._dependentKeys=null,this._itemPropertyKeys={},this._previousItemPropertyKeys={},this.readOnly(),this.cacheable(),this.recomputeOnce=function(e){Ember.run.once(this,n,e)};var n=function(t){var n=(r._dependentKeys,r._instanceMeta(this,t)),i=r._callbacks();s.call(this,r,t),n.dependentArraysObserver.suspendArrayObservers(function(){O(r._dependentKeys,function(t){if(u(this,t)){var i=e(this,t),o=n.dependentArrays[t];i===o?r._previousItemPropertyKeys[t]&&(delete r._previousItemPropertyKeys[t],n.dependentArraysObserver.setupPropertyObservers(t,r._itemPropertyKeys[t])):(n.dependentArrays[t]=i,o&&n.dependentArraysObserver.teardownObservers(o,t),i&&n.dependentArraysObserver.setupObservers(i,t))}},this)},this),O(r._dependentKeys,function(o){if(u(this,o)){var s=e(this,o);s&&a.call(this,s,i,r,t,n)}},this)};this.func=function(e){return n.call(this,e),r._instanceMeta(this,e).getValue()}}function h(e){return e}var m=Ember.get,p=(Ember.set,Ember.guidFor),f=Ember.meta,d=Ember.propertyWillChange,b=Ember.propertyDidChange,v=Ember.addBeforeObserver,E=Ember.removeBeforeObserver,g=Ember.addObserver,y=Ember.removeObserver,_=Ember.ComputedProperty,w=[].slice,C=Ember.create,O=Ember.EnumerableUtils.forEach,A=(Ember.cacheFor.set,Ember.cacheFor.get,Ember.cacheFor.remove,/^(.*)\.@each\.(.*)/),P=/(.*\.@each){2,}/,T=/\.\[\]$/,x=Ember.expandProperties;t.prototype={setValue:function(e){this.instanceMeta.setValue(e,!0)},getValue:function(){return this.instanceMeta.getValue()},setupObservers:function(e,t){this.dependentKeysByGuid[p(e)]=t,e.addArrayObserver(this,{willChange:"dependentArrayWillChange",didChange:"dependentArrayDidChange"}),this.cp._itemPropertyKeys[t]&&this.setupPropertyObservers(t,this.cp._itemPropertyKeys[t])},teardownObservers:function(e,t){var r=this.cp._itemPropertyKeys[t]||[];delete this.dependentKeysByGuid[p(e)],this.teardownPropertyObservers(t,r),e.removeArrayObserver(this,{willChange:"dependentArrayWillChange",didChange:"dependentArrayDidChange"})},suspendArrayObservers:function(e,t){var r=this.suspended;this.suspended=!0,e.call(t),this.suspended=r},setupPropertyObservers:function(t,r){var n=e(this.instanceMeta.context,t),i=e(n,"length"),o=new Array(i);this.resetTransformations(t,o),O(n,function(e,i){var a=this.createPropertyObserverContext(n,i,this.trackedArraysByGuid[t]);o[i]=a,O(r,function(t){v(e,t,this,a.beforeObserver),g(e,t,this,a.observer)},this)},this)},teardownPropertyObservers:function(e,t){var r,n,i,o=this,a=this.trackedArraysByGuid[e];a&&a.apply(function(e,a,s){s!==Ember.TrackedArray.DELETE&&O(e,function(e){e.destroyed=!0,r=e.beforeObserver,n=e.observer,i=e.item,O(t,function(e){E(i,e,o,r),y(i,e,o,n)})})})},createPropertyObserverContext:function(e,t,n){var i=new r(e,t,n);return this.createPropertyObserver(i),i},createPropertyObserver:function(e){var t=this;e.beforeObserver=function(r,n){return t.itemPropertyWillChange(r,n,e.dependentArray,e)},e.observer=function(r,n){return t.itemPropertyDidChange(r,n,e.dependentArray,e)}},resetTransformations:function(e,t){this.trackedArraysByGuid[e]=new Ember.TrackedArray(t)},trackAdd:function(e,t,r){var n=this.trackedArraysByGuid[e];n&&n.addItems(t,r)},trackRemove:function(e,t,r){var n=this.trackedArraysByGuid[e];return n?n.removeItems(t,r):[]},updateIndexes:function(t,r){var n=e(r,"length");t.apply(function(e,t,r){r!==Ember.TrackedArray.DELETE&&(r!==Ember.TrackedArray.RETAIN||e.length!==n||0!==t)&&O(e,function(e,r){e.index=r+t})})},dependentArrayWillChange:function(t,r,a){function s(e){m[h].destroyed=!0,E(l,e,this,m[h].beforeObserver),y(l,e,this,m[h].observer)}if(!this.suspended){var u,l,c,h,m,f=this.callbacks.removedItem,d=p(t),b=this.dependentKeysByGuid[d],v=this.cp._itemPropertyKeys[b]||[],g=e(t,"length"),_=n(r,g,0),w=i(_,g,a);for(m=this.trackRemove(b,_,w),h=w-1;h>=0&&(c=_+h,!(c>=g));--h)l=t.objectAt(c),O(v,s,this),u=o(t,l,c,this.instanceMeta.propertyName,this.cp),this.setValue(f.call(this.instanceMeta.context,this.getValue(),l,u,this.instanceMeta.sugarMeta))}},dependentArrayDidChange:function(t,r,i,a){if(!this.suspended){var s,u,l=this.callbacks.addedItem,c=p(t),h=this.dependentKeysByGuid[c],m=new Array(a),f=this.cp._itemPropertyKeys[h],d=e(t,"length"),b=n(r,d,a);O(t.slice(b,b+a),function(e,r){f&&(u=m[r]=this.createPropertyObserverContext(t,b+r,this.trackedArraysByGuid[h]),O(f,function(t){v(e,t,this,u.beforeObserver),g(e,t,this,u.observer)},this)),s=o(t,e,b+r,this.instanceMeta.propertyName,this.cp),this.setValue(l.call(this.instanceMeta.context,this.getValue(),e,s,this.instanceMeta.sugarMeta))},this),this.trackAdd(h,b,m)}},itemPropertyWillChange:function(t,r,n,i){var o=p(t);this.changedItems[o]||(this.changedItems[o]={array:n,observerContext:i,obj:t,previousValues:{}}),this.changedItems[o].previousValues[r]=e(t,r)},itemPropertyDidChange:function(){this.flushChanges()},flushChanges:function(){var e,t,r,n=this.changedItems;for(e in n)t=n[e],t.observerContext.destroyed||(this.updateIndexes(t.observerContext.trackedArray,t.observerContext.dependentArray),r=o(t.array,t.obj,t.observerContext.index,this.instanceMeta.propertyName,this.cp,t.previousValues),this.setValue(this.callbacks.removedItem.call(this.instanceMeta.context,this.getValue(),t.obj,r,this.instanceMeta.sugarMeta)),this.setValue(this.callbacks.addedItem.call(this.instanceMeta.context,this.getValue(),t.obj,r,this.instanceMeta.sugarMeta)));this.changedItems={}}},l.prototype={getValue:function(){return this.propertyName in this.cache?this.cache[this.propertyName]:this.initialValue},setValue:function(e,t){e!==this.cache[this.propertyName]&&(t&&d(this.context,this.propertyName),void 0===e?delete this.cache[this.propertyName]:this.cache[this.propertyName]=e,t&&b(this.context,this.propertyName))}},Ember.ReduceComputedProperty=c,c.prototype=C(_.prototype),c.prototype._callbacks=function(){if(!this.callbacks){var e=this.options;this.callbacks={removedItem:e.removedItem||h,addedItem:e.addedItem||h}}return this.callbacks},c.prototype._hasInstanceMeta=function(e,t){return!!f(e).cacheMeta[t]},c.prototype._instanceMeta=function(e,r){var n=f(e).cacheMeta,i=n[r];return i||(i=n[r]=new l(e,r,this.initialValue()),i.dependentArraysObserver=new t(this._callbacks(),this,i,e,r,i.sugarMeta)),i},c.prototype.initialValue=function(){return"function"==typeof this.options.initialValue?this.options.initialValue():this.options.initialValue},c.prototype.resetValue=function(){return this.initialValue()},c.prototype.itemPropertyKey=function(e,t){this._itemPropertyKeys[e]=this._itemPropertyKeys[e]||[],this._itemPropertyKeys[e].push(t)},c.prototype.clearItemPropertyKeys=function(e){this._itemPropertyKeys[e]&&(this._previousItemPropertyKeys[e]=this._itemPropertyKeys[e],this._itemPropertyKeys[e]=[])},c.prototype.property=function(){var e,t,r=this,n=w.call(arguments),i=new Ember.Set;return O(n,function(n){if(P.test(n))throw new Ember.Error("Nested @each properties not supported: "+n);if(e=A.exec(n)){t=e[1];var o=e[2],a=function(e){r.itemPropertyKey(t,e)};x(o,a),i.add(t)}else i.add(n)}),_.prototype.property.apply(this,i.toArray())},Ember.reduceComputed=function(e){var t;if(arguments.length>1&&(t=w.call(arguments,0,-1),e=w.call(arguments,-1)[0]),"object"!=typeof e)throw new Ember.Error("Reduce Computed Property declared without an options hash");if(!("initialValue"in e))throw new Ember.Error("Reduce Computed Property declared without an initial value");var r=new c(e);return t&&r.property.apply(r,t),r}}(),function(){function e(){var e=this; +return t.apply(this,arguments),this.func=function(t){return function(r){return e._hasInstanceMeta(this,r)||i(e._dependentKeys,function(t){Ember.addObserver(this,t,function(){e.recomputeOnce.call(this,r)})},this),t.apply(this,arguments)}}(this.func),this}var t=Ember.ReduceComputedProperty,r=[].slice,n=Ember.create,i=Ember.EnumerableUtils.forEach;Ember.ArrayComputedProperty=e,e.prototype=n(t.prototype),e.prototype.initialValue=function(){return Ember.A()},e.prototype.resetValue=function(e){return e.clear(),e},e.prototype.didChange=function(){},Ember.arrayComputed=function(t){var n;if(arguments.length>1&&(n=r.call(arguments,0,-1),t=r.call(arguments,-1)[0]),"object"!=typeof t)throw new Ember.Error("Array Computed Property declared without an options hash");var i=new e(t);return n&&i.property.apply(i,n),i}}(),function(){function e(e,i,o,a){function s(e){return t.detectInstance(e)?n(r(e,"content")):n(e)}var u,l,c,h,m;return arguments.length<4&&(a=r(e,"length")),arguments.length<3&&(o=0),o===a?o:(u=o+Math.floor((a-o)/2),l=e.objectAt(u),h=s(l),m=s(i),h===m?u:(c=this.order(l,i),0===c&&(c=m>h?-1:1),0>c?this.binarySearch(e,i,u+1,a):c>0?this.binarySearch(e,i,o,u):u))}var t,r=Ember.get,n=(Ember.set,Ember.guidFor),i=Ember.merge,o=[].slice,a=Ember.EnumerableUtils.forEach,s=Ember.EnumerableUtils.map;Ember.computed.sum=function(e){return Ember.reduceComputed(e,{initialValue:0,addedItem:function(e,t){return e+t},removedItem:function(e,t){return e-t}})},Ember.computed.max=function(e){return Ember.reduceComputed(e,{initialValue:-1/0,addedItem:function(e,t){return Math.max(e,t)},removedItem:function(e,t){return e>t?e:void 0}})},Ember.computed.min=function(e){return Ember.reduceComputed(e,{initialValue:1/0,addedItem:function(e,t){return Math.min(e,t)},removedItem:function(e,t){return t>e?e:void 0}})},Ember.computed.map=function(e,t){var r={addedItem:function(e,r,n){var i=t.call(this,r);return e.insertAt(n.index,i),e},removedItem:function(e,t,r){return e.removeAt(r.index,1),e}};return Ember.arrayComputed(e,r)},Ember.computed.mapBy=function(e,t){var n=function(e){return r(e,t)};return Ember.computed.map(e+".@each."+t,n)},Ember.computed.mapProperty=Ember.computed.mapBy,Ember.computed.filter=function(e,t){var r={initialize:function(e,t,r){r.filteredArrayIndexes=new Ember.SubArray},addedItem:function(e,r,n,i){var o=!!t.call(this,r),a=i.filteredArrayIndexes.addItem(n.index,o);return o&&e.insertAt(a,r),e},removedItem:function(e,t,r,n){var i=n.filteredArrayIndexes.removeItem(r.index);return i>-1&&e.removeAt(i),e}};return Ember.arrayComputed(e,r)},Ember.computed.filterBy=function(e,t,n){var i;return i=2===arguments.length?function(e){return r(e,t)}:function(e){return r(e,t)===n},Ember.computed.filter(e+".@each."+t,i)},Ember.computed.filterProperty=Ember.computed.filterBy,Ember.computed.uniq=function(){var e=o.call(arguments);return e.push({initialize:function(e,t,r){r.itemCounts={}},addedItem:function(e,t,r,i){var o=n(t);return i.itemCounts[o]?++i.itemCounts[o]:i.itemCounts[o]=1,e.addObject(t),e},removedItem:function(e,t,r,i){var o=n(t),a=i.itemCounts;return 0===--a[o]&&e.removeObject(t),e}}),Ember.arrayComputed.apply(null,e)},Ember.computed.union=Ember.computed.uniq,Ember.computed.intersect=function(){var e=function(e){return s(e.property._dependentKeys,function(e){return n(e)})},t=o.call(arguments);return t.push({initialize:function(e,t,r){r.itemCounts={}},addedItem:function(t,r,i,o){var a=n(r),s=(e(i),n(i.arrayChanged)),u=i.property._dependentKeys.length,l=o.itemCounts;return l[a]||(l[a]={}),void 0===l[a][s]&&(l[a][s]=0),1===++l[a][s]&&u===Ember.keys(l[a]).length&&t.addObject(r),t},removedItem:function(t,r,i,o){var a,s=n(r),u=(e(i),n(i.arrayChanged)),l=(i.property._dependentKeys.length,o.itemCounts);return void 0===l[s][u]&&(l[s][u]=0),0===--l[s][u]&&(delete l[s][u],a=Ember.keys(l[s]).length,0===a&&delete l[s],t.removeObject(r)),t}}),Ember.arrayComputed.apply(null,t)},Ember.computed.setDiff=function(e,t){if(2!==arguments.length)throw new Ember.Error("setDiff requires exactly two dependent arrays.");return Ember.arrayComputed(e,t,{addedItem:function(n,i,o){var a=r(this,e),s=r(this,t);return o.arrayChanged===a?s.contains(i)||n.addObject(i):n.removeObject(i),n},removedItem:function(n,i,o){var a=r(this,e),s=r(this,t);return o.arrayChanged===s?a.contains(i)&&n.addObject(i):n.removeObject(i),n}})},t=Ember.ObjectProxy.extend(),Ember.computed.sort=function(n,o){var s,u;return"function"==typeof o?s=function(t,r,n){n.order=o,n.binarySearch=e}:(u=o,s=function(i,o,s){function l(){var e,t,i,l=r(this,u),h=s.sortProperties=[],m=s.sortPropertyAscending={};o.property.clearItemPropertyKeys(n),a(l,function(r){-1!==(t=r.indexOf(":"))?(e=r.substring(0,t),i="desc"!==r.substring(t+1).toLowerCase()):(e=r,i=!0),h.push(e),m[e]=i,o.property.itemPropertyKey(n,e)}),l.addObserver("@each",this,c)}function c(){Ember.run.once(this,h,o.propertyName)}function h(e){l.call(this),o.property.recomputeOnce.call(this,e)}Ember.addObserver(this,u,c),l.call(this),s.order=function(e,n){for(var i,o,a,s=n instanceof t,u=0;ue;e++){arguments[e]}return this.observes.apply(this,arguments)},Function.prototype.observesBefore=function(){for(var e=function(e){r.push(e)},r=[],n=0;nr(this,"length"))throw new Ember.Error(e);return this.replace(t,0,[n]),this},removeAt:function(n,i){if("number"==typeof n){if(0>n||n>=r(this,"length"))throw new Ember.Error(e);void 0===i&&(i=1),this.replace(n,i,t)}return this},pushObject:function(e){return this.insertAt(r(this,"length"),e),e},pushObjects:function(e){if(!Ember.Enumerable.detect(e)&&!Ember.isArray(e))throw new TypeError("Must pass Ember.Enumerable to Ember.MutableArray#pushObjects");return this.replace(r(this,"length"),0,e),this},popObject:function(){var e=r(this,"length");if(0===e)return null;var t=this.objectAt(e-1);return this.removeAt(e-1,1),t},shiftObject:function(){if(0===r(this,"length"))return null;var e=this.objectAt(0);return this.removeAt(0),e},unshiftObject:function(e){return this.insertAt(0,e),e},unshiftObjects:function(e){return this.replace(0,0,e),this},reverseObjects:function(){var e=r(this,"length");if(0===e)return this;var t=this.toArray().reverse();return this.replace(0,e,t),this},setObjects:function(e){if(0===e.length)return this.clear();var t=r(this,"length");return this.replace(0,t,e),this},removeObject:function(e){for(var t=r(this,"length")||0;--t>=0;){var n=this.objectAt(t);n===e&&this.removeAt(t)}return this},addObject:function(e){return this.contains(e)||this.pushObject(e),this}})}(),function(){{var e=Ember.get;Ember.set}Ember.TargetActionSupport=Ember.Mixin.create({target:null,action:null,actionContext:null,targetObject:Ember.computed(function(){var t=e(this,"target");if("string"===Ember.typeOf(t)){var r=e(this,t);return void 0===r&&(r=e(Ember.lookup,t)),r}return t}).property("target"),actionContextObject:Ember.computed(function(){var t=e(this,"actionContext");if("string"===Ember.typeOf(t)){var r=e(this,t);return void 0===r&&(r=e(Ember.lookup,t)),r}return t}).property("actionContext"),triggerAction:function(t){function r(e,t){var r=[];return t&&r.push(t),r.concat(e)}t=t||{};var n=t.action||e(this,"action"),i=t.target||e(this,"targetObject"),o=t.actionContext;if("undefined"==typeof o&&(o=e(this,"actionContextObject")||this),i&&n){var a;return a=i.send?i.send.apply(i,r(o,n)):i[n].apply(i,r(o)),a!==!1&&(a=!0),a}return!1}})}(),function(){Ember.Evented=Ember.Mixin.create({on:function(e,t,r){return Ember.addListener(this,e,t,r),this},one:function(e,t,r){return r||(r=t,t=null),Ember.addListener(this,e,t,r,!0),this},trigger:function(e){var t,r,n=[];for(t=1,r=arguments.length;r>t;t++)n.push(arguments[t]);Ember.sendEvent(this,e,n)},off:function(e,t,r){return Ember.removeListener(this,e,t,r),this},has:function(e){return Ember.hasListeners(this,e)}})}(),function(){var e=t("rsvp");if(Ember.FEATURES["ember-runtime-test-friendly-promises"]){var r=function(){Ember.Test&&Ember.Test.adapter&&Ember.Test.adapter.asyncStart()},n=function(){Ember.Test&&Ember.Test.adapter&&Ember.Test.adapter.asyncEnd()};e.configure("async",function(e,t){var i=!Ember.run.currentRunLoop;Ember.testing&&i&&r(),Ember.run.backburner.schedule("actions",function(){Ember.testing&&i&&n(),e(t)})})}else e.configure("async",function(e,t){Ember.run.backburner.schedule("actions",function(){e(t)})});e.Promise.prototype.fail=function(e,t){return this["catch"](e,t)};var i=Ember.get;Ember.DeferredMixin=Ember.Mixin.create({then:function(e,t,r){function n(t){return t===a?e(s):e(t)}var o,a,s;return s=this,o=i(this,"_deferred"),a=o.promise,a.then(e&&n,t,r)},resolve:function(e){var t,r;t=i(this,"_deferred"),r=t.promise,e===this?t.resolve(r):t.resolve(e)},reject:function(e){i(this,"_deferred").reject(e)},_deferred:Ember.computed(function(){return e.defer("Ember: DeferredMixin - "+this)})})}(),function(){var e=Ember.get,t=Ember.typeOf;Ember.ActionHandler=Ember.Mixin.create({mergedProperties:["_actions"],willMergeMixin:function(e){var r;e._actions||("object"===t(e.actions)?r="actions":"object"===t(e.events)&&(r="events"),r&&(e._actions=Ember.merge(e._actions||{},e[r])),delete e[r])},send:function(t){var r,n=[].slice.call(arguments,1);if(this._actions&&this._actions[t]){if(this._actions[t].apply(this,n)!==!0)return}else if(!Ember.FEATURES.isEnabled("ember-routing-drop-deprecated-action-style")&&this.deprecatedSend&&this.deprecatedSendHandles&&this.deprecatedSendHandles(t)&&this.deprecatedSend.apply(this,[].slice.call(arguments))!==!0)return;(r=e(this,"target"))&&r.send.apply(r,arguments)}})}(),function(){function e(e,t){return r(e,"isFulfilled",!1),r(e,"isRejected",!1),t.then(function(t){return r(e,"isFulfilled",!0),r(e,"content",t),t},function(t){throw r(e,"isRejected",!0),r(e,"reason",t),t},"Ember: PromiseProxy")}function t(e){return function(){var t=n(this,"promise");return t[e].apply(t,arguments)}}var r=Ember.set,n=Ember.get,i=Ember.computed.not,o=Ember.computed.or;Ember.PromiseProxyMixin=Ember.Mixin.create({reason:null,isPending:i("isSettled").readOnly(),isSettled:o("isRejected","isFulfilled").readOnly(),isRejected:!1,isFulfilled:!1,promise:Ember.computed(function(t,r){if(2===arguments.length)return e(this,r);throw new Ember.Error("PromiseProxy's promise must be set")}),then:t("then"),"catch":t("catch"),"finally":t("finally")})}(),function(){function e(e,t,r){this.type=e,this.count=t,this.items=r}function t(e,t,r,n){this.operation=e,this.index=t,this.split=r,this.rangeStart=n}var r=Ember.get,n=Ember.EnumerableUtils.forEach,i="r",o="i",a="d";Ember.TrackedArray=function(t){arguments.length<1&&(t=[]);var n=r(t,"length");this._operations=n?[new e(i,n,t)]:[]},Ember.TrackedArray.RETAIN=i,Ember.TrackedArray.INSERT=o,Ember.TrackedArray.DELETE=a,Ember.TrackedArray.prototype={addItems:function(t,n){var i=r(n,"length");if(!(1>i)){var a,s,u=this._findArrayOperation(t),l=u.operation,c=u.index,h=u.rangeStart;s=new e(o,i,n),l?u.split?(this._split(c,t-h,s),a=c+1):(this._operations.splice(c,0,s),a=c):(this._operations.push(s),a=c),this._composeInsert(a)}},removeItems:function(t,r){if(!(1>r)){var n,i,o=this._findArrayOperation(t),s=(o.operation,o.index),u=o.rangeStart;return n=new e(a,r),o.split?(this._split(s,t-u,n),i=s+1):(this._operations.splice(s,0,n),i=s),this._composeDelete(i)}},apply:function(t){var r=[],o=0;n(this._operations,function(e){t(e.items,o,e.type),e.type!==a&&(o+=e.count,r=r.concat(e.items))}),this._operations=[new e(i,r.length,r)]},_findArrayOperation:function(e){var r,n,i,o,s,u=!1;for(r=o=0,n=this._operations.length;n>r;++r)if(i=this._operations[r],i.type!==a){if(s=o+i.count-1,e===o)break;if(e>o&&s>=e){u=!0;break}o=s+1}return new t(i,r,u,o)},_split:function(t,r,n){var i=this._operations[t],o=i.items.slice(r),a=new e(i.type,o.length,o);i.count=r,i.items=i.items.slice(0,r),this._operations.splice(t+1,0,n,a)},_composeInsert:function(e){var t=this._operations[e],r=this._operations[e-1],n=this._operations[e+1],i=r&&r.type,a=n&&n.type;i===o?(r.count+=t.count,r.items=r.items.concat(t.items),a===o?(r.count+=n.count,r.items=r.items.concat(n.items),this._operations.splice(e,2)):this._operations.splice(e,1)):a===o&&(t.count+=n.count,t.items=t.items.concat(n.items),this._operations.splice(e+1,1))},_composeDelete:function(e){var t,r,n,i=this._operations[e],s=i.count,u=this._operations[e-1],l=u&&u.type,c=!1,h=[];l===a&&(i=u,e-=1);for(var m=e+1;s>0;++m)t=this._operations[m],r=t.type,n=t.count,r!==a?(n>s?(h=h.concat(t.items.splice(0,s)),t.count-=s,m-=1,n=s,s=0):(n===s&&(c=!0),h=h.concat(t.items),s-=n),r===o&&(i.count-=n)):i.count+=n;return i.count>0?this._operations.splice(e+1,m-1-e):this._operations.splice(e,c?2:1),h},toString:function(){var e="";return n(this._operations,function(t){e+=" "+t.type+":"+t.count}),e.substring(1)}}}(),function(){function e(e,t){this.type=e,this.count=t}var t=(Ember.get,Ember.EnumerableUtils.forEach),r="r",n="f";Ember.SubArray=function(t){arguments.length<1&&(t=0),this._operations=t>0?[new e(r,t)]:[]},Ember.SubArray.prototype={addItem:function(t,i){var o=-1,a=i?r:n,s=this;return this._findOperation(t,function(n,u,l,c,h){var m,p;a===n.type?++n.count:t===l?s._operations.splice(u,0,new e(a,1)):(m=new e(a,1),p=new e(n.type,c-t+1),n.count=t-l,s._operations.splice(u+1,0,m,p)),i&&(o=n.type===r?h+(t-l):h),s._composeAt(u)},function(t){s._operations.push(new e(a,1)),i&&(o=t),s._composeAt(s._operations.length-1)}),o},removeItem:function(e){var t=-1,n=this;return this._findOperation(e,function(i,o,a,s,u){i.type===r&&(t=u+(e-a)),i.count>1?--i.count:(n._operations.splice(o,1),n._composeAt(o))},function(){throw new Ember.Error("Can't remove an item that has never been added.")}),t},_findOperation:function(e,t,n){var i,o,a,s,u,l=0;for(i=s=0,o=this._operations.length;o>i;s=u+1,++i){if(a=this._operations[i],u=s+a.count-1,e>=s&&u>=e)return t(a,i,s,u,l),void 0;a.type===r&&(l+=a.count)}n(l)},_composeAt:function(e){var t,r=this._operations[e];r&&(e>0&&(t=this._operations[e-1],t.type===r.type&&(r.count+=t.count,this._operations.splice(e-1,1),--e)),er(this,"content.length"))throw new Ember.Error(e);return this._replace(t,0,[n]),this},insertAt:function(e,t){if(r(this,"arrangedContent")===r(this,"content"))return this._insertAt(e,t);throw new Ember.Error("Using insertAt on an arranged ArrayProxy is not allowed.")},removeAt:function(n,i){if("number"==typeof n){var o,a=r(this,"content"),s=r(this,"arrangedContent"),u=[];if(0>n||n>=r(this,"length"))throw new Ember.Error(e);for(void 0===i&&(i=1),o=n;n+i>o;o++)u.push(a.indexOf(s.objectAt(o)));for(u.sort(function(e,t){return t-e}),Ember.beginPropertyChanges(),o=0;o=i;){var u=e.objectAt(o);u&&(Ember.addBeforeObserver(u,t,r,"contentKeyWillChange"),Ember.addObserver(u,t,r,"contentKeyDidChange"),a=n(u),s[a]||(s[a]=[]),s[a].push(o))}}function t(e,t,r,i,a){var s=r._objects;s||(s=r._objects={});for(var u,l;--a>=i;){var c=e.objectAt(a);c&&(Ember.removeBeforeObserver(c,t,r,"contentKeyWillChange"),Ember.removeObserver(c,t,r,"contentKeyDidChange"),l=n(c),u=s[l],u[o.call(u,a)]=null)}}var r=(Ember.set,Ember.get),n=Ember.guidFor,i=Ember.EnumerableUtils.forEach,o=Ember.ArrayPolyfills.indexOf,a=Ember.Object.extend(Ember.Array,{init:function(e,t,r){this._super(),this._keyName=t,this._owner=r,this._content=e},objectAt:function(e){var t=this._content.objectAt(e);return t&&r(t,this._keyName)},length:Ember.computed(function(){var e=this._content;return e?r(e,"length"):0})}),s=/^.+:(before|change)$/;Ember.EachProxy=Ember.Object.extend({init:function(e){this._super(),this._content=e,e.addArrayObserver(this),i(Ember.watchedEvents(this),function(e){this.didAddListener(e)},this)},unknownProperty:function(e){var t;return t=new a(this._content,e,this),Ember.defineProperty(this,e,null,t),this.beginObservingContentKey(e),t},arrayWillChange:function(e,r,n){var i,o,a=this._keys;o=n>0?r+n:-1,Ember.beginPropertyChanges(this);for(i in a)a.hasOwnProperty(i)&&(o>0&&t(e,i,this,r,o),Ember.propertyWillChange(this,i));Ember.propertyWillChange(this._content,"@each"),Ember.endPropertyChanges(this)},arrayDidChange:function(t,r,n,i){var o,a=this._keys;o=i>0?r+i:-1,Ember.changeProperties(function(){for(var n in a)a.hasOwnProperty(n)&&(o>0&&e(t,n,this,r,o),Ember.propertyDidChange(this,n));Ember.propertyDidChange(this._content,"@each")},this)},didAddListener:function(e){s.test(e)&&this.beginObservingContentKey(e.slice(0,-7))},didRemoveListener:function(e){s.test(e)&&this.stopObservingContentKey(e.slice(0,-7))},beginObservingContentKey:function(t){var n=this._keys;if(n||(n=this._keys={}),n[t])n[t]++;else{n[t]=1;var i=this._content,o=r(i,"length");e(i,t,this,0,o)}},stopObservingContentKey:function(e){var n=this._keys;if(n&&n[e]>0&&--n[e]<=0){var i=this._content,o=r(i,"length");t(i,e,this,0,o)}},contentKeyWillChange:function(e,t){Ember.propertyWillChange(this,t)},contentKeyDidChange:function(e,t){Ember.propertyDidChange(this,t)}})}(),function(){var e=Ember.get,t=(Ember.set,Ember.EnumerableUtils._replace),r=Ember.Mixin.create(Ember.MutableArray,Ember.Observable,Ember.Copyable,{get:function(e){return"length"===e?this.length:"number"==typeof e?this[e]:this._super(e)},objectAt:function(e){return this[e]},replace:function(r,n,i){if(this.isFrozen)throw Ember.FROZEN_ERROR;var o=i?e(i,"length"):0;return this.arrayContentWillChange(r,n,o),0===o?this.splice(r,n):t(this,r,n,i),this.arrayContentDidChange(r,n,o),this},unknownProperty:function(e,t){var r;return void 0!==t&&void 0===r&&(r=this[e]=t),r},indexOf:function(e,t){var r,n=this.length;for(t=void 0===t?0:0>t?Math.ceil(t):Math.floor(t),0>t&&(t+=n),r=t;n>r;r++)if(this[r]===e)return r;return-1},lastIndexOf:function(e,t){var r,n=this.length;for(t=void 0===t?n-1:0>t?Math.ceil(t):Math.floor(t),0>t&&(t+=n),r=t;r>=0;r--)if(this[r]===e)return r;return-1},copy:function(e){return e?this.map(function(e){return Ember.copy(e,!0)}):this.slice()}}),n=["length"];Ember.EnumerableUtils.forEach(r.keys(),function(e){Array.prototype[e]&&n.push(e)}),n.length>0&&(r=r.without.apply(r,n)),Ember.NativeArray=r,Ember.A=function(e){return void 0===e&&(e=[]),Ember.Array.detect(e)?e:Ember.NativeArray.apply(e)},Ember.NativeArray.activate=function(){r.apply(Array.prototype),Ember.A=function(e){return e||[]}},(Ember.EXTEND_PROTOTYPES===!0||Ember.EXTEND_PROTOTYPES.Array)&&Ember.NativeArray.activate()}(),function(){var e=Ember.get,t=Ember.set,r=Ember.guidFor,n=Ember.isNone,i=Ember.String.fmt;Ember.Set=Ember.CoreObject.extend(Ember.MutableEnumerable,Ember.Copyable,Ember.Freezable,{length:0,clear:function(){if(this.isFrozen)throw new Ember.Error(Ember.FROZEN_ERROR);var n=e(this,"length");if(0===n)return this;var i;this.enumerableContentWillChange(n,0),Ember.propertyWillChange(this,"firstObject"),Ember.propertyWillChange(this,"lastObject");for(var o=0;n>o;o++)i=r(this[o]),delete this[i],delete this[o];return t(this,"length",0),Ember.propertyDidChange(this,"firstObject"),Ember.propertyDidChange(this,"lastObject"),this.enumerableContentDidChange(n,0),this},isEqual:function(t){if(!Ember.Enumerable.detect(t))return!1;var r=e(this,"length");if(e(t,"length")!==r)return!1;for(;--r>=0;)if(!t.contains(this[r]))return!1;return!0},add:Ember.aliasMethod("addObject"),remove:Ember.aliasMethod("removeObject"),pop:function(){if(e(this,"isFrozen"))throw new Ember.Error(Ember.FROZEN_ERROR);var t=this.length>0?this[this.length-1]:null;return this.remove(t),t},push:Ember.aliasMethod("addObject"),shift:Ember.aliasMethod("pop"),unshift:Ember.aliasMethod("push"),addEach:Ember.aliasMethod("addObjects"),removeEach:Ember.aliasMethod("removeObjects"),init:function(e){this._super(),e&&this.addObjects(e)},nextObject:function(e){return this[e]},firstObject:Ember.computed(function(){return this.length>0?this[0]:void 0}),lastObject:Ember.computed(function(){return this.length>0?this[this.length-1]:void 0}),addObject:function(i){if(e(this,"isFrozen"))throw new Ember.Error(Ember.FROZEN_ERROR);if(n(i))return this;var o,a=r(i),s=this[a],u=e(this,"length");return s>=0&&u>s&&this[s]===i?this:(o=[i],this.enumerableContentWillChange(null,o),Ember.propertyWillChange(this,"lastObject"),u=e(this,"length"),this[a]=u,this[u]=i,t(this,"length",u+1),Ember.propertyDidChange(this,"lastObject"),this.enumerableContentDidChange(null,o),this)},removeObject:function(i){if(e(this,"isFrozen"))throw new Ember.Error(Ember.FROZEN_ERROR);if(n(i))return this;var o,a,s=r(i),u=this[s],l=e(this,"length"),c=0===u,h=u===l-1;return u>=0&&l>u&&this[u]===i&&(a=[i],this.enumerableContentWillChange(a,null),c&&Ember.propertyWillChange(this,"firstObject"),h&&Ember.propertyWillChange(this,"lastObject"),l-1>u&&(o=this[l-1],this[u]=o,this[r(o)]=u),delete this[s],delete this[l-1],t(this,"length",l-1),c&&Ember.propertyDidChange(this,"firstObject"),h&&Ember.propertyDidChange(this,"lastObject"),this.enumerableContentDidChange(a,null)),this},contains:function(e){return this[r(e)]>=0},copy:function(){var n=this.constructor,i=new n,o=e(this,"length");for(t(i,"length",o);--o>=0;)i[o]=this[o],i[r(this[o])]=o;return i},toString:function(){var e,t=this.length,r=[];for(e=0;t>e;e++)r[e]=this[e];return i("Ember.Set<%@>",[r.join(",")])}})}(),function(){var e=Ember.DeferredMixin,t=(Ember.get,Ember.Object.extend(e));t.reopenClass({promise:function(e,r){var n=t.create();return e.call(r,n),n}}),Ember.Deferred=t}(),function(){var e=Ember.ArrayPolyfills.forEach,t=Ember.ENV.EMBER_LOAD_HOOKS||{},r={};Ember.onLoad=function(e,n){var i;t[e]=t[e]||Ember.A(),t[e].pushObject(n),(i=r[e])&&n(i)},Ember.runLoadHooks=function(n,i){if(r[n]=i,"object"==typeof window&&"function"==typeof window.dispatchEvent&&"function"==typeof CustomEvent){var o=new CustomEvent(n,{detail:i,name:n});window.dispatchEvent(o)}t[n]&&e.call(t[n],function(e){e(i)})}}(),function(){Ember.get;Ember.ControllerMixin=Ember.Mixin.create(Ember.ActionHandler,{isController:!0,target:null,container:null,parentController:null,store:null,model:Ember.computed.alias("content"),deprecatedSendHandles:function(e){return!!this[e]},deprecatedSend:function(e){var t=[].slice.call(arguments,1);this[e].apply(this,t)}}),Ember.Controller=Ember.Object.extend(Ember.ControllerMixin)}(),function(){var e=Ember.get,t=(Ember.set,Ember.EnumerableUtils.forEach);Ember.SortableMixin=Ember.Mixin.create(Ember.MutableEnumerable,{sortProperties:null,sortAscending:!0,sortFunction:Ember.compare,orderBy:function(r,n){var i=0,o=e(this,"sortProperties"),a=e(this,"sortAscending"),s=e(this,"sortFunction");return t(o,function(t){0===i&&(i=s(e(r,t),e(n,t)),0===i||a||(i=-1*i))}),i},destroy:function(){var r=e(this,"content"),n=e(this,"sortProperties");return r&&n&&t(r,function(e){t(n,function(t){Ember.removeObserver(e,t,this,"contentItemSortPropertyDidChange")},this)},this),this._super()},isSorted:Ember.computed.bool("sortProperties"),arrangedContent:Ember.computed("content","sortProperties.@each",function(){var r=e(this,"content"),n=e(this,"isSorted"),i=e(this,"sortProperties"),o=this;return r&&n?(r=r.slice(),r.sort(function(e,t){return o.orderBy(e,t)}),t(r,function(e){t(i,function(t){Ember.addObserver(e,t,this,"contentItemSortPropertyDidChange")},this)},this),Ember.A(r)):r}),_contentWillChange:Ember.beforeObserver("content",function(){var r=e(this,"content"),n=e(this,"sortProperties");r&&n&&t(r,function(e){t(n,function(t){Ember.removeObserver(e,t,this,"contentItemSortPropertyDidChange")},this)},this),this._super()}),sortAscendingWillChange:Ember.beforeObserver("sortAscending",function(){this._lastSortAscending=e(this,"sortAscending")}),sortAscendingDidChange:Ember.observer("sortAscending",function(){if(e(this,"sortAscending")!==this._lastSortAscending){var t=e(this,"arrangedContent");t.reverseObjects()}}),contentArrayWillChange:function(r,n,i,o){var a=e(this,"isSorted");if(a){var s=e(this,"arrangedContent"),u=r.slice(n,n+i),l=e(this,"sortProperties");t(u,function(e){s.removeObject(e),t(l,function(t){Ember.removeObserver(e,t,this,"contentItemSortPropertyDidChange")},this)},this)}return this._super(r,n,i,o)},contentArrayDidChange:function(r,n,i,o){var a=e(this,"isSorted"),s=e(this,"sortProperties");if(a){var u=r.slice(n,n+o);t(u,function(e){this.insertItemSorted(e),t(s,function(t){Ember.addObserver(e,t,this,"contentItemSortPropertyDidChange")},this)},this)}return this._super(r,n,i,o)},insertItemSorted:function(t){var r=e(this,"arrangedContent"),n=e(r,"length"),i=this._binarySearch(t,0,n);r.insertAt(i,t)},contentItemSortPropertyDidChange:function(t){var r=e(this,"arrangedContent"),n=r.indexOf(t),i=r.objectAt(n-1),o=r.objectAt(n+1),a=i&&this.orderBy(t,i),s=o&&this.orderBy(t,o);(0>a||s>0)&&(r.removeObject(t),this.insertItemSorted(t))},_binarySearch:function(t,r,n){var i,o,a,s;return r===n?r:(s=e(this,"arrangedContent"),i=r+Math.floor((n-r)/2),o=s.objectAt(i),a=this.orderBy(o,t),0>a?this._binarySearch(t,i+1,n):a>0?this._binarySearch(t,r,i):i)}})}(),function(){var e=Ember.get,t=(Ember.set,Ember.EnumerableUtils.forEach),r=Ember.EnumerableUtils.replace;Ember.ArrayController=Ember.ArrayProxy.extend(Ember.ControllerMixin,Ember.SortableMixin,{itemController:null,lookupItemController:function(){return e(this,"itemController")},objectAtContent:function(t){var r=e(this,"length"),n=e(this,"arrangedContent"),i=n&&n.objectAt(t);if(t>=0&&r>t){var o=this.lookupItemController(i);if(o)return this.controllerAt(t,i,o)}return i},arrangedContentDidChange:function(){this._super(),this._resetSubControllers()},arrayContentDidChange:function(n,i,o){var a=e(this,"_subControllers"),s=a.slice(n,n+i);t(s,function(e){e&&e.destroy()}),r(a,n,i,new Array(o)),this._super(n,i,o)},init:function(){this._super(),this.set("_subControllers",Ember.A())},content:Ember.computed(function(){return Ember.A()}),_isVirtual:!1,controllerAt:function(t,r,n){var i,o=e(this,"container"),a=e(this,"_subControllers"),s=a[t];if(s)return s;if(i="controller:"+n,!o.has(i))throw new Ember.Error('Could not resolve itemController: "'+n+'"');var u;return this._isVirtual&&(u=e(this,"parentController")),u=u||this,s=o.lookupFactory(i).create({target:this,parentController:u,content:r}),a[t]=s,s},_subControllers:null,_resetSubControllers:function(){var r=e(this,"_subControllers");r&&t(r,function(e){e&&e.destroy()}),this.set("_subControllers",Ember.A())}})}(),function(){Ember.ObjectController=Ember.ObjectProxy.extend(Ember.ControllerMixin)}(),function(){var e=Ember.imports&&Ember.imports.jQuery||this&&this.jQuery;e||"function"!=typeof r||(e=r("jquery")),Ember.$=e}(),function(){if(Ember.$){var e=Ember.String.w("dragstart drag dragenter dragleave dragover drop dragend"); +Ember.EnumerableUtils.forEach(e,function(e){Ember.$.event.fixHooks[e]={props:["dataTransfer"]}})}}(),function(){function e(e){var t=e.shiftKey||e.metaKey||e.altKey||e.ctrlKey,r=e.which>1;return!t&&!r}var t="undefined"!=typeof document&&function(){var e=document.createElement("div");return e.innerHTML="
",e.firstChild.innerHTML="",""===e.firstChild.innerHTML}(),r="undefined"!=typeof document&&function(){var e=document.createElement("div");return e.innerHTML="Test: Value","Test:"===e.childNodes[0].nodeValue&&" Value"===e.childNodes[2].nodeValue}(),n=function(e,t){if(e.getAttribute("id")===t)return e;var r,i,o,a=e.childNodes.length;for(r=0;a>r;r++)if(i=e.childNodes[r],o=1===i.nodeType&&n(i,t))return o},i=function(e,i){t&&(i="­"+i);var o=[];if(r&&(i=i.replace(/(\s+)(",""===e.firstChild.innerHTML}(),o=document&&function(){var e=document.createElement("div");return e.innerHTML="Test: Value","Test:"===e.childNodes[0].nodeValue&&" Value"===e.childNodes[2].nodeValue}(),a=function(r){var n;n=this instanceof a?this:new e,n.innerHTML=r;var i="metamorph-"+t++;return n.start=i+"-start",n.end=i+"-end",n};e.prototype=a.prototype;var s,u,l,c,h,m,p,f,d;if(c=function(){return this.startTag()+this.innerHTML+this.endTag()},f=function(){return""},d=function(){return""},n)s=function(e,t){var r=document.createRange(),n=document.getElementById(e.start),i=document.getElementById(e.end);return t?(r.setStartBefore(n),r.setEndAfter(i)):(r.setStartAfter(n),r.setEndBefore(i)),r},u=function(e,t){var r=s(this,t);r.deleteContents();var n=r.createContextualFragment(e);r.insertNode(n)},l=function(){var e=s(this,!0);e.deleteContents()},h=function(e){var t=document.createRange();t.setStart(e),t.collapse(!1);var r=t.createContextualFragment(this.outerHTML());e.appendChild(r)},m=function(e){var t=document.createRange(),r=document.getElementById(this.end);t.setStartAfter(r),t.setEndAfter(r);var n=t.createContextualFragment(e);t.insertNode(n)},p=function(e){var t=document.createRange(),r=document.getElementById(this.start);t.setStartAfter(r),t.setEndAfter(r);var n=t.createContextualFragment(e);t.insertNode(n)};else{var b={select:[1,""],fieldset:[1,"
","
"],table:[1,"","
"],tbody:[2,"","
"],tr:[3,"","
"],colgroup:[2,"","
"],map:[1,"",""],_default:[0,"",""]},v=function(e,t){if(e.getAttribute("id")===t)return e;var r,n,i,o=e.childNodes.length;for(r=0;o>r;r++)if(n=e.childNodes[r],i=1===n.nodeType&&v(n,t))return i},E=function(e,t){var r=[];if(o&&(t=t.replace(/(\s+)("; + 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 = ''; + + 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 += ''; } + }, + + 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) { + 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; + }); + + rootElement.on(event + '.ember', '[data-ember-action]', function(evt) { + 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); + } + }); + }, + + _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(object, handler, 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(view, 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() { + +})(); + + + +(function() { +var states = {}; + +/** +@module ember +@submodule ember-views +*/ + +var get = Ember.get, set = Ember.set, + guidFor = Ember.guidFor, + a_forEach = Ember.EnumerableUtils.forEach, + a_addObject = Ember.EnumerableUtils.addObject, + meta = Ember.meta, + defineProperty = Ember.defineProperty; + +function nullViewsBuffer(view) { + view.buffer = null; +} + +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'); + this._isVisible = get(this, 'isVisible'); + }, + + /** + 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('_parentView', function() { + var parent = this._parentView; + + if (parent && parent.isVirtual) { + return get(parent, 'parentView'); + } else { + return parent; + } + }), + + state: null, + + _parentView: null, + + // return the current view, not including virtual views + concreteView: Ember.computed('parentView', function() { + if (!this.isVirtual) { return this; } + else { return get(this, '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('templateName', 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'); + }), + + /** + 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('_parentView', function(key) { + var parentView = get(this, '_parentView'); + return parentView ? get(parentView, 'controller') : null; + }), + + /** + 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. + + @method 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. + + @method 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); + }, + + _unspecifiedAttributeBindings: null, + + /** + 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, + unspecifiedAttributeBindings = this._unspecifiedAttributeBindings = this._unspecifiedAttributeBindings || {}; + + a_forEach(attributeBindings, function(binding) { + var split = binding.split(':'), + property = split[0], + attributeName = split[1] || property; + + if (property in this) { + this._setupAttributeBindingObservation(property, attributeName); + + // Determine the current value and add it to the render buffer + // if necessary. + attributeValue = get(this, property); + Ember.View.applyAttributeBindings(buffer, attributeName, attributeValue); + } else { + unspecifiedAttributeBindings[property] = attributeName; + } + }, this); + + // Lazily setup setUnknownProperty after attributeBindings are initially applied + this.setUnknownProperty = this._setUnknownProperty; + }, + + _setupAttributeBindingObservation: function(property, attributeName) { + var attributeValue, elem; + + // 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); + }, + + /** + We're using setUnknownProperty as a hook to setup attributeBinding observers for + properties that aren't defined on a view at initialization time. + + Note: setUnknownProperty will only be called once for each property. + + @method setUnknownProperty + @param key + @param value + @private + */ + setUnknownProperty: null, // Gets defined after initialization by _applyAttributeBindings + + _setUnknownProperty: function(key, value) { + var attributeName = this._unspecifiedAttributeBindings && this._unspecifiedAttributeBindings[key]; + if (attributeName) { + this._setupAttributeBindingObservation(key, attributeName); + } + + defineProperty(this, key); + return set(this, key, value); + }, + + /** + 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('_parentView', function(key, value) { + if (value !== undefined) { + return this.currentState.setElement(this, value); + } else { + return this.currentState.getElement(this); + } + }), + + /** + 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() { + if (this._isVisible === get(this, 'isVisible')) { return ; } + Ember.run.scheduleOnce('render', this, this._toggleVisibility); + }), + + _toggleVisibility: function() { + var $el = this.$(); + if (!$el) { return; } + + var isVisible = get(this, 'isVisible'); + + if (this._isVisible === isVisible) { return ; } + + $el.toggle(isVisible); + + this._isVisible = 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(nullViewsBuffer); + }, + + 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'); + if (document.body.contains(element)) { + 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() { +var get = Ember.get; + +/** + 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 || get(this, 'layoutName')); + + 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 + + +

{{person.title}}

+ {{yield}} + + + ``` + + Components must have a `-` in their name to avoid + conflicts with built-in controls that wrap HTML + elements. This is consistent with the same + requirement in web components. + + @class Component + @namespace Ember + @extends Ember.View +*/ +Ember.Component = Ember.View.extend(Ember.TargetActionSupport, Ember.ComponentTemplateDeprecation, { + init: function() { + this._super(); + set(this, 'context', this); + set(this, 'controller', this); + }, + + defaultLayout: function(context, options){ + Ember.Handlebars.helpers['yield'].call(context, options); + }, + + /** + A components template property is set by passing a block + during its invocation. It is executed within the parent context. + + Example: + + ```handlebars + {{#my-component}} + // something that is run in the context + // of the parent context + {{/my-component}} + ``` + + Specifying a template directly to a component is deprecated without + also specifying the layout property. + + @deprecated + @property template + */ + 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'), + + /** + Specifying a components `templateName` is deprecated without also + providing the `layout` or `layoutName` properties. + + @deprecated + @property templateName + */ + templateName: null, + + // during render, isolate keywords + cloneKeywords: function() { + return { + view: this, + controller: this + }; + }, + + _yield: function(context, options) { + var view = options.data.view, + parentView = this._parentView, + template = get(this, 'template'); + + if (template) { + Ember.assert("A Component must have a parent view in order to yield.", parentView); + + view.appendChild(Ember.View, { + isVirtual: true, + tagName: '', + _contextView: parentView, + template: template, + context: get(parentView, 'context'), + controller: get(parentView, 'controller'), + templateData: { keywords: parentView.cloneKeywords() } + }); + } + }, + + /** + If the component is currently inserted into the DOM of a parent view, this + property will point to the controller of the parent view. + + @property targetObject + @type Ember.Controller + @default null + */ + targetObject: Ember.computed(function(key) { + var parentView = get(this, '_parentView'); + return parentView ? get(parentView, 'controller') : null; + }).property('_parentView'), + + /** + Triggers a named action on the controller context where the component is used if + this controller has registered for notifications of the action. + + For example a component for playing or pausing music may translate click events + into action notifications of "play" or "stop" depending on some internal state + of the component: + + + ```javascript + App.PlayButtonComponent = Ember.Component.extend({ + click: function(){ + if (this.get('isPlaying')) { + this.sendAction('play'); + } else { + this.sendAction('stop'); + } + } + }); + ``` + + When used inside a template these component actions are configured to + trigger actions in the outer application context: + + ```handlebars + {{! application.hbs }} + {{play-button play="musicStarted" stop="musicStopped"}} + ``` + + When the component receives a browser `click` event it translate this + interaction into application-specific semantics ("play" or "stop") and + triggers the specified action name on the controller for the template + where the component is used: + + + ```javascript + App.ApplicationController = Ember.Controller.extend({ + actions: { + musicStarted: function(){ + // called when the play button is clicked + // and the music started playing + }, + musicStopped: function(){ + // called when the play button is clicked + // and the music stopped playing + } + } + }); + ``` + + If no action name is passed to `sendAction` a default name of "action" + is assumed. + + ```javascript + App.NextButtonComponent = Ember.Component.extend({ + click: function(){ + this.sendAction(); + } + }); + ``` + + ```handlebars + {{! application.hbs }} + {{next-button action="playNextSongInAlbum"}} + ``` + + ```javascript + App.ApplicationController = Ember.Controller.extend({ + actions: { + playNextSongInAlbum: function(){ + ... + } + } + }); + ``` + + @method sendAction + @param [action] {String} the action to trigger + @param [context] {*} a context to send with the action + */ + sendAction: function(action) { + var actionName, + contexts = a_slice.call(arguments, 1); + + // Send the default action + if (action === undefined) { + actionName = get(this, 'action'); + Ember.assert("The default action was triggered on the component " + this.toString() + + ", but the action name (" + actionName + ") was not a string.", + isNone(actionName) || typeof actionName === 'string'); + } else { + actionName = get(this, action); + Ember.assert("The " + action + " action was triggered on the component " + + this.toString() + ", but the action name (" + actionName + + ") was not a string.", + isNone(actionName) || typeof actionName === 'string'); + } + + // If no action name for that action could be found, just abort. + if (actionName === undefined) { return; } + + this.triggerAction({ + action: actionName, + actionContext: contexts + }); + } +}); + +})(); + + + +(function() { + +})(); + + + +(function() { +/** +`Ember.ViewTargetActionSupport` is a mixin that can be included in a +view class to add a `triggerAction` method with semantics similar to +the Handlebars `{{action}}` helper. It provides intelligent defaults +for the action's target: the view's controller; and the context that is +sent with the action: the view's context. + +Note: 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 custom View subclasses. + +For example: + +```javascript +App.SaveButtonView = Ember.View.extend(Ember.ViewTargetActionSupport, { + action: 'save', + click: function() { + this.triggerAction(); // Sends the `save` action, along with the current context + // to the current controller + } +}); +``` + +The `action` can be provided as properties of an optional object argument +to `triggerAction` as well. + +```javascript +App.SaveButtonView = Ember.View.extend(Ember.ViewTargetActionSupport, { + click: function() { + this.triggerAction({ + action: 'save' + }); // Sends the `save` action, along with the current context + // to the current controller + } +}); +``` + +@class ViewTargetActionSupport +@namespace Ember +@extends Ember.TargetActionSupport +*/ +Ember.ViewTargetActionSupport = Ember.Mixin.create(Ember.TargetActionSupport, { + /** + @property target + */ + target: Ember.computed.alias('controller'), + /** + @property actionContext + */ + actionContext: Ember.computed.alias('context') +}); + +})(); + + + +(function() { + +})(); + + + +(function() { +/** +Ember Views + +@module ember +@submodule ember-views +@requires ember-runtime +@main ember-views +*/ + +})(); + +(function() { +define("metamorph", + [], + function() { + "use strict"; + // ========================================================================== + // Project: metamorph + // Copyright: ©2014 Tilde, Inc. All rights reserved. + // ========================================================================== + + var K = function() {}, + guid = 0, + disableRange = (function(){ + if ('undefined' !== typeof MetamorphENV) { + return MetamorphENV.DISABLE_RANGE_API; + } else if ('undefined' !== ENV) { + return ENV.DISABLE_RANGE_API; + } else { + return false; + } + })(), + + // Feature-detect the W3C range API, the extended check is for IE9 which only partially supports ranges + supportsRange = (!disableRange) && typeof document !== 'undefined' && ('createRange' in document) && (typeof Range !== 'undefined') && Range.prototype.createContextualFragment, + + // 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 ­ + 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. + movesWhitespace = document && (function() { + var testEl = document.createElement('div'); + testEl.innerHTML = "Test: Value"; + return testEl.childNodes[0].nodeValue === 'Test:' && + testEl.childNodes[2].nodeValue === ' Value'; + })(); + + // Constructor that supports either Metamorph('foo') or new + // Metamorph('foo'); + // + // Takes a string of HTML as the argument. + + var Metamorph = function(html) { + var self; + + if (this instanceof Metamorph) { + self = this; + } else { + self = new K(); + } + + self.innerHTML = html; + var myGuid = 'metamorph-'+(guid++); + self.start = myGuid + '-start'; + self.end = myGuid + '-end'; + + return self; + }; + + K.prototype = Metamorph.prototype; + + var rangeFor, htmlFunc, removeFunc, outerHTMLFunc, appendToFunc, afterFunc, prependFunc, startTagFunc, endTagFunc; + + outerHTMLFunc = function() { + return this.startTag() + this.innerHTML + this.endTag(); + }; + + startTagFunc = function() { + /* + * We replace chevron by its hex code in order to prevent escaping problems. + * Check this thread for more explaination: + * http://stackoverflow.com/questions/8231048/why-use-x3c-instead-of-when-generating-html-from-javascript + */ + return "hi"; + * 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 makeViewHelper + @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); +}; + +/** + 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.params.length || mustache.hash)) { + 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. + + NOTE: The alias should not reuse a name from the bound property path. + For example: `{{#with foo.bar as foo}}` is not supported because it attempts to alias using + the first part of the property path, `foo`. Instead, use `{{#with foo.bar as baz}}`. + + ### `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 + imageTitle + ``` + + 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 + A humorous image of a cat + ``` + + `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 + imageTitle + ``` + + ### `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 +
    {{unbound helperName somePropertyThatDoesntChange}}
    + ``` + + @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; +var handlebarsGet = Ember.Handlebars.get, normalizePath = Ember.Handlebars.normalizePath; +var a_slice = [].slice; + +/** + `log` allows you to output the value of variables in the current rendering + context. `log` also accepts primitive types such as strings or numbers. + + ```handlebars + {{log "myVariable:" myVariable }} + ``` + + @method log + @for Ember.Handlebars.helpers + @param {String} property +*/ +Ember.Handlebars.registerHelper('log', function logHelper() { + var params = a_slice.call(arguments, 0, -1), + options = arguments[arguments.length - 1], + logger = Ember.Logger.log, + values = [], + allowPrimitives = false; + + + allowPrimitives = true; + + + for (var i = 0; i < params.length; i++) { + var type = options.types[i]; + + if (type === 'ID' || !allowPrimitives) { + var context = (options.contexts && options.contexts[i]) || this, + normalized = normalizePath(context, params[i], options.data); + + if (normalized.path === 'this') { + values.push(normalized.root); + } else { + values.push(handlebarsGet(normalized.root, normalized.path, options)); + } + } else { + values.push(params[i]); + } + } + + logger.apply(logger, values); +}); + +/** + 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 && 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; + } +}); + +// Defeatureify doesn't seem to like nested functions that need to be removed +function _addMetamorphCheck() { + Ember.Handlebars.EachView.reopen({ + _checkMetamorph: Ember.on('didInsertElement', function() { + Ember.assert("The metamorph tags, " + + this.morph.start + " and " + this.morph.end + + ", have different parents.\nThe browser has fixed your template to output valid HTML (for example, check that you have properly closed all tags and have used a TBODY tag when creating a table with '{{#each}}')", + document.getElementById( this.morph.start ).parentNode === + document.getElementById( this.morph.end ).parentNode + ); + }) + }); +} + +Ember.runInDebug( function() { + _addMetamorphCheck(); +}); + +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', + 'autofocus', 'form'], + + 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 + @uses Ember.TargetActionSupport + @extends Ember.Mixin + @private +*/ +Ember.TextSupport = Ember.Mixin.create(Ember.TargetActionSupport, { + value: "", + + attributeBindings: ['placeholder', 'disabled', 'maxlength', 'tabindex', 'readonly', + 'autofocus', 'form', 'selectionDirection', 'spellcheck', 'required'], + 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', + 'accept', 'autocomplete', 'autosave', 'formaction', + 'formenctype', 'formmethod', 'formnovalidate', 'formtarget', + 'height', 'inputmode', 'list', 'multiple', 'pattern', 'step', + 'width'], + + /** + 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', 'selectionEnd', 'selectionStart', 'wrap'], + 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 ` + + + ``` + + The `value` attribute of the selected `"); + return buffer; + } + +function program3(depth0,data) { + + var stack1; + stack1 = helpers.each.call(depth0, "view.groupedContent", {hash:{},hashTypes:{},hashContexts:{},inverse:self.noop,fn:self.program(4, program4, data),contexts:[depth0],types:["ID"],data:data}); + if(stack1 || stack1 === 0) { data.buffer.push(stack1); } + else { data.buffer.push(''); } + } +function program4(depth0,data) { + + + data.buffer.push(escapeExpression(helpers.view.call(depth0, "view.groupView", {hash:{ + 'content': ("content"), + 'label': ("label") + },hashTypes:{'content': "ID",'label': "ID"},hashContexts:{'content': depth0,'label': depth0},contexts:[depth0],types:["ID"],data:data}))); + } + +function program6(depth0,data) { + + var stack1; + stack1 = helpers.each.call(depth0, "view.content", {hash:{},hashTypes:{},hashContexts:{},inverse:self.noop,fn:self.program(7, program7, data),contexts:[depth0],types:["ID"],data:data}); + if(stack1 || stack1 === 0) { data.buffer.push(stack1); } + else { data.buffer.push(''); } + } +function program7(depth0,data) { + + + data.buffer.push(escapeExpression(helpers.view.call(depth0, "view.optionView", {hash:{ + 'content': ("") + },hashTypes:{'content': "ID"},hashContexts:{'content': depth0},contexts:[depth0],types:["ID"],data:data}))); + } + + stack1 = helpers['if'].call(depth0, "view.prompt", {hash:{},hashTypes:{},hashContexts:{},inverse:self.noop,fn:self.program(1, program1, data),contexts:[depth0],types:["ID"],data:data}); + if(stack1 || stack1 === 0) { data.buffer.push(stack1); } + stack1 = helpers['if'].call(depth0, "view.optionGroupPath", {hash:{},hashTypes:{},hashContexts:{},inverse:self.program(6, program6, data),fn:self.program(3, program3, data),contexts:[depth0],types:["ID"],data:data}); + if(stack1 || stack1 === 0) { data.buffer.push(stack1); } + return buffer; + +}), + attributeBindings: ['multiple', 'disabled', 'tabindex', 'name', 'required', 'autofocus', + 'form', 'size'], + + /** + The `multiple` attribute of the select element. Indicates whether multiple + options can be selected. + + @property multiple + @type Boolean + @default false + */ + multiple: false, + + /** + The `disabled` attribute of the select element. Indicates whether + the element is disabled from interactions. + + @property disabled + @type Boolean + @default false + */ + disabled: false, + + /** + The `required` attribute of the select element. Indicates whether + a selected option is required for form validation. + + @property required + @type Boolean + @default false + */ + required: false, + + /** + The list of options. + + If `optionLabelPath` and `optionValuePath` are not overridden, this should + be a list of strings, which will serve simultaneously as labels and values. + + Otherwise, this should be a list of objects. For instance: + + ```javascript + Ember.Select.create({ + content: Ember.A([ + { id: 1, firstName: 'Yehuda' }, + { id: 2, firstName: 'Tom' } + ]), + optionLabelPath: 'content.firstName', + optionValuePath: 'content.id' + }); + ``` + + @property content + @type Array + @default null + */ + content: null, + + /** + When `multiple` is `false`, the element of `content` that is currently + selected, if any. + + When `multiple` is `true`, an array of such elements. + + @property selection + @type Object or Array + @default null + */ + selection: null, + + /** + In single selection mode (when `multiple` is `false`), value can be used to + get the current selection's value or set the selection by it's value. + + It is not currently supported in multiple selection mode. + + @property value + @type String + @default null + */ + value: Ember.computed(function(key, value) { + if (arguments.length === 2) { return value; } + var valuePath = get(this, 'optionValuePath').replace(/^content\.?/, ''); + return valuePath ? get(this, 'selection.' + valuePath) : get(this, 'selection'); + }).property('selection'), + + /** + If given, a top-most dummy option will be rendered to serve as a user + prompt. + + @property prompt + @type String + @default null + */ + prompt: null, + + /** + The path of the option labels. See [content](/api/classes/Ember.Select.html#property_content). + + @property optionLabelPath + @type String + @default 'content' + */ + optionLabelPath: 'content', + + /** + The path of the option values. See [content](/api/classes/Ember.Select.html#property_content). + + @property optionValuePath + @type String + @default 'content' + */ + optionValuePath: 'content', + + /** + The path of the option group. + When this property is used, `content` should be sorted by `optionGroupPath`. + + @property optionGroupPath + @type String + @default null + */ + optionGroupPath: null, + + /** + The view class for optgroup. + + @property groupView + @type Ember.View + @default Ember.SelectOptgroup + */ + groupView: Ember.SelectOptgroup, + + groupedContent: Ember.computed(function() { + var groupPath = get(this, 'optionGroupPath'); + var groupedContent = Ember.A(); + var content = get(this, 'content') || []; + + forEach(content, function(item) { + var label = get(item, groupPath); + + if (get(groupedContent, 'lastObject.label') !== label) { + groupedContent.pushObject({ + label: label, + content: Ember.A() + }); + } + + get(groupedContent, 'lastObject.content').push(item); + }); + + return groupedContent; + }).property('optionGroupPath', 'content.@each'), + + /** + The view class for option. + + @property optionView + @type Ember.View + @default Ember.SelectOption + */ + optionView: Ember.SelectOption, + + _change: function() { + if (get(this, 'multiple')) { + this._changeMultiple(); + } else { + this._changeSingle(); + } + }, + + selectionDidChange: Ember.observer('selection.@each', function() { + var selection = get(this, 'selection'); + if (get(this, 'multiple')) { + if (!isArray(selection)) { + set(this, 'selection', Ember.A([selection])); + return; + } + this._selectionDidChangeMultiple(); + } else { + this._selectionDidChangeSingle(); + } + }), + + valueDidChange: Ember.observer('value', function() { + var content = get(this, 'content'), + value = get(this, 'value'), + valuePath = get(this, 'optionValuePath').replace(/^content\.?/, ''), + selectedValue = (valuePath ? get(this, 'selection.' + valuePath) : get(this, 'selection')), + selection; + + if (value !== selectedValue) { + selection = content ? content.find(function(obj) { + return value === (valuePath ? get(obj, valuePath) : obj); + }) : null; + + this.set('selection', selection); + } + }), + + + _triggerChange: function() { + var selection = get(this, 'selection'); + var value = get(this, 'value'); + + if (!Ember.isNone(selection)) { this.selectionDidChange(); } + if (!Ember.isNone(value)) { this.valueDidChange(); } + + this._change(); + }, + + _changeSingle: function() { + var selectedIndex = this.$()[0].selectedIndex, + content = get(this, 'content'), + prompt = get(this, 'prompt'); + + if (!content || !get(content, 'length')) { return; } + if (prompt && selectedIndex === 0) { set(this, 'selection', null); return; } + + if (prompt) { selectedIndex -= 1; } + set(this, 'selection', content.objectAt(selectedIndex)); + }, + + + _changeMultiple: function() { + var options = this.$('option:selected'), + prompt = get(this, 'prompt'), + offset = prompt ? 1 : 0, + content = get(this, 'content'), + selection = get(this, 'selection'); + + if (!content) { return; } + if (options) { + var selectedIndexes = options.map(function() { + return this.index - offset; + }).toArray(); + var newSelection = content.objectsAt(selectedIndexes); + + if (isArray(selection)) { + replace(selection, 0, get(selection, 'length'), newSelection); + } else { + set(this, 'selection', newSelection); + } + } + }, + + _selectionDidChangeSingle: function() { + var el = this.get('element'); + if (!el) { return; } + + var content = get(this, 'content'), + selection = get(this, 'selection'), + selectionIndex = content ? indexOf(content, selection) : -1, + prompt = get(this, 'prompt'); + + if (prompt) { selectionIndex += 1; } + if (el) { el.selectedIndex = selectionIndex; } + }, + + _selectionDidChangeMultiple: function() { + var content = get(this, 'content'), + selection = get(this, 'selection'), + selectedIndexes = content ? indexesOf(content, selection) : [-1], + prompt = get(this, 'prompt'), + offset = prompt ? 1 : 0, + options = this.$('option'), + adjusted; + + if (options) { + options.each(function() { + adjusted = this.index > -1 ? this.index - offset : -1; + this.selected = indexOf(selectedIndexes, adjusted) > -1; + }); + } + }, + + init: function() { + this._super(); + this.on("didInsertElement", this, this._triggerChange); + this.on("change", this, this._change); + } +}); + +})(); + + + +(function() { +/** +@module ember +@submodule ember-handlebars-compiler +*/ + +/** + + The `{{input}}` helper inserts an HTML `` tag into the template, + with a `type` value of either `text` or `checkbox`. If no `type` is provided, + `text` will be the default value applied. The attributes of `{{input}}` + match those of the native HTML tag as closely as possible for these two types. + + ## Use as text field + An `{{input}}` with no `type` or a `type` of `text` will render an HTML text input. + The following HTML attributes can be set via the helper: + + + + + + + + + + + + +
    `readonly``required``autofocus`
    `value``placeholder``disabled`
    `size``tabindex``maxlength`
    `name``min``max`
    `pattern``accept``autocomplete`
    `autosave``formaction``formenctype`
    `formmethod``formnovalidate``formtarget`
    `height``inputmode``multiple`
    `step``width``form`
    `selectionDirection``spellcheck` 
    + + + When set to a quoted string, these values will be directly applied to the HTML + element. When left unquoted, these values will be bound to a property on the + template's current rendering context (most typically a controller instance). + + ## Unbound: + + ```handlebars + {{input value="http://www.facebook.com"}} + ``` + + + ```html + + ``` + + ## Bound: + + ```javascript + App.ApplicationController = Ember.Controller.extend({ + firstName: "Stanley", + entryNotAllowed: true + }); + ``` + + + ```handlebars + {{input type="text" value=firstName disabled=entryNotAllowed size="50"}} + ``` + + + ```html + + ``` + + ## Extension + + Internally, `{{input type="text"}}` creates an instance of `Ember.TextField`, passing + arguments from the helper to `Ember.TextField`'s `create` method. You can extend the + capablilties of text inputs in your applications by reopening this class. For example, + if you are deploying to browsers where the `required` attribute is used, you + can add this to the `TextField`'s `attributeBindings` property: + + + ```javascript + Ember.TextField.reopen({ + attributeBindings: ['required'] + }); + ``` + + Keep in mind when writing `Ember.TextField` subclasses that `Ember.TextField` + itself extends `Ember.Component`, meaning that it does NOT inherit + the `controller` of the parent view. + + See more about [Ember components](api/classes/Ember.Component.html) + + + ## Use as checkbox + + An `{{input}}` with a `type` of `checkbox` will render an HTML checkbox input. + The following HTML attributes can be set via the helper: + +* `checked` +* `disabled` +* `tabindex` +* `indeterminate` +* `name` +* `autofocus` +* `form` + + + When set to a quoted string, these values will be directly applied to the HTML + element. When left unquoted, these values will be bound to a property on the + template's current rendering context (most typically a controller instance). + + ## Unbound: + + ```handlebars + {{input type="checkbox" name="isAdmin"}} + ``` + + ```html + + ``` + + ## Bound: + + ```javascript + App.ApplicationController = Ember.Controller.extend({ + isAdmin: true + }); + ``` + + + ```handlebars + {{input type="checkbox" checked=isAdmin }} + ``` + + + ```html + + ``` + + ## Extension + + Internally, `{{input type="checkbox"}}` creates an instance of `Ember.Checkbox`, passing + arguments from the helper to `Ember.Checkbox`'s `create` method. You can extend the + capablilties of checkbox inputs in your applications by reopening this class. For example, + if you wanted to add a css class to all checkboxes in your application: + + + ```javascript + Ember.Checkbox.reopen({ + classNames: ['my-app-checkbox'] + }); + ``` + + + @method input + @for Ember.Handlebars.helpers + @param {Hash} options +*/ +Ember.Handlebars.registerHelper('input', function(options) { + Ember.assert('You can only pass attributes to the `input` helper, not arguments', arguments.length < 2); + + var hash = options.hash, + types = options.hashTypes, + inputType = hash.type, + onEvent = hash.on; + + delete hash.type; + delete hash.on; + + if (inputType === 'checkbox') { + Ember.assert("{{input type='checkbox'}} does not support setting `value=someBooleanValue`; you must use `checked=someBooleanValue` instead.", options.hashTypes.value !== 'ID'); + return Ember.Handlebars.helpers.view.call(this, Ember.Checkbox, options); + } else { + if (inputType) { hash.type = inputType; } + hash.onEvent = onEvent || 'enter'; + return Ember.Handlebars.helpers.view.call(this, Ember.TextField, options); + } +}); + +/** + `{{textarea}}` inserts a new instance of ` + ``` + + Bound: + + In the following example, the `writtenWords` property on `App.ApplicationController` + will be updated live as the user types 'Lots of text that IS bound' into + the text area of their browser's window. + + ```javascript + App.ApplicationController = Ember.Controller.extend({ + writtenWords: "Lots of text that IS bound" + }); + ``` + + ```handlebars + {{textarea value=writtenWords}} + ``` + + Would result in the following HTML: + + ```html + + ``` + + If you wanted a one way binding between the text area and a div tag + somewhere else on your screen, you could use `Ember.computed.oneWay`: + + ```javascript + App.ApplicationController = Ember.Controller.extend({ + writtenWords: "Lots of text that IS bound", + outputWrittenWords: Ember.computed.oneWay("writtenWords") + }); + ``` + + ```handlebars + {{textarea value=writtenWords}} + +
    + {{outputWrittenWords}} +
    + ``` + + Would result in the following HTML: + + ```html + + + <-- the following div will be updated in real time as you type --> + +
    + Lots of text that IS bound +
    + ``` + + Finally, this example really shows the power and ease of Ember when two + properties are bound to eachother via `Ember.computed.alias`. Type into + either text area box and they'll both stay in sync. Note that + `Ember.computed.alias` costs more in terms of performance, so only use it when + your really binding in both directions: + + ```javascript + App.ApplicationController = Ember.Controller.extend({ + writtenWords: "Lots of text that IS bound", + twoWayWrittenWords: Ember.computed.alias("writtenWords") + }); + ``` + + ```handlebars + {{textarea value=writtenWords}} + {{textarea value=twoWayWrittenWords}} + ``` + + ```html + + + <-- both updated in real time --> + + + ``` + + ## Extension + + Internally, `{{textarea}}` creates an instance of `Ember.TextArea`, passing + arguments from the helper to `Ember.TextArea`'s `create` method. You can + extend the capabilities of text areas in your application by reopening this + class. For example, if you are deploying to browsers where the `required` + attribute is used, you can globally add support for the `required` attribute + on all `{{textarea}}`s' in your app by reopening `Ember.TextArea` or + `Ember.TextSupport` and adding it to the `attributeBindings` concatenated + property: + + ```javascript + Ember.TextArea.reopen({ + attributeBindings: ['required'] + }); + ``` + + Keep in mind when writing `Ember.TextArea` subclasses that `Ember.TextArea` + itself extends `Ember.Component`, meaning that it does NOT inherit + the `controller` of the parent view. + + See more about [Ember components](api/classes/Ember.Component.html) + + @method textarea + @for Ember.Handlebars.helpers + @param {Hash} options +*/ +Ember.Handlebars.registerHelper('textarea', function(options) { + Ember.assert('You can only pass attributes to the `textarea` helper, not arguments', arguments.length < 2); + + var hash = options.hash, + types = options.hashTypes; + + return Ember.Handlebars.helpers.view.call(this, Ember.TextArea, options); +}); + +})(); + + + +(function() { +Ember.ComponentLookup = Ember.Object.extend({ + lookupFactory: function(name, container) { + + container = container || this.container; + + var fullName = 'component:' + name, + templateFullName = 'template:components/' + name, + templateRegistered = container && container.has(templateFullName); + + if (templateRegistered) { + container.injection(fullName, 'layout', templateFullName); + } + + var Component = container.lookupFactory(fullName); + + // Only treat as a component if either the component + // or a template has been registered. + if (templateRegistered || Component) { + if (!Component) { + container.register(fullName, Ember.Component); + Component = container.lookupFactory(fullName); + } + return Component; + } + } +}); + +})(); + + + +(function() { +/*globals Handlebars */ +/** +@module ember +@submodule ember-handlebars +*/ + +/** + Find templates stored in the head tag as script tags and make them available + to `Ember.CoreView` in the global `Ember.TEMPLATES` object. This will be run + as as jQuery DOM-ready callback. + + Script tags with `text/x-handlebars` will be compiled + with Ember's Handlebars and are suitable for use as a view's template. + Those with type `text/x-raw-handlebars` will be compiled with regular + Handlebars and are suitable for use in views' computed properties. + + @private + @method bootstrap + @for Ember.Handlebars + @static + @param ctx +*/ +Ember.Handlebars.bootstrap = function(ctx) { + var selectors = 'script[type="text/x-handlebars"], script[type="text/x-raw-handlebars"]'; + + Ember.$(selectors, ctx) + .each(function() { + // Get a reference to the script tag + var script = Ember.$(this); + + var compile = (script.attr('type') === 'text/x-raw-handlebars') ? + Ember.$.proxy(Handlebars.compile, Handlebars) : + Ember.$.proxy(Ember.Handlebars.compile, Ember.Handlebars), + // Get the name of the script, used by Ember.View's templateName property. + // First look for data-template-name attribute, then fall back to its + // id if no name is found. + templateName = script.attr('data-template-name') || script.attr('id') || 'application', + template = compile(script.html()); + + // Check if template of same name already exists + if (Ember.TEMPLATES[templateName] !== undefined) { + throw new Ember.Error('Template named "' + templateName + '" already exists.'); + } + + // For templates which have a name, we save them and then remove them from the DOM + Ember.TEMPLATES[templateName] = template; + + // Remove script tag from DOM + script.remove(); + }); +}; + +function bootstrap() { + Ember.Handlebars.bootstrap( Ember.$(document) ); +} + +function registerComponentLookup(container) { + container.register('component-lookup:main', Ember.ComponentLookup); +} + +/* + We tie this to application.load to ensure that we've at least + attempted to bootstrap at the point that the application is loaded. + + We also tie this to document ready since we're guaranteed that all + the inline templates are present at this point. + + There's no harm to running this twice, since we remove the templates + from the DOM after processing. +*/ + +Ember.onLoad('Ember.Application', function(Application) { + Application.initializer({ + name: 'domTemplates', + initialize: bootstrap + }); + + Application.initializer({ + name: 'registerComponentLookup', + after: 'domTemplates', + initialize: registerComponentLookup + }); +}); + +})(); + + + +(function() { +/** +Ember Handlebars + +@module ember +@submodule ember-handlebars +@requires ember-views +*/ + +Ember.runLoadHooks('Ember.Handlebars', Ember.Handlebars); + +})(); + +(function() { +define("route-recognizer", + ["exports"], + function(__exports__) { + "use strict"; + var specials = [ + '/', '.', '*', '+', '?', '|', + '(', ')', '[', ']', '{', '}', '\\' + ]; + + var escapeRegex = new RegExp('(\\' + specials.join('|\\') + ')', 'g'); + + function isArray(test) { + return Object.prototype.toString.call(test) === "[object Array]"; + } + + // A Segment represents a segment in the original route description. + // Each Segment type provides an `eachChar` and `regex` method. + // + // The `eachChar` method invokes the callback with one or more character + // specifications. A character specification consumes one or more input + // characters. + // + // The `regex` method returns a regex fragment for the segment. If the + // segment is a dynamic of star segment, the regex fragment also includes + // a capture. + // + // A character specification contains: + // + // * `validChars`: a String with a list of all valid characters, or + // * `invalidChars`: a String with a list of all invalid characters + // * `repeat`: true if the character specification can repeat + + function StaticSegment(string) { this.string = string; } + StaticSegment.prototype = { + eachChar: function(callback) { + var string = this.string, ch; + + for (var i=0, l=string.length; i " + n.nextStates.map(function(s) { return s.debug() }).join(" or ") + " )"; + }).join(", ") + } + END IF **/ + + // This is a somewhat naive strategy, but should work in a lot of cases + // A better strategy would properly resolve /posts/:id/new and /posts/edit/:id. + // + // This strategy generally prefers more static and less dynamic matching. + // Specifically, it + // + // * prefers fewer stars to more, then + // * prefers using stars for less of the match to more, then + // * prefers fewer dynamic segments to more, then + // * prefers more static segments to more + function sortSolutions(states) { + return states.sort(function(a, b) { + if (a.types.stars !== b.types.stars) { return a.types.stars - b.types.stars; } + + if (a.types.stars) { + if (a.types.statics !== b.types.statics) { return b.types.statics - a.types.statics; } + if (a.types.dynamics !== b.types.dynamics) { return b.types.dynamics - a.types.dynamics; } + } + + if (a.types.dynamics !== b.types.dynamics) { return a.types.dynamics - b.types.dynamics; } + if (a.types.statics !== b.types.statics) { return b.types.statics - a.types.statics; } + + return 0; + }); + } + + function recognizeChar(states, ch) { + var nextStates = []; + + for (var i=0, l=states.length; i 2 && key.slice(keyLength -2) === '[]') { + isArray = true; + key = key.slice(0, keyLength - 2); + if(!queryParams[key]) { + queryParams[key] = []; + } + } + value = pair[1] ? decodeURIComponent(pair[1]) : ''; + } + if (isArray) { + queryParams[key].push(value); + } else { + queryParams[key] = value; + } + + } + return queryParams; + }, + + recognize: function(path) { + var states = [ this.rootState ], + pathLen, i, l, queryStart, queryParams = {}, + isSlashDropped = false; + + path = decodeURI(path); + + queryStart = path.indexOf('?'); + if (queryStart !== -1) { + var queryString = path.substr(queryStart + 1, path.length); + path = path.substr(0, queryStart); + queryParams = this.parseQueryString(queryString); + } + + // DEBUG GROUP path + + if (path.charAt(0) !== "/") { path = "/" + path; } + + pathLen = path.length; + if (pathLen > 1 && path.charAt(pathLen - 1) === "/") { + path = path.substr(0, pathLen - 1); + isSlashDropped = true; + } + + for (i=0, l=path.length; i= 0 && proceed; --i) { + var route = routes[i]; + recognizer.add(routes, { as: route.handler }); + proceed = route.path === '/' || route.path === '' || route.handler.slice(-6) === '.index'; + } + }); + }, + + hasRoute: function(route) { + return this.recognizer.hasRoute(route); + }, + + // NOTE: this doesn't really belong here, but here + // it shall remain until our ES6 transpiler can + // handle cyclical deps. + transitionByIntent: function(intent, isIntermediate) { + + var wasTransitioning = !!this.activeTransition; + var oldState = wasTransitioning ? this.activeTransition.state : this.state; + var newTransition; + var router = this; + + try { + var newState = intent.applyToState(oldState, this.recognizer, this.getHandler, isIntermediate); + + if (handlerInfosEqual(newState.handlerInfos, oldState.handlerInfos)) { + + // This is a no-op transition. See if query params changed. + var queryParamChangelist = getChangelist(oldState.queryParams, newState.queryParams); + if (queryParamChangelist) { + + // This is a little hacky but we need some way of storing + // changed query params given that no activeTransition + // is guaranteed to have occurred. + this._changedQueryParams = queryParamChangelist.changed; + trigger(this, newState.handlerInfos, true, ['queryParamsDidChange', queryParamChangelist.changed, queryParamChangelist.all, queryParamChangelist.removed]); + this._changedQueryParams = null; + + if (!wasTransitioning && this.activeTransition) { + // One of the handlers in queryParamsDidChange + // caused a transition. Just return that transition. + return this.activeTransition; + } else { + // Running queryParamsDidChange didn't change anything. + // Just update query params and be on our way. + oldState.queryParams = finalizeQueryParamChange(this, newState.handlerInfos, newState.queryParams); + + // We have to return a noop transition that will + // perform a URL update at the end. This gives + // the user the ability to set the url update + // method (default is replaceState). + newTransition = new Transition(this); + newTransition.urlMethod = 'replace'; + newTransition.promise = newTransition.promise.then(function(result) { + updateURL(newTransition, oldState, true); + if (router.didTransition) { + router.didTransition(router.currentHandlerInfos); + } + return result; + }, null, promiseLabel("Transition complete")); + return newTransition; + } + } + + // No-op. No need to create a new transition. + return new Transition(this); + } + + if (isIntermediate) { + setupContexts(this, newState); + return; + } + + // Create a new transition to the destination route. + newTransition = new Transition(this, intent, newState); + + // Abort and usurp any previously active transition. + if (this.activeTransition) { + this.activeTransition.abort(); + } + this.activeTransition = newTransition; + + // Transition promises by default resolve with resolved state. + // For our purposes, swap out the promise to resolve + // after the transition has been finalized. + newTransition.promise = newTransition.promise.then(function(result) { + return router.async(function() { + return finalizeTransition(newTransition, result.state); + }, "Finalize transition"); + }, null, promiseLabel("Settle transition promise when transition is finalized")); + + if (!wasTransitioning) { + trigger(this, this.state.handlerInfos, true, ['willTransition', newTransition]); + } + + return newTransition; + } catch(e) { + return new Transition(this, intent, null, e); + } + }, + + /** + Clears the current and target route handlers and triggers exit + on each of them starting at the leaf and traversing up through + its ancestors. + */ + reset: function() { + if (this.state) { + forEach(this.state.handlerInfos, function(handlerInfo) { + var handler = handlerInfo.handler; + if (handler.exit) { + handler.exit(); + } + }); + } + + this.state = new TransitionState(); + this.currentHandlerInfos = null; + }, + + activeTransition: null, + + /** + var handler = handlerInfo.handler; + The entry point for handling a change to the URL (usually + via the back and forward button). + + Returns an Array of handlers and the parameters associated + with those parameters. + + @param {String} url a URL to process + + @return {Array} an Array of `[handler, parameter]` tuples + */ + handleURL: function(url) { + // Perform a URL-based transition, but don't change + // the URL afterward, since it already happened. + var args = slice.call(arguments); + if (url.charAt(0) !== '/') { args[0] = '/' + url; } + + return doTransition(this, args).method('replaceQuery'); + }, + + /** + Hook point for updating the URL. + + @param {String} url a URL to update to + */ + updateURL: function() { + throw new Error("updateURL is not implemented"); + }, + + /** + Hook point for replacing the current URL, i.e. with replaceState + + By default this behaves the same as `updateURL` + + @param {String} url a URL to update to + */ + replaceURL: function(url) { + this.updateURL(url); + }, + + /** + Transition into the specified named route. + + If necessary, trigger the exit callback on any handlers + that are no longer represented by the target route. + + @param {String} name the name of the route + */ + transitionTo: function(name) { + return doTransition(this, arguments); + }, + + intermediateTransitionTo: function(name) { + doTransition(this, arguments, true); + }, + + refresh: function(pivotHandler) { + + + var state = this.activeTransition ? this.activeTransition.state : this.state; + var handlerInfos = state.handlerInfos; + var params = {}; + for (var i = 0, len = handlerInfos.length; i < len; ++i) { + var handlerInfo = handlerInfos[i]; + params[handlerInfo.name] = handlerInfo.params || {}; + } + + log(this, "Starting a refresh transition"); + var intent = new NamedTransitionIntent({ + name: handlerInfos[handlerInfos.length - 1].name, + pivotHandler: pivotHandler || handlerInfos[0].handler, + contexts: [], // TODO collect contexts...? + queryParams: this._changedQueryParams || state.queryParams || {} + }); + + return this.transitionByIntent(intent, false); + }, + + /** + Identical to `transitionTo` except that the current URL will be replaced + if possible. + + This method is intended primarily for use with `replaceState`. + + @param {String} name the name of the route + */ + replaceWith: function(name) { + return doTransition(this, arguments).method('replace'); + }, + + /** + Take a named route and context objects and generate a + URL. + + @param {String} name the name of the route to generate + a URL for + @param {...Object} objects a list of objects to serialize + + @return {String} a URL + */ + generate: function(handlerName) { + + var partitionedArgs = extractQueryParams(slice.call(arguments, 1)), + suppliedParams = partitionedArgs[0], + queryParams = partitionedArgs[1]; + + // Construct a TransitionIntent with the provided params + // and apply it to the present state of the router. + var intent = new NamedTransitionIntent({ name: handlerName, contexts: suppliedParams }); + var state = intent.applyToState(this.state, this.recognizer, this.getHandler); + var params = {}; + + for (var i = 0, len = state.handlerInfos.length; i < len; ++i) { + var handlerInfo = state.handlerInfos[i]; + var handlerParams = handlerInfo.params || + serialize(handlerInfo.handler, handlerInfo.context, handlerInfo.names); + merge(params, handlerParams); + } + params.queryParams = queryParams; + + return this.recognizer.generate(handlerName, params); + }, + + isActive: function(handlerName) { + + var partitionedArgs = extractQueryParams(slice.call(arguments, 1)), + contexts = partitionedArgs[0], + queryParams = partitionedArgs[1], + activeQueryParams = this.state.queryParams; + + var targetHandlerInfos = this.state.handlerInfos, + found = false, names, object, handlerInfo, handlerObj, i, len; + + if (!targetHandlerInfos.length) { return false; } + + var targetHandler = targetHandlerInfos[targetHandlerInfos.length - 1].name; + var recogHandlers = this.recognizer.handlersFor(targetHandler); + + var index = 0; + for (len = recogHandlers.length; index < len; ++index) { + handlerInfo = targetHandlerInfos[index]; + if (handlerInfo.name === handlerName) { break; } + } + + if (index === recogHandlers.length) { + // The provided route name isn't even in the route hierarchy. + return false; + } + + var state = new TransitionState(); + state.handlerInfos = targetHandlerInfos.slice(0, index + 1); + recogHandlers = recogHandlers.slice(0, index + 1); + + var intent = new NamedTransitionIntent({ + name: targetHandler, + contexts: contexts + }); + + var newState = intent.applyToHandlers(state, recogHandlers, this.getHandler, targetHandler, true, true); + + // Get a hash of QPs that will still be active on new route + var activeQPsOnNewHandler = {}; + merge(activeQPsOnNewHandler, queryParams); + for (var key in activeQueryParams) { + if (activeQueryParams.hasOwnProperty(key) && + activeQPsOnNewHandler.hasOwnProperty(key)) { + activeQPsOnNewHandler[key] = activeQueryParams[key]; + } + } + + return handlerInfosEqual(newState.handlerInfos, state.handlerInfos) && + !getChangelist(activeQPsOnNewHandler, queryParams); + }, + + trigger: function(name) { + var args = slice.call(arguments); + trigger(this, this.currentHandlerInfos, false, args); + }, + + /** + @private + + Pluggable hook for possibly running route hooks + in a try-catch escaping manner. + + @param {Function} callback the callback that will + be asynchronously called + + @return {Promise} a promise that fulfills with the + value returned from the callback + */ + async: function(callback, label) { + return new Promise(function(resolve) { + resolve(callback()); + }, label); + }, + + /** + Hook point for logging transition status updates. + + @param {String} message The message to log. + */ + log: null + }; + + /** + @private + + Takes an Array of `HandlerInfo`s, figures out which ones are + exiting, entering, or changing contexts, and calls the + proper handler hooks. + + For example, consider the following tree of handlers. Each handler is + followed by the URL segment it handles. + + ``` + |~index ("/") + | |~posts ("/posts") + | | |-showPost ("/:id") + | | |-newPost ("/new") + | | |-editPost ("/edit") + | |~about ("/about/:id") + ``` + + Consider the following transitions: + + 1. A URL transition to `/posts/1`. + 1. Triggers the `*model` callbacks on the + `index`, `posts`, and `showPost` handlers + 2. Triggers the `enter` callback on the same + 3. Triggers the `setup` callback on the same + 2. A direct transition to `newPost` + 1. Triggers the `exit` callback on `showPost` + 2. Triggers the `enter` callback on `newPost` + 3. Triggers the `setup` callback on `newPost` + 3. A direct transition to `about` with a specified + context object + 1. Triggers the `exit` callback on `newPost` + and `posts` + 2. Triggers the `serialize` callback on `about` + 3. Triggers the `enter` callback on `about` + 4. Triggers the `setup` callback on `about` + + @param {Router} transition + @param {TransitionState} newState + */ + function setupContexts(router, newState, transition) { + var partition = partitionHandlers(router.state, newState); + + forEach(partition.exited, function(handlerInfo) { + var handler = handlerInfo.handler; + delete handler.context; + if (handler.exit) { handler.exit(); } + }); + + var oldState = router.oldState = router.state; + router.state = newState; + var currentHandlerInfos = router.currentHandlerInfos = partition.unchanged.slice(); + + try { + forEach(partition.updatedContext, function(handlerInfo) { + return handlerEnteredOrUpdated(currentHandlerInfos, handlerInfo, false, transition); + }); + + forEach(partition.entered, function(handlerInfo) { + return handlerEnteredOrUpdated(currentHandlerInfos, handlerInfo, true, transition); + }); + } catch(e) { + router.state = oldState; + router.currentHandlerInfos = oldState.handlerInfos; + throw e; + } + + router.state.queryParams = finalizeQueryParamChange(router, currentHandlerInfos, newState.queryParams); + } + + + /** + @private + + Helper method used by setupContexts. Handles errors or redirects + that may happen in enter/setup. + */ + function handlerEnteredOrUpdated(currentHandlerInfos, handlerInfo, enter, transition) { + + var handler = handlerInfo.handler, + context = handlerInfo.context; + + if (enter && handler.enter) { handler.enter(transition); } + if (transition && transition.isAborted) { + throw new TransitionAborted(); + } + + handler.context = context; + if (handler.contextDidChange) { handler.contextDidChange(); } + + if (handler.setup) { handler.setup(context, transition); } + if (transition && transition.isAborted) { + throw new TransitionAborted(); + } + + currentHandlerInfos.push(handlerInfo); + + return true; + } + + + /** + @private + + This function is called when transitioning from one URL to + another to determine which handlers are no longer active, + which handlers are newly active, and which handlers remain + active but have their context changed. + + Take a list of old handlers and new handlers and partition + them into four buckets: + + * unchanged: the handler was active in both the old and + new URL, and its context remains the same + * updated context: the handler was active in both the + old and new URL, but its context changed. The handler's + `setup` method, if any, will be called with the new + context. + * exited: the handler was active in the old URL, but is + no longer active. + * entered: the handler was not active in the old URL, but + is now active. + + The PartitionedHandlers structure has four fields: + + * `updatedContext`: a list of `HandlerInfo` objects that + represent handlers that remain active but have a changed + context + * `entered`: a list of `HandlerInfo` objects that represent + handlers that are newly active + * `exited`: a list of `HandlerInfo` objects that are no + longer active. + * `unchanged`: a list of `HanderInfo` objects that remain active. + + @param {Array[HandlerInfo]} oldHandlers a list of the handler + information for the previous URL (or `[]` if this is the + first handled transition) + @param {Array[HandlerInfo]} newHandlers a list of the handler + information for the new URL + + @return {Partition} + */ + function partitionHandlers(oldState, newState) { + var oldHandlers = oldState.handlerInfos; + var newHandlers = newState.handlerInfos; + + var handlers = { + updatedContext: [], + exited: [], + entered: [], + unchanged: [] + }; + + var handlerChanged, contextChanged, queryParamsChanged, i, l; + + for (i=0, l=newHandlers.length; i= 0; --i) { + var handlerInfo = handlerInfos[i]; + merge(params, handlerInfo.params); + if (handlerInfo.handler.inaccessibleByURL) { + urlMethod = null; + } + } + + if (urlMethod) { + params.queryParams = state.queryParams; + var url = router.recognizer.generate(handlerName, params); + + if (urlMethod === 'replaceQuery') { + if (url !== inputUrl) { + router.replaceURL(url); + } + } else if (urlMethod === 'replace') { + router.replaceURL(url); + } else { + router.updateURL(url); + } + } + } + + /** + @private + + Updates the URL (if necessary) and calls `setupContexts` + to update the router's array of `currentHandlerInfos`. + */ + function finalizeTransition(transition, newState) { + + try { + log(transition.router, transition.sequence, "Resolved all models on destination route; finalizing transition."); + + var router = transition.router, + handlerInfos = newState.handlerInfos, + seq = transition.sequence; + + // Run all the necessary enter/setup/exit hooks + setupContexts(router, newState, transition); + + // Check if a redirect occurred in enter/setup + if (transition.isAborted) { + // TODO: cleaner way? distinguish b/w targetHandlerInfos? + router.state.handlerInfos = router.currentHandlerInfos; + return reject(logAbort(transition)); + } + + updateURL(transition, newState, transition.intent.url); + + transition.isActive = false; + router.activeTransition = null; + + trigger(router, router.currentHandlerInfos, true, ['didTransition']); + + if (router.didTransition) { + router.didTransition(router.currentHandlerInfos); + } + + log(router, transition.sequence, "TRANSITION COMPLETE."); + + // Resolve with the final handler. + return handlerInfos[handlerInfos.length - 1].handler; + } catch(e) { + if (!(e instanceof TransitionAborted)) { + //var erroneousHandler = handlerInfos.pop(); + var infos = transition.state.handlerInfos; + transition.trigger(true, 'error', e, transition, infos[infos.length-1].handler); + transition.abort(); + } + + throw e; + } + } + + /** + @private + + Begins and returns a Transition based on the provided + arguments. Accepts arguments in the form of both URL + transitions and named transitions. + + @param {Router} router + @param {Array[Object]} args arguments passed to transitionTo, + replaceWith, or handleURL + */ + function doTransition(router, args, isIntermediate) { + // Normalize blank transitions to root URL transitions. + var name = args[0] || '/'; + + var lastArg = args[args.length-1]; + var queryParams = {}; + if (lastArg && lastArg.hasOwnProperty('queryParams')) { + queryParams = pop.call(args).queryParams; + } + + var intent; + if (args.length === 0) { + + log(router, "Updating query params"); + + // A query param update is really just a transition + // into the route you're already on. + var handlerInfos = router.state.handlerInfos; + intent = new NamedTransitionIntent({ + name: handlerInfos[handlerInfos.length - 1].name, + contexts: [], + queryParams: queryParams + }); + + } else if (name.charAt(0) === '/') { + + log(router, "Attempting URL transition to " + name); + intent = new URLTransitionIntent({ url: name }); + + } else { + + log(router, "Attempting transition to " + name); + intent = new NamedTransitionIntent({ + name: args[0], + contexts: slice.call(args, 1), + queryParams: queryParams + }); + } + + return router.transitionByIntent(intent, isIntermediate); + } + + function handlerInfosEqual(handlerInfos, otherHandlerInfos) { + if (handlerInfos.length !== otherHandlerInfos.length) { + return false; + } + + for (var i = 0, len = handlerInfos.length; i < len; ++i) { + if (handlerInfos[i] !== otherHandlerInfos[i]) { + return false; + } + } + return true; + } + + function finalizeQueryParamChange(router, resolvedHandlers, newQueryParams) { + // We fire a finalizeQueryParamChange event which + // gives the new route hierarchy a chance to tell + // us which query params it's consuming and what + // their final values are. If a query param is + // no longer consumed in the final route hierarchy, + // its serialized segment will be removed + // from the URL. + var finalQueryParamsArray = []; + trigger(router, resolvedHandlers, true, ['finalizeQueryParamChange', newQueryParams, finalQueryParamsArray]); + + var finalQueryParams = {}; + for (var i = 0, len = finalQueryParamsArray.length; i < len; ++i) { + var qp = finalQueryParamsArray[i]; + finalQueryParams[qp.key] = qp.value; + } + return finalQueryParams; + } + + __exports__.Router = Router; + }); +define("router/transition-intent", + ["./utils","exports"], + function(__dependency1__, __exports__) { + "use strict"; + var merge = __dependency1__.merge; + + function TransitionIntent(props) { + if (props) { + merge(this, props); + } + this.data = this.data || {}; + } + + TransitionIntent.prototype.applyToState = function(oldState) { + // Default TransitionIntent is a no-op. + return oldState; + }; + + __exports__.TransitionIntent = TransitionIntent; + }); +define("router/transition-intent/named-transition-intent", + ["../transition-intent","../transition-state","../handler-info","../utils","exports"], + function(__dependency1__, __dependency2__, __dependency3__, __dependency4__, __exports__) { + "use strict"; + var TransitionIntent = __dependency1__.TransitionIntent; + var TransitionState = __dependency2__.TransitionState; + var UnresolvedHandlerInfoByParam = __dependency3__.UnresolvedHandlerInfoByParam; + var UnresolvedHandlerInfoByObject = __dependency3__.UnresolvedHandlerInfoByObject; + var isParam = __dependency4__.isParam; + var forEach = __dependency4__.forEach; + var extractQueryParams = __dependency4__.extractQueryParams; + var oCreate = __dependency4__.oCreate; + var merge = __dependency4__.merge; + + function NamedTransitionIntent(props) { + TransitionIntent.call(this, props); + } + + NamedTransitionIntent.prototype = oCreate(TransitionIntent.prototype); + NamedTransitionIntent.prototype.applyToState = function(oldState, recognizer, getHandler, isIntermediate) { + + var partitionedArgs = extractQueryParams([this.name].concat(this.contexts)), + pureArgs = partitionedArgs[0], + queryParams = partitionedArgs[1], + handlers = recognizer.handlersFor(pureArgs[0]); + + var targetRouteName = handlers[handlers.length-1].handler; + + return this.applyToHandlers(oldState, handlers, getHandler, targetRouteName, isIntermediate); + }; + + NamedTransitionIntent.prototype.applyToHandlers = function(oldState, handlers, getHandler, targetRouteName, isIntermediate, checkingIfActive) { + + var i; + var newState = new TransitionState(); + var objects = this.contexts.slice(0); + + var invalidateIndex = handlers.length; + var nonDynamicIndexes = []; + + // Pivot handlers are provided for refresh transitions + if (this.pivotHandler) { + for (i = 0; i < handlers.length; ++i) { + if (getHandler(handlers[i].handler) === this.pivotHandler) { + invalidateIndex = i; + break; + } + } + } + + var pivotHandlerFound = !this.pivotHandler; + + for (i = handlers.length - 1; i >= 0; --i) { + var result = handlers[i]; + var name = result.handler; + var handler = getHandler(name); + + var oldHandlerInfo = oldState.handlerInfos[i]; + var newHandlerInfo = null; + + if (result.names.length > 0) { + if (i >= invalidateIndex) { + newHandlerInfo = this.createParamHandlerInfo(name, handler, result.names, objects, oldHandlerInfo); + } else { + newHandlerInfo = this.getHandlerInfoForDynamicSegment(name, handler, result.names, objects, oldHandlerInfo, targetRouteName); + } + } else { + // This route has no dynamic segment. + // Therefore treat as a param-based handlerInfo + // with empty params. This will cause the `model` + // hook to be called with empty params, which is desirable. + newHandlerInfo = this.createParamHandlerInfo(name, handler, result.names, objects, oldHandlerInfo); + nonDynamicIndexes.unshift(i); + } + + if (checkingIfActive) { + // If we're performing an isActive check, we want to + // serialize URL params with the provided context, but + // ignore mismatches between old and new context. + newHandlerInfo = newHandlerInfo.becomeResolved(null, newHandlerInfo.context); + var oldContext = oldHandlerInfo && oldHandlerInfo.context; + if (result.names.length > 0 && newHandlerInfo.context === oldContext) { + // If contexts match in isActive test, assume params also match. + // This allows for flexibility in not requiring that every last + // handler provide a `serialize` method + newHandlerInfo.params = oldHandlerInfo && oldHandlerInfo.params; + } + newHandlerInfo.context = oldContext; + } + + var handlerToUse = oldHandlerInfo; + if (i >= invalidateIndex || newHandlerInfo.shouldSupercede(oldHandlerInfo)) { + invalidateIndex = Math.min(i, invalidateIndex); + handlerToUse = newHandlerInfo; + } + + if (isIntermediate && !checkingIfActive) { + handlerToUse = handlerToUse.becomeResolved(null, handlerToUse.context); + } + + newState.handlerInfos.unshift(handlerToUse); + } + + if (objects.length > 0) { + throw new Error("More context objects were passed than there are dynamic segments for the route: " + targetRouteName); + } + + if (!isIntermediate) { + this.invalidateNonDynamicHandlers(newState.handlerInfos, nonDynamicIndexes, invalidateIndex); + } + + merge(newState.queryParams, oldState.queryParams); + merge(newState.queryParams, this.queryParams || {}); + + return newState; + }; + + NamedTransitionIntent.prototype.invalidateNonDynamicHandlers = function(handlerInfos, indexes, invalidateIndex) { + forEach(indexes, function(i) { + if (i >= invalidateIndex) { + var handlerInfo = handlerInfos[i]; + handlerInfos[i] = new UnresolvedHandlerInfoByParam({ + name: handlerInfo.name, + handler: handlerInfo.handler, + params: {} + }); + } + }); + }; + + NamedTransitionIntent.prototype.getHandlerInfoForDynamicSegment = function(name, handler, names, objects, oldHandlerInfo, targetRouteName) { + + var numNames = names.length; + var objectToUse; + if (objects.length > 0) { + + // Use the objects provided for this transition. + objectToUse = objects[objects.length - 1]; + if (isParam(objectToUse)) { + return this.createParamHandlerInfo(name, handler, names, objects, oldHandlerInfo); + } else { + objects.pop(); + } + } else if (oldHandlerInfo && oldHandlerInfo.name === name) { + // Reuse the matching oldHandlerInfo + return oldHandlerInfo; + } else { + // Ideally we should throw this error to provide maximal + // information to the user that not enough context objects + // were provided, but this proves too cumbersome in Ember + // in cases where inner template helpers are evaluated + // before parent helpers un-render, in which cases this + // error somewhat prematurely fires. + //throw new Error("Not enough context objects were provided to complete a transition to " + targetRouteName + ". Specifically, the " + name + " route needs an object that can be serialized into its dynamic URL segments [" + names.join(', ') + "]"); + return oldHandlerInfo; + } + + return new UnresolvedHandlerInfoByObject({ + name: name, + handler: handler, + context: objectToUse, + names: names + }); + }; + + NamedTransitionIntent.prototype.createParamHandlerInfo = function(name, handler, names, objects, oldHandlerInfo) { + var params = {}; + + // Soak up all the provided string/numbers + var numNames = names.length; + while (numNames--) { + + // Only use old params if the names match with the new handler + var oldParams = (oldHandlerInfo && name === oldHandlerInfo.name && oldHandlerInfo.params) || {}; + + var peek = objects[objects.length - 1]; + var paramName = names[numNames]; + if (isParam(peek)) { + params[paramName] = "" + objects.pop(); + } else { + // If we're here, this means only some of the params + // were string/number params, so try and use a param + // value from a previous handler. + if (oldParams.hasOwnProperty(paramName)) { + params[paramName] = oldParams[paramName]; + } else { + throw new Error("You didn't provide enough string/numeric parameters to satisfy all of the dynamic segments for route " + name); + } + } + } + + return new UnresolvedHandlerInfoByParam({ + name: name, + handler: handler, + params: params + }); + }; + + __exports__.NamedTransitionIntent = NamedTransitionIntent; + }); +define("router/transition-intent/url-transition-intent", + ["../transition-intent","../transition-state","../handler-info","../utils","exports"], + function(__dependency1__, __dependency2__, __dependency3__, __dependency4__, __exports__) { + "use strict"; + var TransitionIntent = __dependency1__.TransitionIntent; + var TransitionState = __dependency2__.TransitionState; + var UnresolvedHandlerInfoByParam = __dependency3__.UnresolvedHandlerInfoByParam; + var oCreate = __dependency4__.oCreate; + var merge = __dependency4__.merge; + + function URLTransitionIntent(props) { + TransitionIntent.call(this, props); + } + + URLTransitionIntent.prototype = oCreate(TransitionIntent.prototype); + URLTransitionIntent.prototype.applyToState = function(oldState, recognizer, getHandler) { + var newState = new TransitionState(); + + var results = recognizer.recognize(this.url), + queryParams = {}, + i, len; + + if (!results) { + throw new UnrecognizedURLError(this.url); + } + + var statesDiffer = false; + + for (i = 0, len = results.length; i < len; ++i) { + var result = results[i]; + var name = result.handler; + var handler = getHandler(name); + + if (handler.inaccessibleByURL) { + throw new UnrecognizedURLError(this.url); + } + + var newHandlerInfo = new UnresolvedHandlerInfoByParam({ + name: name, + handler: handler, + params: result.params + }); + + var oldHandlerInfo = oldState.handlerInfos[i]; + if (statesDiffer || newHandlerInfo.shouldSupercede(oldHandlerInfo)) { + statesDiffer = true; + newState.handlerInfos[i] = newHandlerInfo; + } else { + newState.handlerInfos[i] = oldHandlerInfo; + } + } + + merge(newState.queryParams, results.queryParams); + + return newState; + }; + + /** + Promise reject reasons passed to promise rejection + handlers for failed transitions. + */ + function UnrecognizedURLError(message) { + this.message = (message || "UnrecognizedURLError"); + this.name = "UnrecognizedURLError"; + } + + __exports__.URLTransitionIntent = URLTransitionIntent; + }); +define("router/transition-state", + ["./handler-info","./utils","rsvp","exports"], + function(__dependency1__, __dependency2__, __dependency3__, __exports__) { + "use strict"; + var ResolvedHandlerInfo = __dependency1__.ResolvedHandlerInfo; + var forEach = __dependency2__.forEach; + var promiseLabel = __dependency2__.promiseLabel; + var resolve = __dependency3__.resolve; + var reject = __dependency3__.reject; + + function TransitionState(other) { + this.handlerInfos = []; + this.queryParams = {}; + this.params = {}; + } + + TransitionState.prototype = { + handlerInfos: null, + queryParams: null, + params: null, + + promiseLabel: function(label) { + var targetName = ''; + forEach(this.handlerInfos, function(handlerInfo) { + if (targetName !== '') { + targetName += '.'; + } + targetName += handlerInfo.name; + }); + return promiseLabel("'" + targetName + "': " + label); + }, + + resolve: function(async, shouldContinue, payload) { + var self = this; + // First, calculate params for this state. This is useful + // information to provide to the various route hooks. + var params = this.params; + forEach(this.handlerInfos, function(handlerInfo) { + params[handlerInfo.name] = handlerInfo.params || {}; + }); + + payload = payload || {}; + payload.resolveIndex = 0; + + var currentState = this; + var wasAborted = false; + + // The prelude RSVP.resolve() asyncs us into the promise land. + return resolve(null, this.promiseLabel("Start transition")) + .then(resolveOneHandlerInfo, null, this.promiseLabel('Resolve handler'))['catch'](handleError, this.promiseLabel('Handle error')); + + function innerShouldContinue() { + return resolve(shouldContinue(), promiseLabel("Check if should continue"))['catch'](function(reason) { + // We distinguish between errors that occurred + // during resolution (e.g. beforeModel/model/afterModel), + // and aborts due to a rejecting promise from shouldContinue(). + wasAborted = true; + return reject(reason); + }, promiseLabel("Handle abort")); + } + + function handleError(error) { + // This is the only possible + // reject value of TransitionState#resolve + var handlerInfos = currentState.handlerInfos; + var errorHandlerIndex = payload.resolveIndex >= handlerInfos.length ? + handlerInfos.length - 1 : payload.resolveIndex; + return reject({ + error: error, + handlerWithError: currentState.handlerInfos[errorHandlerIndex].handler, + wasAborted: wasAborted, + state: currentState + }); + } + + function proceed(resolvedHandlerInfo) { + // Swap the previously unresolved handlerInfo with + // the resolved handlerInfo + currentState.handlerInfos[payload.resolveIndex++] = resolvedHandlerInfo; + + // Call the redirect hook. The reason we call it here + // vs. afterModel is so that redirects into child + // routes don't re-run the model hooks for this + // already-resolved route. + var handler = resolvedHandlerInfo.handler; + if (handler && handler.redirect) { + handler.redirect(resolvedHandlerInfo.context, payload); + } + + // Proceed after ensuring that the redirect hook + // didn't abort this transition by transitioning elsewhere. + return innerShouldContinue().then(resolveOneHandlerInfo, null, promiseLabel('Resolve handler')); + } + + function resolveOneHandlerInfo() { + if (payload.resolveIndex === currentState.handlerInfos.length) { + // This is is the only possible + // fulfill value of TransitionState#resolve + return { + error: null, + state: currentState + }; + } + + var handlerInfo = currentState.handlerInfos[payload.resolveIndex]; + + return handlerInfo.resolve(async, innerShouldContinue, payload) + .then(proceed, null, promiseLabel('Proceed')); + } + } + }; + + __exports__.TransitionState = TransitionState; + }); +define("router/transition", + ["rsvp","./handler-info","./utils","exports"], + function(__dependency1__, __dependency2__, __dependency3__, __exports__) { + "use strict"; + var reject = __dependency1__.reject; + var resolve = __dependency1__.resolve; + var ResolvedHandlerInfo = __dependency2__.ResolvedHandlerInfo; + var trigger = __dependency3__.trigger; + var slice = __dependency3__.slice; + var log = __dependency3__.log; + var promiseLabel = __dependency3__.promiseLabel; + + /** + @private + + A Transition is a thennable (a promise-like object) that represents + an attempt to transition to another route. It can be aborted, either + explicitly via `abort` or by attempting another transition while a + previous one is still underway. An aborted transition can also + be `retry()`d later. + */ + function Transition(router, intent, state, error) { + var transition = this; + this.state = state || router.state; + this.intent = intent; + this.router = router; + this.data = this.intent && this.intent.data || {}; + this.resolvedModels = {}; + this.queryParams = {}; + + if (error) { + this.promise = reject(error); + return; + } + + if (state) { + this.params = state.params; + this.queryParams = state.queryParams; + + var len = state.handlerInfos.length; + if (len) { + this.targetName = state.handlerInfos[state.handlerInfos.length-1].name; + } + + for (var i = 0; i < len; ++i) { + var handlerInfo = state.handlerInfos[i]; + if (!(handlerInfo instanceof ResolvedHandlerInfo)) { + break; + } + this.pivotHandler = handlerInfo.handler; + } + + this.sequence = Transition.currentSequence++; + this.promise = state.resolve(router.async, checkForAbort, this)['catch'](function(result) { + if (result.wasAborted) { + return reject(logAbort(transition)); + } else { + transition.trigger('error', result.error, transition, result.handlerWithError); + transition.abort(); + return reject(result.error); + } + }, promiseLabel('Handle Abort')); + } else { + this.promise = resolve(this.state); + this.params = {}; + } + + function checkForAbort() { + if (transition.isAborted) { + return reject(undefined, promiseLabel("Transition aborted - reject")); + } + } + } + + Transition.currentSequence = 0; + + Transition.prototype = { + targetName: null, + urlMethod: 'update', + intent: null, + params: null, + pivotHandler: null, + resolveIndex: 0, + handlerInfos: null, + resolvedModels: null, + isActive: true, + state: null, + + /** + @public + + The Transition's internal promise. Calling `.then` on this property + is that same as calling `.then` on the Transition object itself, but + this property is exposed for when you want to pass around a + Transition's promise, but not the Transition object itself, since + Transition object can be externally `abort`ed, while the promise + cannot. + */ + promise: null, + + /** + @public + + Custom state can be stored on a Transition's `data` object. + This can be useful for decorating a Transition within an earlier + hook and shared with a later hook. Properties set on `data` will + be copied to new transitions generated by calling `retry` on this + transition. + */ + data: null, + + /** + @public + + A standard promise hook that resolves if the transition + succeeds and rejects if it fails/redirects/aborts. + + Forwards to the internal `promise` property which you can + use in situations where you want to pass around a thennable, + but not the Transition itself. + + @param {Function} success + @param {Function} failure + */ + then: function(success, failure) { + return this.promise.then(success, failure); + }, + + /** + @public + + Aborts the Transition. Note you can also implicitly abort a transition + by initiating another transition while a previous one is underway. + */ + abort: function() { + if (this.isAborted) { return this; } + log(this.router, this.sequence, this.targetName + ": transition was aborted"); + this.isAborted = true; + this.isActive = false; + this.router.activeTransition = null; + return this; + }, + + /** + @public + + Retries a previously-aborted transition (making sure to abort the + transition if it's still active). Returns a new transition that + represents the new attempt to transition. + */ + retry: function() { + // TODO: add tests for merged state retry()s + this.abort(); + return this.router.transitionByIntent(this.intent, false); + }, + + /** + @public + + Sets the URL-changing method to be employed at the end of a + successful transition. By default, a new Transition will just + use `updateURL`, but passing 'replace' to this method will + cause the URL to update using 'replaceWith' instead. Omitting + a parameter will disable the URL change, allowing for transitions + that don't update the URL at completion (this is also used for + handleURL, since the URL has already changed before the + transition took place). + + @param {String} method the type of URL-changing method to use + at the end of a transition. Accepted values are 'replace', + falsy values, or any other non-falsy value (which is + interpreted as an updateURL transition). + + @return {Transition} this transition + */ + method: function(method) { + this.urlMethod = method; + return this; + }, + + /** + @public + + Fires an event on the current list of resolved/resolving + handlers within this transition. Useful for firing events + on route hierarchies that haven't fully been entered yet. + + Note: This method is also aliased as `send` + + @param {Boolean} [ignoreFailure=false] a boolean specifying whether unhandled events throw an error + @param {String} name the name of the event to fire + */ + trigger: function (ignoreFailure) { + var args = slice.call(arguments); + if (typeof ignoreFailure === 'boolean') { + args.shift(); + } else { + // Throw errors on unhandled trigger events by default + ignoreFailure = false; + } + trigger(this.router, this.state.handlerInfos.slice(0, this.resolveIndex + 1), ignoreFailure, args); + }, + + /** + @public + + Transitions are aborted and their promises rejected + when redirects occur; this method returns a promise + that will follow any redirects that occur and fulfill + with the value fulfilled by any redirecting transitions + that occur. + + @return {Promise} a promise that fulfills with the same + value that the final redirecting transition fulfills with + */ + followRedirects: function() { + var router = this.router; + return this.promise['catch'](function(reason) { + if (router.activeTransition) { + return router.activeTransition.followRedirects(); + } + return reject(reason); + }); + }, + + toString: function() { + return "Transition (sequence " + this.sequence + ")"; + }, + + /** + @private + */ + log: function(message) { + log(this.router, this.sequence, message); + } + }; + + // Alias 'trigger' as 'send' + Transition.prototype.send = Transition.prototype.trigger; + + /** + @private + + Logs and returns a TransitionAborted error. + */ + function logAbort(transition) { + log(transition.router, transition.sequence, "detected abort."); + return new TransitionAborted(); + } + + function TransitionAborted(message) { + this.message = (message || "TransitionAborted"); + this.name = "TransitionAborted"; + } + + __exports__.Transition = Transition; + __exports__.logAbort = logAbort; + __exports__.TransitionAborted = TransitionAborted; + }); +define("router/utils", + ["exports"], + function(__exports__) { + "use strict"; + var slice = Array.prototype.slice; + + function isArray(test) { + return Object.prototype.toString.call(test) === "[object Array]"; + } + + function merge(hash, other) { + for (var prop in other) { + if (other.hasOwnProperty(prop)) { hash[prop] = other[prop]; } + } + } + + var oCreate = Object.create || function(proto) { + function F() {} + F.prototype = proto; + return new F(); + }; + + /** + @private + + Extracts query params from the end of an array + **/ + function extractQueryParams(array) { + var len = (array && array.length), head, queryParams; + + if(len && len > 0 && array[len - 1] && array[len - 1].hasOwnProperty('queryParams')) { + queryParams = array[len - 1].queryParams; + head = slice.call(array, 0, len - 1); + return [head, queryParams]; + } else { + return [array, null]; + } + } + + /** + @private + + Coerces query param properties and array elements into strings. + **/ + function coerceQueryParamsToString(queryParams) { + for (var key in queryParams) { + if (typeof queryParams[key] === 'number') { + queryParams[key] = '' + queryParams[key]; + } else if (isArray(queryParams[key])) { + for (var i = 0, l = queryParams[key].length; i < l; i++) { + queryParams[key][i] = '' + queryParams[key][i]; + } + } + } + } + /** + @private + */ + function log(router, sequence, msg) { + if (!router.log) { return; } + + if (arguments.length === 3) { + router.log("Transition #" + sequence + ": " + msg); + } else { + msg = sequence; + router.log(msg); + } + } + + function bind(fn, context) { + var boundArgs = arguments; + return function(value) { + var args = slice.call(boundArgs, 2); + args.push(value); + return fn.apply(context, args); + }; + } + + function isParam(object) { + return (typeof object === "string" || object instanceof String || typeof object === "number" || object instanceof Number); + } + + + function forEach(array, callback) { + for (var i=0, l=array.length; i=0; i--) { + var handlerInfo = handlerInfos[i], + handler = handlerInfo.handler; + + if (handler.events && handler.events[name]) { + if (handler.events[name].apply(handler, args) === true) { + eventWasHandled = true; + } else { + return; + } + } + } + + if (!eventWasHandled && !ignoreFailure) { + throw new Error("Nothing handled the event '" + name + "'."); + } + } + + function getChangelist(oldObject, newObject) { + var key; + var results = { + all: {}, + changed: {}, + removed: {} + }; + + merge(results.all, newObject); + + var didChange = false; + coerceQueryParamsToString(oldObject); + coerceQueryParamsToString(newObject); + + // Calculate removals + for (key in oldObject) { + if (oldObject.hasOwnProperty(key)) { + if (!newObject.hasOwnProperty(key)) { + didChange = true; + results.removed[key] = oldObject[key]; + } + } + } + + // Calculate changes + for (key in newObject) { + if (newObject.hasOwnProperty(key)) { + if (isArray(oldObject[key]) && isArray(newObject[key])) { + if (oldObject[key].length !== newObject[key].length) { + results.changed[key] = newObject[key]; + didChange = true; + } else { + for (var i = 0, l = oldObject[key].length; i < l; i++) { + if (oldObject[key][i] !== newObject[key][i]) { + results.changed[key] = newObject[key]; + didChange = true; + } + } + } + } + else { + if (oldObject[key] !== newObject[key]) { + results.changed[key] = newObject[key]; + didChange = true; + } + } + } + } + + return didChange && results; + } + + function promiseLabel(label) { + return 'Router: ' + label; + } + + __exports__.trigger = trigger; + __exports__.log = log; + __exports__.oCreate = oCreate; + __exports__.merge = merge; + __exports__.extractQueryParams = extractQueryParams; + __exports__.bind = bind; + __exports__.isParam = isParam; + __exports__.forEach = forEach; + __exports__.slice = slice; + __exports__.serialize = serialize; + __exports__.getChangelist = getChangelist; + __exports__.coerceQueryParamsToString = coerceQueryParamsToString; + __exports__.promiseLabel = promiseLabel; + }); +define("router", + ["./router/router","exports"], + function(__dependency1__, __exports__) { + "use strict"; + var Router = __dependency1__.Router; + + __exports__.Router = Router; + }); +})(); + + + +(function() { +/** +@module ember +@submodule ember-routing +*/ + +function DSL(name) { + this.parent = name; + this.matches = []; +} + +DSL.prototype = { + resource: function(name, options, callback) { + Ember.assert("'basic' cannot be used as a resource name.", name !== 'basic'); + + if (arguments.length === 2 && typeof options === 'function') { + callback = options; + options = {}; + } + + if (arguments.length === 1) { + options = {}; + } + + if (typeof options.path !== 'string') { + options.path = "/" + name; + } + + if (callback) { + var dsl = new DSL(name); + route(dsl, 'loading'); + route(dsl, 'error', { path: "/_unused_dummy_error_path_route_" + name + "/:error" }); + callback.call(dsl); + this.push(options.path, name, dsl.generate()); + } else { + this.push(options.path, name, null); + } + + + }, + + push: function(url, name, callback) { + var parts = name.split('.'); + if (url === "" || url === "/" || parts[parts.length-1] === "index") { this.explicitIndex = true; } + + this.matches.push([url, name, callback]); + }, + + route: function(name, options) { + Ember.assert("'basic' cannot be used as a route name.", name !== 'basic'); + + route(this, name, options); + }, + + generate: function() { + var dslMatches = this.matches; + + if (!this.explicitIndex) { + this.route("index", { path: "/" }); + } + + return function(match) { + for (var i=0, l=dslMatches.length; i " + fullName, { fullName: fullName }); + } + + return instance; +}; + +})(); + + + +(function() { +/** +@module ember +@submodule ember-routing +*/ + +var routerJsModule = requireModule("router"); +var Router = routerJsModule.Router; +var Transition = routerJsModule.Transition; +var get = Ember.get, set = Ember.set, fmt = Ember.String.fmt; +var defineProperty = Ember.defineProperty; +var slice = Array.prototype.slice; +var forEach = Ember.EnumerableUtils.forEach; + +var DefaultView = Ember._MetamorphView; +/** + The `Ember.Router` class manages the application state and URLs. Refer to + the [routing guide](http://emberjs.com/guides/routing/) for documentation. + + @class Router + @namespace Ember + @extends Ember.Object +*/ +Ember.Router = Ember.Object.extend(Ember.Evented, { + /** + The `location` property determines the type of URL's that your + application will use. + + The following location types are currently available: + + * `hash` + * `history` + * `none` + + @property location + @default 'hash' + @see {Ember.Location} + */ + location: 'hash', + + /** + Represents the URL of the root of the application, often '/'. This prefix is + assumed on all routes defined on this router. + + @property rootURL + @default '/' + */ + rootURL: '/', + + init: function() { + this.router = this.constructor.router || this.constructor.map(Ember.K); + this._activeViews = {}; + this._setupLocation(); + + if (get(this, 'namespace.LOG_TRANSITIONS_INTERNAL')) { + this.router.log = Ember.Logger.debug; + } + }, + + /** + Represents the current URL. + + @method url + @returns {String} The current URL. + */ + url: Ember.computed(function() { + return get(this, 'location').getURL(); + }), + + /** + Initializes the current router instance and sets up the change handling + event listeners used by the instances `location` implementation. + + A property named `initialURL` will be used to determine the initial URL. + If no value is found `/` will be used. + + @method startRouting + @private + */ + startRouting: function() { + this.router = this.router || this.constructor.map(Ember.K); + + var router = this.router, + location = get(this, 'location'), + container = this.container, + self = this, + initialURL = get(this, 'initialURL'); + + + // Allow the Location class to cancel the router setup while it refreshes + // the page + if (get(location, 'cancelRouterSetup')) { + return; + } + + + this._setupRouter(router, location); + + container.register('view:default', DefaultView); + container.register('view:toplevel', Ember.View.extend()); + + location.onUpdateURL(function(url) { + self.handleURL(url); + }); + + if (typeof initialURL === "undefined") { + initialURL = location.getURL(); + } + + this.handleURL(initialURL); + }, + + /** + Handles updating the paths and notifying any listeners of the URL + change. + + Triggers the router level `didTransition` hook. + + @method didTransition + @private + */ + didTransition: function(infos) { + updatePaths(this); + + this._cancelLoadingEvent(); + + this.notifyPropertyChange('url'); + + // Put this in the runloop so url will be accurate. Seems + // less surprising than didTransition being out of sync. + Ember.run.once(this, this.trigger, 'didTransition'); + + if (get(this, 'namespace').LOG_TRANSITIONS) { + Ember.Logger.log("Transitioned into '" + Ember.Router._routePath(infos) + "'"); + } + }, + + handleURL: function(url) { + return this._doTransition('handleURL', [url]); + }, + + transitionTo: function() { + return this._doTransition('transitionTo', arguments); + }, + + intermediateTransitionTo: function() { + this.router.intermediateTransitionTo.apply(this.router, arguments); + + updatePaths(this); + + var infos = this.router.currentHandlerInfos; + if (get(this, 'namespace').LOG_TRANSITIONS) { + Ember.Logger.log("Intermediate-transitioned into '" + Ember.Router._routePath(infos) + "'"); + } + }, + + replaceWith: function() { + return this._doTransition('replaceWith', arguments); + }, + + generate: function() { + var url = this.router.generate.apply(this.router, arguments); + return this.location.formatURL(url); + }, + + /** + Determines if the supplied route is currently active. + + @method isActive + @param routeName + @returns {Boolean} + @private + */ + isActive: function(routeName) { + var router = this.router; + return router.isActive.apply(router, arguments); + }, + + send: function(name, context) { + this.router.trigger.apply(this.router, arguments); + }, + + /** + Does this router instance have the given route. + + @method hasRoute + @returns {Boolean} + @private + */ + hasRoute: function(route) { + return this.router.hasRoute(route); + }, + + /** + Resets the state of the router by clearing the current route + handlers and deactivating them. + + @private + @method reset + */ + reset: function() { + this.router.reset(); + }, + + _lookupActiveView: function(templateName) { + var active = this._activeViews[templateName]; + return active && active[0]; + }, + + _connectActiveView: function(templateName, view) { + var existing = this._activeViews[templateName]; + + if (existing) { + existing[0].off('willDestroyElement', this, existing[1]); + } + + var disconnect = function() { + delete this._activeViews[templateName]; + }; + + this._activeViews[templateName] = [view, disconnect]; + view.one('willDestroyElement', this, disconnect); + }, + + _setupLocation: function() { + var location = get(this, 'location'), + rootURL = get(this, 'rootURL'); + + if (rootURL && !this.container.has('-location-setting:root-url')) { + this.container.register('-location-setting:root-url', rootURL, { instantiate: false }); + } + + if ('string' === typeof location && this.container) { + var resolvedLocation = this.container.lookup('location:' + location); + + if ('undefined' !== typeof resolvedLocation) { + location = set(this, 'location', resolvedLocation); + } else { + // Allow for deprecated registration of custom location API's + var options = {implementation: location}; + + location = set(this, 'location', Ember.Location.create(options)); + } + } + + if (rootURL && typeof rootURL === 'string') { + location.rootURL = rootURL; + } + + // ensure that initState is called AFTER the rootURL is set on + // the location instance + if (typeof location.initState === 'function') { location.initState(); } + }, + + _getHandlerFunction: function() { + var seen = {}, container = this.container, + DefaultRoute = container.lookupFactory('route:basic'), + self = this; + + return function(name) { + var routeName = 'route:' + name, + handler = container.lookup(routeName); + + if (seen[name]) { return handler; } + + seen[name] = true; + + if (!handler) { + container.register(routeName, DefaultRoute.extend()); + handler = container.lookup(routeName); + + if (get(self, 'namespace.LOG_ACTIVE_GENERATION')) { + Ember.Logger.info("generated -> " + routeName, { fullName: routeName }); + } + } + + handler.routeName = name; + return handler; + }; + }, + + _setupRouter: function(router, location) { + var lastURL, emberRouter = this; + + router.getHandler = this._getHandlerFunction(); + + var doUpdateURL = function() { + location.setURL(lastURL); + }; + + router.updateURL = function(path) { + lastURL = path; + Ember.run.once(doUpdateURL); + }; + + if (location.replaceURL) { + var doReplaceURL = function() { + location.replaceURL(lastURL); + }; + + router.replaceURL = function(path) { + lastURL = path; + Ember.run.once(doReplaceURL); + }; + } + + router.didTransition = function(infos) { + emberRouter.didTransition(infos); + }; + }, + + _doTransition: function(method, args) { + // Normalize blank route to root URL. + args = slice.call(args); + args[0] = args[0] || '/'; + + var name = args[0], self = this, + isQueryParamsOnly = false, queryParams; + + + if (!isQueryParamsOnly && name.charAt(0) !== '/') { + Ember.assert("The route " + name + " was not found", this.router.hasRoute(name)); + } + + if (queryParams) { + // router.js expects queryParams to be passed in in + // their final serialized form, so we need to translate. + + if (!name) { + // Need to determine destination route name. + var handlerInfos = this.router.activeTransition ? + this.router.activeTransition.state.handlerInfos : + this.router.state.handlerInfos; + name = handlerInfos[handlerInfos.length - 1].name; + args.unshift(name); + } + + var qpMappings = this._queryParamNamesFor(name); + + + Ember.Router._translateQueryParams(queryParams, qpMappings.translations, name); + var value; + for (var key in queryParams) { + var descopedParam = Ember.Router._descopeQueryParam(key); + if (key in qpMappings.queryParams) { + value = queryParams[key]; + delete queryParams[key]; + queryParams[qpMappings.queryParams[key]] = value; + } else if (descopedParam in qpMappings.validQueryParams) { + value = queryParams[key]; + delete queryParams[key]; + queryParams[descopedParam] = value; + } + } + } + + var transitionPromise = this.router[method].apply(this.router, args); + + transitionPromise.then(null, function(error) { + if (error && error.name === "UnrecognizedURLError") { + Ember.assert("The URL '" + error.message + "' did not match any routes in your application"); + } + }, 'Ember: Check for Router unrecognized URL error'); + + // We want to return the configurable promise object + // so that callers of this function can use `.method()` on it, + // which obviously doesn't exist for normal RSVP promises. + return transitionPromise; + }, + + _scheduleLoadingEvent: function(transition, originRoute) { + this._cancelLoadingEvent(); + this._loadingStateTimer = Ember.run.scheduleOnce('routerTransitions', this, '_fireLoadingEvent', transition, originRoute); + }, + + _fireLoadingEvent: function(transition, originRoute) { + if (!this.router.activeTransition) { + // Don't fire an event if we've since moved on from + // the transition that put us in a loading state. + return; + } + + transition.trigger(true, 'loading', transition, originRoute); + }, + + _cancelLoadingEvent: function () { + if (this._loadingStateTimer) { + Ember.run.cancel(this._loadingStateTimer); + } + this._loadingStateTimer = null; + }, + + _queryParamNamesFor: function(routeName) { + + // TODO: add caching + + var handlerInfos = this.router.recognizer.handlersFor(routeName); + var result = { queryParams: Ember.create(null), translations: Ember.create(null), validQueryParams: Ember.create(null) }; + var routerjs = this.router; + forEach(handlerInfos, function(recogHandler) { + var route = routerjs.getHandler(recogHandler.handler); + getQueryParamsForRoute(route, result); + }); + + descopeQueryParams(result.queryParams); + + for (var k in result.queryParams) { + result.validQueryParams[result.queryParams[k]] = true; + } + return result; + }, + + _queryParamNamesForSingle: function(routeName) { + + // TODO: add caching + + var result = { queryParams: Ember.create(null), translations: Ember.create(null) }; + var route = this.router.getHandler(routeName); + + getQueryParamsForRoute(route, result); + + // Descope non duplicate params. + if (routeName !== 'application') { + var allParams = this._queryParamNamesFor(routeName); + for (var k in result.queryParams) { + result.queryParams[k] = allParams.queryParams[k]; + } + } + + return result; + }, + + /** + @private + + Utility function for fetching all the current query params + values from a controller. + */ + _queryParamOverrides: function(results, queryParams, callback) { + for (var name in queryParams) { + var parts = name.split(':'); + + var controller = controllerOrProtoFor(parts[0], this.container); + Ember.assert(fmt("Could not lookup controller '%@' while setting up query params", [controller]), controller); + + // Now assign the final URL-serialized key-value pair, + // e.g. "foo[propName]": "value" + results[queryParams[name]] = get(controller, parts[1]); + + if (callback) { + // Give callback a chance to override. + callback(name, queryParams[name], name); + } + } + } +}); + +/** + @private + */ +function getQueryParamsForRoute(route, result) { + var controllerName = route.controllerName || route.routeName, + controller = controllerOrProtoFor(controllerName, route.container), + queryParams = get(controller, 'queryParams'); + + if (queryParams) { + forEach(queryParams, function(propName) { + + var parts = propName.split(':'); + + var urlKeyName; + if (parts.length > 1) { + urlKeyName = parts[1]; + } else { + // TODO: use _queryParamScope here? + if (controllerName !== 'application') { + urlKeyName = controllerName + '[' + propName + ']'; + } else { + urlKeyName = propName; + } + } + + var controllerFullname = controllerName + ':' + propName; + + result.queryParams[controllerFullname] = urlKeyName; + result.translations[parts[0]] = controllerFullname; + }); + } +} + +function controllerOrProtoFor(controllerName, container) { + var fullName = 'controller:' + controllerName; + if (container.cache.has(fullName)) { + return container.lookup(fullName); + } else { + // Controller hasn't been instantiated yet; just return its proto. + var controllerClass = container.lookupFactory(fullName); + if (controllerClass && typeof controllerClass.proto === 'function') { + return controllerClass.proto(); + } else { + return {}; + } + } +} + +function descopeQueryParams(params) { + var paramCounts = {}, + descopedParam, + k; + + // Loop through params and count the occurance of descoped param + for (k in params) { + descopedParam = Ember.Router._descopeQueryParam(params[k]); + + if (!paramCounts[descopedParam]) { + paramCounts[descopedParam] = 1; + } else { + paramCounts[descopedParam] = paramCounts[descopedParam] + 1; + } + } + + // Loop through again descoping params if the descoped key only occurs once + for (k in params) { + descopedParam = Ember.Router._descopeQueryParam(params[k]); + + if (paramCounts[descopedParam] === 1) { + params[k] = descopedParam; + } + } +} + +/** + Helper function for iterating root-ward, starting + from (but not including) the provided `originRoute`. + + Returns true if the last callback fired requested + to bubble upward. + + @private + */ +function forEachRouteAbove(originRoute, transition, callback) { + var handlerInfos = transition.state.handlerInfos, + originRouteFound = false; + + for (var i = handlerInfos.length - 1; i >= 0; --i) { + var handlerInfo = handlerInfos[i], + route = handlerInfo.handler; + + if (!originRouteFound) { + if (originRoute === route) { + originRouteFound = true; + } + continue; + } + + if (callback(route, handlerInfos[i + 1].handler) !== true) { + return false; + } + } + return true; +} + +// These get invoked when an action bubbles above ApplicationRoute +// and are not meant to be overridable. +var defaultActionHandlers = { + + willResolveModel: function(transition, originRoute) { + originRoute.router._scheduleLoadingEvent(transition, originRoute); + }, + + error: function(error, transition, originRoute) { + // Attempt to find an appropriate error substate to enter. + var router = originRoute.router; + + var tryTopLevel = forEachRouteAbove(originRoute, transition, function(route, childRoute) { + var childErrorRouteName = findChildRouteName(route, childRoute, 'error'); + if (childErrorRouteName) { + router.intermediateTransitionTo(childErrorRouteName, error); + return; + } + return true; + }); + + if (tryTopLevel) { + // Check for top-level error state to enter. + if (routeHasBeenDefined(originRoute.router, 'application_error')) { + router.intermediateTransitionTo('application_error', error); + return; + } + } else { + // Don't fire an assertion if we found an error substate. + return; + } + + Ember.Logger.error('Error while loading route: ' + (error && error.stack)); + }, + + loading: function(transition, originRoute) { + // Attempt to find an appropriate loading substate to enter. + var router = originRoute.router; + + var tryTopLevel = forEachRouteAbove(originRoute, transition, function(route, childRoute) { + var childLoadingRouteName = findChildRouteName(route, childRoute, 'loading'); + + if (childLoadingRouteName) { + router.intermediateTransitionTo(childLoadingRouteName); + return; + } + + // Don't bubble above pivot route. + if (transition.pivotHandler !== route) { + return true; + } + }); + + if (tryTopLevel) { + // Check for top-level loading state to enter. + if (routeHasBeenDefined(originRoute.router, 'application_loading')) { + router.intermediateTransitionTo('application_loading'); + return; + } + } + } +}; + +function findChildRouteName(parentRoute, originatingChildRoute, name) { + var router = parentRoute.router, + childName, + targetChildRouteName = originatingChildRoute.routeName.split('.').pop(), + namespace = parentRoute.routeName === 'application' ? '' : parentRoute.routeName + '.'; + + + // Second, try general loading state, e.g. 'loading' + childName = namespace + name; + if (routeHasBeenDefined(router, childName)) { + return childName; + } +} + +function routeHasBeenDefined(router, name) { + var container = router.container; + return router.hasRoute(name) && + (container.has('template:' + name) || container.has('route:' + name)); +} + +function triggerEvent(handlerInfos, ignoreFailure, args) { + var name = args.shift(); + + if (!handlerInfos) { + if (ignoreFailure) { return; } + throw new Ember.Error("Can't trigger action '" + name + "' because your app hasn't finished transitioning into its first route. To trigger an action on destination routes during a transition, you can call `.send()` on the `Transition` object passed to the `model/beforeModel/afterModel` hooks."); + } + + var eventWasHandled = false; + + for (var i = handlerInfos.length - 1; i >= 0; i--) { + var handlerInfo = handlerInfos[i], + handler = handlerInfo.handler; + + if (handler._actions && handler._actions[name]) { + if (handler._actions[name].apply(handler, args) === true) { + eventWasHandled = true; + } else { + return; + } + } + } + + if (defaultActionHandlers[name]) { + defaultActionHandlers[name].apply(null, args); + return; + } + + if (!eventWasHandled && !ignoreFailure) { + throw new Ember.Error("Nothing handled the action '" + name + "'. If you did handle the action, this error can be caused by returning true from an action handler in a controller, causing the action to bubble."); + } +} + +function updatePaths(router) { + var appController = router.container.lookup('controller:application'); + + if (!appController) { + // appController might not exist when top-level loading/error + // substates have been entered since ApplicationRoute hasn't + // actually been entered at that point. + return; + } + + var infos = router.router.currentHandlerInfos, + path = Ember.Router._routePath(infos); + + if (!('currentPath' in appController)) { + defineProperty(appController, 'currentPath'); + } + + set(appController, 'currentPath', path); + + if (!('currentRouteName' in appController)) { + defineProperty(appController, 'currentRouteName'); + } + + set(appController, 'currentRouteName', infos[infos.length - 1].name); +} + +Ember.Router.reopenClass({ + router: null, + map: function(callback) { + var router = this.router; + if (!router) { + router = new Router(); + router.callbacks = []; + router.triggerEvent = triggerEvent; + this.reopenClass({ router: router }); + } + + var dsl = Ember.RouterDSL.map(function() { + this.resource('application', { path: "/" }, function() { + for (var i=0; i < router.callbacks.length; i++) { + router.callbacks[i].call(this); + } + callback.call(this); + }); + }); + + router.callbacks.push(callback); + router.map(dsl.generate()); + return router; + }, + + _routePath: function(handlerInfos) { + var path = []; + + // We have to handle coalescing resource names that + // are prefixed with their parent's names, e.g. + // ['foo', 'foo.bar.baz'] => 'foo.bar.baz', not 'foo.foo.bar.baz' + + function intersectionMatches(a1, a2) { + for (var i = 0, len = a1.length; i < len; ++i) { + if (a1[i] !== a2[i]) { + return false; + } + } + return true; + } + + for (var i=1, l=handlerInfos.length; i 0 ? !Ember.isNone(arguments[0]) : true); + + var namePassed = !!name; + + if (typeof name === 'object' && !options) { + options = name; + name = this.routeName; + } + + options = options || {}; + + var templateName; + + if (name) { + name = name.replace(/\//g, '.'); + templateName = name; + } else { + name = this.routeName; + templateName = this.templateName || name; + } + + var viewName = options.view || this.viewName || name; + + var container = this.container, + view = container.lookup('view:' + viewName), + template = view ? view.get('template') : null; + + if (!template) { + template = container.lookup('template:' + templateName); + } + + if (!view && !template) { + Ember.assert("Could not find \"" + name + "\" template or view.", !namePassed); + if (get(this.router, 'namespace.LOG_VIEW_LOOKUPS')) { + Ember.Logger.info("Could not find \"" + name + "\" template or view. Nothing will be rendered", { fullName: 'template:' + name }); + } + return; + } + + options = normalizeOptions(this, name, template, options); + view = setupView(view, container, options); + + if (options.outlet === 'main') { this.lastRenderedTemplate = name; } + + appendView(this, view, options); + }, + + /** + Disconnects a view that has been rendered into an outlet. + + You may pass any or all of the following options to `disconnectOutlet`: + + * `outlet`: the name of the outlet to clear (default: 'main') + * `parentView`: the name of the view containing the outlet to clear + (default: the view rendered by the parent route) + + Example: + + ```js + App.ApplicationRoute = App.Route.extend({ + actions: { + showModal: function(evt) { + this.render(evt.modalName, { + outlet: 'modal', + into: 'application' + }); + }, + hideModal: function(evt) { + this.disconnectOutlet({ + outlet: 'modal', + parentView: 'application' + }); + } + } + }); + ``` + + Alternatively, you can pass the `outlet` name directly as a string. + + Example: + + ```js + hideModal: function(evt) { + this.disconnectOutlet('modal'); + } + ``` + + @method disconnectOutlet + @param {Object|String} options the options hash or outlet name + */ + disconnectOutlet: function(options) { + if (!options || typeof options === "string") { + var outletName = options; + options = {}; + options.outlet = outletName; + } + options.parentView = options.parentView ? options.parentView.replace(/\//g, '.') : parentTemplate(this); + options.outlet = options.outlet || 'main'; + + var parentView = this.router._lookupActiveView(options.parentView); + if (parentView) { parentView.disconnectOutlet(options.outlet); } + }, + + willDestroy: function() { + this.teardownViews(); + }, + + /** + @private + + @method teardownViews + */ + teardownViews: function() { + // Tear down the top level view + if (this.teardownTopLevelView) { this.teardownTopLevelView(); } + + // Tear down any outlets rendered with 'into' + var teardownOutletViews = this.teardownOutletViews || []; + a_forEach(teardownOutletViews, function(teardownOutletView) { + teardownOutletView(); + }); + + delete this.teardownTopLevelView; + delete this.teardownOutletViews; + delete this.lastRenderedTemplate; + } +}); + + + +function parentRoute(route) { + var handlerInfos = route.router.router.state.handlerInfos; + + if (!handlerInfos) { return; } + + var parent, current; + + for (var i=0, l=handlerInfos.length; i maximumContexts) + currentWhen = routeArgs[0]; + + var isActive = router.isActive.apply(router, [currentWhen].concat(contexts)); + + if (isActive) { return get(this, 'activeClass'); } + }).property('resolvedParams', 'routeArgs'), + + /** + Accessed as a classname binding to apply the `LinkView`'s `loadingClass` + CSS `class` to the element when the link is loading. + + A `LinkView` is considered loading when it has at least one + parameter whose value is currently null or undefined. During + this time, clicking the link will perform no transition and + emit a warning that the link is still in a loading state. + + @property loading + **/ + loading: Ember.computed(function computeLinkViewLoading() { + if (!get(this, 'routeArgs')) { return get(this, 'loadingClass'); } + }).property('routeArgs'), + + /** + Returns the application's main router from the container. + + @private + @property router + **/ + router: Ember.computed(function() { + return get(this, 'controller').container.lookup('router:main'); + }), + + /** + Event handler that invokes the link, activating the associated route. + + @private + @method _invoke + @param {Event} event + */ + _invoke: function(event) { + if (!isSimpleClick(event)) { return true; } + + if (this.preventDefault !== false) { event.preventDefault(); } + if (this.bubbles === false) { event.stopPropagation(); } + + if (get(this, '_isDisabled')) { return false; } + + if (get(this, 'loading')) { + Ember.Logger.warn("This link-to is in an inactive loading state because at least one of its parameters presently has a null/undefined value, or the provided route name is invalid."); + return false; + } + + var router = get(this, 'router'), + routeArgs = get(this, 'routeArgs'); + + var transition; + if (get(this, 'replace')) { + transition = router.replaceWith.apply(router, routeArgs); + } else { + transition = router.transitionTo.apply(router, routeArgs); + } + + // Schedule eager URL update, but after we've given the transition + // a chance to synchronously redirect. + + // We need to always generate the URL instead of using the href because + // the href will include any rootURL set, but the router expects a URL + // without it! Note that we don't use the first level router because it + // calls location.formatURL(), which also would add the rootURL! + var url = router.router.generate.apply(router.router, get(this, 'routeArgs')); + Ember.run.scheduleOnce('routerTransitions', this, this._eagerUpdateUrl, transition, url); + + }, + + /** + @private + */ + _eagerUpdateUrl: function(transition, href) { + if (!transition.isActive || !transition.urlMethod) { + // transition was aborted, already ran to completion, + // or it has a null url-updated method. + return; + } + + if (href.indexOf('#') === 0) { + href = href.slice(1); + } + + // Re-use the routerjs hooks set up by the Ember router. + var routerjs = get(this, 'router.router'); + if (transition.urlMethod === 'update') { + routerjs.updateURL(href); + } else if (transition.urlMethod === 'replace') { + routerjs.replaceURL(href); + } + + // Prevent later update url refire. + transition.method(null); + }, + + /** + Computed property that returns an array of the + resolved parameters passed to the `link-to` helper, + e.g.: + + ```hbs + {{link-to a b '123' c}} + ``` + + will generate a `resolvedParams` of: + + ```js + [aObject, bObject, '123', cObject] + ``` + + @private + @property + @return {Array} + */ + resolvedParams: Ember.computed(function() { + var parameters = this.parameters, + options = parameters.options, + types = options.types, + data = options.data; + + if (parameters.params.length === 0) { + var appController = this.container.lookup('controller:application'); + return [get(appController, 'currentRouteName')]; + } else { + return resolveParams(parameters.context, parameters.params, { types: types, data: data }); + } + }).property('router.url'), + + /** + Computed property that returns the current route name and + any dynamic segments. + + @private + @property + @return {Array} An array with the route name and any dynamic segments + */ + routeArgs: Ember.computed(function computeLinkViewRouteArgs() { + var resolvedParams = get(this, 'resolvedParams').slice(0), + router = get(this, 'router'), + namedRoute = resolvedParams[0]; + + if (!namedRoute) { return; } + + Ember.assert(fmt("The attempt to link-to route '%@' failed. " + + "The router did not find '%@' in its possible routes: '%@'", + [namedRoute, namedRoute, Ember.keys(router.router.recognizer.names).join("', '")]), + router.hasRoute(namedRoute)); + + //normalize route name + var handlers = router.router.recognizer.handlersFor(namedRoute); + var normalizedPath = handlers[handlers.length - 1].handler; + if (namedRoute !== normalizedPath) { + this.set('currentWhen', namedRoute); + namedRoute = handlers[handlers.length - 1].handler; + resolvedParams[0] = namedRoute; + } + + for (var i = 1, len = resolvedParams.length; i < len; ++i) { + var param = resolvedParams[i]; + if (param === null || typeof param === 'undefined') { + // If contexts aren't present, consider the linkView unloaded. + return; + } + } + + + return resolvedParams; + }).property('resolvedParams', 'queryParams'), + + queryParamsObject: null, + queryParams: Ember.computed(function computeLinkViewQueryParams() { + + var queryParamsObject = get(this, 'queryParamsObject'), + suppliedParams = {}; + + if (queryParamsObject) { + Ember.merge(suppliedParams, queryParamsObject.values); + } + + var resolvedParams = get(this, 'resolvedParams'), + router = get(this, 'router'), + routeName = resolvedParams[0], + paramsForRoute = router._queryParamNamesFor(routeName), + queryParams = paramsForRoute.queryParams, + translations = paramsForRoute.translations, + paramsForRecognizer = {}; + + // Normalize supplied params into their long-form name + // e.g. 'foo' -> 'controllername:foo' + translateQueryParams(suppliedParams, translations, routeName); + + var helperParameters = this.parameters; + router._queryParamOverrides(paramsForRecognizer, queryParams, function(name, resultsName) { + if (!(name in suppliedParams)) { return; } + + var parts = name.split(':'); + + var type = queryParamsObject.types[parts[1]]; + + var value; + if (type === 'ID') { + var normalizedPath = Ember.Handlebars.normalizePath(helperParameters.context, suppliedParams[name], helperParameters.options.data); + value = Ember.Handlebars.get(normalizedPath.root, normalizedPath.path, helperParameters.options); + } else { + value = suppliedParams[name]; + } + + delete suppliedParams[name]; + + paramsForRecognizer[resultsName] = value; + }); + + return paramsForRecognizer; + }).property('resolvedParams.[]'), + + /** + Sets the element's `href` attribute to the url for + the `LinkView`'s targeted route. + + If the `LinkView`'s `tagName` is changed to a value other + than `a`, this property will be ignored. + + @property href + **/ + href: Ember.computed(function computeLinkViewHref() { + if (get(this, 'tagName') !== 'a') { return; } + + var router = get(this, 'router'), + routeArgs = get(this, 'routeArgs'); + + return routeArgs ? router.generate.apply(router, routeArgs) : get(this, 'loadingHref'); + }).property('routeArgs'), + + /** + The default href value to use while a link-to is loading. + Only applies when tagName is 'a' + + @property loadingHref + @type String + @default # + */ + loadingHref: '#' + }); + + LinkView.toString = function() { return "LinkView"; }; + + /** + The `{{link-to}}` helper renders a link to the supplied + `routeName` passing an optionally supplied model to the + route as its `model` context of the route. The block + for `{{link-to}}` becomes the innerHTML of the rendered + element: + + ```handlebars + {{#link-to 'photoGallery'}} + Great Hamster Photos + {{/link-to}} + ``` + + ```html + + Great Hamster Photos + + ``` + + ### Supplying a tagName + By default `{{link-to}}` renders an `` element. This can + be overridden for a single use of `{{link-to}}` by supplying + a `tagName` option: + + ```handlebars + {{#link-to 'photoGallery' tagName="li"}} + Great Hamster Photos + {{/link-to}} + ``` + + ```html +
  • + Great Hamster Photos +
  • + ``` + + To override this option for your entire application, see + "Overriding Application-wide Defaults". + + ### Disabling the `link-to` helper + By default `{{link-to}}` is enabled. + any passed value to `disabled` helper property will disable the `link-to` helper. + + static use: the `disabled` option: + + ```handlebars + {{#link-to 'photoGallery' disabled=true}} + Great Hamster Photos + {{/link-to}} + ``` + + dynamic use: the `disabledWhen` option: + + ```handlebars + {{#link-to 'photoGallery' disabledWhen=controller.someProperty}} + Great Hamster Photos + {{/link-to}} + ``` + + any passed value to `disabled` will disable it except `undefined`. + to ensure that only `true` disable the `link-to` helper you can + override the global behaviour of `Ember.LinkView`. + + ```javascript + Ember.LinkView.reopen({ + disabled: Ember.computed(function(key, value) { + if (value !== undefined) { + this.set('_isDisabled', value === true); + } + return value === true ? get(this, 'disabledClass') : false; + }) + }); + ``` + + see "Overriding Application-wide Defaults" for more. + + ### Handling `href` + `{{link-to}}` will use your application's Router to + fill the element's `href` property with a url that + matches the path to the supplied `routeName` for your + routers's configured `Location` scheme, which defaults + to Ember.HashLocation. + + ### Handling current route + `{{link-to}}` will apply a CSS class name of 'active' + when the application's current route matches + the supplied routeName. For example, if the application's + current route is 'photoGallery.recent' the following + use of `{{link-to}}`: + + ```handlebars + {{#link-to 'photoGallery.recent'}} + Great Hamster Photos from the last week + {{/link-to}} + ``` + + will result in + + ```html +
    + Great Hamster Photos + + ``` + + The CSS class name used for active classes can be customized + for a single use of `{{link-to}}` by passing an `activeClass` + option: + + ```handlebars + {{#link-to 'photoGallery.recent' activeClass="current-url"}} + Great Hamster Photos from the last week + {{/link-to}} + ``` + + ```html + + Great Hamster Photos + + ``` + + To override this option for your entire application, see + "Overriding Application-wide Defaults". + + ### Supplying a model + An optional model argument can be used for routes whose + paths contain dynamic segments. This argument will become + the model context of the linked route: + + ```javascript + App.Router.map(function() { + this.resource("photoGallery", {path: "hamster-photos/:photo_id"}); + }); + ``` + + ```handlebars + {{#link-to 'photoGallery' aPhoto}} + {{aPhoto.title}} + {{/link-to}} + ``` + + ```html + + Tomster + + ``` + + ### Supplying multiple models + For deep-linking to route paths that contain multiple + dynamic segments, multiple model arguments can be used. + As the router transitions through the route path, each + supplied model argument will become the context for the + route with the dynamic segments: + + ```javascript + App.Router.map(function() { + this.resource("photoGallery", {path: "hamster-photos/:photo_id"}, function() { + this.route("comment", {path: "comments/:comment_id"}); + }); + }); + ``` + This argument will become the model context of the linked route: + + ```handlebars + {{#link-to 'photoGallery.comment' aPhoto comment}} + {{comment.body}} + {{/link-to}} + ``` + + ```html + + A+++ would snuggle again. + + ``` + + ### Supplying an explicit dynamic segment value + If you don't have a model object available to pass to `{{link-to}}`, + an optional string or integer argument can be passed for routes whose + paths contain dynamic segments. This argument will become the value + of the dynamic segment: + + ```javascript + App.Router.map(function() { + this.resource("photoGallery", {path: "hamster-photos/:photo_id"}); + }); + ``` + + ```handlebars + {{#link-to 'photoGallery' aPhotoId}} + {{aPhoto.title}} + {{/link-to}} + ``` + + ```html + + Tomster + + ``` + + When transitioning into the linked route, the `model` hook will + be triggered with parameters including this passed identifier. + + ### Allowing Default Action + + By default the `{{link-to}}` helper prevents the default browser action + by calling `preventDefault()` as this sort of action bubbling is normally + handled internally and we do not want to take the browser to a new URL (for + example). + + If you need to override this behavior specify `preventDefault=false` in + your template: + + ```handlebars + {{#link-to 'photoGallery' aPhotoId preventDefault=false}} + {{aPhotoId.title}} + {{/link-to}} + ``` + + ### Overriding attributes + You can override any given property of the Ember.LinkView + that is generated by the `{{link-to}}` helper by passing + key/value pairs, like so: + + ```handlebars + {{#link-to aPhoto tagName='li' title='Following this link will change your life' classNames='pic sweet'}} + Uh-mazing! + {{/link-to}} + ``` + + See [Ember.LinkView](/api/classes/Ember.LinkView.html) for a + complete list of overrideable properties. Be sure to also + check out inherited properties of `LinkView`. + + ### Overriding Application-wide Defaults + ``{{link-to}}`` creates an instance of Ember.LinkView + for rendering. To override options for your entire + application, reopen Ember.LinkView and supply the + desired values: + + ``` javascript + Ember.LinkView.reopen({ + activeClass: "is-active", + tagName: 'li' + }) + ``` + + It is also possible to override the default event in + this manner: + + ``` javascript + Ember.LinkView.reopen({ + eventName: 'customEventName' + }); + ``` + + @method link-to + @for Ember.Handlebars.helpers + @param {String} routeName + @param {Object} [context]* + @param [options] {Object} Handlebars key/value pairs of options, you can override any property of Ember.LinkView + @return {String} HTML string + @see {Ember.LinkView} + */ + Ember.Handlebars.registerHelper('link-to', function linkToHelper(name) { + var options = slice.call(arguments, -1)[0], + params = slice.call(arguments, 0, -1), + hash = options.hash; + + if (params[params.length - 1] instanceof QueryParams) { + hash.queryParamsObject = params.pop(); + } + + hash.disabledBinding = hash.disabledWhen; + + if (!options.fn) { + var linkTitle = params.shift(); + var linkType = options.types.shift(); + var context = this; + if (linkType === 'ID') { + options.linkTextPath = linkTitle; + options.fn = function() { + return Ember.Handlebars.getEscaped(context, linkTitle, options); + }; + } else { + options.fn = function() { + return linkTitle; + }; + } + } + + hash.parameters = { + context: this, + options: options, + params: params + }; + + return Ember.Handlebars.helpers.view.call(this, LinkView, options); + }); + + + + /** + See [link-to](/api/classes/Ember.Handlebars.helpers.html#method_link-to) + + @method linkTo + @for Ember.Handlebars.helpers + @deprecated + @param {String} routeName + @param {Object} [context]* + @return {String} HTML string + */ + Ember.Handlebars.registerHelper('linkTo', function linkToHelper() { + Ember.warn("The 'linkTo' view helper is deprecated in favor of 'link-to'"); + return Ember.Handlebars.helpers['link-to'].apply(this, arguments); + }); +}); + + + +})(); + + + +(function() { +/** +@module ember +@submodule ember-routing +*/ + +var get = Ember.get, set = Ember.set; +Ember.onLoad('Ember.Handlebars', function(Handlebars) { + /** + @module ember + @submodule ember-routing + */ + + Handlebars.OutletView = Ember.ContainerView.extend(Ember._Metamorph); + + /** + The `outlet` helper is a placeholder that the router will fill in with + the appropriate template based on the current state of the application. + + ``` handlebars + {{outlet}} + ``` + + By default, a template based on Ember's naming conventions will be rendered + into the `outlet` (e.g. `App.PostsRoute` will render the `posts` template). + + You can render a different template by using the `render()` method in the + route's `renderTemplate` hook. The following will render the `favoritePost` + template into the `outlet`. + + ``` javascript + App.PostsRoute = Ember.Route.extend({ + renderTemplate: function() { + this.render('favoritePost'); + } + }); + ``` + + You can create custom named outlets for more control. + + ``` handlebars + {{outlet 'favoritePost'}} + {{outlet 'posts'}} + ``` + + Then you can define what template is rendered into each outlet in your + route. + + + ``` javascript + App.PostsRoute = Ember.Route.extend({ + renderTemplate: function() { + this.render('favoritePost', { outlet: 'favoritePost' }); + this.render('posts', { outlet: 'posts' }); + } + }); + ``` + + You can specify the view that the outlet uses to contain and manage the + templates rendered into it. + + ``` handlebars + {{outlet view='sectionContainer'}} + ``` + + ``` javascript + App.SectionContainer = Ember.ContainerView.extend({ + tagName: 'section', + classNames: ['special'] + }); + ``` + + @method outlet + @for Ember.Handlebars.helpers + @param {String} property the property on the controller + that holds the view for this outlet + @return {String} HTML string + */ + Handlebars.registerHelper('outlet', function outletHelper(property, options) { + + var outletSource, + container, + viewName, + viewClass, + viewFullName; + + if (property && property.data && property.data.isRenderData) { + options = property; + property = 'main'; + } + + container = options.data.view.container; + + outletSource = options.data.view; + while (!outletSource.get('template.isTop')) { + outletSource = outletSource.get('_parentView'); + } + + // provide controller override + viewName = options.hash.view; + + if (viewName) { + viewFullName = 'view:' + viewName; + Ember.assert("Using a quoteless view parameter with {{outlet}} is not supported. Please update to quoted usage '{{outlet \"" + viewName + "\"}}.", options.hashTypes.view !== 'ID'); + Ember.assert("The view name you supplied '" + viewName + "' did not resolve to a view.", container.has(viewFullName)); + } + + viewClass = viewName ? container.lookupFactory(viewFullName) : options.hash.viewClass || Handlebars.OutletView; + + options.data.view.set('outletSource', outletSource); + options.hash.currentViewBinding = '_view.outletSource._outlets.' + property; + + return Handlebars.helpers.view.call(this, viewClass, options); + }); +}); + +})(); + + + +(function() { +/** +@module ember +@submodule ember-routing +*/ + +var get = Ember.get, set = Ember.set; +Ember.onLoad('Ember.Handlebars', function(Handlebars) { + + /** + Calling ``{{render}}`` from within a template will insert another + template that matches the provided name. The inserted template will + access its properties on its own controller (rather than the controller + of the parent template). + + If a view class with the same name exists, the view class also will be used. + + Note: A given controller may only be used *once* in your app in this manner. + A singleton instance of the controller will be created for you. + + Example: + + ```javascript + App.NavigationController = Ember.Controller.extend({ + who: "world" + }); + ``` + + ```handlebars + + Hello, {{who}}. + ``` + + ```handelbars + +

    My great app

    + {{render "navigation"}} + ``` + + ```html +

    My great app

    +
    + Hello, world. +
    + ``` + + Optionally you may provide a second argument: a property path + that will be bound to the `model` property of the controller. + + If a `model` property path is specified, then a new instance of the + controller will be created and `{{render}}` can be used multiple times + with the same name. + + For example if you had this `author` template. + + ```handlebars +
    + Written by {{firstName}} {{lastName}}. + Total Posts: {{postCount}} +
    + ``` + + You could render it inside the `post` template using the `render` helper. + + ```handlebars +
    +

    {{title}}

    +
    {{body}}
    + {{render "author" author}} +
    + ``` + + @method render + @for Ember.Handlebars.helpers + @param {String} name + @param {Object?} contextString + @param {Hash} options + @return {String} HTML string + */ + Ember.Handlebars.registerHelper('render', function renderHelper(name, contextString, options) { + var length = arguments.length; + + var contextProvided = length === 3, + container, router, controller, view, context, lookupOptions; + + container = (options || contextString).data.keywords.controller.container; + router = container.lookup('router:main'); + + if (length === 2) { + // use the singleton controller + options = contextString; + contextString = undefined; + Ember.assert("You can only use the {{render}} helper once without a model object as its second argument, as in {{render \"post\" post}}.", !router || !router._lookupActiveView(name)); + } else if (length === 3) { + // create a new controller + context = Ember.Handlebars.get(options.contexts[1], contextString, options); + } else { + throw Ember.Error("You must pass a templateName to render"); + } + + Ember.deprecate("Using a quoteless parameter with {{render}} is deprecated. Please update to quoted usage '{{render \"" + name + "\"}}.", options.types[0] !== 'ID'); + + // # legacy namespace + name = name.replace(/\//g, '.'); + // \ legacy slash as namespace support + + + view = container.lookup('view:' + name) || container.lookup('view:default'); + + // provide controller override + var controllerName = options.hash.controller || name; + var controllerFullName = 'controller:' + controllerName; + + if (options.hash.controller) { + Ember.assert("The controller name you supplied '" + controllerName + "' did not resolve to a controller.", container.has(controllerFullName)); + } + + var parentController = options.data.keywords.controller; + + // choose name + if (length > 2) { + var factory = container.lookupFactory(controllerFullName) || + Ember.generateControllerFactory(container, controllerName, context); + + controller = factory.create({ + model: context, + parentController: parentController, + target: parentController + }); + + } else { + controller = container.lookup(controllerFullName) || + Ember.generateController(container, controllerName); + + controller.setProperties({ + target: parentController, + parentController: parentController + }); + } + + var root = options.contexts[1]; + + if (root) { + view.registerObserver(root, contextString, function() { + controller.set('model', Ember.Handlebars.get(root, contextString, options)); + }); + } + + options.hash.viewName = Ember.String.camelize(name); + + var templateName = 'template:' + name; + Ember.assert("You used `{{render '" + name + "'}}`, but '" + name + "' can not be found as either a template or a view.", container.has("view:" + name) || container.has(templateName) || options.fn); + options.hash.template = container.lookup(templateName); + + options.hash.controller = controller; + + if (router && !context) { + router._connectActiveView(name, view); + } + + Ember.Handlebars.helpers.view.call(this, view, options); + }); +}); + +})(); + + + +(function() { +/** +@module ember +@submodule ember-routing +*/ +Ember.onLoad('Ember.Handlebars', function(Handlebars) { + + var resolveParams = Ember.Router.resolveParams, + isSimpleClick = Ember.ViewUtils.isSimpleClick; + + var EmberHandlebars = Ember.Handlebars, + handlebarsGet = EmberHandlebars.get, + SafeString = EmberHandlebars.SafeString, + forEach = Ember.ArrayPolyfills.forEach, + get = Ember.get, + a_slice = Array.prototype.slice; + + function args(options, actionName) { + var ret = []; + if (actionName) { ret.push(actionName); } + + var types = options.options.types.slice(1), + data = options.options.data; + + return ret.concat(resolveParams(options.context, options.params, { types: types, data: data })); + } + + var ActionHelper = EmberHandlebars.ActionHelper = { + registeredActions: {} + }; + + var keys = ["alt", "shift", "meta", "ctrl"]; + + var POINTER_EVENT_TYPE_REGEX = /^click|mouse|touch/; + + var isAllowedEvent = function(event, allowedKeys) { + if (typeof allowedKeys === "undefined") { + if (POINTER_EVENT_TYPE_REGEX.test(event.type)) { + return isSimpleClick(event); + } else { + allowedKeys = ''; + } + } + + if (allowedKeys.indexOf("any") >= 0) { + return true; + } + + var allowed = true; + + forEach.call(keys, function(key) { + if (event[key + "Key"] && allowedKeys.indexOf(key) === -1) { + allowed = false; + } + }); + + return allowed; + }; + + ActionHelper.registerAction = function(actionNameOrPath, options, allowedKeys) { + var actionId = ++Ember.uuid; + + ActionHelper.registeredActions[actionId] = { + eventName: options.eventName, + handler: function handleRegisteredAction(event) { + if (!isAllowedEvent(event, allowedKeys)) { return true; } + + if (options.preventDefault !== false) { + event.preventDefault(); + } + + if (options.bubbles === false) { + event.stopPropagation(); + } + + var target = options.target, + actionName; + + if (target.target) { + target = handlebarsGet(target.root, target.target, target.options); + } else { + target = target.root; + } + + + if (options.boundProperty) { + actionName = handlebarsGet(target, actionNameOrPath, options.options); + + if(typeof actionName === 'undefined' || typeof actionName === 'function') { + Ember.assert("You specified a quoteless path to the {{action}} helper '" + actionNameOrPath + "' which did not resolve to an actionName. Perhaps you meant to use a quoted actionName? (e.g. {{action '" + actionNameOrPath + "'}}).", true); + actionName = actionNameOrPath; + } + } + + if (!actionName) { + actionName = actionNameOrPath; + } + + Ember.run(function runRegisteredAction() { + if (target.send) { + target.send.apply(target, args(options.parameters, actionName)); + } else { + Ember.assert("The action '" + actionName + "' did not exist on " + target, typeof target[actionName] === 'function'); + target[actionName].apply(target, args(options.parameters)); + } + }); + } + }; + + options.view.on('willClearRender', function() { + delete ActionHelper.registeredActions[actionId]; + }); + + return actionId; + }; + + /** + The `{{action}}` helper registers an HTML element within a template for DOM + event handling and forwards that interaction to the templates's controller + or supplied `target` option (see 'Specifying a Target'). + + If the controller does not implement the event, the event is sent + to the current route, and it bubbles up the route hierarchy from there. + + User interaction with that element will invoke the supplied action name on + the appropriate target. Specifying a non-quoted action name will result in + a bound property lookup at the time the event will be triggered. + + Given the following application Handlebars template on the page + + ```handlebars +
    + click me +
    + ``` + + And application code + + ```javascript + App.ApplicationController = Ember.Controller.extend({ + actions: { + anActionName: function() { + } + } + }); + ``` + + Will result in the following rendered HTML + + ```html +
    +
    + click me +
    +
    + ``` + + Clicking "click me" will trigger the `anActionName` action of the + `App.ApplicationController`. In this case, no additional parameters will be passed. + + If you provide additional parameters to the helper: + + ```handlebars + + ``` + + Those parameters will be passed along as arguments to the JavaScript + function implementing the action. + + ### Event Propagation + + Events triggered through the action helper will automatically have + `.preventDefault()` called on them. You do not need to do so in your event + handlers. If you need to allow event propagation (to handle file inputs for + example) you can supply the `preventDefault=false` option to the `{{action}}` helper: + + ```handlebars +
    + + +
    + ``` + + To disable bubbling, pass `bubbles=false` to the helper: + + ```handlebars + + ``` + + If you need the default handler to trigger you should either register your + own event handler, or use event methods on your view class. See [Ember.View](/api/classes/Ember.View.html) + 'Responding to Browser Events' for more information. + + ### Specifying DOM event type + + By default the `{{action}}` helper registers for DOM `click` events. You can + supply an `on` option to the helper to specify a different DOM event name: + + ```handlebars +
    + click me +
    + ``` + + See `Ember.View` 'Responding to Browser Events' for a list of + acceptable DOM event names. + + NOTE: Because `{{action}}` depends on Ember's event dispatch system it will + only function if an `Ember.EventDispatcher` instance is available. An + `Ember.EventDispatcher` instance will be created when a new `Ember.Application` + is created. Having an instance of `Ember.Application` will satisfy this + requirement. + + ### Specifying whitelisted modifier keys + + By default the `{{action}}` helper will ignore click event with pressed modifier + keys. You can supply an `allowedKeys` option to specify which keys should not be ignored. + + ```handlebars +
    + click me +
    + ``` + + This way the `{{action}}` will fire when clicking with the alt key pressed down. + + Alternatively, supply "any" to the `allowedKeys` option to accept any combination of modifier keys. + + ```handlebars +
    + click me with any key pressed +
    + ``` + + ### Specifying a Target + + There are several possible target objects for `{{action}}` helpers: + + In a typical Ember application, where views are managed through use of the + `{{outlet}}` helper, actions will bubble to the current controller, then + to the current route, and then up the route hierarchy. + + Alternatively, a `target` option can be provided to the helper to change + which object will receive the method call. This option must be a path + to an object, accessible in the current context: + + ```handlebars + {{! the application template }} +
    + click me +
    + ``` + + ```javascript + App.ApplicationView = Ember.View.extend({ + actions: { + anActionName: function(){} + } + }); + + ``` + + ### Additional Parameters + + You may specify additional parameters to the `{{action}}` helper. These + parameters are passed along as the arguments to the JavaScript function + implementing the action. + + ```handlebars + {{#each person in people}} +
    + click me +
    + {{/each}} + ``` + + Clicking "click me" will trigger the `edit` method on the current controller + with the value of `person` as a parameter. + + @method action + @for Ember.Handlebars.helpers + @param {String} actionName + @param {Object} [context]* + @param {Hash} options + */ + EmberHandlebars.registerHelper('action', function actionHelper(actionName) { + var options = arguments[arguments.length - 1], + contexts = a_slice.call(arguments, 1, -1); + + var hash = options.hash, + controller = options.data.keywords.controller; + + // create a hash to pass along to registerAction + var action = { + eventName: hash.on || "click", + parameters: { + context: this, + options: options, + params: contexts + }, + view: options.data.view, + bubbles: hash.bubbles, + preventDefault: hash.preventDefault, + target: { options: options }, + boundProperty: options.types[0] === "ID" + }; + + if (hash.target) { + action.target.root = this; + action.target.target = hash.target; + } else if (controller) { + action.target.root = controller; + } + + var actionId = ActionHelper.registerAction(actionName, action, hash.allowedKeys); + return new SafeString('data-ember-action="' + actionId + '"'); + }); +}); + +})(); + + + +(function() { + +})(); + + + +(function() { +/** +@module ember +@submodule ember-routing +*/ + +var get = Ember.get, set = Ember.set, + map = Ember.EnumerableUtils.map; + +var queuedQueryParamChanges = {}; + +Ember.ControllerMixin.reopen({ + /** + Transition the application into another route. The route may + be either a single route or route path: + + ```javascript + aController.transitionToRoute('blogPosts'); + aController.transitionToRoute('blogPosts.recentEntries'); + ``` + + Optionally supply a model for the route in question. The model + will be serialized into the URL using the `serialize` hook of + the route: + + ```javascript + aController.transitionToRoute('blogPost', aPost); + ``` + + If a literal is passed (such as a number or a string), it will + be treated as an identifier instead. In this case, the `model` + hook of the route will be triggered: + + ```javascript + aController.transitionToRoute('blogPost', 1); + ``` + + Multiple models will be applied last to first recursively up the + resource tree. + + ```javascript + App.Router.map(function() { + this.resource('blogPost', {path:':blogPostId'}, function(){ + this.resource('blogComment', {path: ':blogCommentId'}); + }); + }); + + aController.transitionToRoute('blogComment', aPost, aComment); + aController.transitionToRoute('blogComment', 1, 13); + ``` + + It is also possible to pass a URL (a string that starts with a + `/`). This is intended for testing and debugging purposes and + should rarely be used in production code. + + ```javascript + aController.transitionToRoute('/'); + aController.transitionToRoute('/blog/post/1/comment/13'); + ``` + + See also [replaceRoute](/api/classes/Ember.ControllerMixin.html#method_replaceRoute). + + @param {String} name the name of the route or a URL + @param {...Object} models the model(s) or identifier(s) to be used + while transitioning to the route. + @for Ember.ControllerMixin + @method transitionToRoute + */ + transitionToRoute: function() { + // target may be either another controller or a router + var target = get(this, 'target'), + method = target.transitionToRoute || target.transitionTo; + return method.apply(target, arguments); + }, + + /** + @deprecated + @for Ember.ControllerMixin + @method transitionTo + */ + transitionTo: function() { + Ember.deprecate("transitionTo is deprecated. Please use transitionToRoute."); + return this.transitionToRoute.apply(this, arguments); + }, + + /** + Transition into another route while replacing the current URL, if possible. + This will replace the current history entry instead of adding a new one. + Beside that, it is identical to `transitionToRoute` in all other respects. + + ```javascript + aController.replaceRoute('blogPosts'); + aController.replaceRoute('blogPosts.recentEntries'); + ``` + + Optionally supply a model for the route in question. The model + will be serialized into the URL using the `serialize` hook of + the route: + + ```javascript + aController.replaceRoute('blogPost', aPost); + ``` + + If a literal is passed (such as a number or a string), it will + be treated as an identifier instead. In this case, the `model` + hook of the route will be triggered: + + ```javascript + aController.replaceRoute('blogPost', 1); + ``` + + Multiple models will be applied last to first recursively up the + resource tree. + + ```javascript + App.Router.map(function() { + this.resource('blogPost', {path:':blogPostId'}, function(){ + this.resource('blogComment', {path: ':blogCommentId'}); + }); + }); + + aController.replaceRoute('blogComment', aPost, aComment); + aController.replaceRoute('blogComment', 1, 13); + ``` + + It is also possible to pass a URL (a string that starts with a + `/`). This is intended for testing and debugging purposes and + should rarely be used in production code. + + ```javascript + aController.replaceRoute('/'); + aController.replaceRoute('/blog/post/1/comment/13'); + ``` + + @param {String} name the name of the route or a URL + @param {...Object} models the model(s) or identifier(s) to be used + while transitioning to the route. + @for Ember.ControllerMixin + @method replaceRoute + */ + replaceRoute: function() { + // target may be either another controller or a router + var target = get(this, 'target'), + method = target.replaceRoute || target.replaceWith; + return method.apply(target, arguments); + }, + + /** + @deprecated + @for Ember.ControllerMixin + @method replaceWith + */ + replaceWith: function() { + Ember.deprecate("replaceWith is deprecated. Please use replaceRoute."); + return this.replaceRoute.apply(this, arguments); + } +}); + + +})(); + + + +(function() { +/** +@module ember +@submodule ember-routing +*/ + +var get = Ember.get, set = Ember.set; + +Ember.View.reopen({ + + /** + Sets the private `_outlets` object on the view. + + @method init + */ + init: function() { + set(this, '_outlets', {}); + this._super(); + }, + + /** + Manually fill any of a view's `{{outlet}}` areas with the + supplied view. + + Example + + ```javascript + var MyView = Ember.View.extend({ + template: Ember.Handlebars.compile('Child view: {{outlet "main"}} ') + }); + var myView = MyView.create(); + myView.appendTo('body'); + // The html for myView now looks like: + //
    Child view:
    + + var FooView = Ember.View.extend({ + template: Ember.Handlebars.compile('

    Foo

    ') + }); + var fooView = FooView.create(); + myView.connectOutlet('main', fooView); + // The html for myView now looks like: + //
    Child view: + //

    Foo

    + //
    + ``` + @method connectOutlet + @param {String} outletName A unique name for the outlet + @param {Object} view An Ember.View + */ + connectOutlet: function(outletName, view) { + if (this._pendingDisconnections) { + delete this._pendingDisconnections[outletName]; + } + + if (this._hasEquivalentView(outletName, view)) { + view.destroy(); + return; + } + + var outlets = get(this, '_outlets'), + container = get(this, 'container'), + router = container && container.lookup('router:main'), + renderedName = get(view, 'renderedName'); + + set(outlets, outletName, view); + + if (router && renderedName) { + router._connectActiveView(renderedName, view); + } + }, + + /** + Determines if the view has already been created by checking if + the view has the same constructor, template, and context as the + view in the `_outlets` object. + + @private + @method _hasEquivalentView + @param {String} outletName The name of the outlet we are checking + @param {Object} view An Ember.View + @return {Boolean} + */ + _hasEquivalentView: function(outletName, view) { + var existingView = get(this, '_outlets.'+outletName); + return existingView && + existingView.constructor === view.constructor && + existingView.get('template') === view.get('template') && + existingView.get('context') === view.get('context'); + }, + + /** + Removes an outlet from the view. + + Example + + ```javascript + var MyView = Ember.View.extend({ + template: Ember.Handlebars.compile('Child view: {{outlet "main"}} ') + }); + var myView = MyView.create(); + myView.appendTo('body'); + // myView's html: + //
    Child view:
    + + var FooView = Ember.View.extend({ + template: Ember.Handlebars.compile('

    Foo

    ') + }); + var fooView = FooView.create(); + myView.connectOutlet('main', fooView); + // myView's html: + //
    Child view: + //

    Foo

    + //
    + + myView.disconnectOutlet('main'); + // myView's html: + //
    Child view:
    + ``` + + @method disconnectOutlet + @param {String} outletName The name of the outlet to be removed + */ + disconnectOutlet: function(outletName) { + if (!this._pendingDisconnections) { + this._pendingDisconnections = {}; + } + this._pendingDisconnections[outletName] = true; + Ember.run.once(this, '_finishDisconnections'); + }, + + /** + Gets an outlet that is pending disconnection and then + nullifys the object on the `_outlet` object. + + @private + @method _finishDisconnections + */ + _finishDisconnections: function() { + if (this.isDestroyed) return; // _outlets will be gone anyway + var outlets = get(this, '_outlets'); + var pendingDisconnections = this._pendingDisconnections; + this._pendingDisconnections = null; + + for (var outletName in pendingDisconnections) { + set(outlets, outletName, null); + } + } +}); + +})(); + + + +(function() { +/** +@module ember +@submodule ember-views +*/ + +// Add a new named queue after the 'actions' queue (where RSVP promises +// resolve), which is used in router transitions to prevent unnecessary +// loading state entry if all context promises resolve on the +// 'actions' queue first. + +var queues = Ember.run.queues, + indexOf = Ember.ArrayPolyfills.indexOf; +queues.splice(indexOf.call(queues, 'actions') + 1, 0, 'routerTransitions'); + +})(); + + + +(function() { +/** +@module ember +@submodule ember-routing +*/ + +var get = Ember.get, set = Ember.set; + +/** + Ember.Location returns an instance of the correct implementation of + the `location` API. + + ## Implementations + + You can pass an implementation name (`hash`, `history`, `none`) to force a + particular implementation to be used in your application. + + ### HashLocation + + Using `HashLocation` results in URLs with a `#` (hash sign) separating the + server side URL portion of the URL from the portion that is used by Ember. + This relies upon the `hashchange` event existing in the browser. + + Example: + + ```javascript + App.Router.map(function() { + this.resource('posts', function() { + this.route('new'); + }); + }); + + App.Router.reopen({ + location: 'hash' + }); + ``` + + This will result in a posts.new url of `/#/posts/new`. + + ### HistoryLocation + + Using `HistoryLocation` results in URLs that are indistinguishable from a + standard URL. This relies upon the browser's `history` API. + + Example: + + ```javascript + App.Router.map(function() { + this.resource('posts', function() { + this.route('new'); + }); + }); + + App.Router.reopen({ + location: 'history' + }); + ``` + + This will result in a posts.new url of `/posts/new`. + + Keep in mind that your server must serve the Ember app at all the routes you + define. + + ### AutoLocation + + Using `AutoLocation`, the router will use the best Location class supported by + the browser it is running in. + + Browsers that support the `history` API will use `HistoryLocation`, those that + do not, but still support the `hashchange` event will use `HashLocation`, and + in the rare case neither is supported will use `NoneLocation`. + + Example: + + ```javascript + App.Router.map(function() { + this.resource('posts', function() { + this.route('new'); + }); + }); + + App.Router.reopen({ + location: 'auto' + }); + ``` + + This will result in a posts.new url of `/posts/new` for modern browsers that + support the `history` api or `/#/posts/new` for older ones, like Internet + Explorer 9 and below. + + When a user visits a link to your application, they will be automatically + upgraded or downgraded to the appropriate `Location` class, with the URL + transformed accordingly, if needed. + + Keep in mind that since some of your users will use `HistoryLocation`, your + server must serve the Ember app at all the routes you define. + + ### NoneLocation + + Using `NoneLocation` causes Ember to not store the applications URL state + in the actual URL. This is generally used for testing purposes, and is one + of the changes made when calling `App.setupForTesting()`. + + ## Location API + + Each location implementation must provide the following methods: + + * implementation: returns the string name used to reference the implementation. + * getURL: returns the current URL. + * setURL(path): sets the current URL. + * replaceURL(path): replace the current URL (optional). + * onUpdateURL(callback): triggers the callback when the URL changes. + * formatURL(url): formats `url` to be placed into `href` attribute. + + Calling setURL or replaceURL will not trigger onUpdateURL callbacks. + + @class Location + @namespace Ember + @static +*/ +Ember.Location = { + /** + This is deprecated in favor of using the container to lookup the location + implementation as desired. + + For example: + + ```javascript + // Given a location registered as follows: + container.register('location:history-test', HistoryTestLocation); + + // You could create a new instance via: + container.lookup('location:history-test'); + ``` + + @method create + @param {Object} options + @return {Object} an instance of an implementation of the `location` API + @deprecated Use the container to lookup the location implementation that you + need. + */ + create: function(options) { + var implementation = options && options.implementation; + Ember.assert("Ember.Location.create: you must specify a 'implementation' option", !!implementation); + + var implementationClass = this.implementations[implementation]; + Ember.assert("Ember.Location.create: " + implementation + " is not a valid implementation", !!implementationClass); + + return implementationClass.create.apply(implementationClass, arguments); + }, + + /** + This is deprecated in favor of using the container to register the + location implementation as desired. + + Example: + + ```javascript + Application.initializer({ + name: "history-test-location", + + initialize: function(container, application) { + application.register('location:history-test', HistoryTestLocation); + } + }); + ``` + + @method registerImplementation + @param {String} name + @param {Object} implementation of the `location` API + @deprecated Register your custom location implementation with the + container directly. + */ + registerImplementation: function(name, implementation) { + Ember.deprecate('Using the Ember.Location.registerImplementation is no longer supported. Register your custom location implementation with the container instead.', false); + + this.implementations[name] = implementation; + }, + + implementations: {}, + _location: window.location, + + /** + Returns the current `location.hash` by parsing location.href since browsers + inconsistently URL-decode `location.hash`. + + https://bugzilla.mozilla.org/show_bug.cgi?id=483304 + + @private + @method getHash + */ + _getHash: function () { + // AutoLocation has it at _location, HashLocation at .location. + // Being nice and not changing + var href = (this._location || this.location).href, + hashIndex = href.indexOf('#'); + + if (hashIndex === -1) { + return ''; + } else { + return href.substr(hashIndex); + } + } +}; + +})(); + + + +(function() { +/** +@module ember +@submodule ember-routing +*/ + +var get = Ember.get, set = Ember.set; + +/** + Ember.NoneLocation does not interact with the browser. It is useful for + testing, or when you need to manage state with your Router, but temporarily + don't want it to muck with the URL (for example when you embed your + application in a larger page). + + @class NoneLocation + @namespace Ember + @extends Ember.Object +*/ +Ember.NoneLocation = Ember.Object.extend({ + implementation: 'none', + path: '', + + /** + Returns the current path. + + @private + @method getURL + @return {String} path + */ + getURL: function() { + return get(this, 'path'); + }, + + /** + Set the path and remembers what was set. Using this method + to change the path will not invoke the `updateURL` callback. + + @private + @method setURL + @param path {String} + */ + setURL: function(path) { + set(this, 'path', path); + }, + + /** + Register a callback to be invoked when the path changes. These + callbacks will execute when the user presses the back or forward + button, but not after `setURL` is invoked. + + @private + @method onUpdateURL + @param callback {Function} + */ + onUpdateURL: function(callback) { + this.updateCallback = callback; + }, + + /** + Sets the path and calls the `updateURL` callback. + + @private + @method handleURL + @param callback {Function} + */ + handleURL: function(url) { + set(this, 'path', url); + this.updateCallback(url); + }, + + /** + Given a URL, formats it to be placed into the page as part + of an element's `href` attribute. + + This is used, for example, when using the {{action}} helper + to generate a URL based on an event. + + @private + @method formatURL + @param url {String} + @return {String} url + */ + formatURL: function(url) { + // The return value is not overly meaningful, but we do not want to throw + // errors when test code renders templates containing {{action href=true}} + // helpers. + return url; + } +}); + +})(); + + + +(function() { +/** +@module ember +@submodule ember-routing +*/ + +var get = Ember.get, set = Ember.set; + +/** + `Ember.HashLocation` implements the location API using the browser's + hash. At present, it relies on a `hashchange` event existing in the + browser. + + @class HashLocation + @namespace Ember + @extends Ember.Object +*/ +Ember.HashLocation = Ember.Object.extend({ + implementation: 'hash', + + init: function() { + set(this, 'location', get(this, '_location') || window.location); + }, + + /** + @private + + Returns normalized location.hash + + @method getHash + */ + getHash: Ember.Location._getHash, + + /** + Returns the current `location.hash`, minus the '#' at the front. + + @private + @method getURL + */ + getURL: function() { + return this.getHash().substr(1); + }, + + /** + Set the `location.hash` and remembers what was set. This prevents + `onUpdateURL` callbacks from triggering when the hash was set by + `HashLocation`. + + @private + @method setURL + @param path {String} + */ + setURL: function(path) { + get(this, 'location').hash = path; + set(this, 'lastSetURL', path); + }, + + /** + Uses location.replace to update the url without a page reload + or history modification. + + @private + @method replaceURL + @param path {String} + */ + replaceURL: function(path) { + get(this, 'location').replace('#' + path); + set(this, 'lastSetURL', path); + }, + + /** + Register a callback to be invoked when the hash changes. These + callbacks will execute when the user presses the back or forward + button, but not after `setURL` is invoked. + + @private + @method onUpdateURL + @param callback {Function} + */ + onUpdateURL: function(callback) { + var self = this; + var guid = Ember.guidFor(this); + + Ember.$(window).on('hashchange.ember-location-'+guid, function() { + Ember.run(function() { + var path = self.getURL(); + if (get(self, 'lastSetURL') === path) { return; } + + set(self, 'lastSetURL', null); + + callback(path); + }); + }); + }, + + /** + Given a URL, formats it to be placed into the page as part + of an element's `href` attribute. + + This is used, for example, when using the {{action}} helper + to generate a URL based on an event. + + @private + @method formatURL + @param url {String} + */ + formatURL: function(url) { + return '#'+url; + }, + + /** + Cleans up the HashLocation event listener. + + @private + @method willDestroy + */ + willDestroy: function() { + var guid = Ember.guidFor(this); + + Ember.$(window).off('hashchange.ember-location-'+guid); + } +}); + +})(); + + + +(function() { +/** +@module ember +@submodule ember-routing +*/ + +var get = Ember.get, set = Ember.set; +var popstateFired = false; +var supportsHistoryState = window.history && 'state' in window.history; + +/** + Ember.HistoryLocation implements the location API using the browser's + history.pushState API. + + @class HistoryLocation + @namespace Ember + @extends Ember.Object +*/ +Ember.HistoryLocation = Ember.Object.extend({ + implementation: 'history', + + init: function() { + set(this, 'location', get(this, 'location') || window.location); + set(this, 'baseURL', Ember.$('base').attr('href') || ''); + }, + + /** + Used to set state on first call to setURL + + @private + @method initState + */ + initState: function() { + set(this, 'history', get(this, 'history') || window.history); + this.replaceState(this.formatURL(this.getURL())); + }, + + /** + Will be pre-pended to path upon state change + + @property rootURL + @default '/' + */ + rootURL: '/', + + /** + Returns the current `location.pathname` without `rootURL` or `baseURL` + + @private + @method getURL + @return url {String} + */ + getURL: function() { + var rootURL = get(this, 'rootURL'), + location = get(this, 'location'), + path = location.pathname, + baseURL = get(this, 'baseURL'); + + rootURL = rootURL.replace(/\/$/, ''); + baseURL = baseURL.replace(/\/$/, ''); + var url = path.replace(baseURL, '').replace(rootURL, ''); + + + return url; + }, + + /** + Uses `history.pushState` to update the url without a page reload. + + @private + @method setURL + @param path {String} + */ + setURL: function(path) { + var state = this.getState(); + path = this.formatURL(path); + + if (!state || state.path !== path) { + this.pushState(path); + } + }, + + /** + Uses `history.replaceState` to update the url without a page reload + or history modification. + + @private + @method replaceURL + @param path {String} + */ + replaceURL: function(path) { + var state = this.getState(); + path = this.formatURL(path); + + if (!state || state.path !== path) { + this.replaceState(path); + } + }, + + /** + Get the current `history.state`. Checks for if a polyfill is + required and if so fetches this._historyState. The state returned + from getState may be null if an iframe has changed a window's + history. + + @private + @method getState + @return state {Object} + */ + getState: function() { + return supportsHistoryState ? get(this, 'history').state : this._historyState; + }, + + /** + Pushes a new state. + + @private + @method pushState + @param path {String} + */ + pushState: function(path) { + var state = { path: path }; + + get(this, 'history').pushState(state, null, path); + + // store state if browser doesn't support `history.state` + if (!supportsHistoryState) { + this._historyState = state; + } + + // used for webkit workaround + this._previousURL = this.getURL(); + }, + + /** + Replaces the current state. + + @private + @method replaceState + @param path {String} + */ + replaceState: function(path) { + var state = { path: path }; + + get(this, 'history').replaceState(state, null, path); + + // store state if browser doesn't support `history.state` + if (!supportsHistoryState) { + this._historyState = state; + } + + // used for webkit workaround + this._previousURL = this.getURL(); + }, + + /** + Register a callback to be invoked whenever the browser + history changes, including using forward and back buttons. + + @private + @method onUpdateURL + @param callback {Function} + */ + onUpdateURL: function(callback) { + var guid = Ember.guidFor(this), + self = this; + + Ember.$(window).on('popstate.ember-location-'+guid, function(e) { + // Ignore initial page load popstate event in Chrome + if (!popstateFired) { + popstateFired = true; + if (self.getURL() === self._previousURL) { return; } + } + callback(self.getURL()); + }); + }, + + /** + Used when using `{{action}}` helper. The url is always appended to the rootURL. + + @private + @method formatURL + @param url {String} + @return formatted url {String} + */ + formatURL: function(url) { + var rootURL = get(this, 'rootURL'), + baseURL = get(this, 'baseURL'); + + if (url !== '') { + rootURL = rootURL.replace(/\/$/, ''); + baseURL = baseURL.replace(/\/$/, ''); + } else if(baseURL.match(/^\//) && rootURL.match(/^\//)) { + baseURL = baseURL.replace(/\/$/, ''); + } + + return baseURL + rootURL + url; + }, + + /** + Cleans up the HistoryLocation event listener. + + @private + @method willDestroy + */ + willDestroy: function() { + var guid = Ember.guidFor(this); + + Ember.$(window).off('popstate.ember-location-'+guid); + } +}); + +})(); + + + +(function() { + + /** + @module ember + @submodule ember-routing + */ + + var get = Ember.get, set = Ember.set, + HistoryLocation = Ember.HistoryLocation, + HashLocation = Ember.HashLocation, + NoneLocation = Ember.NoneLocation, + EmberLocation = Ember.Location; + + /** + Ember.AutoLocation will select the best location option based off browser + support with the priority order: history, hash, none. + + Clean pushState paths accessed by hashchange-only browsers will be redirected + to the hash-equivalent and vice versa so future transitions are consistent. + + Keep in mind that since some of your users will use `HistoryLocation`, your + server must serve the Ember app at all the routes you define. + + @class AutoLocation + @namespace Ember + @static + */ + var AutoLocation = Ember.AutoLocation = { + + /** + @private + + This property is used by router:main to know whether to cancel the routing + setup process, which is needed while we redirect the browser. + + @property cancelRouterSetup + @default false + */ + cancelRouterSetup: false, + + /** + @private + + Will be pre-pended to path upon state change. + + @property rootURL + @default '/' + */ + rootURL: '/', + + /** + @private + + Attached for mocking in tests + + @property _window + @default window + */ + _window: window, + + /** + @private + + Attached for mocking in tests + + @property location + @default window.location + */ + _location: window.location, + + /** + @private + + Attached for mocking in tests + + @property _history + @default window.history + */ + _history: window.history, + + /** + @private + + Attached for mocking in tests + + @property _HistoryLocation + @default Ember.HistoryLocation + */ + _HistoryLocation: HistoryLocation, + + /** + @private + + Attached for mocking in tests + + @property _HashLocation + @default Ember.HashLocation + */ + _HashLocation: HashLocation, + + /** + @private + + Attached for mocking in tests + + @property _NoneLocation + @default Ember.NoneLocation + */ + _NoneLocation: NoneLocation, + + /** + @private + + Returns location.origin or builds it if device doesn't support it. + + @method _getOrigin + */ + _getOrigin: function () { + var location = this._location, + origin = location.origin; + + // Older browsers, especially IE, don't have origin + if (!origin) { + origin = location.protocol + '//' + location.hostname; + + if (location.port) { + origin += ':' + location.port; + } + } + + return origin; + }, + + /** + @private + + We assume that if the history object has a pushState method, the host should + support HistoryLocation. + + @method _getSupportsHistory + */ + _getSupportsHistory: function () { + // Boosted from Modernizr: https://github.com/Modernizr/Modernizr/blob/master/feature-detects/history.js + // The stock browser on Android 2.2 & 2.3 returns positive on history support + // Unfortunately support is really buggy and there is no clean way to detect + // these bugs, so we fall back to a user agent sniff :( + var userAgent = this._window.navigator.userAgent; + + // We only want Android 2, stock browser, and not Chrome which identifies + // itself as 'Mobile Safari' as well + if (userAgent.indexOf('Android 2') !== -1 && + userAgent.indexOf('Mobile Safari') !== -1 && + userAgent.indexOf('Chrome') === -1) { + return false; + } + + return !!(this._history && 'pushState' in this._history); + }, + + /** + @private + + IE8 running in IE7 compatibility mode gives false positive, so we must also + check documentMode. + + @method _getSupportsHashChange + */ + _getSupportsHashChange: function () { + var window = this._window, + documentMode = window.document.documentMode; + + return ('onhashchange' in window && (documentMode === undefined || documentMode > 7 )); + }, + + /** + @private + + Redirects the browser using location.replace, prepending the locatin.origin + to prevent phishing attempts + + @method _replacePath + */ + _replacePath: function (path) { + this._location.replace(this._getOrigin() + path); + }, + + /** + @private + @method _getRootURL + */ + _getRootURL: function () { + return this.rootURL; + }, + + /** + @private + + Returns the current `location.pathname`, normalized for IE inconsistencies. + + @method _getPath + */ + _getPath: function () { + var pathname = this._location.pathname; + // Various versions of IE/Opera don't always return a leading slash + if (pathname.charAt(0) !== '/') { + pathname = '/' + pathname; + } + + return pathname; + }, + + /** + @private + + Returns normalized location.hash as an alias to Ember.Location._getHash + + @method _getHash + */ + _getHash: EmberLocation._getHash, + + /** + @private + + Returns location.search + + @method _getQuery + */ + _getQuery: function () { + return this._location.search; + }, + + /** + @private + + Returns the full pathname including query and hash + + @method _getFullPath + */ + _getFullPath: function () { + return this._getPath() + this._getQuery() + this._getHash(); + }, + + /** + @private + + Returns the current path as it should appear for HistoryLocation supported + browsers. This may very well differ from the real current path (e.g. if it + starts off as a hashed URL) + + @method _getHistoryPath + */ + _getHistoryPath: function () { + var rootURL = this._getRootURL(), + path = this._getPath(), + hash = this._getHash(), + query = this._getQuery(), + rootURLIndex = path.indexOf(rootURL), + routeHash, hashParts; + + Ember.assert('Path ' + path + ' does not start with the provided rootURL ' + rootURL, rootURLIndex === 0); + + // By convention, Ember.js routes using HashLocation are required to start + // with `#/`. Anything else should NOT be considered a route and should + // be passed straight through, without transformation. + if (hash.substr(0, 2) === '#/') { + // There could be extra hash segments after the route + hashParts = hash.substr(1).split('#'); + // The first one is always the route url + routeHash = hashParts.shift(); + + // If the path already has a trailing slash, remove the one + // from the hashed route so we don't double up. + if (path.slice(-1) === '/') { + routeHash = routeHash.substr(1); + } + + // This is the "expected" final order + path += routeHash; + path += query; + + if (hashParts.length) { + path += '#' + hashParts.join('#'); + } + } else { + path += query; + path += hash; + } + + return path; + }, + + /** + @private + + Returns the current path as it should appear for HashLocation supported + browsers. This may very well differ from the real current path. + + @method _getHashPath + */ + _getHashPath: function () { + var rootURL = this._getRootURL(), + path = rootURL, + historyPath = this._getHistoryPath(), + routePath = historyPath.substr(rootURL.length); + + if (routePath !== '') { + if (routePath.charAt(0) !== '/') { + routePath = '/' + routePath; + } + + path += '#' + routePath; + } + + return path; + }, + + /** + Selects the best location option based off browser support and returns an + instance of that Location class. + + @see Ember.AutoLocation + @method create + */ + create: function (options) { + if (options && options.rootURL) { + Ember.assert('rootURL must end with a trailing forward slash e.g. "/app/"', options.rootURL.charAt(options.rootURL.length-1) === '/'); + this.rootURL = options.rootURL; + } + + var historyPath, hashPath, + cancelRouterSetup = false, + implementationClass = this._NoneLocation, + currentPath = this._getFullPath(); + + if (this._getSupportsHistory()) { + historyPath = this._getHistoryPath(); + + // Since we support history paths, let's be sure we're using them else + // switch the location over to it. + if (currentPath === historyPath) { + implementationClass = this._HistoryLocation; + } else { + cancelRouterSetup = true; + this._replacePath(historyPath); + } + + } else if (this._getSupportsHashChange()) { + hashPath = this._getHashPath(); + + // Be sure we're using a hashed path, otherwise let's switch over it to so + // we start off clean and consistent. We'll count an index path with no + // hash as "good enough" as well. + if (currentPath === hashPath || (currentPath === '/' && hashPath === '/#/')) { + implementationClass = this._HashLocation; + } else { + // Our URL isn't in the expected hash-supported format, so we want to + // cancel the router setup and replace the URL to start off clean + cancelRouterSetup = true; + this._replacePath(hashPath); + } + } + + var implementation = implementationClass.create.apply(implementationClass, arguments); + + if (cancelRouterSetup) { + set(implementation, 'cancelRouterSetup', true); + } + + return implementation; + } + }; + +})(); + + + +(function() { + +})(); + + + +(function() { +/** +Ember Routing + +@module ember +@submodule ember-routing +@requires ember-views +*/ + +})(); + +(function() { +function visit(vertex, fn, visited, path) { + var name = vertex.name, + vertices = vertex.incoming, + names = vertex.incomingNames, + len = names.length, + i; + if (!visited) { + visited = {}; + } + if (!path) { + path = []; + } + if (visited.hasOwnProperty(name)) { + return; + } + path.push(name); + visited[name] = true; + for (i = 0; i < len; i++) { + visit(vertices[names[i]], fn, visited, path); + } + fn(vertex, path); + path.pop(); +} + +function DAG() { + this.names = []; + this.vertices = {}; +} + +DAG.prototype.add = function(name) { + if (!name) { return; } + if (this.vertices.hasOwnProperty(name)) { + return this.vertices[name]; + } + var vertex = { + name: name, incoming: {}, incomingNames: [], hasOutgoing: false, value: null + }; + this.vertices[name] = vertex; + this.names.push(name); + return vertex; +}; + +DAG.prototype.map = function(name, value) { + this.add(name).value = value; +}; + +DAG.prototype.addEdge = function(fromName, toName) { + if (!fromName || !toName || fromName === toName) { + return; + } + var from = this.add(fromName), to = this.add(toName); + if (to.incoming.hasOwnProperty(fromName)) { + return; + } + function checkCycle(vertex, path) { + if (vertex.name === toName) { + throw new Ember.Error("cycle detected: " + toName + " <- " + path.join(" <- ")); + } + } + visit(from, checkCycle); + from.hasOutgoing = true; + to.incoming[fromName] = from; + to.incomingNames.push(fromName); +}; + +DAG.prototype.topsort = function(fn) { + var visited = {}, + vertices = this.vertices, + names = this.names, + len = names.length, + i, vertex; + for (i = 0; i < len; i++) { + vertex = vertices[names[i]]; + if (!vertex.hasOutgoing) { + visit(vertex, fn, visited); + } + } +}; + +DAG.prototype.addEdges = function(name, value, before, after) { + var i; + this.map(name, value); + if (before) { + if (typeof before === 'string') { + this.addEdge(name, before); + } else { + for (i = 0; i < before.length; i++) { + this.addEdge(name, before[i]); + } + } + } + if (after) { + if (typeof after === 'string') { + this.addEdge(after, name); + } else { + for (i = 0; i < after.length; i++) { + this.addEdge(after[i], name); + } + } + } +}; + +Ember.DAG = DAG; + +})(); + + + +(function() { +/** +@module ember +@submodule ember-application +*/ + +var get = Ember.get, + classify = Ember.String.classify, + capitalize = Ember.String.capitalize, + decamelize = Ember.String.decamelize; + +Ember.Resolver = Ember.Object.extend({ + /** + This will be set to the Application instance when it is + created. + + @property namespace + */ + namespace: null, + normalize: function(fullName) { + throw new Error("Invalid call to `resolver.normalize(fullName)`. Please override the 'normalize' method in subclass of `Ember.AbstractResolver` to prevent falling through to this error."); + }, + resolve: function(fullName) { + throw new Error("Invalid call to `resolver.resolve(parsedName)`. Please override the 'resolve' method in subclass of `Ember.AbstractResolver` to prevent falling through to this error."); + }, + parseName: function(parsedName) { + throw new Error("Invalid call to `resolver.resolveByType(parsedName)`. Please override the 'resolveByType' method in subclass of `Ember.AbstractResolver` to prevent falling through to this error."); + }, + lookupDescription: function(fullName) { + throw new Error("Invalid call to `resolver.lookupDescription(fullName)`. Please override the 'lookupDescription' method in subclass of `Ember.AbstractResolver` to prevent falling through to this error."); + }, + makeToString: function(factory, fullName) { + throw new Error("Invalid call to `resolver.makeToString(factory, fullName)`. Please override the 'makeToString' method in subclass of `Ember.AbstractResolver` to prevent falling through to this error."); + }, + resolveOther: function(parsedName) { + throw new Error("Invalid call to `resolver.resolveDefault(parsedName)`. Please override the 'resolveDefault' method in subclass of `Ember.AbstractResolver` to prevent falling through to this error."); + } +}); + + + +/** + The DefaultResolver defines the default lookup rules to resolve + container lookups before consulting the container for registered + items: + + * templates are looked up on `Ember.TEMPLATES` + * other names are looked up on the application after converting + the name. For example, `controller:post` looks up + `App.PostController` by default. + * there are some nuances (see examples below) + + ### How Resolving Works + + The container calls this object's `resolve` method with the + `fullName` argument. + + It first parses the fullName into an object using `parseName`. + + Then it checks for the presence of a type-specific instance + method of the form `resolve[Type]` and calls it if it exists. + For example if it was resolving 'template:post', it would call + the `resolveTemplate` method. + + Its last resort is to call the `resolveOther` method. + + The methods of this object are designed to be easy to override + in a subclass. For example, you could enhance how a template + is resolved like so: + + ```javascript + App = Ember.Application.create({ + Resolver: Ember.DefaultResolver.extend({ + resolveTemplate: function(parsedName) { + var resolvedTemplate = this._super(parsedName); + if (resolvedTemplate) { return resolvedTemplate; } + return Ember.TEMPLATES['not_found']; + } + }) + }); + ``` + + Some examples of how names are resolved: + + ``` + 'template:post' //=> Ember.TEMPLATES['post'] + 'template:posts/byline' //=> Ember.TEMPLATES['posts/byline'] + 'template:posts.byline' //=> Ember.TEMPLATES['posts/byline'] + 'template:blogPost' //=> Ember.TEMPLATES['blogPost'] + // OR + // Ember.TEMPLATES['blog_post'] + 'controller:post' //=> App.PostController + 'controller:posts.index' //=> App.PostsIndexController + 'controller:blog/post' //=> Blog.PostController + 'controller:basic' //=> Ember.Controller + 'route:post' //=> App.PostRoute + 'route:posts.index' //=> App.PostsIndexRoute + 'route:blog/post' //=> Blog.PostRoute + 'route:basic' //=> Ember.Route + 'view:post' //=> App.PostView + 'view:posts.index' //=> App.PostsIndexView + 'view:blog/post' //=> Blog.PostView + 'view:basic' //=> Ember.View + 'foo:post' //=> App.PostFoo + 'model:post' //=> App.Post + ``` + + @class DefaultResolver + @namespace Ember + @extends Ember.Object +*/ +Ember.DefaultResolver = Ember.Object.extend({ + /** + This will be set to the Application instance when it is + created. + + @property namespace + */ + namespace: null, + + normalize: function(fullName) { + var split = fullName.split(':', 2), + type = split[0], + name = split[1]; + + Ember.assert("Tried to normalize a container name without a colon (:) in it. You probably tried to lookup a name that did not contain a type, a colon, and a name. A proper lookup name would be `view:post`.", split.length === 2); + + if (type !== 'template') { + var result = name; + + if (result.indexOf('.') > -1) { + result = result.replace(/\.(.)/g, function(m) { return m.charAt(1).toUpperCase(); }); + } + + if (name.indexOf('_') > -1) { + result = result.replace(/_(.)/g, function(m) { return m.charAt(1).toUpperCase(); }); + } + + return type + ':' + result; + } else { + return fullName; + } + }, + + + /** + This method is called via the container's resolver method. + It parses the provided `fullName` and then looks up and + returns the appropriate template or class. + + @method resolve + @param {String} fullName the lookup string + @return {Object} the resolved factory + */ + resolve: function(fullName) { + var parsedName = this.parseName(fullName), + resolveMethodName = parsedName.resolveMethodName; + + if (!(parsedName.name && parsedName.type)) { + throw new TypeError("Invalid fullName: `" + fullName + "`, must be of the form `type:name` "); + } + + if (this[resolveMethodName]) { + var resolved = this[resolveMethodName](parsedName); + if (resolved) { return resolved; } + } + return this.resolveOther(parsedName); + }, + /** + Convert the string name of the form "type:name" to + a Javascript object with the parsed aspects of the name + broken out. + + @protected + @param {String} fullName the lookup string + @method parseName + */ + parseName: function(fullName) { + var nameParts = fullName.split(":"), + type = nameParts[0], fullNameWithoutType = nameParts[1], + name = fullNameWithoutType, + namespace = get(this, 'namespace'), + root = namespace; + + if (type !== 'template' && name.indexOf('/') !== -1) { + var parts = name.split('/'); + name = parts[parts.length - 1]; + var namespaceName = capitalize(parts.slice(0, -1).join('.')); + root = Ember.Namespace.byName(namespaceName); + + Ember.assert('You are looking for a ' + name + ' ' + type + ' in the ' + namespaceName + ' namespace, but the namespace could not be found', root); + } + + return { + fullName: fullName, + type: type, + fullNameWithoutType: fullNameWithoutType, + name: name, + root: root, + resolveMethodName: "resolve" + classify(type) + }; + }, + + /** + Returns a human-readable description for a fullName. Used by the + Application namespace in assertions to describe the + precise name of the class that Ember is looking for, rather than + container keys. + + @protected + @param {String} fullName the lookup string + @method lookupDescription + */ + lookupDescription: function(fullName) { + var parsedName = this.parseName(fullName); + + if (parsedName.type === 'template') { + return "template at " + parsedName.fullNameWithoutType.replace(/\./g, '/'); + } + + var description = parsedName.root + "." + classify(parsedName.name); + if (parsedName.type !== 'model') { description += classify(parsedName.type); } + + return description; + }, + + makeToString: function(factory, fullName) { + return factory.toString(); + }, + /** + Given a parseName object (output from `parseName`), apply + the conventions expected by `Ember.Router` + + @protected + @param {Object} parsedName a parseName object with the parsed + fullName lookup string + @method useRouterNaming + */ + useRouterNaming: function(parsedName) { + parsedName.name = parsedName.name.replace(/\./g, '_'); + if (parsedName.name === 'basic') { + parsedName.name = ''; + } + }, + /** + Look up the template in Ember.TEMPLATES + + @protected + @param {Object} parsedName a parseName object with the parsed + fullName lookup string + @method resolveTemplate + */ + resolveTemplate: function(parsedName) { + var templateName = parsedName.fullNameWithoutType.replace(/\./g, '/'); + + if (Ember.TEMPLATES[templateName]) { + return Ember.TEMPLATES[templateName]; + } + + templateName = decamelize(templateName); + if (Ember.TEMPLATES[templateName]) { + return Ember.TEMPLATES[templateName]; + } + }, + /** + Lookup the view using `resolveOther` + + @protected + @param {Object} parsedName a parseName object with the parsed + fullName lookup string + @method resolveView + */ + resolveView: function(parsedName) { + this.useRouterNaming(parsedName); + return this.resolveOther(parsedName); + }, + /** + Lookup the controller using `resolveOther` + + @protected + @param {Object} parsedName a parseName object with the parsed + fullName lookup string + @method resolveController + */ + resolveController: function(parsedName) { + this.useRouterNaming(parsedName); + return this.resolveOther(parsedName); + }, + /** + Lookup the route using `resolveOther` + + @protected + @param {Object} parsedName a parseName object with the parsed + fullName lookup string + @method resolveRoute + */ + resolveRoute: function(parsedName) { + this.useRouterNaming(parsedName); + return this.resolveOther(parsedName); + }, + + /** + Lookup the model on the Application namespace + + @protected + @param {Object} parsedName a parseName object with the parsed + fullName lookup string + @method resolveModel + */ + resolveModel: function(parsedName) { + var className = classify(parsedName.name), + factory = get(parsedName.root, className); + + if (factory) { return factory; } + }, + /** + Look up the specified object (from parsedName) on the appropriate + namespace (usually on the Application) + + @protected + @param {Object} parsedName a parseName object with the parsed + fullName lookup string + @method resolveHelper + */ + resolveHelper: function(parsedName) { + return this.resolveOther(parsedName) || Ember.Handlebars.helpers[parsedName.fullNameWithoutType]; + }, + /** + Look up the specified object (from parsedName) on the appropriate + namespace (usually on the Application) + + @protected + @param {Object} parsedName a parseName object with the parsed + fullName lookup string + @method resolveOther + */ + resolveOther: function(parsedName) { + var className = classify(parsedName.name) + classify(parsedName.type), + factory = get(parsedName.root, className); + if (factory) { return factory; } + } +}); + +})(); + + + +(function() { +/** +@module ember +@submodule ember-application +*/ + +var get = Ember.get, set = Ember.set; + +function DeprecatedContainer(container) { + this._container = container; +} + +DeprecatedContainer.deprecate = function(method) { + return function() { + var container = this._container; + + Ember.deprecate('Using the defaultContainer is no longer supported. [defaultContainer#' + method + '] see: http://git.io/EKPpnA', false); + return container[method].apply(container, arguments); + }; +}; + +DeprecatedContainer.prototype = { + _container: null, + lookup: DeprecatedContainer.deprecate('lookup'), + resolve: DeprecatedContainer.deprecate('resolve'), + register: DeprecatedContainer.deprecate('register') +}; + +/** + An instance of `Ember.Application` is the starting point for every Ember + application. It helps to instantiate, initialize and coordinate the many + objects that make up your app. + + Each Ember app has one and only one `Ember.Application` object. In fact, the + very first thing you should do in your application is create the instance: + + ```javascript + window.App = Ember.Application.create(); + ``` + + Typically, the application object is the only global variable. All other + classes in your app should be properties on the `Ember.Application` instance, + which highlights its first role: a global namespace. + + For example, if you define a view class, it might look like this: + + ```javascript + App.MyView = Ember.View.extend(); + ``` + + By default, calling `Ember.Application.create()` will automatically initialize + your application by calling the `Ember.Application.initialize()` method. If + you need to delay initialization, you can call your app's `deferReadiness()` + method. When you are ready for your app to be initialized, call its + `advanceReadiness()` method. + + You can define a `ready` method on the `Ember.Application` instance, which + will be run by Ember when the application is initialized. + + Because `Ember.Application` inherits from `Ember.Namespace`, any classes + you create will have useful string representations when calling `toString()`. + See the `Ember.Namespace` documentation for more information. + + While you can think of your `Ember.Application` as a container that holds the + other classes in your application, there are several other responsibilities + going on under-the-hood that you may want to understand. + + ### Event Delegation + + Ember uses a technique called _event delegation_. This allows the framework + to set up a global, shared event listener instead of requiring each view to + do it manually. For example, instead of each view registering its own + `mousedown` listener on its associated element, Ember sets up a `mousedown` + listener on the `body`. + + If a `mousedown` event occurs, Ember will look at the target of the event and + start walking up the DOM node tree, finding corresponding views and invoking + their `mouseDown` method as it goes. + + `Ember.Application` has a number of default events that it listens for, as + well as a mapping from lowercase events to camel-cased view method names. For + example, the `keypress` event causes the `keyPress` method on the view to be + called, the `dblclick` event causes `doubleClick` to be called, and so on. + + If there is a bubbling browser event that Ember does not listen for by + default, you can specify custom events and their corresponding view method + names by setting the application's `customEvents` property: + + ```javascript + App = Ember.Application.create({ + customEvents: { + // add support for the paste event + paste: "paste" + } + }); + ``` + + By default, the application sets up these event listeners on the document + body. However, in cases where you are embedding an Ember application inside + an existing page, you may want it to set up the listeners on an element + inside the body. + + For example, if only events inside a DOM element with the ID of `ember-app` + should be delegated, set your application's `rootElement` property: + + ```javascript + window.App = Ember.Application.create({ + rootElement: '#ember-app' + }); + ``` + + The `rootElement` can be either a DOM element or a jQuery-compatible selector + string. Note that *views appended to the DOM outside the root element will + not receive events.* If you specify a custom root element, make sure you only + append views inside it! + + To learn more about the advantages of event delegation and the Ember view + layer, and a list of the event listeners that are setup by default, visit the + [Ember View Layer guide](http://emberjs.com/guides/understanding-ember/the-view-layer/#toc_event-delegation). + + ### Initializers + + Libraries on top of Ember can add initializers, like so: + + ```javascript + Ember.Application.initializer({ + name: 'api-adapter', + + initialize: function(container, application) { + application.register('api-adapter:main', ApiAdapter); + } + }); + ``` + + Initializers provide an opportunity to access the container, which + organizes the different components of an Ember application. Additionally + they provide a chance to access the instantiated application. Beyond + being used for libraries, initializers are also a great way to organize + dependency injection or setup in your own application. + + ### Routing + + In addition to creating your application's router, `Ember.Application` is + also responsible for telling the router when to start routing. Transitions + between routes can be logged with the `LOG_TRANSITIONS` flag, and more + detailed intra-transition logging can be logged with + the `LOG_TRANSITIONS_INTERNAL` flag: + + ```javascript + window.App = Ember.Application.create({ + LOG_TRANSITIONS: true, // basic logging of successful transitions + LOG_TRANSITIONS_INTERNAL: true // detailed logging of all routing steps + }); + ``` + + By default, the router will begin trying to translate the current URL into + application state once the browser emits the `DOMContentReady` event. If you + need to defer routing, you can call the application's `deferReadiness()` + method. Once routing can begin, call the `advanceReadiness()` method. + + If there is any setup required before routing begins, you can implement a + `ready()` method on your app that will be invoked immediately before routing + begins. + ``` + + @class Application + @namespace Ember + @extends Ember.Namespace +*/ + +var Application = Ember.Application = Ember.Namespace.extend(Ember.DeferredMixin, { + + /** + The root DOM element of the Application. This can be specified as an + element or a + [jQuery-compatible selector string](http://api.jquery.com/category/selectors/). + + This is the element that will be passed to the Application's, + `eventDispatcher`, which sets up the listeners for event delegation. Every + view in your application should be a child of the element you specify here. + + @property rootElement + @type DOMElement + @default 'body' + */ + rootElement: 'body', + + /** + The `Ember.EventDispatcher` responsible for delegating events to this + application's views. + + The event dispatcher is created by the application at initialization time + and sets up event listeners on the DOM element described by the + application's `rootElement` property. + + See the documentation for `Ember.EventDispatcher` for more information. + + @property eventDispatcher + @type Ember.EventDispatcher + @default null + */ + eventDispatcher: null, + + /** + The DOM events for which the event dispatcher should listen. + + By default, the application's `Ember.EventDispatcher` listens + for a set of standard DOM events, such as `mousedown` and + `keyup`, and delegates them to your application's `Ember.View` + instances. + + If you would like additional bubbling events to be delegated to your + views, set your `Ember.Application`'s `customEvents` property + to a hash containing the DOM event name as the key and the + corresponding view method name as the value. For example: + + ```javascript + App = Ember.Application.create({ + customEvents: { + // add support for the paste event + paste: "paste" + } + }); + ``` + + @property customEvents + @type Object + @default null + */ + customEvents: null, + + // Start off the number of deferrals at 1. This will be + // decremented by the Application's own `initialize` method. + _readinessDeferrals: 1, + + init: function() { + if (!this.$) { this.$ = Ember.$; } + this.__container__ = this.buildContainer(); + + this.Router = this.defaultRouter(); + + this._super(); + + this.scheduleInitialize(); + + Ember.libraries.registerCoreLibrary('Handlebars', Ember.Handlebars.VERSION); + Ember.libraries.registerCoreLibrary('jQuery', Ember.$().jquery); + + if ( Ember.LOG_VERSION ) { + Ember.LOG_VERSION = false; // we only need to see this once per Application#init + var maxNameLength = Math.max.apply(this, Ember.A(Ember.libraries).mapBy("name.length")); + + Ember.debug('-------------------------------'); + Ember.libraries.each(function(name, version) { + var spaces = new Array(maxNameLength - name.length + 1).join(" "); + Ember.debug([name, spaces, ' : ', version].join("")); + }); + Ember.debug('-------------------------------'); + } + }, + + /** + Build the container for the current application. + + Also register a default application view in case the application + itself does not. + + @private + @method buildContainer + @return {Ember.Container} the configured container + */ + buildContainer: function() { + var container = this.__container__ = Application.buildContainer(this); + + return container; + }, + + /** + If the application has not opted out of routing and has not explicitly + defined a router, supply a default router for the application author + to configure. + + This allows application developers to do: + + ```javascript + var App = Ember.Application.create(); + + App.Router.map(function() { + this.resource('posts'); + }); + ``` + + @private + @method defaultRouter + @return {Ember.Router} the default router + */ + + defaultRouter: function() { + if (this.Router === false) { return; } + var container = this.__container__; + + if (this.Router) { + container.unregister('router:main'); + container.register('router:main', this.Router); + } + + return container.lookupFactory('router:main'); + }, + + /** + Automatically initialize the application once the DOM has + become ready. + + The initialization itself is scheduled on the actions queue + which ensures that application loading finishes before + booting. + + If you are asynchronously loading code, you should call + `deferReadiness()` to defer booting, and then call + `advanceReadiness()` once all of your code has finished + loading. + + @private + @method scheduleInitialize + */ + scheduleInitialize: function() { + var self = this; + + if (!this.$ || this.$.isReady) { + Ember.run.schedule('actions', self, '_initialize'); + } else { + this.$().ready(function runInitialize() { + Ember.run(self, '_initialize'); + }); + } + }, + + /** + Use this to defer readiness until some condition is true. + + Example: + + ```javascript + App = Ember.Application.create(); + App.deferReadiness(); + + jQuery.getJSON("/auth-token", function(token) { + App.token = token; + App.advanceReadiness(); + }); + ``` + + This allows you to perform asynchronous setup logic and defer + booting your application until the setup has finished. + + However, if the setup requires a loading UI, it might be better + to use the router for this purpose. + + @method deferReadiness + */ + deferReadiness: function() { + Ember.assert("You must call deferReadiness on an instance of Ember.Application", this instanceof Ember.Application); + Ember.assert("You cannot defer readiness since the `ready()` hook has already been called.", this._readinessDeferrals > 0); + this._readinessDeferrals++; + }, + + /** + Call `advanceReadiness` after any asynchronous setup logic has completed. + Each call to `deferReadiness` must be matched by a call to `advanceReadiness` + or the application will never become ready and routing will not begin. + + @method advanceReadiness + @see {Ember.Application#deferReadiness} + */ + advanceReadiness: function() { + Ember.assert("You must call advanceReadiness on an instance of Ember.Application", this instanceof Ember.Application); + this._readinessDeferrals--; + + if (this._readinessDeferrals === 0) { + Ember.run.once(this, this.didBecomeReady); + } + }, + + /** + Registers a factory that can be used for dependency injection (with + `App.inject`) or for service lookup. Each factory is registered with + a full name including two parts: `type:name`. + + A simple example: + + ```javascript + var App = Ember.Application.create(); + App.Orange = Ember.Object.extend(); + App.register('fruit:favorite', App.Orange); + ``` + + Ember will resolve factories from the `App` namespace automatically. + For example `App.CarsController` will be discovered and returned if + an application requests `controller:cars`. + + An example of registering a controller with a non-standard name: + + ```javascript + var App = Ember.Application.create(), + Session = Ember.Controller.extend(); + + App.register('controller:session', Session); + + // The Session controller can now be treated like a normal controller, + // despite its non-standard name. + App.ApplicationController = Ember.Controller.extend({ + needs: ['session'] + }); + ``` + + Registered factories are **instantiated** by having `create` + called on them. Additionally they are **singletons**, each time + they are looked up they return the same instance. + + Some examples modifying that default behavior: + + ```javascript + var App = Ember.Application.create(); + + App.Person = Ember.Object.extend(); + App.Orange = Ember.Object.extend(); + App.Email = Ember.Object.extend(); + App.session = Ember.Object.create(); + + App.register('model:user', App.Person, {singleton: false }); + App.register('fruit:favorite', App.Orange); + App.register('communication:main', App.Email, {singleton: false}); + App.register('session', App.session, {instantiate: false}); + ``` + + @method register + @param fullName {String} type:name (e.g., 'model:user') + @param factory {Function} (e.g., App.Person) + @param options {Object} (optional) disable instantiation or singleton usage + **/ + register: function() { + var container = this.__container__; + container.register.apply(container, arguments); + }, + + /** + Define a dependency injection onto a specific factory or all factories + of a type. + + When Ember instantiates a controller, view, or other framework component + it can attach a dependency to that component. This is often used to + provide services to a set of framework components. + + An example of providing a session object to all controllers: + + ```javascript + var App = Ember.Application.create(), + Session = Ember.Object.extend({ isAuthenticated: false }); + + // A factory must be registered before it can be injected + App.register('session:main', Session); + + // Inject 'session:main' onto all factories of the type 'controller' + // with the name 'session' + App.inject('controller', 'session', 'session:main'); + + App.IndexController = Ember.Controller.extend({ + isLoggedIn: Ember.computed.alias('session.isAuthenticated') + }); + ``` + + Injections can also be performed on specific factories. + + ```javascript + App.inject(, , ) + App.inject('route', 'source', 'source:main') + App.inject('route:application', 'email', 'model:email') + ``` + + It is important to note that injections can only be performed on + classes that are instantiated by Ember itself. Instantiating a class + directly (via `create` or `new`) bypasses the dependency injection + system. + + Ember-Data instantiates its models in a unique manner, and consequently + injections onto models (or all models) will not work as expected. Injections + on models can be enabled by setting `Ember.MODEL_FACTORY_INJECTIONS` + to `true`. + + @method inject + @param factoryNameOrType {String} + @param property {String} + @param injectionName {String} + **/ + inject: function() { + var container = this.__container__; + container.injection.apply(container, arguments); + }, + + /** + Calling initialize manually is not supported. + + Please see Ember.Application#advanceReadiness and + Ember.Application#deferReadiness. + + @private + @deprecated + @method initialize + **/ + initialize: function() { + Ember.deprecate('Calling initialize manually is not supported. Please see Ember.Application#advanceReadiness and Ember.Application#deferReadiness'); + }, + + /** + Initialize the application. This happens automatically. + + Run any initializers and run the application load hook. These hooks may + choose to defer readiness. For example, an authentication hook might want + to defer readiness until the auth token has been retrieved. + + @private + @method _initialize + */ + _initialize: function() { + if (this.isDestroyed) { return; } + + // At this point, the App.Router must already be assigned + if (this.Router) { + var container = this.__container__; + container.unregister('router:main'); + container.register('router:main', this.Router); + } + + this.runInitializers(); + Ember.runLoadHooks('application', this); + + // At this point, any initializers or load hooks that would have wanted + // to defer readiness have fired. In general, advancing readiness here + // will proceed to didBecomeReady. + this.advanceReadiness(); + + return this; + }, + + /** + Reset the application. This is typically used only in tests. It cleans up + the application in the following order: + + 1. Deactivate existing routes + 2. Destroy all objects in the container + 3. Create a new application container + 4. Re-route to the existing url + + Typical Example: + + ```javascript + + var App; + + Ember.run(function() { + App = Ember.Application.create(); + }); + + module("acceptance test", { + setup: function() { + App.reset(); + } + }); + + test("first test", function() { + // App is freshly reset + }); + + test("first test", function() { + // App is again freshly reset + }); + ``` + + Advanced Example: + + Occasionally you may want to prevent the app from initializing during + setup. This could enable extra configuration, or enable asserting prior + to the app becoming ready. + + ```javascript + + var App; + + Ember.run(function() { + App = Ember.Application.create(); + }); + + module("acceptance test", { + setup: function() { + Ember.run(function() { + App.reset(); + App.deferReadiness(); + }); + } + }); + + test("first test", function() { + ok(true, 'something before app is initialized'); + + Ember.run(function() { + App.advanceReadiness(); + }); + ok(true, 'something after app is initialized'); + }); + ``` + + @method reset + **/ + reset: function() { + this._readinessDeferrals = 1; + + function handleReset() { + var router = this.__container__.lookup('router:main'); + router.reset(); + + Ember.run(this.__container__, 'destroy'); + + this.buildContainer(); + + Ember.run.schedule('actions', this, function() { + this._initialize(); + }); + } + + Ember.run.join(this, handleReset); + }, + + /** + @private + @method runInitializers + */ + runInitializers: function() { + var initializers = get(this.constructor, 'initializers'), + container = this.__container__, + graph = new Ember.DAG(), + namespace = this, + name, initializer; + + for (name in initializers) { + initializer = initializers[name]; + graph.addEdges(initializer.name, initializer.initialize, initializer.before, initializer.after); + } + + graph.topsort(function (vertex) { + var initializer = vertex.value; + Ember.assert("No application initializer named '"+vertex.name+"'", initializer); + initializer(container, namespace); + }); + }, + + /** + @private + @method didBecomeReady + */ + didBecomeReady: function() { + this.setupEventDispatcher(); + this.ready(); // user hook + this.startRouting(); + + if (!Ember.testing) { + // Eagerly name all classes that are already loaded + Ember.Namespace.processAll(); + Ember.BOOTED = true; + } + + this.resolve(this); + }, + + /** + Setup up the event dispatcher to receive events on the + application's `rootElement` with any registered + `customEvents`. + + @private + @method setupEventDispatcher + */ + setupEventDispatcher: function() { + var customEvents = get(this, 'customEvents'), + rootElement = get(this, 'rootElement'), + dispatcher = this.__container__.lookup('event_dispatcher:main'); + + set(this, 'eventDispatcher', dispatcher); + dispatcher.setup(customEvents, rootElement); + }, + + /** + trigger a new call to `route` whenever the URL changes. + If the application has a router, use it to route to the current URL, and + + @private + @method startRouting + @property router {Ember.Router} + */ + startRouting: function() { + var router = this.__container__.lookup('router:main'); + if (!router) { return; } + + router.startRouting(); + }, + + handleURL: function(url) { + var router = this.__container__.lookup('router:main'); + + router.handleURL(url); + }, + + /** + Called when the Application has become ready. + The call will be delayed until the DOM has become ready. + + @event ready + */ + ready: Ember.K, + + /** + @deprecated Use 'Resolver' instead + Set this to provide an alternate class to `Ember.DefaultResolver` + + + @property resolver + */ + resolver: null, + + /** + Set this to provide an alternate class to `Ember.DefaultResolver` + + @property resolver + */ + Resolver: null, + + willDestroy: function() { + Ember.BOOTED = false; + // Ensure deactivation of routes before objects are destroyed + this.__container__.lookup('router:main').reset(); + + this.__container__.destroy(); + }, + + initializer: function(options) { + this.constructor.initializer(options); + } +}); + +Ember.Application.reopenClass({ + initializers: {}, + initializer: function(initializer) { + // If this is the first initializer being added to a subclass, we are going to reopen the class + // to make sure we have a new `initializers` object, which extends from the parent class' using + // prototypal inheritance. Without this, attempting to add initializers to the subclass would + // pollute the parent class as well as other subclasses. + if (this.superclass.initializers !== undefined && this.superclass.initializers === this.initializers) { + this.reopenClass({ + initializers: Ember.create(this.initializers) + }); + } + + Ember.assert("The initializer '" + initializer.name + "' has already been registered", !this.initializers[initializer.name]); + Ember.assert("An initializer cannot be registered with both a before and an after", !(initializer.before && initializer.after)); + Ember.assert("An initializer cannot be registered without an initialize function", Ember.canInvoke(initializer, 'initialize')); + + this.initializers[initializer.name] = initializer; + }, + + /** + This creates a container with the default Ember naming conventions. + + It also configures the container: + + * registered views are created every time they are looked up (they are + not singletons) + * registered templates are not factories; the registered value is + returned directly. + * the router receives the application as its `namespace` property + * all controllers receive the router as their `target` and `controllers` + properties + * all controllers receive the application as their `namespace` property + * the application view receives the application controller as its + `controller` property + * the application view receives the application template as its + `defaultTemplate` property + + @private + @method buildContainer + @static + @param {Ember.Application} namespace the application to build the + container for. + @return {Ember.Container} the built container + */ + buildContainer: function(namespace) { + var container = new Ember.Container(); + + Ember.Container.defaultContainer = new DeprecatedContainer(container); + + container.set = Ember.set; + container.resolver = resolverFor(namespace); + container.normalize = container.resolver.normalize; + container.describe = container.resolver.describe; + container.makeToString = container.resolver.makeToString; + + container.optionsForType('component', { singleton: false }); + container.optionsForType('view', { singleton: false }); + container.optionsForType('template', { instantiate: false }); + container.optionsForType('helper', { instantiate: false }); + + container.register('application:main', namespace, { instantiate: false }); + + container.register('controller:basic', Ember.Controller, { instantiate: false }); + container.register('controller:object', Ember.ObjectController, { instantiate: false }); + container.register('controller:array', Ember.ArrayController, { instantiate: false }); + container.register('route:basic', Ember.Route, { instantiate: false }); + container.register('event_dispatcher:main', Ember.EventDispatcher); + + container.register('router:main', Ember.Router); + container.injection('router:main', 'namespace', 'application:main'); + + + container.register('location:auto', Ember.AutoLocation); + + + container.register('location:hash', Ember.HashLocation); + container.register('location:history', Ember.HistoryLocation); + container.register('location:none', Ember.NoneLocation); + + container.injection('controller', 'target', 'router:main'); + container.injection('controller', 'namespace', 'application:main'); + + container.injection('route', 'router', 'router:main'); + container.injection('location', 'rootURL', '-location-setting:root-url'); + + // DEBUGGING + container.register('resolver-for-debugging:main', container.resolver.__resolver__, { instantiate: false }); + container.injection('container-debug-adapter:main', 'resolver', 'resolver-for-debugging:main'); + container.injection('data-adapter:main', 'containerDebugAdapter', 'container-debug-adapter:main'); + // Custom resolver authors may want to register their own ContainerDebugAdapter with this key + container.register('container-debug-adapter:main', Ember.ContainerDebugAdapter); + + return container; + } +}); + +/** + This function defines the default lookup rules for container lookups: + + * templates are looked up on `Ember.TEMPLATES` + * other names are looked up on the application after classifying the name. + For example, `controller:post` looks up `App.PostController` by default. + * if the default lookup fails, look for registered classes on the container + + This allows the application to register default injections in the container + that could be overridden by the normal naming convention. + + @private + @method resolverFor + @param {Ember.Namespace} namespace the namespace to look for classes + @return {*} the resolved value for a given lookup +*/ +function resolverFor(namespace) { + if (namespace.get('resolver')) { + Ember.deprecate('Application.resolver is deprecated in favor of Application.Resolver', false); + } + + var ResolverClass = namespace.get('resolver') || namespace.get('Resolver') || Ember.DefaultResolver; + var resolver = ResolverClass.create({ + namespace: namespace + }); + + function resolve(fullName) { + return resolver.resolve(fullName); + } + + resolve.describe = function(fullName) { + return resolver.lookupDescription(fullName); + }; + + resolve.makeToString = function(factory, fullName) { + return resolver.makeToString(factory, fullName); + }; + + resolve.normalize = function(fullName) { + if (resolver.normalize) { + return resolver.normalize(fullName); + } else { + Ember.deprecate('The Resolver should now provide a \'normalize\' function', false); + return fullName; + } + }; + + resolve.__resolver__ = resolver; + + return resolve; +} + +Ember.runLoadHooks('Ember.Application', Ember.Application); + +})(); + + + +(function() { + +})(); + + + +(function() { +/** +@module ember +@submodule ember-application +*/ + +var get = Ember.get, set = Ember.set; + +function verifyNeedsDependencies(controller, container, needs) { + var dependency, i, l, missing = []; + + for (i=0, l=needs.length; i 1 ? 'they' : 'it') + " could not be found"); + } +} + +var defaultControllersComputedProperty = Ember.computed(function() { + var controller = this; + + return { + needs: get(controller, 'needs'), + container: get(controller, 'container'), + unknownProperty: function(controllerName) { + var needs = this.needs, + dependency, i, l; + for (i=0, l=needs.length; i 0) { + Ember.assert(' `' + Ember.inspect(this) + ' specifies `needs`, but does ' + + "not have a container. Please ensure this controller was " + + "instantiated with a container.", + this.container || Ember.meta(this, false).descs.controllers !== defaultControllersComputedProperty); + + if (this.container) { + verifyNeedsDependencies(this, this.container, needs); + } + + // if needs then initialize controllers proxy + get(this, 'controllers'); + } + + this._super.apply(this, arguments); + }, + + /** + @method controllerFor + @see {Ember.Route#controllerFor} + @deprecated Use `needs` instead + */ + controllerFor: function(controllerName) { + Ember.deprecate("Controller#controllerFor is deprecated, please use Controller#needs instead"); + return Ember.controllerFor(get(this, 'container'), controllerName); + }, + + /** + Stores the instances of other controllers available from within + this controller. Any controller listed by name in the `needs` + property will be accessible by name through this property. + + ```javascript + App.CommentsController = Ember.ArrayController.extend({ + needs: ['post'], + postTitle: function(){ + var currentPost = this.get('controllers.post'); // instance of App.PostController + return currentPost.get('title'); + }.property('controllers.post.title') + }); + ``` + + @see {Ember.ControllerMixin#needs} + @property {Object} controllers + @default null + */ + controllers: defaultControllersComputedProperty +}); + +})(); + + + +(function() { + +})(); + + + +(function() { +/** +Ember Application + +@module ember +@submodule ember-application +@requires ember-views, ember-routing +*/ + +})(); + +(function() { +/** +@module ember +@submodule ember-extension-support +*/ +/** + The `ContainerDebugAdapter` helps the container and resolver interface + with tools that debug Ember such as the + [Ember Extension](https://github.com/tildeio/ember-extension) + for Chrome and Firefox. + + This class can be extended by a custom resolver implementer + to override some of the methods with library-specific code. + + The methods likely to be overridden are: + + * `canCatalogEntriesByType` + * `catalogEntriesByType` + + The adapter will need to be registered + in the application's container as `container-debug-adapter:main` + + Example: + + ```javascript + Application.initializer({ + name: "containerDebugAdapter", + + initialize: function(container, application) { + application.register('container-debug-adapter:main', require('app/container-debug-adapter')); + } + }); + ``` + + @class ContainerDebugAdapter + @namespace Ember + @extends Ember.Object +*/ +Ember.ContainerDebugAdapter = Ember.Object.extend({ + /** + The container of the application being debugged. + This property will be injected + on creation. + + @property container + @default null + */ + container: null, + + /** + The resolver instance of the application + being debugged. This property will be injected + on creation. + + @property resolver + @default null + */ + resolver: null, + + /** + Returns true if it is possible to catalog a list of available + classes in the resolver for a given type. + + @method canCatalogEntriesByType + @param {string} type The type. e.g. "model", "controller", "route" + @return {boolean} whether a list is available for this type. + */ + canCatalogEntriesByType: function(type) { + if (type === 'model' || type === 'template') return false; + return true; + }, + + /** + Returns the available classes a given type. + + @method catalogEntriesByType + @param {string} type The type. e.g. "model", "controller", "route" + @return {Array} An array of strings. + */ + catalogEntriesByType: function(type) { + var namespaces = Ember.A(Ember.Namespace.NAMESPACES), types = Ember.A(), self = this; + var typeSuffixRegex = new RegExp(Ember.String.classify(type) + "$"); + + namespaces.forEach(function(namespace) { + if (namespace !== Ember) { + for (var key in namespace) { + if (!namespace.hasOwnProperty(key)) { continue; } + if (typeSuffixRegex.test(key)) { + var klass = namespace[key]; + if (Ember.typeOf(klass) === 'class') { + types.push(Ember.String.dasherize(key.replace(typeSuffixRegex, ''))); + } + } + } + } + }); + return types; + } +}); + + +})(); + + + +(function() { +/** +@module ember +@submodule ember-extension-support +*/ +/** + The `DataAdapter` helps a data persistence library + interface with tools that debug Ember such + as the [Ember Extension](https://github.com/tildeio/ember-extension) + for Chrome and Firefox. + + This class will be extended by a persistence library + which will override some of the methods with + library-specific code. + + The methods likely to be overridden are: + + * `getFilters` + * `detect` + * `columnsForType` + * `getRecords` + * `getRecordColumnValues` + * `getRecordKeywords` + * `getRecordFilterValues` + * `getRecordColor` + * `observeRecord` + + The adapter will need to be registered + in the application's container as `dataAdapter:main` + + Example: + + ```javascript + Application.initializer({ + name: "data-adapter", + + initialize: function(container, application) { + application.register('data-adapter:main', DS.DataAdapter); + } + }); + ``` + + @class DataAdapter + @namespace Ember + @extends Ember.Object +*/ +Ember.DataAdapter = Ember.Object.extend({ + init: function() { + this._super(); + this.releaseMethods = Ember.A(); + }, + + /** + The container of the application being debugged. + This property will be injected + on creation. + + @property container + @default null + */ + container: null, + + + /** + The container-debug-adapter which is used + to list all models. + + @property containerDebugAdapter + @default undefined + **/ + containerDebugAdapter: undefined, + + /** + Number of attributes to send + as columns. (Enough to make the record + identifiable). + + @private + @property attributeLimit + @default 3 + */ + attributeLimit: 3, + + /** + Stores all methods that clear observers. + These methods will be called on destruction. + + @private + @property releaseMethods + */ + releaseMethods: Ember.A(), + + /** + Specifies how records can be filtered. + Records returned will need to have a `filterValues` + property with a key for every name in the returned array. + + @public + @method getFilters + @return {Array} List of objects defining filters. + The object should have a `name` and `desc` property. + */ + getFilters: function() { + return Ember.A(); + }, + + /** + Fetch the model types and observe them for changes. + + @public + @method watchModelTypes + + @param {Function} typesAdded Callback to call to add types. + Takes an array of objects containing wrapped types (returned from `wrapModelType`). + + @param {Function} typesUpdated Callback to call when a type has changed. + Takes an array of objects containing wrapped types. + + @return {Function} Method to call to remove all observers + */ + watchModelTypes: function(typesAdded, typesUpdated) { + var modelTypes = this.getModelTypes(), + self = this, typesToSend, releaseMethods = Ember.A(); + + typesToSend = modelTypes.map(function(type) { + var klass = type.klass; + var wrapped = self.wrapModelType(klass, type.name); + releaseMethods.push(self.observeModelType(klass, typesUpdated)); + return wrapped; + }); + + typesAdded(typesToSend); + + var release = function() { + releaseMethods.forEach(function(fn) { fn(); }); + self.releaseMethods.removeObject(release); + }; + this.releaseMethods.pushObject(release); + return release; + }, + + _nameToClass: function(type) { + if (typeof type === 'string') { + type = this.container.lookupFactory('model:' + type); + } + return type; + }, + + /** + Fetch the records of a given type and observe them for changes. + + @public + @method watchRecords + + @param {Function} recordsAdded Callback to call to add records. + Takes an array of objects containing wrapped records. + The object should have the following properties: + columnValues: {Object} key and value of a table cell + object: {Object} the actual record object + + @param {Function} recordsUpdated Callback to call when a record has changed. + Takes an array of objects containing wrapped records. + + @param {Function} recordsRemoved Callback to call when a record has removed. + Takes the following parameters: + index: the array index where the records were removed + count: the number of records removed + + @return {Function} Method to call to remove all observers + */ + watchRecords: function(type, recordsAdded, recordsUpdated, recordsRemoved) { + var self = this, releaseMethods = Ember.A(), records = this.getRecords(type), release; + + var recordUpdated = function(updatedRecord) { + recordsUpdated([updatedRecord]); + }; + + var recordsToSend = records.map(function(record) { + releaseMethods.push(self.observeRecord(record, recordUpdated)); + return self.wrapRecord(record); + }); + + + var contentDidChange = function(array, idx, removedCount, addedCount) { + for (var i = idx; i < idx + addedCount; i++) { + var record = array.objectAt(i); + var wrapped = self.wrapRecord(record); + releaseMethods.push(self.observeRecord(record, recordUpdated)); + recordsAdded([wrapped]); + } + + if (removedCount) { + recordsRemoved(idx, removedCount); + } + }; + + var observer = { didChange: contentDidChange, willChange: Ember.K }; + records.addArrayObserver(self, observer); + + release = function() { + releaseMethods.forEach(function(fn) { fn(); }); + records.removeArrayObserver(self, observer); + self.releaseMethods.removeObject(release); + }; + + recordsAdded(recordsToSend); + + this.releaseMethods.pushObject(release); + return release; + }, + + /** + Clear all observers before destruction + @private + */ + willDestroy: function() { + this._super(); + this.releaseMethods.forEach(function(fn) { + fn(); + }); + }, + + /** + Detect whether a class is a model. + + Test that against the model class + of your persistence library + + @private + @method detect + @param {Class} klass The class to test + @return boolean Whether the class is a model class or not + */ + detect: function(klass) { + return false; + }, + + /** + Get the columns for a given model type. + + @private + @method columnsForType + @param {Class} type The model type + @return {Array} An array of columns of the following format: + name: {String} name of the column + desc: {String} Humanized description (what would show in a table column name) + */ + columnsForType: function(type) { + return Ember.A(); + }, + + /** + Adds observers to a model type class. + + @private + @method observeModelType + @param {Class} type The model type class + @param {Function} typesUpdated Called when a type is modified. + @return {Function} The function to call to remove observers + */ + + observeModelType: function(type, typesUpdated) { + var self = this, records = this.getRecords(type); + + var onChange = function() { + typesUpdated([self.wrapModelType(type)]); + }; + var observer = { + didChange: function() { + Ember.run.scheduleOnce('actions', this, onChange); + }, + willChange: Ember.K + }; + + records.addArrayObserver(this, observer); + + var release = function() { + records.removeArrayObserver(self, observer); + }; + + return release; + }, + + + /** + Wraps a given model type and observes changes to it. + + @private + @method wrapModelType + @param {Class} type A model class + @param {String} Optional name of the class + @return {Object} contains the wrapped type and the function to remove observers + Format: + type: {Object} the wrapped type + The wrapped type has the following format: + name: {String} name of the type + count: {Integer} number of records available + columns: {Columns} array of columns to describe the record + object: {Class} the actual Model type class + release: {Function} The function to remove observers + */ + wrapModelType: function(type, name) { + var release, records = this.getRecords(type), + typeToSend, self = this; + + typeToSend = { + name: name || type.toString(), + count: Ember.get(records, 'length'), + columns: this.columnsForType(type), + object: type + }; + + + return typeToSend; + }, + + + /** + Fetches all models defined in the application. + + @private + @method getModelTypes + @return {Array} Array of model types + */ + getModelTypes: function() { + var types, self = this, + containerDebugAdapter = this.get('containerDebugAdapter'); + + if (containerDebugAdapter.canCatalogEntriesByType('model')) { + types = containerDebugAdapter.catalogEntriesByType('model'); + } else { + types = this._getObjectsOnNamespaces(); + } + // New adapters return strings instead of classes + return types.map(function(name) { + return { + klass: self._nameToClass(name), + name: name + }; + }).filter(function(type) { + return self.detect(type.klass); + }); + }, + + /** + Loops over all namespaces and all objects + attached to them + + @private + @method _getObjectsOnNamespaces + @return {Array} Array of model type strings + */ + _getObjectsOnNamespaces: function() { + var namespaces = Ember.A(Ember.Namespace.NAMESPACES), types = Ember.A(); + + namespaces.forEach(function(namespace) { + for (var key in namespace) { + if (!namespace.hasOwnProperty(key)) { continue; } + var name = Ember.String.dasherize(key); + if (!(namespace instanceof Ember.Application) && namespace.toString()) { + name = namespace + '/' + name; + } + types.push(name); + } + }); + return types; + }, + + /** + Fetches all loaded records for a given type. + + @private + @method getRecords + @return {Array} An array of records. + This array will be observed for changes, + so it should update when new records are added/removed. + */ + getRecords: function(type) { + return Ember.A(); + }, + + /** + Wraps a record and observers changes to it. + + @private + @method wrapRecord + @param {Object} record The record instance. + @return {Object} The wrapped record. Format: + columnValues: {Array} + searchKeywords: {Array} + */ + wrapRecord: function(record) { + var recordToSend = { object: record }, columnValues = {}, self = this; + + recordToSend.columnValues = this.getRecordColumnValues(record); + recordToSend.searchKeywords = this.getRecordKeywords(record); + recordToSend.filterValues = this.getRecordFilterValues(record); + recordToSend.color = this.getRecordColor(record); + + return recordToSend; + }, + + /** + Gets the values for each column. + + @private + @method getRecordColumnValues + @return {Object} Keys should match column names defined + by the model type. + */ + getRecordColumnValues: function(record) { + return {}; + }, + + /** + Returns keywords to match when searching records. + + @private + @method getRecordKeywords + @return {Array} Relevant keywords for search. + */ + getRecordKeywords: function(record) { + return Ember.A(); + }, + + /** + Returns the values of filters defined by `getFilters`. + + @private + @method getRecordFilterValues + @param {Object} record The record instance + @return {Object} The filter values + */ + getRecordFilterValues: function(record) { + return {}; + }, + + /** + Each record can have a color that represents its state. + + @private + @method getRecordColor + @param {Object} record The record instance + @return {String} The record's color + Possible options: black, red, blue, green + */ + getRecordColor: function(record) { + return null; + }, + + /** + Observes all relevant properties and re-sends the wrapped record + when a change occurs. + + @private + @method observerRecord + @param {Object} record The record instance + @param {Function} recordUpdated The callback to call when a record is updated. + @return {Function} The function to call to remove all observers. + */ + observeRecord: function(record, recordUpdated) { + return function(){}; + } + +}); + + +})(); + + + +(function() { +/** +Ember Extension Support + +@module ember +@submodule ember-extension-support +@requires ember-application +*/ + +})(); + +(function() { +/** + @module ember + @submodule ember-testing + */ +var slice = [].slice, + helpers = {}, + injectHelpersCallbacks = []; + +/** + This is a container for an assortment of testing related functionality: + + * Choose your default test adapter (for your framework of choice). + * Register/Unregister additional test helpers. + * Setup callbacks to be fired when the test helpers are injected into + your application. + + @class Test + @namespace Ember +*/ +Ember.Test = { + + /** + `registerHelper` is used to register a test helper that will be injected + when `App.injectTestHelpers` is called. + + The helper method will always be called with the current Application as + the first parameter. + + For example: + + ```javascript + Ember.Test.registerHelper('boot', function(app) { + Ember.run(app, app.advanceReadiness); + }); + ``` + + This helper can later be called without arguments because it will be + called with `app` as the first parameter. + + ```javascript + App = Ember.Application.create(); + App.injectTestHelpers(); + boot(); + ``` + + @public + @method registerHelper + @param {String} name The name of the helper method to add. + @param {Function} helperMethod + @param options {Object} + */ + registerHelper: function(name, helperMethod) { + helpers[name] = { + method: helperMethod, + meta: { wait: false } + }; + }, + + /** + `registerAsyncHelper` is used to register an async test helper that will be injected + when `App.injectTestHelpers` is called. + + The helper method will always be called with the current Application as + the first parameter. + + For example: + + ```javascript + Ember.Test.registerAsyncHelper('boot', function(app) { + Ember.run(app, app.advanceReadiness); + }); + ``` + + The advantage of an async helper is that it will not run + until the last async helper has completed. All async helpers + after it will wait for it complete before running. + + + For example: + + ```javascript + Ember.Test.registerAsyncHelper('deletePost', function(app, postId) { + click('.delete-' + postId); + }); + + // ... in your test + visit('/post/2'); + deletePost(2); + visit('/post/3'); + deletePost(3); + ``` + + @public + @method registerAsyncHelper + @param {String} name The name of the helper method to add. + @param {Function} helperMethod + */ + registerAsyncHelper: function(name, helperMethod) { + helpers[name] = { + method: helperMethod, + meta: { wait: true } + }; + }, + + /** + Remove a previously added helper method. + + Example: + + ```javascript + Ember.Test.unregisterHelper('wait'); + ``` + + @public + @method unregisterHelper + @param {String} name The helper to remove. + */ + unregisterHelper: function(name) { + delete helpers[name]; + delete Ember.Test.Promise.prototype[name]; + }, + + /** + Used to register callbacks to be fired whenever `App.injectTestHelpers` + is called. + + The callback will receive the current application as an argument. + + Example: + + ```javascript + Ember.Test.onInjectHelpers(function() { + Ember.$(document).ajaxSend(function() { + Test.pendingAjaxRequests++; + }); + + Ember.$(document).ajaxComplete(function() { + Test.pendingAjaxRequests--; + }); + }); + ``` + + @public + @method onInjectHelpers + @param {Function} callback The function to be called. + */ + onInjectHelpers: function(callback) { + injectHelpersCallbacks.push(callback); + }, + + /** + This returns a thenable tailored for testing. It catches failed + `onSuccess` callbacks and invokes the `Ember.Test.adapter.exception` + callback in the last chained then. + + This method should be returned by async helpers such as `wait`. + + @public + @method promise + @param {Function} resolver The function used to resolve the promise. + */ + promise: function(resolver) { + return new Ember.Test.Promise(resolver); + }, + + /** + Used to allow ember-testing to communicate with a specific testing + framework. + + You can manually set it before calling `App.setupForTesting()`. + + Example: + + ```javascript + Ember.Test.adapter = MyCustomAdapter.create() + ``` + + If you do not set it, ember-testing will default to `Ember.Test.QUnitAdapter`. + + @public + @property adapter + @type {Class} The adapter to be used. + @default Ember.Test.QUnitAdapter + */ + adapter: null, + + /** + Replacement for `Ember.RSVP.resolve` + The only difference is this uses + and instance of `Ember.Test.Promise` + + @public + @method resolve + @param {Mixed} The value to resolve + */ + resolve: function(val) { + return Ember.Test.promise(function(resolve) { + return resolve(val); + }); + }, + + /** + This allows ember-testing to play nicely with other asynchronous + events, such as an application that is waiting for a CSS3 + transition or an IndexDB transaction. + + For example: + + ```javascript + Ember.Test.registerWaiter(function() { + return myPendingTransactions() == 0; + }); + ``` + The `context` argument allows you to optionally specify the `this` + with which your callback will be invoked. + + For example: + + ```javascript + Ember.Test.registerWaiter(MyDB, MyDB.hasPendingTransactions); + ``` + + @public + @method registerWaiter + @param {Object} context (optional) + @param {Function} callback + */ + registerWaiter: function(context, callback) { + if (arguments.length === 1) { + callback = context; + context = null; + } + if (!this.waiters) { + this.waiters = Ember.A(); + } + this.waiters.push([context, callback]); + }, + /** + `unregisterWaiter` is used to unregister a callback that was + registered with `registerWaiter`. + + @public + @method unregisterWaiter + @param {Object} context (optional) + @param {Function} callback + */ + unregisterWaiter: function(context, callback) { + var pair; + if (!this.waiters) { return; } + if (arguments.length === 1) { + callback = context; + context = null; + } + pair = [context, callback]; + this.waiters = Ember.A(this.waiters.filter(function(elt) { + return Ember.compare(elt, pair)!==0; + })); + } +}; + +function helper(app, name) { + var fn = helpers[name].method, + meta = helpers[name].meta; + + return function() { + var args = slice.call(arguments), + lastPromise = Ember.Test.lastPromise; + + args.unshift(app); + + // some helpers are not async and + // need to return a value immediately. + // example: `find` + if (!meta.wait) { + return fn.apply(app, args); + } + + if (!lastPromise) { + // It's the first async helper in current context + lastPromise = fn.apply(app, args); + } else { + // wait for last helper's promise to resolve + // and then execute + run(function() { + lastPromise = Ember.Test.resolve(lastPromise).then(function() { + return fn.apply(app, args); + }); + }); + } + + return lastPromise; + }; +} + +function run(fn) { + if (!Ember.run.currentRunLoop) { + Ember.run(fn); + } else { + fn(); + } +} + +Ember.Application.reopen({ + /** + This property contains the testing helpers for the current application. These + are created once you call `injectTestHelpers` on your `Ember.Application` + instance. The included helpers are also available on the `window` object by + default, but can be used from this object on the individual application also. + + @property testHelpers + @type {Object} + @default {} + */ + testHelpers: {}, + + /** + This property will contain the original methods that were registered + on the `helperContainer` before `injectTestHelpers` is called. + + When `removeTestHelpers` is called, these methods are restored to the + `helperContainer`. + + @property originalMethods + @type {Object} + @default {} + @private + */ + originalMethods: {}, + + + /** + This property indicates whether or not this application is currently in + testing mode. This is set when `setupForTesting` is called on the current + application. + + @property testing + @type {Boolean} + @default false + */ + testing: false, + + /** + This hook defers the readiness of the application, so that you can start + the app when your tests are ready to run. It also sets the router's + location to 'none', so that the window's location will not be modified + (preventing both accidental leaking of state between tests and interference + with your testing framework). + + Example: + + ``` + App.setupForTesting(); + ``` + + @method setupForTesting + */ + setupForTesting: function() { + Ember.setupForTesting(); + + this.testing = true; + + this.Router.reopen({ + location: 'none' + }); + }, + + /** + This will be used as the container to inject the test helpers into. By + default the helpers are injected into `window`. + + @property helperContainer + @type {Object} The object to be used for test helpers. + @default window + */ + helperContainer: window, + + /** + This injects the test helpers into the `helperContainer` object. If an object is provided + it will be used as the helperContainer. If `helperContainer` is not set it will default + to `window`. If a function of the same name has already been defined it will be cached + (so that it can be reset if the helper is removed with `unregisterHelper` or + `removeTestHelpers`). + + Any callbacks registered with `onInjectHelpers` will be called once the + helpers have been injected. + + Example: + ``` + App.injectTestHelpers(); + ``` + + @method injectTestHelpers + */ + injectTestHelpers: function(helperContainer) { + if (helperContainer) { this.helperContainer = helperContainer; } + + this.testHelpers = {}; + for (var name in helpers) { + this.originalMethods[name] = this.helperContainer[name]; + this.testHelpers[name] = this.helperContainer[name] = helper(this, name); + protoWrap(Ember.Test.Promise.prototype, name, helper(this, name), helpers[name].meta.wait); + } + + for(var i = 0, l = injectHelpersCallbacks.length; i < l; i++) { + injectHelpersCallbacks[i](this); + } + }, + + /** + This removes all helpers that have been registered, and resets and functions + that were overridden by the helpers. + + Example: + + ```javascript + App.removeTestHelpers(); + ``` + + @public + @method removeTestHelpers + */ + removeTestHelpers: function() { + for (var name in helpers) { + this.helperContainer[name] = this.originalMethods[name]; + delete this.testHelpers[name]; + delete this.originalMethods[name]; + } + } +}); + +// This method is no longer needed +// But still here for backwards compatibility +// of helper chaining +function protoWrap(proto, name, callback, isAsync) { + proto[name] = function() { + var args = arguments; + if (isAsync) { + return callback.apply(this, args); + } else { + return this.then(function() { + return callback.apply(this, args); + }); + } + }; +} + +Ember.Test.Promise = function() { + Ember.RSVP.Promise.apply(this, arguments); + Ember.Test.lastPromise = this; +}; + +Ember.Test.Promise.prototype = Ember.create(Ember.RSVP.Promise.prototype); +Ember.Test.Promise.prototype.constructor = Ember.Test.Promise; + +// Patch `then` to isolate async methods +// specifically `Ember.Test.lastPromise` +var originalThen = Ember.RSVP.Promise.prototype.then; +Ember.Test.Promise.prototype.then = function(onSuccess, onFailure) { + return originalThen.call(this, function(val) { + return isolate(onSuccess, val); + }, onFailure); +}; + +// This method isolates nested async methods +// so that they don't conflict with other last promises. +// +// 1. Set `Ember.Test.lastPromise` to null +// 2. Invoke method +// 3. Return the last promise created during method +// 4. Restore `Ember.Test.lastPromise` to original value +function isolate(fn, val) { + var value, lastPromise; + + // Reset lastPromise for nested helpers + Ember.Test.lastPromise = null; + + value = fn(val); + + lastPromise = Ember.Test.lastPromise; + + // If the method returned a promise + // return that promise. If not, + // return the last async helper's promise + if ((value && (value instanceof Ember.Test.Promise)) || !lastPromise) { + return value; + } else { + run(function() { + lastPromise = Ember.Test.resolve(lastPromise).then(function() { + return value; + }); + }); + return lastPromise; + } +} + +})(); + + + +(function() { +var Test = Ember.Test; + +function incrementAjaxPendingRequests(){ + Test.pendingAjaxRequests++; +} + +function decrementAjaxPendingRequests(){ + Ember.assert("An ajaxComplete event which would cause the number of pending AJAX " + + "requests to be negative has been triggered. This is most likely " + + "caused by AJAX events that were started before calling " + + "`injectTestHelpers()`.", Test.pendingAjaxRequests !== 0); + Test.pendingAjaxRequests--; +} + +/** + Sets Ember up for testing. This is useful to perform + basic setup steps in order to unit test. + + Use `App.setupForTesting` to perform integration tests (full + application testing). + + @method setupForTesting + @namespace Ember +*/ +Ember.setupForTesting = function() { + Ember.testing = true; + + // if adapter is not manually set default to QUnit + if (!Ember.Test.adapter) { + Ember.Test.adapter = Ember.Test.QUnitAdapter.create(); + } + + if (!Test.pendingAjaxRequests) { + Test.pendingAjaxRequests = 0; + } + + Ember.$(document).off('ajaxSend', incrementAjaxPendingRequests); + Ember.$(document).off('ajaxComplete', decrementAjaxPendingRequests); + Ember.$(document).on('ajaxSend', incrementAjaxPendingRequests); + Ember.$(document).on('ajaxComplete', decrementAjaxPendingRequests); +}; + +})(); + + + +(function() { +Ember.onLoad('Ember.Application', function(Application) { + Application.initializer({ + name: 'deferReadiness in `testing` mode', + + initialize: function(container, application){ + if (application.testing) { + application.deferReadiness(); + } + } + }); +}); + +})(); + + + +(function() { +/** + @module ember + @submodule ember-testing + */ + +var $ = Ember.$; + +/** + This method creates a checkbox and triggers the click event to fire the + passed in handler. It is used to correct for a bug in older versions + of jQuery (e.g 1.8.3). + + @private + @method testCheckboxClick +*/ +function testCheckboxClick(handler) { + $('') + .css({ position: 'absolute', left: '-1000px', top: '-1000px' }) + .appendTo('body') + .on('click', handler) + .trigger('click') + .remove(); +} + +$(function() { + /* + Determine whether a checkbox checked using jQuery's "click" method will have + the correct value for its checked property. + + If we determine that the current jQuery version exhibits this behavior, + patch it to work correctly as in the commit for the actual fix: + https://github.com/jquery/jquery/commit/1fb2f92. + */ + testCheckboxClick(function() { + if (!this.checked && !$.event.special.click) { + $.event.special.click = { + // For checkbox, fire native event so checked state will be right + trigger: function() { + if ($.nodeName( this, "input" ) && this.type === "checkbox" && this.click) { + this.click(); + return false; + } + } + }; + } + }); + + // Try again to verify that the patch took effect or blow up. + testCheckboxClick(function() { + Ember.warn("clicked checkboxes should be checked! the jQuery patch didn't work", this.checked); + }); +}); + +})(); + + + +(function() { +/** + @module ember + @submodule ember-testing +*/ + +var Test = Ember.Test; + +/** + The primary purpose of this class is to create hooks that can be implemented + by an adapter for various test frameworks. + + @class Adapter + @namespace Ember.Test +*/ +Test.Adapter = Ember.Object.extend({ + /** + This callback will be called whenever an async operation is about to start. + + Override this to call your framework's methods that handle async + operations. + + @public + @method asyncStart + */ + asyncStart: Ember.K, + + /** + This callback will be called whenever an async operation has completed. + + @public + @method asyncEnd + */ + asyncEnd: Ember.K, + + /** + Override this method with your testing framework's false assertion. + This function is called whenever an exception occurs causing the testing + promise to fail. + + QUnit example: + + ```javascript + exception: function(error) { + ok(false, error); + }; + ``` + + @public + @method exception + @param {String} error The exception to be raised. + */ + exception: function(error) { + throw error; + } +}); + +/** + This class implements the methods defined by Ember.Test.Adapter for the + QUnit testing framework. + + @class QUnitAdapter + @namespace Ember.Test + @extends Ember.Test.Adapter +*/ +Test.QUnitAdapter = Test.Adapter.extend({ + asyncStart: function() { + stop(); + }, + asyncEnd: function() { + start(); + }, + exception: function(error) { + ok(false, Ember.inspect(error)); + } +}); + +})(); + + + +(function() { +/** +* @module ember +* @submodule ember-testing +*/ + +var get = Ember.get, + Test = Ember.Test, + helper = Test.registerHelper, + asyncHelper = Test.registerAsyncHelper, + countAsync = 0; + +function currentRouteName(app){ + var appController = app.__container__.lookup('controller:application'); + + return get(appController, 'currentRouteName'); +} + +function currentPath(app){ + var appController = app.__container__.lookup('controller:application'); + + return get(appController, 'currentPath'); +} + +function currentURL(app){ + var router = app.__container__.lookup('router:main'); + + return get(router, 'location').getURL(); +} + +function visit(app, url) { + var router = app.__container__.lookup('router:main'); + router.location.setURL(url); + + if (app._readinessDeferrals > 0) { + router['initialURL'] = url; + Ember.run(app, 'advanceReadiness'); + delete router['initialURL']; + } else { + Ember.run(app, app.handleURL, url); + } + + return wait(app); +} + +function click(app, selector, context) { + var $el = findWithAssert(app, selector, context); + Ember.run($el, 'mousedown'); + + if ($el.is(':input')) { + var type = $el.prop('type'); + if (type !== 'checkbox' && type !== 'radio' && type !== 'hidden') { + Ember.run($el, function(){ + // Firefox does not trigger the `focusin` event if the window + // does not have focus. If the document doesn't have focus just + // use trigger('focusin') instead. + if (!document.hasFocus || document.hasFocus()) { + this.focus(); + } else { + this.trigger('focusin'); + } + }); + } + } + + Ember.run($el, 'mouseup'); + Ember.run($el, 'click'); + + return wait(app); +} + +function triggerEvent(app, selector, context, type, options){ + if (arguments.length === 3) { + type = context; + context = null; + } + + if (typeof options === 'undefined') { + options = {}; + } + + var $el = findWithAssert(app, selector, context); + + var event = Ember.$.Event(type, options); + + Ember.run($el, 'trigger', event); + + return wait(app); +} + +function keyEvent(app, selector, context, type, keyCode) { + if (typeof keyCode === 'undefined') { + keyCode = type; + type = context; + context = null; + } + + return triggerEvent(app, selector, context, type, { keyCode: keyCode, which: keyCode }); +} + +function fillIn(app, selector, context, text) { + var $el; + if (typeof text === 'undefined') { + text = context; + context = null; + } + $el = findWithAssert(app, selector, context); + Ember.run(function() { + $el.val(text).change(); + }); + return wait(app); +} + +function findWithAssert(app, selector, context) { + var $el = find(app, selector, context); + if ($el.length === 0) { + throw new Ember.Error("Element " + selector + " not found."); + } + return $el; +} + +function find(app, selector, context) { + var $el; + context = context || get(app, 'rootElement'); + $el = app.$(selector, context); + + return $el; +} + +function andThen(app, callback) { + return wait(app, callback(app)); +} + +function wait(app, value) { + return Test.promise(function(resolve) { + // If this is the first async promise, kick off the async test + if (++countAsync === 1) { + Test.adapter.asyncStart(); + } + + // Every 10ms, poll for the async thing to have finished + var watcher = setInterval(function() { + // 1. If the router is loading, keep polling + var routerIsLoading = !!app.__container__.lookup('router:main').router.activeTransition; + if (routerIsLoading) { return; } + + // 2. If there are pending Ajax requests, keep polling + if (Test.pendingAjaxRequests) { return; } + + // 3. If there are scheduled timers or we are inside of a run loop, keep polling + if (Ember.run.hasScheduledTimers() || Ember.run.currentRunLoop) { return; } + if (Test.waiters && Test.waiters.any(function(waiter) { + var context = waiter[0]; + var callback = waiter[1]; + return !callback.call(context); + })) { return; } + // Stop polling + clearInterval(watcher); + + // If this is the last async promise, end the async test + if (--countAsync === 0) { + Test.adapter.asyncEnd(); + } + + // Synchronously resolve the promise + Ember.run(null, resolve, value); + }, 10); + }); + +} + + +/** +* Loads a route, sets up any controllers, and renders any templates associated +* with the route as though a real user had triggered the route change while +* using your app. +* +* Example: +* +* ```javascript +* visit('posts/index').then(function() { +* // assert something +* }); +* ``` +* +* @method visit +* @param {String} url the name of the route +* @return {RSVP.Promise} +*/ +asyncHelper('visit', visit); + +/** +* Clicks an element and triggers any actions triggered by the element's `click` +* event. +* +* Example: +* +* ```javascript +* click('.some-jQuery-selector').then(function() { +* // assert something +* }); +* ``` +* +* @method click +* @param {String} selector jQuery selector for finding element on the DOM +* @return {RSVP.Promise} +*/ +asyncHelper('click', click); + +/** +* Simulates a key event, e.g. `keypress`, `keydown`, `keyup` with the desired keyCode +* +* Example: +* +* ```javascript +* keyEvent('.some-jQuery-selector', 'keypress', 13).then(function() { +* // assert something +* }); +* ``` +* +* @method keyEvent +* @param {String} selector jQuery selector for finding element on the DOM +* @param {String} type the type of key event, e.g. `keypress`, `keydown`, `keyup` +* @param {Number} keyCode the keyCode of the simulated key event +* @return {RSVP.Promise} +*/ +asyncHelper('keyEvent', keyEvent); + +/** +* Fills in an input element with some text. +* +* Example: +* +* ```javascript +* fillIn('#email', 'you@example.com').then(function() { +* // assert something +* }); +* ``` +* +* @method fillIn +* @param {String} selector jQuery selector finding an input element on the DOM +* to fill text with +* @param {String} text text to place inside the input element +* @return {RSVP.Promise} +*/ +asyncHelper('fillIn', fillIn); + +/** +* Finds an element in the context of the app's container element. A simple alias +* for `app.$(selector)`. +* +* Example: +* +* ```javascript +* var $el = find('.my-selector'); +* ``` +* +* @method find +* @param {String} selector jQuery string selector for element lookup +* @return {Object} jQuery object representing the results of the query +*/ +helper('find', find); + +/** +* Like `find`, but throws an error if the element selector returns no results. +* +* Example: +* +* ```javascript +* var $el = findWithAssert('.doesnt-exist'); // throws error +* ``` +* +* @method findWithAssert +* @param {String} selector jQuery selector string for finding an element within +* the DOM +* @return {Object} jQuery object representing the results of the query +* @throws {Error} throws error if jQuery object returned has a length of 0 +*/ +helper('findWithAssert', findWithAssert); + +/** + Causes the run loop to process any pending events. This is used to ensure that + any async operations from other helpers (or your assertions) have been processed. + + This is most often used as the return value for the helper functions (see 'click', + 'fillIn','visit',etc). + + Example: + + ```javascript + Ember.Test.registerAsyncHelper('loginUser', function(app, username, password) { + visit('secured/path/here') + .fillIn('#username', username) + .fillIn('#password', username) + .click('.submit') + + return wait(); + }); + + @method wait + @param {Object} value The value to be returned. + @return {RSVP.Promise} +*/ +asyncHelper('wait', wait); +asyncHelper('andThen', andThen); + + + + /** + Returns the currently active route name. + + Example: + + ```javascript + function validateRouteName(){ + equal(currentRouteName(), 'some.path', "correct route was transitioned into."); + } + + visit('/some/path').then(validateRouteName) + ``` + + @method currentRouteName + @return {Object} The name of the currently active route. + */ + helper('currentRouteName', currentRouteName); + + /** + Returns the current path. + + Example: + + ```javascript + function validateURL(){ + equal(currentPath(), 'some.path.index', "correct path was transitioned into."); + } + + click('#some-link-id').then(validateURL); + ``` + + @method currentPath + @return {Object} The currently active path. + */ + helper('currentPath', currentPath); + + /** + Returns the current URL. + + Example: + + ```javascript + function validateURL(){ + equal(currentURL(), '/some/path', "correct URL was transitioned into."); + } + + click('#some-link-id').then(validateURL); + ``` + + @method currentURL + @return {Object} The currently active URL. + */ + helper('currentURL', currentURL); + + + + /** + Triggers the given event on the element identified by the provided selector. + + Example: + + ```javascript + triggerEvent('#some-elem-id', 'blur'); + ``` + + This is actually used internally by the `keyEvent` helper like so: + + ```javascript + triggerEvent('#some-elem-id', 'keypress', { keyCode: 13 }); + ``` + + @method triggerEvent + @param {String} selector jQuery selector for finding element on the DOM + @param {String} type The event type to be triggered. + @param {String} options The options to be passed to jQuery.Event. + @return {RSVP.Promise} + */ + asyncHelper('triggerEvent', triggerEvent); + + +})(); + + + +(function() { +/** + Ember Testing + + @module ember + @submodule ember-testing + @requires ember-application +*/ + +})(); + +(function() { +/** +Ember + +@module ember +*/ + +function throwWithMessage(msg) { + return function() { + throw new Ember.Error(msg); + }; +} + +function generateRemovedClass(className) { + var msg = " has been moved into a plugin: https://github.com/emberjs/ember-states"; + + return { + extend: throwWithMessage(className + msg), + create: throwWithMessage(className + msg) + }; +} + +Ember.StateManager = generateRemovedClass("Ember.StateManager"); + +/** + This was exported to ember-states plugin for v 1.0.0 release. See: https://github.com/emberjs/ember-states + + @class StateManager + @namespace Ember +*/ + +Ember.State = generateRemovedClass("Ember.State"); + +/** + This was exported to ember-states plugin for v 1.0.0 release. See: https://github.com/emberjs/ember-states + + @class State + @namespace Ember +*/ + +})(); + + +})(); diff --git a/blog/2014-09-21-reactive-modeling-with-ember/handlebars-v1.3.0.js b/blog/2014-09-21-reactive-modeling-with-ember/handlebars-v1.3.0.js new file mode 100644 index 00000000..bec7085c --- /dev/null +++ b/blog/2014-09-21-reactive-modeling-with-ember/handlebars-v1.3.0.js @@ -0,0 +1,2746 @@ +/*! + + handlebars v1.3.0 + +Copyright (C) 2011 by Yehuda Katz + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + +@license +*/ +/* exported Handlebars */ +var Handlebars = (function() { +// handlebars/safe-string.js +var __module4__ = (function() { + "use strict"; + var __exports__; + // Build out our basic SafeString type + function SafeString(string) { + this.string = string; + } + + SafeString.prototype.toString = function() { + return "" + this.string; + }; + + __exports__ = SafeString; + return __exports__; +})(); + +// handlebars/utils.js +var __module3__ = (function(__dependency1__) { + "use strict"; + var __exports__ = {}; + /*jshint -W004 */ + var SafeString = __dependency1__; + + var escape = { + "&": "&", + "<": "<", + ">": ">", + '"': """, + "'": "'", + "`": "`" + }; + + var badChars = /[&<>"'`]/g; + var possible = /[&<>"'`]/; + + function escapeChar(chr) { + return escape[chr] || "&"; + } + + function extend(obj, value) { + for(var key in value) { + if(Object.prototype.hasOwnProperty.call(value, key)) { + obj[key] = value[key]; + } + } + } + + __exports__.extend = extend;var toString = Object.prototype.toString; + __exports__.toString = toString; + // Sourced from lodash + // https://github.com/bestiejs/lodash/blob/master/LICENSE.txt + var isFunction = function(value) { + return typeof value === 'function'; + }; + // fallback for older versions of Chrome and Safari + if (isFunction(/x/)) { + isFunction = function(value) { + return typeof value === 'function' && toString.call(value) === '[object Function]'; + }; + } + var isFunction; + __exports__.isFunction = isFunction; + var isArray = Array.isArray || function(value) { + return (value && typeof value === 'object') ? toString.call(value) === '[object Array]' : false; + }; + __exports__.isArray = isArray; + + function escapeExpression(string) { + // don't escape SafeStrings, since they're already safe + if (string instanceof SafeString) { + return string.toString(); + } else if (!string && string !== 0) { + return ""; + } + + // Force a string conversion as this will be done by the append regardless and + // the regex test will do this transparently behind the scenes, causing issues if + // an object's to string has escaped characters in it. + string = "" + string; + + if(!possible.test(string)) { return string; } + return string.replace(badChars, escapeChar); + } + + __exports__.escapeExpression = escapeExpression;function isEmpty(value) { + if (!value && value !== 0) { + return true; + } else if (isArray(value) && value.length === 0) { + return true; + } else { + return false; + } + } + + __exports__.isEmpty = isEmpty; + return __exports__; +})(__module4__); + +// handlebars/exception.js +var __module5__ = (function() { + "use strict"; + var __exports__; + + var errorProps = ['description', 'fileName', 'lineNumber', 'message', 'name', 'number', 'stack']; + + function Exception(message, node) { + var line; + if (node && node.firstLine) { + line = node.firstLine; + + message += ' - ' + line + ':' + node.firstColumn; + } + + var tmp = Error.prototype.constructor.call(this, message); + + // 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]]; + } + + if (line) { + this.lineNumber = line; + this.column = node.firstColumn; + } + } + + Exception.prototype = new Error(); + + __exports__ = Exception; + return __exports__; +})(); + +// handlebars/base.js +var __module2__ = (function(__dependency1__, __dependency2__) { + "use strict"; + var __exports__ = {}; + var Utils = __dependency1__; + var Exception = __dependency2__; + + var VERSION = "1.3.0"; + __exports__.VERSION = VERSION;var COMPILER_REVISION = 4; + __exports__.COMPILER_REVISION = COMPILER_REVISION; + var REVISION_CHANGES = { + 1: '<= 1.0.rc.2', // 1.0.rc.2 is actually rev2 but doesn't report it + 2: '== 1.0.0-rc.3', + 3: '== 1.0.0-rc.4', + 4: '>= 1.0.0' + }; + __exports__.REVISION_CHANGES = REVISION_CHANGES; + var isArray = Utils.isArray, + isFunction = Utils.isFunction, + toString = Utils.toString, + objectType = '[object Object]'; + + function HandlebarsEnvironment(helpers, partials) { + this.helpers = helpers || {}; + this.partials = partials || {}; + + registerDefaultHelpers(this); + } + + __exports__.HandlebarsEnvironment = HandlebarsEnvironment;HandlebarsEnvironment.prototype = { + constructor: HandlebarsEnvironment, + + logger: logger, + log: log, + + registerHelper: function(name, fn, inverse) { + if (toString.call(name) === objectType) { + if (inverse || fn) { throw new Exception('Arg not supported with multiple helpers'); } + Utils.extend(this.helpers, name); + } else { + if (inverse) { fn.not = inverse; } + this.helpers[name] = fn; + } + }, + + registerPartial: function(name, str) { + if (toString.call(name) === objectType) { + Utils.extend(this.partials, name); + } else { + this.partials[name] = str; + } + } + }; + + function registerDefaultHelpers(instance) { + instance.registerHelper('helperMissing', function(arg) { + if(arguments.length === 2) { + return undefined; + } else { + throw new Exception("Missing helper: '" + arg + "'"); + } + }); + + instance.registerHelper('blockHelperMissing', function(context, options) { + var inverse = options.inverse || function() {}, fn = options.fn; + + if (isFunction(context)) { context = context.call(this); } + + if(context === true) { + return fn(this); + } else if(context === false || context == null) { + return inverse(this); + } else if (isArray(context)) { + if(context.length > 0) { + return instance.helpers.each(context, options); + } else { + return inverse(this); + } + } else { + return fn(context); + } + }); + + instance.registerHelper('each', function(context, options) { + var fn = options.fn, inverse = options.inverse; + var i = 0, ret = "", data; + + if (isFunction(context)) { context = context.call(this); } + + if (options.data) { + data = createFrame(options.data); + } + + if(context && typeof context === 'object') { + if (isArray(context)) { + for(var j = context.length; i 0) { + throw new Exception("Invalid path: " + original, this); + } else if (part === "..") { + depth++; + } else { + this.isScoped = true; + } + } else { + dig.push(part); + } + } + + this.original = original; + this.parts = dig; + this.string = dig.join('.'); + this.depth = depth; + + // an ID is simple if it only has one part, and that part is not + // `..` or `this`. + this.isSimple = parts.length === 1 && !this.isScoped && depth === 0; + + this.stringModeValue = this.string; + }, + + PartialNameNode: function(name, locInfo) { + LocationInfo.call(this, locInfo); + this.type = "PARTIAL_NAME"; + this.name = name.original; + }, + + DataNode: function(id, locInfo) { + LocationInfo.call(this, locInfo); + this.type = "DATA"; + this.id = id; + }, + + StringNode: function(string, locInfo) { + LocationInfo.call(this, locInfo); + this.type = "STRING"; + this.original = + this.string = + this.stringModeValue = string; + }, + + IntegerNode: function(integer, locInfo) { + LocationInfo.call(this, locInfo); + this.type = "INTEGER"; + this.original = + this.integer = integer; + this.stringModeValue = Number(integer); + }, + + BooleanNode: function(bool, locInfo) { + LocationInfo.call(this, locInfo); + this.type = "BOOLEAN"; + this.bool = bool; + this.stringModeValue = bool === "true"; + }, + + CommentNode: function(comment, locInfo) { + LocationInfo.call(this, locInfo); + this.type = "comment"; + this.comment = comment; + } + }; + + // Must be exported as an object rather than the root of the module as the jison lexer + // most modify the object to operate properly. + __exports__ = AST; + return __exports__; +})(__module5__); + +// handlebars/compiler/parser.js +var __module9__ = (function() { + "use strict"; + var __exports__; + /* jshint ignore:start */ + /* Jison generated parser */ + var handlebars = (function(){ + var parser = {trace: function trace() { }, + yy: {}, + symbols_: {"error":2,"root":3,"statements":4,"EOF":5,"program":6,"simpleInverse":7,"statement":8,"openInverse":9,"closeBlock":10,"openBlock":11,"mustache":12,"partial":13,"CONTENT":14,"COMMENT":15,"OPEN_BLOCK":16,"sexpr":17,"CLOSE":18,"OPEN_INVERSE":19,"OPEN_ENDBLOCK":20,"path":21,"OPEN":22,"OPEN_UNESCAPED":23,"CLOSE_UNESCAPED":24,"OPEN_PARTIAL":25,"partialName":26,"partial_option0":27,"sexpr_repetition0":28,"sexpr_option0":29,"dataName":30,"param":31,"STRING":32,"INTEGER":33,"BOOLEAN":34,"OPEN_SEXPR":35,"CLOSE_SEXPR":36,"hash":37,"hash_repetition_plus0":38,"hashSegment":39,"ID":40,"EQUALS":41,"DATA":42,"pathSegments":43,"SEP":44,"$accept":0,"$end":1}, + terminals_: {2:"error",5:"EOF",14:"CONTENT",15:"COMMENT",16:"OPEN_BLOCK",18:"CLOSE",19:"OPEN_INVERSE",20:"OPEN_ENDBLOCK",22:"OPEN",23:"OPEN_UNESCAPED",24:"CLOSE_UNESCAPED",25:"OPEN_PARTIAL",32:"STRING",33:"INTEGER",34:"BOOLEAN",35:"OPEN_SEXPR",36:"CLOSE_SEXPR",40:"ID",41:"EQUALS",42:"DATA",44:"SEP"}, + productions_: [0,[3,2],[3,1],[6,2],[6,3],[6,2],[6,1],[6,1],[6,0],[4,1],[4,2],[8,3],[8,3],[8,1],[8,1],[8,1],[8,1],[11,3],[9,3],[10,3],[12,3],[12,3],[13,4],[7,2],[17,3],[17,1],[31,1],[31,1],[31,1],[31,1],[31,1],[31,3],[37,1],[39,3],[26,1],[26,1],[26,1],[30,2],[21,1],[43,3],[43,1],[27,0],[27,1],[28,0],[28,2],[29,0],[29,1],[38,1],[38,2]], + performAction: function anonymous(yytext,yyleng,yylineno,yy,yystate,$$,_$) { + + var $0 = $$.length - 1; + switch (yystate) { + case 1: return new yy.ProgramNode($$[$0-1], this._$); + break; + case 2: return new yy.ProgramNode([], this._$); + break; + case 3:this.$ = new yy.ProgramNode([], $$[$0-1], $$[$0], this._$); + break; + case 4:this.$ = new yy.ProgramNode($$[$0-2], $$[$0-1], $$[$0], this._$); + break; + case 5:this.$ = new yy.ProgramNode($$[$0-1], $$[$0], [], this._$); + break; + case 6:this.$ = new yy.ProgramNode($$[$0], this._$); + break; + case 7:this.$ = new yy.ProgramNode([], this._$); + break; + case 8:this.$ = new yy.ProgramNode([], this._$); + break; + case 9:this.$ = [$$[$0]]; + break; + case 10: $$[$0-1].push($$[$0]); this.$ = $$[$0-1]; + break; + case 11:this.$ = new yy.BlockNode($$[$0-2], $$[$0-1].inverse, $$[$0-1], $$[$0], this._$); + break; + case 12:this.$ = new yy.BlockNode($$[$0-2], $$[$0-1], $$[$0-1].inverse, $$[$0], this._$); + break; + case 13:this.$ = $$[$0]; + break; + case 14:this.$ = $$[$0]; + break; + case 15:this.$ = new yy.ContentNode($$[$0], this._$); + break; + case 16:this.$ = new yy.CommentNode($$[$0], this._$); + break; + case 17:this.$ = new yy.MustacheNode($$[$0-1], null, $$[$0-2], stripFlags($$[$0-2], $$[$0]), this._$); + break; + case 18:this.$ = new yy.MustacheNode($$[$0-1], null, $$[$0-2], stripFlags($$[$0-2], $$[$0]), this._$); + break; + case 19:this.$ = {path: $$[$0-1], strip: stripFlags($$[$0-2], $$[$0])}; + break; + case 20:this.$ = new yy.MustacheNode($$[$0-1], null, $$[$0-2], stripFlags($$[$0-2], $$[$0]), this._$); + break; + case 21:this.$ = new yy.MustacheNode($$[$0-1], null, $$[$0-2], stripFlags($$[$0-2], $$[$0]), this._$); + break; + case 22:this.$ = new yy.PartialNode($$[$0-2], $$[$0-1], stripFlags($$[$0-3], $$[$0]), this._$); + break; + case 23:this.$ = stripFlags($$[$0-1], $$[$0]); + break; + case 24:this.$ = new yy.SexprNode([$$[$0-2]].concat($$[$0-1]), $$[$0], this._$); + break; + case 25:this.$ = new yy.SexprNode([$$[$0]], null, this._$); + break; + case 26:this.$ = $$[$0]; + break; + case 27:this.$ = new yy.StringNode($$[$0], this._$); + break; + case 28:this.$ = new yy.IntegerNode($$[$0], this._$); + break; + case 29:this.$ = new yy.BooleanNode($$[$0], this._$); + break; + case 30:this.$ = $$[$0]; + break; + case 31:$$[$0-1].isHelper = true; this.$ = $$[$0-1]; + break; + case 32:this.$ = new yy.HashNode($$[$0], this._$); + break; + case 33:this.$ = [$$[$0-2], $$[$0]]; + break; + case 34:this.$ = new yy.PartialNameNode($$[$0], this._$); + break; + case 35:this.$ = new yy.PartialNameNode(new yy.StringNode($$[$0], this._$), this._$); + break; + case 36:this.$ = new yy.PartialNameNode(new yy.IntegerNode($$[$0], this._$)); + break; + case 37:this.$ = new yy.DataNode($$[$0], this._$); + break; + case 38:this.$ = new yy.IdNode($$[$0], this._$); + break; + case 39: $$[$0-2].push({part: $$[$0], separator: $$[$0-1]}); this.$ = $$[$0-2]; + break; + case 40:this.$ = [{part: $$[$0]}]; + break; + case 43:this.$ = []; + break; + case 44:$$[$0-1].push($$[$0]); + break; + case 47:this.$ = [$$[$0]]; + break; + case 48:$$[$0-1].push($$[$0]); + break; + } + }, + table: [{3:1,4:2,5:[1,3],8:4,9:5,11:6,12:7,13:8,14:[1,9],15:[1,10],16:[1,12],19:[1,11],22:[1,13],23:[1,14],25:[1,15]},{1:[3]},{5:[1,16],8:17,9:5,11:6,12:7,13:8,14:[1,9],15:[1,10],16:[1,12],19:[1,11],22:[1,13],23:[1,14],25:[1,15]},{1:[2,2]},{5:[2,9],14:[2,9],15:[2,9],16:[2,9],19:[2,9],20:[2,9],22:[2,9],23:[2,9],25:[2,9]},{4:20,6:18,7:19,8:4,9:5,11:6,12:7,13:8,14:[1,9],15:[1,10],16:[1,12],19:[1,21],20:[2,8],22:[1,13],23:[1,14],25:[1,15]},{4:20,6:22,7:19,8:4,9:5,11:6,12:7,13:8,14:[1,9],15:[1,10],16:[1,12],19:[1,21],20:[2,8],22:[1,13],23:[1,14],25:[1,15]},{5:[2,13],14:[2,13],15:[2,13],16:[2,13],19:[2,13],20:[2,13],22:[2,13],23:[2,13],25:[2,13]},{5:[2,14],14:[2,14],15:[2,14],16:[2,14],19:[2,14],20:[2,14],22:[2,14],23:[2,14],25:[2,14]},{5:[2,15],14:[2,15],15:[2,15],16:[2,15],19:[2,15],20:[2,15],22:[2,15],23:[2,15],25:[2,15]},{5:[2,16],14:[2,16],15:[2,16],16:[2,16],19:[2,16],20:[2,16],22:[2,16],23:[2,16],25:[2,16]},{17:23,21:24,30:25,40:[1,28],42:[1,27],43:26},{17:29,21:24,30:25,40:[1,28],42:[1,27],43:26},{17:30,21:24,30:25,40:[1,28],42:[1,27],43:26},{17:31,21:24,30:25,40:[1,28],42:[1,27],43:26},{21:33,26:32,32:[1,34],33:[1,35],40:[1,28],43:26},{1:[2,1]},{5:[2,10],14:[2,10],15:[2,10],16:[2,10],19:[2,10],20:[2,10],22:[2,10],23:[2,10],25:[2,10]},{10:36,20:[1,37]},{4:38,8:4,9:5,11:6,12:7,13:8,14:[1,9],15:[1,10],16:[1,12],19:[1,11],20:[2,7],22:[1,13],23:[1,14],25:[1,15]},{7:39,8:17,9:5,11:6,12:7,13:8,14:[1,9],15:[1,10],16:[1,12],19:[1,21],20:[2,6],22:[1,13],23:[1,14],25:[1,15]},{17:23,18:[1,40],21:24,30:25,40:[1,28],42:[1,27],43:26},{10:41,20:[1,37]},{18:[1,42]},{18:[2,43],24:[2,43],28:43,32:[2,43],33:[2,43],34:[2,43],35:[2,43],36:[2,43],40:[2,43],42:[2,43]},{18:[2,25],24:[2,25],36:[2,25]},{18:[2,38],24:[2,38],32:[2,38],33:[2,38],34:[2,38],35:[2,38],36:[2,38],40:[2,38],42:[2,38],44:[1,44]},{21:45,40:[1,28],43:26},{18:[2,40],24:[2,40],32:[2,40],33:[2,40],34:[2,40],35:[2,40],36:[2,40],40:[2,40],42:[2,40],44:[2,40]},{18:[1,46]},{18:[1,47]},{24:[1,48]},{18:[2,41],21:50,27:49,40:[1,28],43:26},{18:[2,34],40:[2,34]},{18:[2,35],40:[2,35]},{18:[2,36],40:[2,36]},{5:[2,11],14:[2,11],15:[2,11],16:[2,11],19:[2,11],20:[2,11],22:[2,11],23:[2,11],25:[2,11]},{21:51,40:[1,28],43:26},{8:17,9:5,11:6,12:7,13:8,14:[1,9],15:[1,10],16:[1,12],19:[1,11],20:[2,3],22:[1,13],23:[1,14],25:[1,15]},{4:52,8:4,9:5,11:6,12:7,13:8,14:[1,9],15:[1,10],16:[1,12],19:[1,11],20:[2,5],22:[1,13],23:[1,14],25:[1,15]},{14:[2,23],15:[2,23],16:[2,23],19:[2,23],20:[2,23],22:[2,23],23:[2,23],25:[2,23]},{5:[2,12],14:[2,12],15:[2,12],16:[2,12],19:[2,12],20:[2,12],22:[2,12],23:[2,12],25:[2,12]},{14:[2,18],15:[2,18],16:[2,18],19:[2,18],20:[2,18],22:[2,18],23:[2,18],25:[2,18]},{18:[2,45],21:56,24:[2,45],29:53,30:60,31:54,32:[1,57],33:[1,58],34:[1,59],35:[1,61],36:[2,45],37:55,38:62,39:63,40:[1,64],42:[1,27],43:26},{40:[1,65]},{18:[2,37],24:[2,37],32:[2,37],33:[2,37],34:[2,37],35:[2,37],36:[2,37],40:[2,37],42:[2,37]},{14:[2,17],15:[2,17],16:[2,17],19:[2,17],20:[2,17],22:[2,17],23:[2,17],25:[2,17]},{5:[2,20],14:[2,20],15:[2,20],16:[2,20],19:[2,20],20:[2,20],22:[2,20],23:[2,20],25:[2,20]},{5:[2,21],14:[2,21],15:[2,21],16:[2,21],19:[2,21],20:[2,21],22:[2,21],23:[2,21],25:[2,21]},{18:[1,66]},{18:[2,42]},{18:[1,67]},{8:17,9:5,11:6,12:7,13:8,14:[1,9],15:[1,10],16:[1,12],19:[1,11],20:[2,4],22:[1,13],23:[1,14],25:[1,15]},{18:[2,24],24:[2,24],36:[2,24]},{18:[2,44],24:[2,44],32:[2,44],33:[2,44],34:[2,44],35:[2,44],36:[2,44],40:[2,44],42:[2,44]},{18:[2,46],24:[2,46],36:[2,46]},{18:[2,26],24:[2,26],32:[2,26],33:[2,26],34:[2,26],35:[2,26],36:[2,26],40:[2,26],42:[2,26]},{18:[2,27],24:[2,27],32:[2,27],33:[2,27],34:[2,27],35:[2,27],36:[2,27],40:[2,27],42:[2,27]},{18:[2,28],24:[2,28],32:[2,28],33:[2,28],34:[2,28],35:[2,28],36:[2,28],40:[2,28],42:[2,28]},{18:[2,29],24:[2,29],32:[2,29],33:[2,29],34:[2,29],35:[2,29],36:[2,29],40:[2,29],42:[2,29]},{18:[2,30],24:[2,30],32:[2,30],33:[2,30],34:[2,30],35:[2,30],36:[2,30],40:[2,30],42:[2,30]},{17:68,21:24,30:25,40:[1,28],42:[1,27],43:26},{18:[2,32],24:[2,32],36:[2,32],39:69,40:[1,70]},{18:[2,47],24:[2,47],36:[2,47],40:[2,47]},{18:[2,40],24:[2,40],32:[2,40],33:[2,40],34:[2,40],35:[2,40],36:[2,40],40:[2,40],41:[1,71],42:[2,40],44:[2,40]},{18:[2,39],24:[2,39],32:[2,39],33:[2,39],34:[2,39],35:[2,39],36:[2,39],40:[2,39],42:[2,39],44:[2,39]},{5:[2,22],14:[2,22],15:[2,22],16:[2,22],19:[2,22],20:[2,22],22:[2,22],23:[2,22],25:[2,22]},{5:[2,19],14:[2,19],15:[2,19],16:[2,19],19:[2,19],20:[2,19],22:[2,19],23:[2,19],25:[2,19]},{36:[1,72]},{18:[2,48],24:[2,48],36:[2,48],40:[2,48]},{41:[1,71]},{21:56,30:60,31:73,32:[1,57],33:[1,58],34:[1,59],35:[1,61],40:[1,28],42:[1,27],43:26},{18:[2,31],24:[2,31],32:[2,31],33:[2,31],34:[2,31],35:[2,31],36:[2,31],40:[2,31],42:[2,31]},{18:[2,33],24:[2,33],36:[2,33],40:[2,33]}], + defaultActions: {3:[2,2],16:[2,1],50:[2,42]}, + parseError: function parseError(str, hash) { + throw new Error(str); + }, + parse: function parse(input) { + var self = this, stack = [0], vstack = [null], lstack = [], table = this.table, yytext = "", yylineno = 0, yyleng = 0, recovering = 0, TERROR = 2, EOF = 1; + this.lexer.setInput(input); + this.lexer.yy = this.yy; + this.yy.lexer = this.lexer; + this.yy.parser = this; + if (typeof this.lexer.yylloc == "undefined") + this.lexer.yylloc = {}; + var yyloc = this.lexer.yylloc; + lstack.push(yyloc); + var ranges = this.lexer.options && this.lexer.options.ranges; + if (typeof this.yy.parseError === "function") + this.parseError = this.yy.parseError; + function popStack(n) { + stack.length = stack.length - 2 * n; + vstack.length = vstack.length - n; + lstack.length = lstack.length - n; + } + function lex() { + var token; + token = self.lexer.lex() || 1; + if (typeof token !== "number") { + token = self.symbols_[token] || token; + } + return token; + } + var symbol, preErrorSymbol, state, action, a, r, yyval = {}, p, len, newState, expected; + while (true) { + state = stack[stack.length - 1]; + if (this.defaultActions[state]) { + action = this.defaultActions[state]; + } else { + if (symbol === null || typeof symbol == "undefined") { + symbol = lex(); + } + action = table[state] && table[state][symbol]; + } + if (typeof action === "undefined" || !action.length || !action[0]) { + var errStr = ""; + if (!recovering) { + expected = []; + for (p in table[state]) + if (this.terminals_[p] && p > 2) { + expected.push("'" + this.terminals_[p] + "'"); + } + if (this.lexer.showPosition) { + errStr = "Parse error on line " + (yylineno + 1) + ":\n" + this.lexer.showPosition() + "\nExpecting " + expected.join(", ") + ", got '" + (this.terminals_[symbol] || symbol) + "'"; + } else { + errStr = "Parse error on line " + (yylineno + 1) + ": Unexpected " + (symbol == 1?"end of input":"'" + (this.terminals_[symbol] || symbol) + "'"); + } + this.parseError(errStr, {text: this.lexer.match, token: this.terminals_[symbol] || symbol, line: this.lexer.yylineno, loc: yyloc, expected: expected}); + } + } + if (action[0] instanceof Array && action.length > 1) { + throw new Error("Parse Error: multiple actions possible at state: " + state + ", token: " + symbol); + } + switch (action[0]) { + case 1: + stack.push(symbol); + vstack.push(this.lexer.yytext); + lstack.push(this.lexer.yylloc); + stack.push(action[1]); + symbol = null; + if (!preErrorSymbol) { + yyleng = this.lexer.yyleng; + yytext = this.lexer.yytext; + yylineno = this.lexer.yylineno; + yyloc = this.lexer.yylloc; + if (recovering > 0) + recovering--; + } else { + symbol = preErrorSymbol; + preErrorSymbol = null; + } + break; + case 2: + len = this.productions_[action[1]][1]; + yyval.$ = vstack[vstack.length - len]; + yyval._$ = {first_line: lstack[lstack.length - (len || 1)].first_line, last_line: lstack[lstack.length - 1].last_line, first_column: lstack[lstack.length - (len || 1)].first_column, last_column: lstack[lstack.length - 1].last_column}; + if (ranges) { + yyval._$.range = [lstack[lstack.length - (len || 1)].range[0], lstack[lstack.length - 1].range[1]]; + } + r = this.performAction.call(yyval, yytext, yyleng, yylineno, this.yy, action[1], vstack, lstack); + if (typeof r !== "undefined") { + return r; + } + if (len) { + stack = stack.slice(0, -1 * len * 2); + vstack = vstack.slice(0, -1 * len); + lstack = lstack.slice(0, -1 * len); + } + stack.push(this.productions_[action[1]][0]); + vstack.push(yyval.$); + lstack.push(yyval._$); + newState = table[stack[stack.length - 2]][stack[stack.length - 1]]; + stack.push(newState); + break; + case 3: + return true; + } + } + return true; + } + }; + + + function stripFlags(open, close) { + return { + left: open.charAt(2) === '~', + right: close.charAt(0) === '~' || close.charAt(1) === '~' + }; + } + + /* Jison generated lexer */ + var lexer = (function(){ + var lexer = ({EOF:1, + parseError:function parseError(str, hash) { + if (this.yy.parser) { + this.yy.parser.parseError(str, hash); + } else { + throw new Error(str); + } + }, + setInput:function (input) { + this._input = input; + this._more = this._less = this.done = false; + this.yylineno = this.yyleng = 0; + this.yytext = this.matched = this.match = ''; + this.conditionStack = ['INITIAL']; + this.yylloc = {first_line:1,first_column:0,last_line:1,last_column:0}; + if (this.options.ranges) this.yylloc.range = [0,0]; + this.offset = 0; + return this; + }, + input:function () { + var ch = this._input[0]; + this.yytext += ch; + this.yyleng++; + this.offset++; + this.match += ch; + this.matched += ch; + var lines = ch.match(/(?:\r\n?|\n).*/g); + if (lines) { + this.yylineno++; + this.yylloc.last_line++; + } else { + this.yylloc.last_column++; + } + if (this.options.ranges) this.yylloc.range[1]++; + + this._input = this._input.slice(1); + return ch; + }, + unput:function (ch) { + var len = ch.length; + var lines = ch.split(/(?:\r\n?|\n)/g); + + this._input = ch + this._input; + this.yytext = this.yytext.substr(0, this.yytext.length-len-1); + //this.yyleng -= len; + this.offset -= len; + var oldLines = this.match.split(/(?:\r\n?|\n)/g); + this.match = this.match.substr(0, this.match.length-1); + this.matched = this.matched.substr(0, this.matched.length-1); + + if (lines.length-1) this.yylineno -= lines.length-1; + var r = this.yylloc.range; + + this.yylloc = {first_line: this.yylloc.first_line, + last_line: this.yylineno+1, + first_column: this.yylloc.first_column, + last_column: lines ? + (lines.length === oldLines.length ? this.yylloc.first_column : 0) + oldLines[oldLines.length - lines.length].length - lines[0].length: + this.yylloc.first_column - len + }; + + if (this.options.ranges) { + this.yylloc.range = [r[0], r[0] + this.yyleng - len]; + } + return this; + }, + more:function () { + this._more = true; + return this; + }, + less:function (n) { + this.unput(this.match.slice(n)); + }, + pastInput:function () { + var past = this.matched.substr(0, this.matched.length - this.match.length); + return (past.length > 20 ? '...':'') + past.substr(-20).replace(/\n/g, ""); + }, + upcomingInput:function () { + var next = this.match; + if (next.length < 20) { + next += this._input.substr(0, 20-next.length); + } + return (next.substr(0,20)+(next.length > 20 ? '...':'')).replace(/\n/g, ""); + }, + showPosition:function () { + var pre = this.pastInput(); + var c = new Array(pre.length + 1).join("-"); + return pre + this.upcomingInput() + "\n" + c+"^"; + }, + next:function () { + if (this.done) { + return this.EOF; + } + if (!this._input) this.done = true; + + var token, + match, + tempMatch, + index, + col, + lines; + if (!this._more) { + this.yytext = ''; + this.match = ''; + } + var rules = this._currentRules(); + for (var i=0;i < rules.length; i++) { + tempMatch = this._input.match(this.rules[rules[i]]); + if (tempMatch && (!match || tempMatch[0].length > match[0].length)) { + match = tempMatch; + index = i; + if (!this.options.flex) break; + } + } + if (match) { + lines = match[0].match(/(?:\r\n?|\n).*/g); + if (lines) this.yylineno += lines.length; + this.yylloc = {first_line: this.yylloc.last_line, + last_line: this.yylineno+1, + first_column: this.yylloc.last_column, + last_column: lines ? lines[lines.length-1].length-lines[lines.length-1].match(/\r?\n?/)[0].length : this.yylloc.last_column + match[0].length}; + this.yytext += match[0]; + this.match += match[0]; + this.matches = match; + this.yyleng = this.yytext.length; + if (this.options.ranges) { + this.yylloc.range = [this.offset, this.offset += this.yyleng]; + } + this._more = false; + this._input = this._input.slice(match[0].length); + this.matched += match[0]; + token = this.performAction.call(this, this.yy, this, rules[index],this.conditionStack[this.conditionStack.length-1]); + if (this.done && this._input) this.done = false; + if (token) return token; + else return; + } + if (this._input === "") { + return this.EOF; + } else { + return this.parseError('Lexical error on line '+(this.yylineno+1)+'. Unrecognized text.\n'+this.showPosition(), + {text: "", token: null, line: this.yylineno}); + } + }, + lex:function lex() { + var r = this.next(); + if (typeof r !== 'undefined') { + return r; + } else { + return this.lex(); + } + }, + begin:function begin(condition) { + this.conditionStack.push(condition); + }, + popState:function popState() { + return this.conditionStack.pop(); + }, + _currentRules:function _currentRules() { + return this.conditions[this.conditionStack[this.conditionStack.length-1]].rules; + }, + topState:function () { + return this.conditionStack[this.conditionStack.length-2]; + }, + pushState:function begin(condition) { + this.begin(condition); + }}); + lexer.options = {}; + lexer.performAction = function anonymous(yy,yy_,$avoiding_name_collisions,YY_START) { + + + function strip(start, end) { + return yy_.yytext = yy_.yytext.substr(start, yy_.yyleng-end); + } + + + var YYSTATE=YY_START + switch($avoiding_name_collisions) { + case 0: + if(yy_.yytext.slice(-2) === "\\\\") { + strip(0,1); + this.begin("mu"); + } else if(yy_.yytext.slice(-1) === "\\") { + strip(0,1); + this.begin("emu"); + } else { + this.begin("mu"); + } + if(yy_.yytext) return 14; + + break; + case 1:return 14; + break; + case 2: + this.popState(); + return 14; + + break; + case 3:strip(0,4); this.popState(); return 15; + break; + case 4:return 35; + break; + case 5:return 36; + break; + case 6:return 25; + break; + case 7:return 16; + break; + case 8:return 20; + break; + case 9:return 19; + break; + case 10:return 19; + break; + case 11:return 23; + break; + case 12:return 22; + break; + case 13:this.popState(); this.begin('com'); + break; + case 14:strip(3,5); this.popState(); return 15; + break; + case 15:return 22; + break; + case 16:return 41; + break; + case 17:return 40; + break; + case 18:return 40; + break; + case 19:return 44; + break; + case 20:// ignore whitespace + break; + case 21:this.popState(); return 24; + break; + case 22:this.popState(); return 18; + break; + case 23:yy_.yytext = strip(1,2).replace(/\\"/g,'"'); return 32; + break; + case 24:yy_.yytext = strip(1,2).replace(/\\'/g,"'"); return 32; + break; + case 25:return 42; + break; + case 26:return 34; + break; + case 27:return 34; + break; + case 28:return 33; + break; + case 29:return 40; + break; + case 30:yy_.yytext = strip(1,2); return 40; + break; + case 31:return 'INVALID'; + break; + case 32:return 5; + break; + } + }; + lexer.rules = [/^(?:[^\x00]*?(?=(\{\{)))/,/^(?:[^\x00]+)/,/^(?:[^\x00]{2,}?(?=(\{\{|\\\{\{|\\\\\{\{|$)))/,/^(?:[\s\S]*?--\}\})/,/^(?:\()/,/^(?:\))/,/^(?:\{\{(~)?>)/,/^(?:\{\{(~)?#)/,/^(?:\{\{(~)?\/)/,/^(?:\{\{(~)?\^)/,/^(?:\{\{(~)?\s*else\b)/,/^(?:\{\{(~)?\{)/,/^(?:\{\{(~)?&)/,/^(?:\{\{!--)/,/^(?:\{\{![\s\S]*?\}\})/,/^(?:\{\{(~)?)/,/^(?:=)/,/^(?:\.\.)/,/^(?:\.(?=([=~}\s\/.)])))/,/^(?:[\/.])/,/^(?:\s+)/,/^(?:\}(~)?\}\})/,/^(?:(~)?\}\})/,/^(?:"(\\["]|[^"])*")/,/^(?:'(\\[']|[^'])*')/,/^(?:@)/,/^(?:true(?=([~}\s)])))/,/^(?:false(?=([~}\s)])))/,/^(?:-?[0-9]+(?=([~}\s)])))/,/^(?:([^\s!"#%-,\.\/;->@\[-\^`\{-~]+(?=([=~}\s\/.)]))))/,/^(?:\[[^\]]*\])/,/^(?:.)/,/^(?:$)/]; + lexer.conditions = {"mu":{"rules":[4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32],"inclusive":false},"emu":{"rules":[2],"inclusive":false},"com":{"rules":[3],"inclusive":false},"INITIAL":{"rules":[0,1,32],"inclusive":true}}; + return lexer;})() + parser.lexer = lexer; + function Parser () { this.yy = {}; }Parser.prototype = parser;parser.Parser = Parser; + return new Parser; + })();__exports__ = handlebars; + /* jshint ignore:end */ + return __exports__; +})(); + +// handlebars/compiler/base.js +var __module8__ = (function(__dependency1__, __dependency2__) { + "use strict"; + var __exports__ = {}; + var parser = __dependency1__; + var AST = __dependency2__; + + __exports__.parser = parser; + + function parse(input) { + // Just return if an already-compile AST was passed in. + if(input.constructor === AST.ProgramNode) { return input; } + + parser.yy = AST; + return parser.parse(input); + } + + __exports__.parse = parse; + return __exports__; +})(__module9__, __module7__); + +// handlebars/compiler/compiler.js +var __module10__ = (function(__dependency1__) { + "use strict"; + var __exports__ = {}; + var Exception = __dependency1__; + + function Compiler() {} + + __exports__.Compiler = Compiler;// the foundHelper register will disambiguate helper lookup from finding a + // function in a context. This is necessary for mustache compatibility, which + // requires that context functions in blocks are evaluated by blockHelperMissing, + // and then proceed as if the resulting value was provided to blockHelperMissing. + + Compiler.prototype = { + compiler: Compiler, + + disassemble: function() { + var opcodes = this.opcodes, opcode, out = [], params, param; + + for (var i=0, l=opcodes.length; i 0) { + this.source[1] = this.source[1] + ", " + locals.join(", "); + } + + // Generate minimizer alias mappings + if (!this.isChild) { + for (var alias in this.context.aliases) { + if (this.context.aliases.hasOwnProperty(alias)) { + this.source[1] = this.source[1] + ', ' + alias + '=' + this.context.aliases[alias]; + } + } + } + + if (this.source[1]) { + this.source[1] = "var " + this.source[1].substring(2) + ";"; + } + + // Merge children + if (!this.isChild) { + this.source[1] += '\n' + this.context.programs.join('\n') + '\n'; + } + + if (!this.environment.isSimple) { + this.pushSource("return buffer;"); + } + + var params = this.isChild ? ["depth0", "data"] : ["Handlebars", "depth0", "helpers", "partials", "data"]; + + for(var i=0, l=this.environment.depths.list.length; i this.stackVars.length) { this.stackVars.push("stack" + this.stackSlot); } + return this.topStackName(); + }, + topStackName: function() { + return "stack" + this.stackSlot; + }, + flushInline: function() { + var inlineStack = this.inlineStack; + if (inlineStack.length) { + this.inlineStack = []; + for (var i = 0, len = inlineStack.length; i < len; i++) { + var entry = inlineStack[i]; + if (entry instanceof Literal) { + this.compileStack.push(entry); + } else { + this.pushStack(entry); + } + } + } + }, + isInline: function() { + return this.inlineStack.length; + }, + + popStack: function(wrapped) { + var inline = this.isInline(), + item = (inline ? this.inlineStack : this.compileStack).pop(); + + if (!wrapped && (item instanceof Literal)) { + return item.value; + } else { + if (!inline) { + if (!this.stackSlot) { + throw new Exception('Invalid stack pop'); + } + this.stackSlot--; + } + return item; + } + }, + + topStack: function(wrapped) { + var stack = (this.isInline() ? this.inlineStack : this.compileStack), + item = stack[stack.length - 1]; + + if (!wrapped && (item instanceof Literal)) { + return item.value; + } else { + return item; + } + }, + + quotedString: function(str) { + return '"' + str + .replace(/\\/g, '\\\\') + .replace(/"/g, '\\"') + .replace(/\n/g, '\\n') + .replace(/\r/g, '\\r') + .replace(/\u2028/g, '\\u2028') // Per Ecma-262 7.3 + 7.8.4 + .replace(/\u2029/g, '\\u2029') + '"'; + }, + + setupHelper: function(paramSize, name, missingParams) { + var params = [], + paramsInit = this.setupParams(paramSize, params, missingParams); + var foundHelper = this.nameLookup('helpers', name, 'helper'); + + return { + params: params, + paramsInit: paramsInit, + name: foundHelper, + callParams: ["depth0"].concat(params).join(", "), + helperMissingParams: missingParams && ["depth0", this.quotedString(name)].concat(params).join(", ") + }; + }, + + setupOptions: function(paramSize, params) { + var options = [], contexts = [], types = [], param, inverse, program; + + options.push("hash:" + this.popStack()); + + if (this.options.stringParams) { + options.push("hashTypes:" + this.popStack()); + options.push("hashContexts:" + this.popStack()); + } + + inverse = this.popStack(); + program = this.popStack(); + + // Avoid setting fn and inverse if neither are set. This allows + // helpers to do a check for `if (options.fn)` + if (program || inverse) { + if (!program) { + this.context.aliases.self = "this"; + program = "self.noop"; + } + + if (!inverse) { + this.context.aliases.self = "this"; + inverse = "self.noop"; + } + + options.push("inverse:" + inverse); + options.push("fn:" + program); + } + + for(var i=0; i Anyone can build great UI. There is no technology to master, only a +> way of thinking. +> +> -- Me in ancient Greece, presumably. + +The best user interfaces are the ones that need no documentation. They +are intuitive, and by the very act of using them, they educate you +to as their purpose. They tell you about how to efficiently interact +with them to reap the most value. There are, of course, many concerns +that go into making such an interface, but foremost amongst the key +ingredients is _reactivity_. That is, the principle that any changes +to application state are reflected immediately to the user, visually +or otherwise. + + +There are many [great][1] [examples][2] of reactive user interfaces +out there on the web, but perhaps **the most classic is one with which +you are already familiar: the mouse.** Think about how your mouse +works. As you physically manipulate your pointing device, the +on-screen position of the pointer glyph is updated in real time to +reflect input provided by your hand. When you move your hand the +tiniest bit to the left, so does the pointer. When you move to the +right again, the pointer moves in kind. The result is a tight feedback +loop between your hand and the application state in which a user can +directly perceive the precise cause-effect relationship of even +the smallest input. + +### Why Reactive Modeling? + +This principle of instant feedback is what makes reactive interfaces +like a mouse pointer so low friction because *the consequences of any +action are immediately understood.* This encourages the user to +experiment and probe the relationships between the data in your +application. They can "play" in order to verify or invalidate +hypotheses. What happens if I move person A from this column over into +another? What is the consequence of selecting the option provided by +this checkbox? All of these workflows are rendered more potent by +reactivity. + +The great news is that building exciting and effective interfaces is +something anybody can do once they famliarize themselves with how to +think in terms of reactive UI. In this blog post , I'll demonstrate +some very simple, object-oriented techniques you can use to make your +application highly reactive. I call it *Reactive Modeling*. + +The ideas behind reactive modeling are universal. After all, they +underpin the MVC pattern which is, at this point, [older than many Javsascript developers](http://c2.com/cgi/wiki?ModelViewControllerHistory). Reactive modeling can be +applied inside whatever runtime environment you happen to find +yourself. However, it is my experience that when working on the web, +the reactive primitives provided by [Ember.js][5] are the most +intuitive and expressive to be found in a mainstream framework, and so +all of the following examples are written with it. + +### The Reactive Model + +A reactive model is just an object whose properties form a cluster of +well-understood relationships, where those relationships are always +honored at any given point in time. For example, here's an object named +Charles. Charles is happy *if and only if* he's coding. + +```javascript +var charles = Ember.Object.create({ + isCoding: false, + isHappy: Ember.computed.alias 'isCoding' +}) + +charles.get('isHappy') //=> false + +charles.set('isCoding', true) +charles.get('isHappy') //=> true + +charles.set('isHappy', false) +charles.get('isCoding') //=> false +``` + +In this case, there's a relationship between his happiness and whether +or not he's coding. If the value of `isCoding` becomes `true`, then +the value of `isHappy` is immediately observed to be `true`. By the +same token, if the value of `isHappy` is set to `false`, then +`isCoding` is immediately observed to be `false`. Because of the +explicit relationship between coding and happiness, those properties +need to adjust their values so that the relationship stays true to its +definition. + +And that's it. That's reactive modeling. **It's just an object which +always maintains the integrity of the relationships between its +properties.** It's such a simple idea, but one that yields enormous +dividends when building a user interface that provides instant +feedback about its current state. + +### Relating multiple values + +Relating boolean values to boolean values like I just did is a simple +thing, but in the real world we need to relate many disparate values, +all of which can have very different datatypes. + +An example with which most of us are familiar is specifying color +values. Let's try to build an intuitive (read reactive) color +coordinate; one that let us explore the relationship between the +perceptual value of a color and its representation inside a computer's +memory. + +Let's assume we have an immutable Color object that will serve +as our primitive value. I can construct this color using rgb +values. + +```javascript +var red = Color.fromRGB(255,0,0) //=> #ff0000, very red +var red2 = Color.fromRGB(255,0,0) //=> #ff0000, also very red + +red == red2 //=> true, same color, same object + +red.r //=> 255 +red.g //=> 0 +red.b //=> 0 +``` + +#### A note on modeling in software + +It's important to note here that values of type `Color` in these examples +are perceptual only. Metaphysically speaking, they have nothing to do +with their rgb values. Instead, they correspond to values as perceived +by human beings. This can be a source of confusion because we have to +use rgb values to construct them. Sadly, this is unavoidable. Because our +program runs inside a computer and not a human brain, we are forced to +choose a representation that is accesible to a computer. Think of it like the word +`"friend"`. It is a very complex, very human subject, but the word +itself is represented on my laptop by the number +`110011011100101101001110010111011101100100` + + +In the case of color it helps me to think of the color values, once +they are constructed, as just one more primitive value: unique and +immutable.... like an integer! + +Once we have this, we can make a reactive model relating the `r`, `g`, and +`b` integer values to a corresponding `Color` value. This model will +have four public properties: the `Color` instance itself, as well as +three integer coordinates for red, green and blue. Conceptually, it +looks like this. + + + +Notice the 4 public, bindable properties? The expectation is that any +code can bind a value of type `Color` to the coordinate's `color` +property, and observe the consequences, and, by the same token, bind +any value of type `Integer` to the `r`,`g`, and `b` properties and +observe those consequences. Let's see this in action: + +```javascript +//the coordinate has an initial rgb value +var rgb = RGBColor.create({r: 255, g: 0, b:0}) + +//this implies a color value +rgb.get('color') // => red + +// change the r, and the b +rgb.set('r', 0) +rgb.set('b', 255) + +// the color changes +rgb.get('color') // => blue + +//change the color +rgb.set('color', Color.GREEN) + +//the rgb values change in relation +rgb.getProperties('r','g','b') //=> {r: 0, g: 255, b: 0} +``` + +You might be thinking at this point, why do we even need a separate +coordinate class at all? Don't we already have everything we need with +the `Color` constructor? Doesn't it already relate rgb integer values +to color values? The answer, of course, is reactivity. **Integers +and colors are immutable.** They are a still life; a single value +observed in a single moment. The reactive model on the other hand, has +inputs and outputs that are allowed to change over time and combine a +series of these still lifes into a moving picture. + +A straightforward way to see this is in action is with a little +template that displays the color on screen along with its red value. + +```hbs +

    {{color-swatch color=rgb.color}} r: {{rgb.r}}

    +``` + +We can set the `r` value repeatedly in an animation loop with the +following little snippet of code: + +```js +requestAnimationFrame(function animateRValue(time) { + rgb.set('r', Math.round((time / 20) % 255)); + requestAnimationFrame(animateRValue); +}) +``` + + +This has the effect of gradually fading in the red color over several +seconds. + +While this may seem obvious, it's still worth demonstrating that what +we see is nothing more than a sequence of discrete color values that +are reacting to a sequence of discrete `r` values. + + + + + +

    + +Animation though, is just a special case of reactivity where one of +the values is related to time. What's more common in UI is to relate +application values to user input. So instead of relating the r value +to time, let's relate it to the user's mouse! We can do this by +binding it to the output of an html slider. + +```hbs +{{color-swatch color=rgb.color}} {{x-slider min=0 max=255 value=rgb.r}} + +``` + +The html slider value takes on an integer value between 0 and 255. As +this value changes, it changes the value of "rgb.r" to which it is +bound. As the value of "rgb.r" changes, we have already seen that the +value of "rgb.color" must change, which is then reflected in our +color-swatch. Which is all to say, that our color is now reacting to +the movement of our user's hand. + + + + +
    + +Of course, we can do the same for the green and blue values just by +inserting more html sliders for the their values. + +```hbs +{{color-swatch color=rgb.color}} +
    +r: {{x-slider min=0 max=255 value=rgb.r}} {{rgb.r}}
    +g: {{x-slider min=0 max=255 value=rgb.g}} {{rgb.g}}
    +b: {{x-slider min=0 max=255 value=rgb.b}} {{rgb.b}}
    +``` + + + +
    +

    + +We can even attach multiple sliders to the same value, which is silly, +but does re-enforce the idea that it doesn't matter _where_ values +come from, the reactive model only defines relationships between them. + + + + +

    + +

    + +The model is reactive, the feedback is instantaneous, the experience +is intuitive. + +

    Great news everybody! It's easy.

    + +The actual object that drives all these interactions is quite +simple: + +```javascript +var RGBColor = Ember.Object.extend({ + colorValue: Ember.computed('r', 'g', 'b', function() { + return Color.fromRGB(this.get('r'), this.get('g'), this.get('b')); + }), + colorBinding: Ember.Binding.oneWay('colorValue'), + rBinding: Ember.Binding.oneWay('color.r'), + gBinding: Ember.Binding.oneWay('color.g'), + bBinding: Ember.Binding.oneWay('color.b') +}); +``` + +If you're not familiar with Ember conventions (or even if you are), +this may be appear dense at first, so let's unpack it a bit. Remember +the diagram of the `RGBColor` that had the four public properties +`color`, `r`, `g`, `b`? Well, here's the same diagram, except we'll +pull back the curtain so that you can see how it's wired up inside. + + + +Our object definition for `RGBColor` just draws the same picture in +code. Let's parse it line-by-line. + +```js +colorValue: Ember.computed('r', 'g', 'b', function() { + return Color.fromRGB(this.get('r'), this.get('g'), this.get('b')); +}), +``` + +This declaration tells how to derive the `colorValue` property from +the individual rgb components. + +Everything else is just specifying how data flows. + +```js +colorBinding: Ember.Binding.oneWay('colorValue') +``` + +This binding causes data to flow from the `colorValue` property to the +`color` property. That is, the moment that a new color appears at +`colorValue`, it also appears at `color`. Because it is a one way +binding, the reverse is not true. A new Color can appear at `color`, +but the `colorValue` property will remain the same. + +```js +rBinding: Ember.Binding.oneWay('color.r') +``` + +Going the other way, this declaration causes the `r` value of the color +to flow into the `r` property of the RGBColor object. Again, this is a +one way binding, so values will not flow the other way. In fact, they +couldn't flow the other way even if they wanted to because `color.r` +is immutable. + +`gBinding` and `bBinding` act in a similar fashion to +`rBinding` on their respective properties + +Does this seem all a bit meta-circular to you? It might, and if it +does, that's because it is. + +What we've done here construct a bi-directional data flow by composing +uni-directional data flows, such that values can travel from one end +of the object to the other and back again. They can enter at any of +the public binding points, but once inside, they naturally diffuse +around the entire object. + + + +It is from this cycle that "two-way" bindings are constructed. I +put "two-way" in scare quotes because if you decompose it, you find +that, like all two way bindings, it's just a set of one-way bindings +that form a feedback loop. + +>This is an important point to grasp (and one that critics of two-way +>binding rarely address). While publicly a value may be bound two +>ways, it can proceed internally through an arbitrarily complex +>apparatus of splits, merges, and transformation on its journey from +>point A to B and back again. Mastering both legs of the trip is key +>to mastering the reactive model. + +

    Why understanding this is crucial to UX

    + +Your job is to create fluid, reactive experiences for your users so +that they can delight in both learning and using your systems. But +sometimes doing so can be very difficult. Have you ever tried to build +a date input that parses free-form text? Or how about a credit card +input that continually reformats and self-validates? Have you ever had +to manage by hand multiple lists derived from the same source list and +make sure they remain in sync? + +Luckily, there are a lot of tools available today that, using the +techniques I've described, can transform tasks like these from annoying and +painful to fun and easy. They let you stop thinking about the +mechanics of how values propagate from one area of your application to +another and instead focus on the paths that they travel to get there. + +If you work with a toolset that already supports these reactive +modelling primitives, then try to push the envelope of what you can +achieve simply by composing simple data flows. Otherwise, if you've +never created your own reactive models, what are you waiting for? give +it a try! Go [Download Ember][5] and play with computed properties and +two-way bindings. Not only will understanding them make you a better +developer with a bigger bag of tricks, but you'll build interfaces +that, like the mouse, give users a direct tactile connection with your +application. + +[1]: http://www.mathsisfun.com/algebra/trig-interactive-unit-circle.html +[2]: https://kuler.adobe.com/create/color-wheel/ +[5]: http://emberjs.com/ + + + + + + diff --git a/blog/2014-09-21-reactive-modeling-with-ember/main.js b/blog/2014-09-21-reactive-modeling-with-ember/main.js new file mode 100644 index 00000000..e21683b4 --- /dev/null +++ b/blog/2014-09-21-reactive-modeling-with-ember/main.js @@ -0,0 +1,135 @@ +(function() { + + var App = Ember.Application.create({Router: null}); + App.Router.reopen({ location: 'none' }); + + var RGBSelector = Ember.Mixin.create({ + color: Color.fromRGB(255,0,0), + impliedColor: function() { + return Color.fromRGB(this.getProperties('r','g','b')) + }.property('r', 'g','b'), + setupRgbBindings: function() { + Ember.oneWay(this, 'r', 'color.r') + Ember.oneWay(this, 'g', 'color.g') + Ember.oneWay(this, 'b', 'color.b') + Ember.oneWay(this, 'color', 'impliedColor') + }.on('didInsertElement') + }) + + App.ColorMatcherDemoComponent = Ember.Component.extend({ + layoutName: 'components/color-matcher-demo', + matcher: function() { + return Matcher.create({ + input: "#FF0000" + }) + }.property() + }) + + App.ColorFormatDemoComponent = Ember.Component.extend(RGBSelector, { + layoutName: 'components/formatter-demo', + formatter: function() { + return Formatter.create() + }.property(), + setupColorBindings: function() { + Ember.oneWay(this, 'formatter.color', 'color') + }.on('didInsertElement') + }) + + App.SyntaxLiteDemoComponent = Ember.Component.extend(RGBSelector, { + layoutName: 'components/syntax-lite-demo', + syntax: function() { + return ColorSyntaxLite.create() + }.property(), + bindColorProperties: function() { + Ember.bind(this, 'syntax.color', 'color') + }.on('init'), + logColor: function() { + console.log('Global Color', this.get('color.rgb')) + }.observes('color') + }) + + App.ColorSyntaxDemoComponent = Ember.Component.extend(RGBSelector, { + layoutName: 'components/color-syntax-demo', + + syntax1: function() { + return ColorSyntax.create({ + input: "#FF0000" + }) + }.property(), + syntax2: function() { + return ColorSyntax.create({ + input: "rgb(255,0,0)" + }) + }.property(), + syntaxBindings: function() { + Ember.bind(this, 'syntax1.output', 'color') + Ember.bind(this, 'syntax2.output', 'color') + }.on('didInsertElement') + }) + + App.ColorSwatchComponent = Ember.Component.extend({ + classNameBindings: [':color-swatch', 'color:defined:undefined'], + attributeBindings: ['style'], + color: Color.fromRGB(), + style: function() { + var rgb = this.get('color.rgb') + if (rgb) { + return "background-color: " + "rgb(" + [rgb.r,rgb.g,rgb.b].join(',') + ")" + } else { + return "background-color: " + "transparent;" + } + }.property('color'), + }) + + App.XSliderComponent = Ember.Component.extend({ + classNames: ['x-slider'], + tagName: ['input'], + attributeBindings: ['min', 'max', 'step', 'type', 'name'], + type: "range", + setup: function() { + Ember.oneWay(this, 'element.value', 'value'); + }.on('didInsertElement'), + input: function() { + this.set('value', Number(this.get('element.value')).valueOf()); + } + }); + + App.RgbSelectorComponent = Ember.Component.extend({ + color: Color.fromRGB(), + setupBindings: function() { + Ember.oneWay(this, 'h', 'color.r') + Ember.oneWay(this, 's', 'color.g') + Ember.oneWay(this, 'b', 'color.b') + }.on('init'), + + changeColor: function() { + this.set('color', Color.fromRGB(this.getProperties('r','g','b'))); + }.observes("r", "g", "b") + }); + + $(function() { + $('[data-component]').each(function() { + var name = $(this).data('component'); + var key = "component:" + name.camelize(); + var component = App.__container__.lookup(key); + if (!component) { + var Component = Ember.Component.extend(); + component = Component.create({ + rgb: RGBColor.create({r: 255, g: 0, b: 0}), + classNames: [name], + container: App.__container__, + layoutName: 'components/' + name + }); + } + component.replaceIn(this); + var initializerName = ('initialize_' + name).camelize(); + if (!Em.isEmpty(initializerName)) { + initializerName = initializerName.camelize(); + var initializer = window[initializerName]; + if (initializer) { + initializer.call(this, component); + } + } + }); + }) +})() diff --git a/blog/2014-09-21-reactive-modeling-with-ember/newtons-cradle.jpg b/blog/2014-09-21-reactive-modeling-with-ember/newtons-cradle.jpg new file mode 100644 index 00000000..fb0f816e Binary files /dev/null and b/blog/2014-09-21-reactive-modeling-with-ember/newtons-cradle.jpg differ diff --git a/blog/2014-09-21-reactive-modeling-with-ember/rgb-color-private-labelled.svg b/blog/2014-09-21-reactive-modeling-with-ember/rgb-color-private-labelled.svg new file mode 100644 index 00000000..b556cb47 --- /dev/null +++ b/blog/2014-09-21-reactive-modeling-with-ember/rgb-color-private-labelled.svg @@ -0,0 +1,3 @@ + + + Produced by OmniGraffle 6.0.5 2014-09-23 20:55ZCanvas 1Layer 1colorValuecolor: Colorr:Integerg:Integerb:IntegerrBindinggBindingbBindingcolorBinding diff --git a/blog/2014-09-21-reactive-modeling-with-ember/rgb-color-private.svg b/blog/2014-09-21-reactive-modeling-with-ember/rgb-color-private.svg new file mode 100644 index 00000000..ef695473 --- /dev/null +++ b/blog/2014-09-21-reactive-modeling-with-ember/rgb-color-private.svg @@ -0,0 +1,3 @@ + + + Produced by OmniGraffle 6.0.5 2014-09-23 20:55ZCanvas 1Layer 1colorValuecolor: Colorr:Integerg:Integerb:Integer diff --git a/blog/2014-09-21-reactive-modeling-with-ember/rgb-color-public.svg b/blog/2014-09-21-reactive-modeling-with-ember/rgb-color-public.svg new file mode 100644 index 00000000..881f4935 --- /dev/null +++ b/blog/2014-09-21-reactive-modeling-with-ember/rgb-color-public.svg @@ -0,0 +1,3 @@ + + + Produced by OmniGraffle 6.0.5 2014-09-19 21:13ZCanvas 1Layer 1colorValuecolor: Colorr:Integerg:Integerb:IntegerRGBColor diff --git a/blog/2015-08-14-x-select-status-update/index.md b/blog/2015-08-14-x-select-status-update/index.md new file mode 100644 index 00000000..862bc129 --- /dev/null +++ b/blog/2015-08-14-x-select-status-update/index.md @@ -0,0 +1,69 @@ +--- +title: X-Select Status Update +author: Robert DeLuca +tags: + - oss + - ember + - ember-addon + - x-select +--- +At The Frontside, we love open source, and maintaining OSS projects is a high priority. + +`emberx-select` has grown in popularity. [Even the ember deprecation guide recommends it](http://emberjs.com/deprecations/v1.x/#toc_ember-select). We are aiming to keep this component up to date with the latest versions of Ember. + +Before Glimmer (1.13) compatibility, we are making a release for Ember 1.12 or below. + +## What's new in 1.1.3? + +- Title attr bound to an option or select. [PR](https://github.com/thefrontside/emberx-select/pull/32) +- Add optionValuePath [PR](https://github.com/thefrontside/emberx-select/pull/31) +- Fixed blockless form [PR](https://github.com/thefrontside/emberx-select/commit/46c7acca9f7bd3a67b08f1fc1f6174759d47f465) +- Default tabindex to 0 to make it tabable. [PR](https://github.com/thefrontside/emberx-select/pull/37) +- Introduce `registerSelectHelper` [PR](https://github.com/thefrontside/emberx-select/pull/14) +- Improve test helper [PR](https://github.com/thefrontside/emberx-select/pull/27) +- Improved testing coverage + +See the [diff here](https://github.com/thefrontside/emberx-select/compare/v1.1.2...v1.1.3). + +Thank you to everyone that has helped by submitting issues and pull requests! 1.1.3 will be the last version released that's compatible with Ember 1.12 or below. + +## What's new in 2.0? + +In x-select 2.0 we dropped support for the blockless form. While it is useful for transitioning +from `Ember.select`, we strongly prefer to standardize writing `selects` in Ember as in HTML. +We no longer bundle x-select with the blockless form. To continue using blockless x-select, [use this addon](https://github.com/thefrontside/emberx-select-blockless) it from x-select. + +x-select 2.0 arrays are now immutable. Every user selection creates a new state of the selected content. +Previously, multi-selects required updating content as a user interacts with the select component. This was unstable especially with async ember data relationships. + +Expect Glimmer/Ember 2.0 compatability in x-select 2.0 thanks to [this PR](https://github.com/thefrontside/emberx-select/pull/33) +from James Rosen! + +### Known Glimmer issue with x-select + +To use x-select with Glimmer (Ember 1.13.x) requires 1.13.4+. Anything below +1.13.4 will throw +``` +Error: Assertion Failed: x-option component declared without enclosing x-select +``` +because non-dirty component child views are not getting the correct parentView. You can see the related +ticket [here](https://github.com/emberjs/ember.js/pull/11651). + +We also applied a [small hack to work around](https://github.com/thefrontside/emberx-select/commit/843f76d6a033f587f9b5edb5fb0758c05e5629c3) +form attribute bindings which are broken in Ember 1.12-1.13.2 and fixed in Ember 1.13.3. +Ember data bindings are lost, so the parent form should not change. If data bindings are still necessary, call `rerender()` on the component after the data change. + +Once again, thanks to all the contributors that helped make the `x-select` component better for everyone! + +### Using x-select-blockless + +To use `x-select-blockless` you'll need to install both `x-select-blockless` and `x-select` ember addons. This is because +[we rely on](https://github.com/thefrontside/emberx-select-blockless/blob/master/package.json#L42) x-select. + + +### Compatibility Table + +| x-select | Ember | +|-----------|---------| +| v1.1.4 and before | 1.12 or lower | +| v2.x.x and after | 1.13.4 or higher | diff --git a/blog/2016-01-22-functional-templating-in-ember/choose-files-with-preview.svg b/blog/2016-01-22-functional-templating-in-ember/choose-files-with-preview.svg new file mode 100644 index 00000000..cfd34fbe --- /dev/null +++ b/blog/2016-01-22-functional-templating-in-ember/choose-files-with-preview.svg @@ -0,0 +1,3 @@ + + + Produced by OmniGraffle 6.4.1 2016-01-12 22:24:45 +0000Canvas 1Layer 1Top-Level Context files: FileListaction(action (mut files)){{x-file-input}} {{each}} file: File{{x-object-url}} url: String diff --git a/blog/2016-01-22-functional-templating-in-ember/demo/.editorconfig b/blog/2016-01-22-functional-templating-in-ember/demo/.editorconfig new file mode 100644 index 00000000..219985c2 --- /dev/null +++ b/blog/2016-01-22-functional-templating-in-ember/demo/.editorconfig @@ -0,0 +1,20 @@ +# EditorConfig helps developers define and maintain consistent +# coding styles between different editors and IDEs +# editorconfig.org + +root = true + + +[*] +end_of_line = lf +charset = utf-8 +trim_trailing_whitespace = true +insert_final_newline = true +indent_style = space +indent_size = 2 + +[*.hbs] +insert_final_newline = false + +[*.{diff,md}] +trim_trailing_whitespace = false diff --git a/blog/2016-01-22-functional-templating-in-ember/demo/.ember-cli b/blog/2016-01-22-functional-templating-in-ember/demo/.ember-cli new file mode 100644 index 00000000..ee64cfed --- /dev/null +++ b/blog/2016-01-22-functional-templating-in-ember/demo/.ember-cli @@ -0,0 +1,9 @@ +{ + /** + Ember CLI sends analytics information by default. The data is completely + anonymous, but there are times when you might want to disable this behavior. + + Setting `disableAnalytics` to true will prevent any data from being sent. + */ + "disableAnalytics": false +} diff --git a/blog/2016-01-22-functional-templating-in-ember/demo/.eslintrc.js b/blog/2016-01-22-functional-templating-in-ember/demo/.eslintrc.js new file mode 100644 index 00000000..2873e2fc --- /dev/null +++ b/blog/2016-01-22-functional-templating-in-ember/demo/.eslintrc.js @@ -0,0 +1,13 @@ +module.exports = { + root: true, + parserOptions: { + ecmaVersion: 2017, + sourceType: 'module' + }, + extends: 'eslint:recommended', + env: { + browser: true + }, + rules: { + } +}; diff --git a/blog/2016-01-22-functional-templating-in-ember/demo/.gitignore b/blog/2016-01-22-functional-templating-in-ember/demo/.gitignore new file mode 100644 index 00000000..716d800c --- /dev/null +++ b/blog/2016-01-22-functional-templating-in-ember/demo/.gitignore @@ -0,0 +1,22 @@ +# See https://help.github.com/ignore-files/ for more about ignoring files. + +# compiled output +/tmp + +# dependencies +/node_modules +/bower_components + +# misc +/.sass-cache +/connect.lock +/coverage/* +/libpeerconnection.log +npm-debug.log* +yarn-error.log +testem.log + +# ember-try +.node_modules.ember-try/ +bower.json.ember-try +package.json.ember-try diff --git a/blog/2016-01-22-functional-templating-in-ember/demo/.jshintrc b/blog/2016-01-22-functional-templating-in-ember/demo/.jshintrc new file mode 100644 index 00000000..2b6f469f --- /dev/null +++ b/blog/2016-01-22-functional-templating-in-ember/demo/.jshintrc @@ -0,0 +1,3 @@ +{ + "esversion": 6 +} diff --git a/blog/2016-01-22-functional-templating-in-ember/demo/.travis.yml b/blog/2016-01-22-functional-templating-in-ember/demo/.travis.yml new file mode 100644 index 00000000..47a97a56 --- /dev/null +++ b/blog/2016-01-22-functional-templating-in-ember/demo/.travis.yml @@ -0,0 +1,23 @@ +--- +language: node_js +node_js: + - "6" + +sudo: false +dist: trusty + +addons: + chrome: stable + +cache: + yarn: true + +before_install: + - curl -o- -L https://yarnpkg.com/install.sh | bash + - export PATH=$HOME/.yarn/bin:$PATH + +install: + - yarn install --non-interactive + +script: + - yarn test diff --git a/blog/2016-01-22-functional-templating-in-ember/demo/.watchmanconfig b/blog/2016-01-22-functional-templating-in-ember/demo/.watchmanconfig new file mode 100644 index 00000000..e7834e3e --- /dev/null +++ b/blog/2016-01-22-functional-templating-in-ember/demo/.watchmanconfig @@ -0,0 +1,3 @@ +{ + "ignore_dirs": ["tmp", "dist"] +} diff --git a/blog/2016-01-22-functional-templating-in-ember/demo/README.md b/blog/2016-01-22-functional-templating-in-ember/demo/README.md new file mode 100644 index 00000000..7022f80b --- /dev/null +++ b/blog/2016-01-22-functional-templating-in-ember/demo/README.md @@ -0,0 +1,50 @@ +# file-upload-demo + +This README outlines the details of collaborating on this Ember application. +A short introduction of this app could easily go here. + +## Prerequisites + +You will need the following things properly installed on your computer. + +* [Git](https://git-scm.com/) +* [Node.js](https://nodejs.org/) (with NPM) +* [Ember CLI](https://ember-cli.com/) +* [Google Chrome](https://google.com/chrome/) + +## Installation + +* `git clone ` this repository +* `cd file-upload-demo` +* `npm install` + +## Running / Development + +* `ember serve` +* Visit your app at [http://localhost:4200](http://localhost:4200). + +### Code Generators + +Make use of the many generators for code, try `ember help generate` for more details + +### Running Tests + +* `ember test` +* `ember test --server` + +### Building + +* `ember build` (development) +* `ember build --environment production` (production) + +### Deploying + +Specify what it takes to deploy your app. + +## Further Reading / Useful Links + +* [ember.js](https://emberjs.com/) +* [ember-cli](https://ember-cli.com/) +* Development Browser Extensions + * [ember inspector for chrome](https://chrome.google.com/webstore/detail/ember-inspector/bmdblncegkenkacieihfhpjfppoconhi) + * [ember inspector for firefox](https://addons.mozilla.org/en-US/firefox/addon/ember-inspector/) diff --git a/blog/2016-01-22-functional-templating-in-ember/demo/app/app.js b/blog/2016-01-22-functional-templating-in-ember/demo/app/app.js new file mode 100644 index 00000000..c15f9359 --- /dev/null +++ b/blog/2016-01-22-functional-templating-in-ember/demo/app/app.js @@ -0,0 +1,14 @@ +import Ember from 'ember'; +import Resolver from './resolver'; +import loadInitializers from 'ember-load-initializers'; +import config from './config/environment'; + +const App = Ember.Application.extend({ + modulePrefix: config.modulePrefix, + podModulePrefix: config.podModulePrefix, + Resolver +}); + +loadInitializers(App, config.modulePrefix); + +export default App; diff --git a/blog/2016-01-22-functional-templating-in-ember/demo/app/components/.gitkeep b/blog/2016-01-22-functional-templating-in-ember/demo/app/components/.gitkeep new file mode 100644 index 00000000..e69de29b diff --git a/blog/2016-01-22-functional-templating-in-ember/demo/app/components/choose-files-with-preview.js b/blog/2016-01-22-functional-templating-in-ember/demo/app/components/choose-files-with-preview.js new file mode 100644 index 00000000..cd7d91c5 --- /dev/null +++ b/blog/2016-01-22-functional-templating-in-ember/demo/app/components/choose-files-with-preview.js @@ -0,0 +1,8 @@ +import Ember from 'ember'; + +export default Ember.Component.extend({ + files: [], + filesArray: Ember.computed('files', function() { + return Array.from(this.get('files')); + }) +}); diff --git a/blog/2016-01-22-functional-templating-in-ember/demo/app/components/demo-pane.js b/blog/2016-01-22-functional-templating-in-ember/demo/app/components/demo-pane.js new file mode 100644 index 00000000..4e2b456b --- /dev/null +++ b/blog/2016-01-22-functional-templating-in-ember/demo/app/components/demo-pane.js @@ -0,0 +1,9 @@ +import Ember from 'ember'; + +export default Ember.Component.extend({ + classNameBindings: [':demo-pane'], + + snippetName: Ember.computed(function () { + return `${this.get('name')}.hbs`; + }) +}); diff --git a/blog/2016-01-22-functional-templating-in-ember/demo/app/components/file-chooser-only.js b/blog/2016-01-22-functional-templating-in-ember/demo/app/components/file-chooser-only.js new file mode 100644 index 00000000..7dd583aa --- /dev/null +++ b/blog/2016-01-22-functional-templating-in-ember/demo/app/components/file-chooser-only.js @@ -0,0 +1,9 @@ +import Ember from 'ember'; + +export default Ember.Component.extend({ + classNameBindings: [':file-chooser-only'], + files: [], + filesArray: Ember.computed('files', function() { + return Array.from(this.get('files')); + }) +}); diff --git a/blog/2016-01-22-functional-templating-in-ember/demo/app/components/full-demo-with-confirmation.js b/blog/2016-01-22-functional-templating-in-ember/demo/app/components/full-demo-with-confirmation.js new file mode 100644 index 00000000..cd7d91c5 --- /dev/null +++ b/blog/2016-01-22-functional-templating-in-ember/demo/app/components/full-demo-with-confirmation.js @@ -0,0 +1,8 @@ +import Ember from 'ember'; + +export default Ember.Component.extend({ + files: [], + filesArray: Ember.computed('files', function() { + return Array.from(this.get('files')); + }) +}); diff --git a/blog/2016-01-22-functional-templating-in-ember/demo/app/components/full-demo.js b/blog/2016-01-22-functional-templating-in-ember/demo/app/components/full-demo.js new file mode 100644 index 00000000..cd7d91c5 --- /dev/null +++ b/blog/2016-01-22-functional-templating-in-ember/demo/app/components/full-demo.js @@ -0,0 +1,8 @@ +import Ember from 'ember'; + +export default Ember.Component.extend({ + files: [], + filesArray: Ember.computed('files', function() { + return Array.from(this.get('files')); + }) +}); diff --git a/blog/2016-01-22-functional-templating-in-ember/demo/app/components/x-object-url.js b/blog/2016-01-22-functional-templating-in-ember/demo/app/components/x-object-url.js new file mode 100644 index 00000000..98950dd8 --- /dev/null +++ b/blog/2016-01-22-functional-templating-in-ember/demo/app/components/x-object-url.js @@ -0,0 +1,21 @@ +import Ember from 'ember'; +import { layout } from '../templates/components/x-object-url'; + +export default Ember.Component.extend({ + classNameBindings: [':x-object-url'], + layout: layout, + url: Ember.computed('blob', function() { + this.revokeCurrentURL(); + return this._url = URL.createObjectURL(this.get('blob')); //jshint ignore: line + }), + + revokeCurrentURL() { + if (this._url) { + URL.revokeObjectURL(this._url); + } + }, + + willDestroyElement() { + this.revokeCurrentURL(); + } +}); diff --git a/blog/2016-01-22-functional-templating-in-ember/demo/app/controllers/.gitkeep b/blog/2016-01-22-functional-templating-in-ember/demo/app/controllers/.gitkeep new file mode 100644 index 00000000..e69de29b diff --git a/blog/2016-01-22-functional-templating-in-ember/demo/app/helpers/.gitkeep b/blog/2016-01-22-functional-templating-in-ember/demo/app/helpers/.gitkeep new file mode 100644 index 00000000..e69de29b diff --git a/blog/2016-01-22-functional-templating-in-ember/demo/app/index.html b/blog/2016-01-22-functional-templating-in-ember/demo/app/index.html new file mode 100644 index 00000000..bd2363c5 --- /dev/null +++ b/blog/2016-01-22-functional-templating-in-ember/demo/app/index.html @@ -0,0 +1,25 @@ + + + + + + FileUploadDemo + + + + {{content-for "head"}} + + + + + {{content-for "head-footer"}} + + + {{content-for "body"}} + + + + + {{content-for "body-footer"}} + + diff --git a/blog/2016-01-22-functional-templating-in-ember/demo/app/models/.gitkeep b/blog/2016-01-22-functional-templating-in-ember/demo/app/models/.gitkeep new file mode 100644 index 00000000..e69de29b diff --git a/blog/2016-01-22-functional-templating-in-ember/demo/app/resolver.js b/blog/2016-01-22-functional-templating-in-ember/demo/app/resolver.js new file mode 100644 index 00000000..2fb563d6 --- /dev/null +++ b/blog/2016-01-22-functional-templating-in-ember/demo/app/resolver.js @@ -0,0 +1,3 @@ +import Resolver from 'ember-resolver'; + +export default Resolver; diff --git a/blog/2016-01-22-functional-templating-in-ember/demo/app/router.js b/blog/2016-01-22-functional-templating-in-ember/demo/app/router.js new file mode 100644 index 00000000..d3faf6ff --- /dev/null +++ b/blog/2016-01-22-functional-templating-in-ember/demo/app/router.js @@ -0,0 +1,22 @@ +import Ember from 'ember'; +import config from './config/environment'; + + +function getLocation() { + if (typeof window !== 'undefined' && window.disableEmberRouter) { + return 'none'; + } else { + return config.locationType; + } +} + + +const Router = Ember.Router.extend({ + location: getLocation() +}); + +Router.map(function() { + this.route('blog/2016/01/22/functional-templating-in-ember.html'); +}); + +export default Router; diff --git a/blog/2016-01-22-functional-templating-in-ember/demo/app/routes/.gitkeep b/blog/2016-01-22-functional-templating-in-ember/demo/app/routes/.gitkeep new file mode 100644 index 00000000..e69de29b diff --git a/blog/2016-01-22-functional-templating-in-ember/demo/app/routes/file-chooser-only.js b/blog/2016-01-22-functional-templating-in-ember/demo/app/routes/file-chooser-only.js new file mode 100644 index 00000000..26d9f312 --- /dev/null +++ b/blog/2016-01-22-functional-templating-in-ember/demo/app/routes/file-chooser-only.js @@ -0,0 +1,4 @@ +import Ember from 'ember'; + +export default Ember.Route.extend({ +}); diff --git a/blog/2016-01-22-functional-templating-in-ember/demo/app/routes/full-demo.js b/blog/2016-01-22-functional-templating-in-ember/demo/app/routes/full-demo.js new file mode 100644 index 00000000..26d9f312 --- /dev/null +++ b/blog/2016-01-22-functional-templating-in-ember/demo/app/routes/full-demo.js @@ -0,0 +1,4 @@ +import Ember from 'ember'; + +export default Ember.Route.extend({ +}); diff --git a/blog/2016-01-22-functional-templating-in-ember/demo/app/styles/app.css b/blog/2016-01-22-functional-templating-in-ember/demo/app/styles/app.css new file mode 100644 index 00000000..5eef0a75 --- /dev/null +++ b/blog/2016-01-22-functional-templating-in-ember/demo/app/styles/app.css @@ -0,0 +1,83 @@ +.demo-pane { + position: relative; + background-color: white; + border-radius: 2px; + border-bottom-right-radius: 4px; + border-bottom-left-radius: 4px; + border: 1px solid #ccc; + padding-top: 45px; +} + +.demo-pane__title { + position: absolute; + top: 15px; + left: 15px; + font-size: 12px; + font-weight: 700; + color: #959595; + text-transform: uppercase; + letter-spacing: 1px; +} + +.demo-pane__content { + padding-left: 15px; +} + + +.demo-pane__source { + position: relative; + border-top: 1px solid #ccc; + margin-top: 15px; +} + +.demo-pane__source pre { + margin: 0; + padding: 45px 15px 15px; + border-bottom-right-radius: 4px; + border-bottom-left-radius: 4px; + background-color: rgb(248,248,248); + color: rgb(51,51,51); +} + +.demo-pane__source pre:after { + position: absolute; + content: "Source"; + top: 15px; + left: 15px; + font-size: 12px; + font-weight: 700; + color: #959595; + text-transform: uppercase; + letter-spacing: 1px; +} + +.image-preview { + border-radius: 50%; + height: 25px; + width: 25px; + background-size: cover; + background-position: center; + background-repeat: no-repeat; + display: inline-block; + background-color: lightgray; + border: 1px solid #D3D3D3; +} + +.image-file-name { + display: inline-block; + position: relative; + top: -5px; +} + +.file-upload { + margin-top: 5px; +} + + +.file-chooser-only label { + font-weight: bold; +} + +.file-chooser-only .file { + margin-top: 15px; +} diff --git a/blog/2016-01-22-functional-templating-in-ember/demo/app/templates/application.hbs b/blog/2016-01-22-functional-templating-in-ember/demo/app/templates/application.hbs new file mode 100644 index 00000000..65b9e9c3 --- /dev/null +++ b/blog/2016-01-22-functional-templating-in-ember/demo/app/templates/application.hbs @@ -0,0 +1,2 @@ +{{ember-islands}} +{{outlet}} diff --git a/blog/2016-01-22-functional-templating-in-ember/demo/app/templates/components/.gitkeep b/blog/2016-01-22-functional-templating-in-ember/demo/app/templates/components/.gitkeep new file mode 100644 index 00000000..e69de29b diff --git a/blog/2016-01-22-functional-templating-in-ember/demo/app/templates/components/choose-files-with-preview.hbs b/blog/2016-01-22-functional-templating-in-ember/demo/app/templates/components/choose-files-with-preview.hbs new file mode 100644 index 00000000..511b3fea --- /dev/null +++ b/blog/2016-01-22-functional-templating-in-ember/demo/app/templates/components/choose-files-with-preview.hbs @@ -0,0 +1,14 @@ +{{!BEGIN-SNIPPET choose-files-with-preview}} +{{#x-file-input multiple=true action=(action (mut files)) accept="image/gif,image/jpg,image/png"}} + +{{/x-file-input}} + +{{#each filesArray as |file|}} +
    + {{#x-object-url blob=file as |url|}} +
    +
    {{file.name}}
    + {{/x-object-url}} +
    +{{/each}} +{{!END-SNIPPET}} diff --git a/blog/2016-01-22-functional-templating-in-ember/demo/app/templates/components/demo-pane.hbs b/blog/2016-01-22-functional-templating-in-ember/demo/app/templates/components/demo-pane.hbs new file mode 100644 index 00000000..62b5eb6d --- /dev/null +++ b/blog/2016-01-22-functional-templating-in-ember/demo/app/templates/components/demo-pane.hbs @@ -0,0 +1,7 @@ +
    Demo: {{title}}
    +
    + {{component name}} +
    +
    + {{code-snippet name=snippetName}} +
    diff --git a/blog/2016-01-22-functional-templating-in-ember/demo/app/templates/components/file-chooser-only.hbs b/blog/2016-01-22-functional-templating-in-ember/demo/app/templates/components/file-chooser-only.hbs new file mode 100644 index 00000000..d8122833 --- /dev/null +++ b/blog/2016-01-22-functional-templating-in-ember/demo/app/templates/components/file-chooser-only.hbs @@ -0,0 +1,19 @@ +{{!BEGIN-SNIPPET file-chooser-only}} +{{#x-file-input multiple=true action=(action (mut files))}} + +{{/x-file-input}} + +{{#each filesArray as |file|}} +
      +
    • + {{file.name}} +
    • +
    • + {{file.type}} +
    • +
    • + {{file.size}} +
    • +
    +{{/each}} +{{!END-SNIPPET}} diff --git a/blog/2016-01-22-functional-templating-in-ember/demo/app/templates/components/full-demo-with-confirmation.hbs b/blog/2016-01-22-functional-templating-in-ember/demo/app/templates/components/full-demo-with-confirmation.hbs new file mode 100644 index 00000000..9536ac3b --- /dev/null +++ b/blog/2016-01-22-functional-templating-in-ember/demo/app/templates/components/full-demo-with-confirmation.hbs @@ -0,0 +1,17 @@ +{{!BEGIN-SNIPPET full-demo-with-confirmation}} +{{#x-file-input multiple=true accept="image/gif,image/jpg,image/png" action=(action (mut files))}} + +{{/x-file-input}} + +{{#each filesArray as |file|}} +
    + {{#x-object-url blob=file as |url|}} +
    +
    {{file.name}}
    + {{/x-object-url}} + {{#x-xml-http-request method="POST" data=file url="https://posttestserver.com/post.php" as |xhr|}} +
    + {{/x-xml-http-request}} +
    +{{/each}} +{{!END-SNIPPET}} diff --git a/blog/2016-01-22-functional-templating-in-ember/demo/app/templates/components/full-demo.hbs b/blog/2016-01-22-functional-templating-in-ember/demo/app/templates/components/full-demo.hbs new file mode 100644 index 00000000..fa9379ce --- /dev/null +++ b/blog/2016-01-22-functional-templating-in-ember/demo/app/templates/components/full-demo.hbs @@ -0,0 +1,17 @@ +{{!BEGIN-SNIPPET full-demo}} +{{#x-file-input multiple=true action=(action (mut files)) accept="image/gif,image/jpg,image/png"}} + +{{/x-file-input}} + +{{#each filesArray as |file|}} +
    + {{#x-object-url blob=file as |url|}} +
    +
    {{file.name}}
    + {{/x-object-url}} + {{#x-xml-http-request method="POST" data=file url="https://posttestserver.com/post.php" as |xhr|}} +
    + {{/x-xml-http-request}} +
    +{{/each}} +{{!END-SNIPPET}} diff --git a/blog/2016-01-22-functional-templating-in-ember/demo/app/templates/components/x-object-url.hbs b/blog/2016-01-22-functional-templating-in-ember/demo/app/templates/components/x-object-url.hbs new file mode 100644 index 00000000..f63d2414 --- /dev/null +++ b/blog/2016-01-22-functional-templating-in-ember/demo/app/templates/components/x-object-url.hbs @@ -0,0 +1 @@ +{{yield url}} diff --git a/blog/2016-01-22-functional-templating-in-ember/demo/app/templates/demos.hbs b/blog/2016-01-22-functional-templating-in-ember/demo/app/templates/demos.hbs new file mode 100644 index 00000000..dc50c86b --- /dev/null +++ b/blog/2016-01-22-functional-templating-in-ember/demo/app/templates/demos.hbs @@ -0,0 +1,15 @@ +

    + {{demo-pane name="file-chooser-only" title="Choose Files"}} +

    + +

    + {{demo-pane name="choose-files-with-preview" title="Choose Images With Preview"}} +

    + +

    + {{demo-pane name="full-demo" title="Upload With Progress"}} +

    + +

    + {{demo-pane name="full-demo-with-confirmation" title=""}} +

    diff --git a/blog/2016-01-22-functional-templating-in-ember/demo/app/templates/file-chooser-only.hbs b/blog/2016-01-22-functional-templating-in-ember/demo/app/templates/file-chooser-only.hbs new file mode 100644 index 00000000..c24cd689 --- /dev/null +++ b/blog/2016-01-22-functional-templating-in-ember/demo/app/templates/file-chooser-only.hbs @@ -0,0 +1 @@ +{{outlet}} diff --git a/blog/2016-01-22-functional-templating-in-ember/demo/config/environment.js b/blog/2016-01-22-functional-templating-in-ember/demo/config/environment.js new file mode 100644 index 00000000..8eb5d94f --- /dev/null +++ b/blog/2016-01-22-functional-templating-in-ember/demo/config/environment.js @@ -0,0 +1,51 @@ +/* eslint-env node */ +'use strict'; + +module.exports = function(environment) { + let ENV = { + modulePrefix: 'file-upload-demo', + environment, + rootURL: '/', + locationType: 'none', + EmberENV: { + FEATURES: { + // Here you can enable experimental features on an ember canary build + // e.g. 'with-controller': true + }, + EXTEND_PROTOTYPES: { + // Prevent Ember Data from overriding Date.parse. + Date: false + } + }, + + APP: { + // Here you can pass flags/options to your application instance + // when it is created + } + }; + + if (environment === 'development') { + // ENV.APP.LOG_RESOLVER = true; + // ENV.APP.LOG_ACTIVE_GENERATION = true; + // ENV.APP.LOG_TRANSITIONS = true; + // ENV.APP.LOG_TRANSITIONS_INTERNAL = true; + // ENV.APP.LOG_VIEW_LOOKUPS = true; + } + + if (environment === 'test') { + // Testem prefers this... + ENV.locationType = 'none'; + + // keep test console output quieter + ENV.APP.LOG_ACTIVE_GENERATION = false; + ENV.APP.LOG_VIEW_LOOKUPS = false; + + ENV.APP.rootElement = '#ember-testing'; + } + + if (environment === 'production') { + ENV.locationType = 'none'; + } + + return ENV; +}; diff --git a/blog/2016-01-22-functional-templating-in-ember/demo/config/targets.js b/blog/2016-01-22-functional-templating-in-ember/demo/config/targets.js new file mode 100644 index 00000000..df7664c2 --- /dev/null +++ b/blog/2016-01-22-functional-templating-in-ember/demo/config/targets.js @@ -0,0 +1,9 @@ +/* eslint-env node */ +module.exports = { + browsers: [ + 'ie 9', + 'last 1 Chrome versions', + 'last 1 Firefox versions', + 'last 1 Safari versions' + ] +}; diff --git a/blog/2016-01-22-functional-templating-in-ember/demo/dist/assets/file-upload-demo-7b74960a3002f8e0d5b8456f8dd2c526.js b/blog/2016-01-22-functional-templating-in-ember/demo/dist/assets/file-upload-demo-7b74960a3002f8e0d5b8456f8dd2c526.js new file mode 100644 index 00000000..fee52922 --- /dev/null +++ b/blog/2016-01-22-functional-templating-in-ember/demo/dist/assets/file-upload-demo-7b74960a3002f8e0d5b8456f8dd2c526.js @@ -0,0 +1,31 @@ +"use strict" +define("file-upload-demo/app",["exports","file-upload-demo/resolver","ember-load-initializers","file-upload-demo/config/environment"],function(e,t,l,n){Object.defineProperty(e,"__esModule",{value:!0}) +var o=Ember.Application.extend({modulePrefix:n.default.modulePrefix,podModulePrefix:n.default.podModulePrefix,Resolver:t.default});(0,l.default)(o,n.default.modulePrefix),e.default=o}),define("file-upload-demo/components/choose-files-with-preview",["exports"],function(e){Object.defineProperty(e,"__esModule",{value:!0}),e.default=Ember.Component.extend({files:[],filesArray:Ember.computed("files",function(){return Array.from(this.get("files"))})})}),define("file-upload-demo/components/code-snippet",["exports","file-upload-demo/snippets"],function(e,t){Object.defineProperty(e,"__esModule",{value:!0}) +var l=self.require("highlight.js") +e.default=Ember.Component.extend({tagName:"pre",classNameBindings:["language"],unindent:!0,_unindent:function(e){if(!this.get("unindent"))return e +for(var t,l,n=e.split("\n").filter(function(e){return""!==e}),o=0;ot[0].length)&&(l=t[0].length) +return void 0!==l&&l>0&&(e=e.replace(new RegExp("^[ \t]{"+l+"}","gm"),"")),e},source:Ember.computed("name",function(){return this._unindent((t.default[this.get("name")]||"").replace(/^(\s*\n)*/,"").replace(/\s*$/,""))}),didInsertElement:function(){l.highlightBlock(this.get("element"))},language:Ember.computed("name",function(){var e=/\.(\w+)$/i.exec(this.get("name")) +if(e)switch(e[1].toLowerCase()){case"js":return"javascript" +case"coffee":return"coffeescript" +case"hbs":return"htmlbars" +case"css":return"css" +case"scss":return"scss" +case"less":return"less" +case"emblem":return"emblem" +case"ts":return"typescript"}})})}),define("file-upload-demo/components/demo-pane",["exports"],function(e){Object.defineProperty(e,"__esModule",{value:!0}),e.default=Ember.Component.extend({classNameBindings:[":demo-pane"],snippetName:Ember.computed(function(){return this.get("name")+".hbs"})})}),define("file-upload-demo/components/ember-islands",["exports","ember-islands/components/ember-islands"],function(e,t){Object.defineProperty(e,"__esModule",{value:!0}),Object.defineProperty(e,"default",{enumerable:!0,get:function(){return t.default}})}),define("file-upload-demo/components/ember-islands/missing-component",["exports"],function(e){Object.defineProperty(e,"__esModule",{value:!0}),e.default=Ember.Component.extend()}),define("file-upload-demo/components/file-chooser-only",["exports"],function(e){Object.defineProperty(e,"__esModule",{value:!0}),e.default=Ember.Component.extend({classNameBindings:[":file-chooser-only"],files:[],filesArray:Ember.computed("files",function(){return Array.from(this.get("files"))})})}),define("file-upload-demo/components/full-demo-with-confirmation",["exports"],function(e){Object.defineProperty(e,"__esModule",{value:!0}),e.default=Ember.Component.extend({files:[],filesArray:Ember.computed("files",function(){return Array.from(this.get("files"))})})}),define("file-upload-demo/components/full-demo",["exports"],function(e){Object.defineProperty(e,"__esModule",{value:!0}),e.default=Ember.Component.extend({files:[],filesArray:Ember.computed("files",function(){return Array.from(this.get("files"))})})}),define("file-upload-demo/components/x-file-input",["exports","emberx-file-input/components/x-file-input"],function(e,t){Object.defineProperty(e,"__esModule",{value:!0}),Object.defineProperty(e,"default",{enumerable:!0,get:function(){return t.default}})}),define("file-upload-demo/components/x-file-reader",["exports","emberx-file-reader/components/x-file-reader"],function(e,t){Object.defineProperty(e,"__esModule",{value:!0}),Object.defineProperty(e,"default",{enumerable:!0,get:function(){return t.default}})}),define("file-upload-demo/components/x-object-url",["exports","file-upload-demo/templates/components/x-object-url"],function(e,t){Object.defineProperty(e,"__esModule",{value:!0}),e.default=Ember.Component.extend({classNameBindings:[":x-object-url"],layout:t.layout,url:Ember.computed("blob",function(){return this.revokeCurrentURL(),this._url=URL.createObjectURL(this.get("blob"))}),revokeCurrentURL:function(){this._url&&URL.revokeObjectURL(this._url)},willDestroyElement:function(){this.revokeCurrentURL()}})}),define("file-upload-demo/components/x-xml-http-request",["exports","emberx-xml-http-request/components/x-xml-http-request"],function(e,t){Object.defineProperty(e,"__esModule",{value:!0}),Object.defineProperty(e,"default",{enumerable:!0,get:function(){return t.default}})}),define("file-upload-demo/helpers/app-version",["exports","file-upload-demo/config/environment","ember-cli-app-version/utils/regexp"],function(e,t,l){function n(e){var t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{} +return t.hideSha?o.match(l.versionRegExp)[0]:t.hideVersion?o.match(l.shaRegExp)[0]:o}Object.defineProperty(e,"__esModule",{value:!0}),e.appVersion=n +var o=t.default.APP.version +e.default=Ember.Helper.helper(n)}),define("file-upload-demo/initializers/app-version",["exports","ember-cli-app-version/initializer-factory","file-upload-demo/config/environment"],function(e,t,l){Object.defineProperty(e,"__esModule",{value:!0}) +var n=l.default.APP,o=n.name,a=n.version +e.default={name:"App Version",initialize:(0,t.default)(o,a)}}),define("file-upload-demo/initializers/container-debug-adapter",["exports","ember-resolver/resolvers/classic/container-debug-adapter"],function(e,t){Object.defineProperty(e,"__esModule",{value:!0}),e.default={name:"container-debug-adapter",initialize:function(){var e=arguments[1]||arguments[0] +e.register("container-debug-adapter:main",t.default),e.inject("container-debug-adapter:main","namespace","application:main")}}}),define("file-upload-demo/initializers/export-application-global",["exports","file-upload-demo/config/environment"],function(e,t){function l(){var e=arguments[1]||arguments[0] +if(!1!==t.default.exportApplicationGlobal){var l +if("undefined"!=typeof window)l=window +else if("undefined"!=typeof global)l=global +else{if("undefined"==typeof self)return +l=self}var n,o=t.default.exportApplicationGlobal +n="string"==typeof o?o:Ember.String.classify(t.default.modulePrefix),l[n]||(l[n]=e,e.reopen({willDestroy:function(){this._super.apply(this,arguments),delete l[n]}}))}}Object.defineProperty(e,"__esModule",{value:!0}),e.initialize=l,e.default={name:"export-application-global",initialize:l}}),define("file-upload-demo/resolver",["exports","ember-resolver"],function(e,t){Object.defineProperty(e,"__esModule",{value:!0}),e.default=t.default}),define("file-upload-demo/router",["exports","file-upload-demo/config/environment"],function(e,t){Object.defineProperty(e,"__esModule",{value:!0}) +var l=Ember.Router.extend({location:function(){return"undefined"!=typeof window&&window.disableEmberRouter?"none":t.default.locationType}()}) +l.map(function(){this.route("blog/2016/01/22/functional-templating-in-ember.html")}),e.default=l}),define("file-upload-demo/routes/file-chooser-only",["exports"],function(e){Object.defineProperty(e,"__esModule",{value:!0}),e.default=Ember.Route.extend({})}),define("file-upload-demo/routes/full-demo",["exports"],function(e){Object.defineProperty(e,"__esModule",{value:!0}),e.default=Ember.Route.extend({})}),define("file-upload-demo/services/ajax",["exports","ember-ajax/services/ajax"],function(e,t){Object.defineProperty(e,"__esModule",{value:!0}),Object.defineProperty(e,"default",{enumerable:!0,get:function(){return t.default}})}),define("file-upload-demo/snippets",["exports"],function(e){Object.defineProperty(e,"__esModule",{value:!0}),e.default={"choose-files-with-preview.hbs":'{{#x-file-input multiple=true action=(action (mut files)) accept="image/gif,image/jpg,image/png"}}\n \n{{/x-file-input}}\n\n{{#each filesArray as |file|}}\n
    \n {{#x-object-url blob=file as |url|}}\n
    \n
    {{file.name}}
    \n {{/x-object-url}}\n
    \n{{/each}}',"file-chooser-only.hbs":'{{#x-file-input multiple=true action=(action (mut files))}}\n \n{{/x-file-input}}\n\n{{#each filesArray as |file|}}\n
      \n
    • \n {{file.name}}\n
    • \n
    • \n {{file.type}}\n
    • \n
    • \n {{file.size}}\n
    • \n
    \n{{/each}}',"full-demo-with-confirmation.hbs":'{{#x-file-input multiple=true accept="image/gif,image/jpg,image/png" action=(action (mut files))}}\n \n{{/x-file-input}}\n\n{{#each filesArray as |file|}}\n
    \n {{#x-object-url blob=file as |url|}}\n
    \n
    {{file.name}}
    \n {{/x-object-url}}\n {{#x-xml-http-request method="POST" data=file url="https://posttestserver.com/post.php" as |xhr|}}\n
    \n {{/x-xml-http-request}}\n
    \n{{/each}}',"full-demo.hbs":'{{#x-file-input multiple=true action=(action (mut files)) accept="image/gif,image/jpg,image/png"}}\n \n{{/x-file-input}}\n\n{{#each filesArray as |file|}}\n
    \n {{#x-object-url blob=file as |url|}}\n
    \n
    {{file.name}}
    \n {{/x-object-url}}\n {{#x-xml-http-request method="POST" data=file url="https://posttestserver.com/post.php" as |xhr|}}\n
    \n {{/x-xml-http-request}}\n
    \n{{/each}}'}}),define("file-upload-demo/templates/application",["exports"],function(e){Object.defineProperty(e,"__esModule",{value:!0}),e.default=Ember.HTMLBars.template({id:"thDHQPG0",block:'{"symbols":[],"statements":[[1,[18,"ember-islands"],false],[0,"\\n"],[1,[18,"outlet"],false],[0,"\\n"]],"hasEval":false}',meta:{moduleName:"file-upload-demo/templates/application.hbs"}})}),define("file-upload-demo/templates/components/choose-files-with-preview",["exports"],function(e){Object.defineProperty(e,"__esModule",{value:!0}),e.default=Ember.HTMLBars.template({id:"UwHAbmoG",block:'{"symbols":["file","url"],"statements":[[4,"x-file-input",null,[["multiple","action","accept"],[true,[25,"action",[[19,0,[]],[25,"mut",[[19,0,["files"]]],null]],null],"image/gif,image/jpg,image/png"]],{"statements":[[0," "],[6,"button"],[7],[0,"⇧ Choose Files"],[8],[0,"\\n"]],"parameters":[]},null],[0,"\\n"],[4,"each",[[19,0,["filesArray"]]],null,{"statements":[[0," "],[6,"div"],[9,"class","file-upload"],[7],[0,"\\n"],[4,"x-object-url",null,[["blob"],[[19,1,[]]]],{"statements":[[0," "],[6,"div"],[10,"style",[26,["background-image: url(",[19,2,[]],")"]]],[9,"class","image-preview"],[9,"alt","file preview"],[7],[8],[0,"\\n "],[6,"div"],[9,"class","image-file-name"],[7],[1,[19,1,["name"]],false],[8],[0,"\\n"]],"parameters":[2]},null],[0," "],[8],[0,"\\n"]],"parameters":[1]},null]],"hasEval":false}',meta:{moduleName:"file-upload-demo/templates/components/choose-files-with-preview.hbs"}})}),define("file-upload-demo/templates/components/code-snippet",["exports"],function(e){Object.defineProperty(e,"__esModule",{value:!0}),e.default=Ember.HTMLBars.template({id:"o0p4u+M1",block:'{"symbols":[],"statements":[[1,[18,"source"],false],[0,"\\n"]],"hasEval":false}',meta:{moduleName:"file-upload-demo/templates/components/code-snippet.hbs"}})}),define("file-upload-demo/templates/components/demo-pane",["exports"],function(e){Object.defineProperty(e,"__esModule",{value:!0}),e.default=Ember.HTMLBars.template({id:"uz1zfjJi",block:'{"symbols":[],"statements":[[6,"div"],[9,"class","demo-pane__title"],[7],[0,"Demo: "],[1,[18,"title"],false],[8],[0,"\\n"],[6,"div"],[9,"class","demo-pane__content"],[7],[0,"\\n "],[1,[25,"component",[[19,0,["name"]]],null],false],[0,"\\n"],[8],[0,"\\n"],[6,"div"],[9,"class","demo-pane__source"],[7],[0,"\\n "],[1,[25,"code-snippet",null,[["name"],[[19,0,["snippetName"]]]]],false],[0,"\\n"],[8],[0,"\\n"]],"hasEval":false}',meta:{moduleName:"file-upload-demo/templates/components/demo-pane.hbs"}})}),define("file-upload-demo/templates/components/ember-islands/missing-component",["exports"],function(e){Object.defineProperty(e,"__esModule",{value:!0}),e.default=Ember.HTMLBars.template({id:"bKuTCm3d",block:'{"symbols":[],"statements":[],"hasEval":false}',meta:{moduleName:"file-upload-demo/templates/components/ember-islands/missing-component.hbs"}})}),define("file-upload-demo/templates/components/file-chooser-only",["exports"],function(e){Object.defineProperty(e,"__esModule",{value:!0}),e.default=Ember.HTMLBars.template({id:"VMzQzhEu",block:'{"symbols":["file"],"statements":[[4,"x-file-input",null,[["multiple","action"],[true,[25,"action",[[19,0,[]],[25,"mut",[[19,0,["files"]]],null]],null]]],{"statements":[[0," "],[6,"button"],[7],[0,"⇧ Choose Files"],[8],[0,"\\n"]],"parameters":[]},null],[0,"\\n"],[4,"each",[[19,0,["filesArray"]]],null,{"statements":[[0," "],[6,"ul"],[9,"class","file"],[7],[0,"\\n "],[6,"li"],[7],[0,"\\n "],[6,"label"],[7],[0,"name:"],[8],[0," "],[1,[19,1,["name"]],false],[0,"\\n "],[8],[0,"\\n "],[6,"li"],[7],[0,"\\n "],[6,"label"],[7],[0,"type:"],[8],[0," "],[1,[19,1,["type"]],false],[0,"\\n "],[8],[0,"\\n "],[6,"li"],[7],[0,"\\n "],[6,"label"],[7],[0,"size:"],[8],[0," "],[1,[19,1,["size"]],false],[0,"\\n "],[8],[0,"\\n "],[8],[0,"\\n"]],"parameters":[1]},null]],"hasEval":false}',meta:{moduleName:"file-upload-demo/templates/components/file-chooser-only.hbs"}})}),define("file-upload-demo/templates/components/full-demo-with-confirmation",["exports"],function(e){Object.defineProperty(e,"__esModule",{value:!0}),e.default=Ember.HTMLBars.template({id:"gNUOMEkg",block:'{"symbols":["file","xhr","url"],"statements":[[4,"x-file-input",null,[["multiple","accept","action"],[true,"image/gif,image/jpg,image/png",[25,"action",[[19,0,[]],[25,"mut",[[19,0,["files"]]],null]],null]]],{"statements":[[0," "],[6,"button"],[7],[0,"⇧ Choose Files"],[8],[0,"\\n"]],"parameters":[]},null],[0,"\\n"],[4,"each",[[19,0,["filesArray"]]],null,{"statements":[[0," "],[6,"div"],[9,"class","file-upload"],[7],[0,"\\n"],[4,"x-object-url",null,[["blob"],[[19,1,[]]]],{"statements":[[0," "],[6,"div"],[9,"alt","file preview"],[9,"class","image-preview"],[10,"style",[26,["background-image: url(",[19,3,[]],")"]]],[7],[8],[0,"\\n "],[6,"div"],[9,"class","image-file-name"],[7],[1,[19,1,["name"]],false],[8],[0,"\\n"]],"parameters":[3]},null],[4,"x-xml-http-request",null,[["method","data","url"],["POST",[19,1,[]],"https://posttestserver.com/post.php"]],{"statements":[[0," "],[6,"div"],[10,"style",[26,["width: 200px; height: 5px; background: linear-gradient(90deg, orange 0, orange ",[19,2,["upload","percentage"]],"%, rgba(100,100,100,1) ",[19,2,["upload","percentage"]],"%, rgba(100,100,100,1) 100%); "]]],[7],[8],[0,"\\n"]],"parameters":[2]},null],[0," "],[8],[0,"\\n"]],"parameters":[1]},null]],"hasEval":false}',meta:{moduleName:"file-upload-demo/templates/components/full-demo-with-confirmation.hbs"}})}) +define("file-upload-demo/templates/components/full-demo",["exports"],function(e){Object.defineProperty(e,"__esModule",{value:!0}),e.default=Ember.HTMLBars.template({id:"5bZQ/Mds",block:'{"symbols":["file","xhr","url"],"statements":[[4,"x-file-input",null,[["multiple","action","accept"],[true,[25,"action",[[19,0,[]],[25,"mut",[[19,0,["files"]]],null]],null],"image/gif,image/jpg,image/png"]],{"statements":[[0," "],[6,"button"],[7],[0,"⇧ Choose Files"],[8],[0,"\\n"]],"parameters":[]},null],[0,"\\n"],[4,"each",[[19,0,["filesArray"]]],null,{"statements":[[0," "],[6,"div"],[9,"class","file-upload"],[7],[0,"\\n"],[4,"x-object-url",null,[["blob"],[[19,1,[]]]],{"statements":[[0," "],[6,"div"],[9,"alt","file preview"],[9,"class","image-preview"],[10,"style",[26,["background-image: url(",[19,3,[]],")"]]],[7],[8],[0,"\\n "],[6,"div"],[9,"class","image-file-name"],[7],[1,[19,1,["name"]],false],[8],[0,"\\n"]],"parameters":[3]},null],[4,"x-xml-http-request",null,[["method","data","url"],["POST",[19,1,[]],"https://posttestserver.com/post.php"]],{"statements":[[0," "],[6,"div"],[10,"style",[26,["width: 200px; height: 5px; background: linear-gradient(90deg, orange 0, orange ",[19,2,["upload","percentage"]],"%, rgba(100,100,100,1) ",[19,2,["upload","percentage"]],"%, rgba(100,100,100,1) 100%); "]]],[7],[8],[0,"\\n"]],"parameters":[2]},null],[0," "],[8],[0,"\\n"]],"parameters":[1]},null]],"hasEval":false}',meta:{moduleName:"file-upload-demo/templates/components/full-demo.hbs"}})}),define("file-upload-demo/templates/components/x-object-url",["exports"],function(e){Object.defineProperty(e,"__esModule",{value:!0}),e.default=Ember.HTMLBars.template({id:"mSEv0X0g",block:'{"symbols":["&default"],"statements":[[11,1,[[19,0,["url"]]]],[0,"\\n"]],"hasEval":false}',meta:{moduleName:"file-upload-demo/templates/components/x-object-url.hbs"}})}),define("file-upload-demo/templates/demos",["exports"],function(e){Object.defineProperty(e,"__esModule",{value:!0}),e.default=Ember.HTMLBars.template({id:"prqlLcR/",block:'{"symbols":[],"statements":[[6,"p"],[7],[0,"\\n "],[1,[25,"demo-pane",null,[["name","title"],["file-chooser-only","Choose Files"]]],false],[0,"\\n"],[8],[0,"\\n\\n"],[6,"p"],[7],[0,"\\n "],[1,[25,"demo-pane",null,[["name","title"],["choose-files-with-preview","Choose Images With Preview"]]],false],[0,"\\n"],[8],[0,"\\n\\n"],[6,"p"],[7],[0,"\\n "],[1,[25,"demo-pane",null,[["name","title"],["full-demo","Upload With Progress"]]],false],[0,"\\n"],[8],[0,"\\n\\n"],[6,"p"],[7],[0,"\\n "],[1,[25,"demo-pane",null,[["name","title"],["full-demo-with-confirmation",""]]],false],[0,"\\n"],[8],[0,"\\n"]],"hasEval":false}',meta:{moduleName:"file-upload-demo/templates/demos.hbs"}})}),define("file-upload-demo/templates/file-chooser-only",["exports"],function(e){Object.defineProperty(e,"__esModule",{value:!0}),e.default=Ember.HTMLBars.template({id:"r0mNCgrR",block:'{"symbols":[],"statements":[[1,[18,"outlet"],false],[0,"\\n"]],"hasEval":false}',meta:{moduleName:"file-upload-demo/templates/file-chooser-only.hbs"}})}),define("file-upload-demo/config/environment",["ember"],function(e){var t={default:{modulePrefix:"file-upload-demo",environment:"production",rootURL:"/",locationType:"none",EmberENV:{FEATURES:{},EXTEND_PROTOTYPES:{Date:!1}},APP:{name:"file-upload-demo",version:"0.0.0+7e4324a8"},exportApplicationGlobal:!1}} +return Object.defineProperty(t,"__esModule",{value:!0}),t}),runningTests||require("file-upload-demo/app").default.create({name:"file-upload-demo",version:"0.0.0+7e4324a8"}) diff --git a/blog/2016-01-22-functional-templating-in-ember/demo/dist/assets/file-upload-demo-90508b2fd97899bf2a671c13f6824821.css b/blog/2016-01-22-functional-templating-in-ember/demo/dist/assets/file-upload-demo-90508b2fd97899bf2a671c13f6824821.css new file mode 100644 index 00000000..3b356df0 --- /dev/null +++ b/blog/2016-01-22-functional-templating-in-ember/demo/dist/assets/file-upload-demo-90508b2fd97899bf2a671c13f6824821.css @@ -0,0 +1 @@ +.demo-pane__source pre:after,.demo-pane__title{top:15px;left:15px;font-size:12px;text-transform:uppercase;letter-spacing:1px;font-weight:700}.demo-pane{position:relative;background-color:#fff;border-radius:2px 2px 4px 4px;border:1px solid #ccc;padding-top:45px}.demo-pane__title{position:absolute;color:#959595}.demo-pane__content{padding-left:15px}.demo-pane__source{position:relative;border-top:1px solid #ccc;margin-top:15px}.demo-pane__source pre{margin:0;padding:45px 15px 15px;border-bottom-right-radius:4px;border-bottom-left-radius:4px;background-color:#f8f8f8;color:#333}.demo-pane__source pre:after{position:absolute;content:"Source";color:#959595}.image-preview{border-radius:50%;height:25px;width:25px;background-size:cover;background-position:center;background-repeat:no-repeat;display:inline-block;background-color:#d3d3d3;border:1px solid #D3D3D3}.image-file-name{display:inline-block;position:relative;top:-5px}.file-upload{margin-top:5px}.file-chooser-only label{font-weight:700}.file-chooser-only .file{margin-top:15px} \ No newline at end of file diff --git a/blog/2016-01-22-functional-templating-in-ember/demo/dist/assets/vendor-390ead2855ba5f4fc8645ea85c394a56.js b/blog/2016-01-22-functional-templating-in-ember/demo/dist/assets/vendor-390ead2855ba5f4fc8645ea85c394a56.js new file mode 100644 index 00000000..8075a5c5 --- /dev/null +++ b/blog/2016-01-22-functional-templating-in-ember/demo/dist/assets/vendor-390ead2855ba5f4fc8645ea85c394a56.js @@ -0,0 +1,3680 @@ +function createDeprecatedModule(e){define(e,["exports","ember-resolver/resolver","ember"],function(t,n,r){r.default.deprecate("Usage of `"+e+"` module is deprecated, please update to `ember-resolver`.",!1,{id:"ember-resolver.legacy-shims",until:"3.0.0"}),t.default=n.default})}window.EmberENV={FEATURES:{},EXTEND_PROTOTYPES:{Date:!1}} +var runningTests=!1,loader,define,requireModule,require,requirejs;(function(e){"use strict" +function t(){var e=Object.create(null) +return e.__=void 0,delete e.__,e}function n(e){throw new Error("an unsupported module was defined, expected `define(id, deps, module)` instead got: `"+e+"` arguments to define`")}function r(e,t,n,r){this.uuid=h++,this.id=e,this.deps=!t.length&&n.length?d:t,this.module={exports:{}},this.callback=n,this.hasExportsAsDep=!1,this.isAlias=r,this.reified=new Array(t.length),this.state="new"}function i(){}function o(e){this.id=e}function s(e,t){throw new Error("Could not find module `"+e+"` imported from `"+t+"`")}function a(e,t,n){for(var r=p[e]||p[e+"/index"];r&&r.isAlias;)r=p[r.id] +return r||s(e,t),n&&"pending"!==r.state&&"finalized"!==r.state&&(r.findDeps(n),n.push(r)),r}function u(e,t){if("."!==e.charAt(0))return e +for(var n=e.split("/"),r=t.split("/"),i=r.slice(0,-1),o=0,s=n.length;o=0;r--)t[r].exports() +return n.module.exports},loader={noConflict:function(t){var n,r +for(n in t)t.hasOwnProperty(n)&&l.hasOwnProperty(n)&&(r=t[n],e[r]=e[n],e[n]=l[n])},makeDefaultExport:!0} +var p=t(),f=t(),h=0,d=["require","exports","module"] +r.prototype.makeDefaultExport=function(){var e=this.module.exports +null===e||"object"!=typeof e&&"function"!=typeof e||void 0!==e.default||!Object.isExtensible(e)||(e.default=e)},r.prototype.exports=function(){if("finalized"===this.state||"reifying"===this.state)return this.module.exports +loader.wrapModules&&(this.callback=loader.wrapModules(this.id,this.callback)),this.reify() +var e=this.callback.apply(this,this.reified) +return this.reified.length=0,this.state="finalized",this.hasExportsAsDep&&void 0===e||(this.module.exports=e),loader.makeDefaultExport&&this.makeDefaultExport(),this.module.exports},r.prototype.unsee=function(){this.state="new",this.module={exports:{}}},r.prototype.reify=function(){if("reified"!==this.state){this.state="reifying" +try{this.reified=this._reify(),this.state="reified"}finally{"reifying"===this.state&&(this.state="errored")}}},r.prototype._reify=function(){for(var e=this.reified.slice(),t=0;t0&&t-1 in e)}function i(e,t){return e.nodeName&&e.nodeName.toLowerCase()===t.toLowerCase()}function o(e,t,n){return de.isFunction(t)?de.grep(e,function(e,r){return!!t.call(e,r,e)!==n}):t.nodeType?de.grep(e,function(e){return e===t!==n}):"string"!=typeof t?de.grep(e,function(e){return ae.call(t,e)>-1!==n}):Oe.test(t)?de.filter(t,e,n):(t=de.filter(t,e),de.grep(e,function(e){return ae.call(t,e)>-1!==n&&1===e.nodeType}))}function s(e,t){for(;(e=e[t])&&1!==e.nodeType;);return e}function a(e){var t={} +return de.each(e.match(Re)||[],function(e,n){t[n]=!0}),t}function u(e){return e}function c(e){throw e}function l(e,t,n,r){var i +try{e&&de.isFunction(i=e.promise)?i.call(e).done(t).fail(n):e&&de.isFunction(i=e.then)?i.call(e,t,n):t.apply(void 0,[e].slice(r))}catch(e){n.apply(void 0,[e])}}function p(){ne.removeEventListener("DOMContentLoaded",p),e.removeEventListener("load",p),de.ready()}function f(){this.expando=de.expando+f.uid++}function h(e){return"true"===e||"false"!==e&&("null"===e?null:e===+e+""?+e:Le.test(e)?JSON.parse(e):e)}function d(e,t,n){var r +if(void 0===n&&1===e.nodeType)if(r="data-"+t.replace(Ie,"-$&").toLowerCase(),"string"==typeof(n=e.getAttribute(r))){try{n=h(n)}catch(e){}Me.set(e,t,n)}else n=void 0 +return n}function m(e,t,n,r){var i,o=1,s=20,a=r?function(){return r.cur()}:function(){return de.css(e,t,"")},u=a(),c=n&&n[3]||(de.cssNumber[t]?"":"px"),l=(de.cssNumber[t]||"px"!==c&&+u)&&qe.exec(de.css(e,t)) +if(l&&l[3]!==c){c=c||l[3],n=n||[],l=+u||1 +do{o=o||".5",l/=o,de.style(e,t,l+c)}while(o!==(o=a()/u)&&1!==o&&--s)}return n&&(l=+l||+u||0,i=n[1]?l+(n[1]+1)*n[2]:+n[2],r&&(r.unit=c,r.start=l,r.end=i)),i}function g(e){var t,n=e.ownerDocument,r=e.nodeName,i=He[r] +return i||(t=n.body.appendChild(n.createElement(r)),i=de.css(t,"display"),t.parentNode.removeChild(t),"none"===i&&(i="block"),He[r]=i,i)}function y(e,t){for(var n,r,i=[],o=0,s=e.length;o-1)i&&i.push(o) +else if(c=de.contains(o.ownerDocument,o),s=v(p.appendChild(o),"script"),c&&b(s),n)for(l=0;o=s[l++];)Ge.test(o.type||"")&&n.push(o) +return p}function w(){return!0}function E(){return!1}function x(){try{return ne.activeElement}catch(e){}}function O(e,t,n,r,i,o){var s,a +if("object"==typeof t){"string"!=typeof n&&(r=r||n,n=void 0) +for(a in t)O(e,a,n,r,t[a],o) +return e}if(null==r&&null==i?(i=n,r=n=void 0):null==i&&("string"==typeof n?(i=r,r=void 0):(i=r,r=n,n=void 0)),!1===i)i=E +else if(!i)return e +return 1===o&&(s=i,i=function(e){return de().off(e),s.apply(this,arguments)},i.guid=s.guid||(s.guid=de.guid++)),e.each(function(){de.event.add(this,t,i,r,n)})}function C(e,t){return i(e,"table")&&i(11!==t.nodeType?t:t.firstChild,"tr")?de(">tbody",e)[0]||e:e}function S(e){return e.type=(null!==e.getAttribute("type"))+"/"+e.type,e}function T(e){var t=nt.exec(e.type) +return t?e.type=t[1]:e.removeAttribute("type"),e}function A(e,t){var n,r,i,o,s,a,u,c +if(1===t.nodeType){if(De.hasData(e)&&(o=De.access(e),s=De.set(t,o),c=o.events)){delete s.handle,s.events={} +for(i in c)for(n=0,r=c[i].length;n1&&"string"==typeof d&&!he.checkClone&&tt.test(d))return e.each(function(n){var o=e.eq(n) +m&&(t[0]=d.call(this,n,o.html())),k(o,t,r,i)}) +if(f&&(o=_(t,e[0].ownerDocument,!1,e,i),s=o.firstChild,1===o.childNodes.length&&(o=s),s||i)){for(a=de.map(v(o,"script"),S),u=a.length;p=0&&nw.cacheLength&&delete e[t.shift()],e[n+" "]=r}var t=[] +return e}function r(e){return e[F]=!0,e}function i(e){var t=j.createElement("fieldset") +try{return!!e(t)}catch(e){return!1}finally{t.parentNode&&t.parentNode.removeChild(t),t=null}}function o(e,t){for(var n=e.split("|"),r=n.length;r--;)w.attrHandle[n[r]]=t}function s(e,t){var n=t&&e,r=n&&1===e.nodeType&&1===t.nodeType&&e.sourceIndex-t.sourceIndex +if(r)return r +if(n)for(;n=n.nextSibling;)if(n===t)return-1 +return e?1:-1}function a(e){return function(t){return"form"in t?t.parentNode&&!1===t.disabled?"label"in t?"label"in t.parentNode?t.parentNode.disabled===e:t.disabled===e:t.isDisabled===e||t.isDisabled!==!e&&Ee(t)===e:t.disabled===e:"label"in t&&t.disabled===e}}function u(e){return r(function(t){return t=+t,r(function(n,r){for(var i,o=e([],n.length,t),s=o.length;s--;)n[i=o[s]]&&(n[i]=!(r[i]=n[i]))})})}function c(e){return e&&void 0!==e.getElementsByTagName&&e}function l(){}function p(e){for(var t=0,n=e.length,r="";t1?function(t,n,r){for(var i=e.length;i--;)if(!e[i](t,n,r))return!1 +return!0}:e[0]}function d(e,n,r){for(var i=0,o=n.length;i-1&&(r[c]=!(s[c]=p))}}else b=m(b===s?b.splice(g,b.length):b),o?o(null,s,b,u):Y.apply(s,b)})}function y(e){for(var t,n,r,i=e.length,o=w.relative[e[0].type],s=o||w.relative[" "],a=o?1:0,u=f(function(e){return e===t},s,!0),c=f(function(e){return J(t,e)>-1},s,!0),l=[function(e,n,r){var i=!o&&(r||n!==T)||((t=n).nodeType?u(e,n,r):c(e,n,r)) +return t=null,i}];a1&&h(l),a>1&&p(e.slice(0,a-1).concat({value:" "===e[a-2].type?"*":""})).replace(oe,"$1"),n,a0,o=e.length>0,s=function(r,s,a,u,c){var l,p,f,h=0,d="0",g=r&&[],y=[],v=T,b=r||o&&w.find.TAG("*",c),_=B+=null==v?1:Math.random()||.1,E=b.length +for(c&&(T=s===j||s||c);d!==E&&null!=(l=b[d]);d++){if(o&&l){for(p=0,s||l.ownerDocument===j||(k(l),a=!N);f=e[p++];)if(f(l,s||j,a)){u.push(l) +break}c&&(B=_)}i&&((l=!f&&l)&&h--,r&&g.push(l))}if(h+=d,i&&d!==h){for(p=0;f=n[p++];)f(g,y,s,a) +if(r){if(h>0)for(;d--;)g[d]||y[d]||(y[d]=Q.call(u)) +y=m(y)}Y.apply(u,y),c&&!r&&y.length>0&&h+n.length>1&&t.uniqueSort(u)}return c&&(B=_,T=v),g} +return i?r(s):s}var b,_,w,E,x,O,C,S,T,A,R,k,j,P,N,D,M,L,I,F="sizzle"+1*new Date,q=e.document,B=0,U=0,z=n(),H=n(),V=n(),W=function(e,t){return e===t&&(R=!0),0},G={}.hasOwnProperty,K=[],Q=K.pop,$=K.push,Y=K.push,X=K.slice,J=function(e,t){for(var n=0,r=e.length;n+~]|"+ee+")"+ee+"*"),ue=new RegExp("="+ee+"*([^\\]'\"]*?)"+ee+"*\\]","g"),ce=new RegExp(re),le=new RegExp("^"+te+"$"),pe={ID:new RegExp("^#("+te+")"),CLASS:new RegExp("^\\.("+te+")"),TAG:new RegExp("^("+te+"|[*])"),ATTR:new RegExp("^"+ne),PSEUDO:new RegExp("^"+re),CHILD:new RegExp("^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\("+ee+"*(even|odd|(([+-]|)(\\d*)n|)"+ee+"*(?:([+-]|)"+ee+"*(\\d+)|))"+ee+"*\\)|)","i"),bool:new RegExp("^(?:"+Z+")$","i"),needsContext:new RegExp("^"+ee+"*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\("+ee+"*((?:-\\d)?\\d*)"+ee+"*\\)|)(?=[^-]|$)","i")},fe=/^(?:input|select|textarea|button)$/i,he=/^h\d$/i,de=/^[^{]+\{\s*\[native \w/,me=/^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/,ge=/[+~]/,ye=new RegExp("\\\\([\\da-f]{1,6}"+ee+"?|("+ee+")|.)","ig"),ve=function(e,t,n){var r="0x"+t-65536 +return r!==r||n?t:r<0?String.fromCharCode(r+65536):String.fromCharCode(r>>10|55296,1023&r|56320)},be=/([\0-\x1f\x7f]|^-?\d)|^-$|[^\0-\x1f\x7f-\uFFFF\w-]/g,_e=function(e,t){return t?"\0"===e?"�":e.slice(0,-1)+"\\"+e.charCodeAt(e.length-1).toString(16)+" ":"\\"+e},we=function(){k()},Ee=f(function(e){return!0===e.disabled&&("form"in e||"label"in e)},{dir:"parentNode",next:"legend"}) +try{Y.apply(K=X.call(q.childNodes),q.childNodes),K[q.childNodes.length].nodeType}catch(e){Y={apply:K.length?function(e,t){$.apply(e,X.call(t))}:function(e,t){for(var n=e.length,r=0;e[n++]=t[r++];);e.length=n-1}}}_=t.support={},x=t.isXML=function(e){var t=e&&(e.ownerDocument||e).documentElement +return!!t&&"HTML"!==t.nodeName},k=t.setDocument=function(e){var t,n,r=e?e.ownerDocument||e:q +return r!==j&&9===r.nodeType&&r.documentElement?(j=r,P=j.documentElement,N=!x(j),q!==j&&(n=j.defaultView)&&n.top!==n&&(n.addEventListener?n.addEventListener("unload",we,!1):n.attachEvent&&n.attachEvent("onunload",we)),_.attributes=i(function(e){return e.className="i",!e.getAttribute("className")}),_.getElementsByTagName=i(function(e){return e.appendChild(j.createComment("")),!e.getElementsByTagName("*").length}),_.getElementsByClassName=de.test(j.getElementsByClassName),_.getById=i(function(e){return P.appendChild(e).id=F,!j.getElementsByName||!j.getElementsByName(F).length}),_.getById?(w.filter.ID=function(e){var t=e.replace(ye,ve) +return function(e){return e.getAttribute("id")===t}},w.find.ID=function(e,t){if(void 0!==t.getElementById&&N){var n=t.getElementById(e) +return n?[n]:[]}}):(w.filter.ID=function(e){var t=e.replace(ye,ve) +return function(e){var n=void 0!==e.getAttributeNode&&e.getAttributeNode("id") +return n&&n.value===t}},w.find.ID=function(e,t){if(void 0!==t.getElementById&&N){var n,r,i,o=t.getElementById(e) +if(o){if((n=o.getAttributeNode("id"))&&n.value===e)return[o] +for(i=t.getElementsByName(e),r=0;o=i[r++];)if((n=o.getAttributeNode("id"))&&n.value===e)return[o]}return[]}}),w.find.TAG=_.getElementsByTagName?function(e,t){return void 0!==t.getElementsByTagName?t.getElementsByTagName(e):_.qsa?t.querySelectorAll(e):void 0}:function(e,t){var n,r=[],i=0,o=t.getElementsByTagName(e) +if("*"===e){for(;n=o[i++];)1===n.nodeType&&r.push(n) +return r}return o},w.find.CLASS=_.getElementsByClassName&&function(e,t){if(void 0!==t.getElementsByClassName&&N)return t.getElementsByClassName(e)},M=[],D=[],(_.qsa=de.test(j.querySelectorAll))&&(i(function(e){P.appendChild(e).innerHTML="",e.querySelectorAll("[msallowcapture^='']").length&&D.push("[*^$]="+ee+"*(?:''|\"\")"),e.querySelectorAll("[selected]").length||D.push("\\["+ee+"*(?:value|"+Z+")"),e.querySelectorAll("[id~="+F+"-]").length||D.push("~="),e.querySelectorAll(":checked").length||D.push(":checked"),e.querySelectorAll("a#"+F+"+*").length||D.push(".#.+[+~]")}),i(function(e){e.innerHTML="" +var t=j.createElement("input") +t.setAttribute("type","hidden"),e.appendChild(t).setAttribute("name","D"),e.querySelectorAll("[name=d]").length&&D.push("name"+ee+"*[*^$|!~]?="),2!==e.querySelectorAll(":enabled").length&&D.push(":enabled",":disabled"),P.appendChild(e).disabled=!0,2!==e.querySelectorAll(":disabled").length&&D.push(":enabled",":disabled"),e.querySelectorAll("*,:x"),D.push(",.*:")})),(_.matchesSelector=de.test(L=P.matches||P.webkitMatchesSelector||P.mozMatchesSelector||P.oMatchesSelector||P.msMatchesSelector))&&i(function(e){_.disconnectedMatch=L.call(e,"*"),L.call(e,"[s!='']:x"),M.push("!=",re)}),D=D.length&&new RegExp(D.join("|")),M=M.length&&new RegExp(M.join("|")),t=de.test(P.compareDocumentPosition),I=t||de.test(P.contains)?function(e,t){var n=9===e.nodeType?e.documentElement:e,r=t&&t.parentNode +return e===r||!(!r||1!==r.nodeType||!(n.contains?n.contains(r):e.compareDocumentPosition&&16&e.compareDocumentPosition(r)))}:function(e,t){if(t)for(;t=t.parentNode;)if(t===e)return!0 +return!1},W=t?function(e,t){if(e===t)return R=!0,0 +var n=!e.compareDocumentPosition-!t.compareDocumentPosition +return n||(n=(e.ownerDocument||e)===(t.ownerDocument||t)?e.compareDocumentPosition(t):1,1&n||!_.sortDetached&&t.compareDocumentPosition(e)===n?e===j||e.ownerDocument===q&&I(q,e)?-1:t===j||t.ownerDocument===q&&I(q,t)?1:A?J(A,e)-J(A,t):0:4&n?-1:1)}:function(e,t){if(e===t)return R=!0,0 +var n,r=0,i=e.parentNode,o=t.parentNode,a=[e],u=[t] +if(!i||!o)return e===j?-1:t===j?1:i?-1:o?1:A?J(A,e)-J(A,t):0 +if(i===o)return s(e,t) +for(n=e;n=n.parentNode;)a.unshift(n) +for(n=t;n=n.parentNode;)u.unshift(n) +for(;a[r]===u[r];)r++ +return r?s(a[r],u[r]):a[r]===q?-1:u[r]===q?1:0},j):j},t.matches=function(e,n){return t(e,null,null,n)},t.matchesSelector=function(e,n){if((e.ownerDocument||e)!==j&&k(e),n=n.replace(ue,"='$1']"),_.matchesSelector&&N&&!V[n+" "]&&(!M||!M.test(n))&&(!D||!D.test(n)))try{var r=L.call(e,n) +if(r||_.disconnectedMatch||e.document&&11!==e.document.nodeType)return r}catch(e){}return t(n,j,null,[e]).length>0},t.contains=function(e,t){return(e.ownerDocument||e)!==j&&k(e),I(e,t)},t.attr=function(e,t){(e.ownerDocument||e)!==j&&k(e) +var n=w.attrHandle[t.toLowerCase()],r=n&&G.call(w.attrHandle,t.toLowerCase())?n(e,t,!N):void 0 +return void 0!==r?r:_.attributes||!N?e.getAttribute(t):(r=e.getAttributeNode(t))&&r.specified?r.value:null},t.escape=function(e){return(e+"").replace(be,_e)},t.error=function(e){throw new Error("Syntax error, unrecognized expression: "+e)},t.uniqueSort=function(e){var t,n=[],r=0,i=0 +if(R=!_.detectDuplicates,A=!_.sortStable&&e.slice(0),e.sort(W),R){for(;t=e[i++];)t===e[i]&&(r=n.push(i)) +for(;r--;)e.splice(n[r],1)}return A=null,e},E=t.getText=function(e){var t,n="",r=0,i=e.nodeType +if(i){if(1===i||9===i||11===i){if("string"==typeof e.textContent)return e.textContent +for(e=e.firstChild;e;e=e.nextSibling)n+=E(e)}else if(3===i||4===i)return e.nodeValue}else for(;t=e[r++];)n+=E(t) +return n},w=t.selectors={cacheLength:50,createPseudo:r,match:pe,attrHandle:{},find:{},relative:{">":{dir:"parentNode",first:!0}," ":{dir:"parentNode"},"+":{dir:"previousSibling",first:!0},"~":{dir:"previousSibling"}},preFilter:{ATTR:function(e){return e[1]=e[1].replace(ye,ve),e[3]=(e[3]||e[4]||e[5]||"").replace(ye,ve),"~="===e[2]&&(e[3]=" "+e[3]+" "),e.slice(0,4)},CHILD:function(e){return e[1]=e[1].toLowerCase(),"nth"===e[1].slice(0,3)?(e[3]||t.error(e[0]),e[4]=+(e[4]?e[5]+(e[6]||1):2*("even"===e[3]||"odd"===e[3])),e[5]=+(e[7]+e[8]||"odd"===e[3])):e[3]&&t.error(e[0]),e},PSEUDO:function(e){var t,n=!e[6]&&e[2] +return pe.CHILD.test(e[0])?null:(e[3]?e[2]=e[4]||e[5]||"":n&&ce.test(n)&&(t=O(n,!0))&&(t=n.indexOf(")",n.length-t)-n.length)&&(e[0]=e[0].slice(0,t),e[2]=n.slice(0,t)),e.slice(0,3))}},filter:{TAG:function(e){var t=e.replace(ye,ve).toLowerCase() +return"*"===e?function(){return!0}:function(e){return e.nodeName&&e.nodeName.toLowerCase()===t}},CLASS:function(e){var t=z[e+" "] +return t||(t=new RegExp("(^|"+ee+")"+e+"("+ee+"|$)"))&&z(e,function(e){return t.test("string"==typeof e.className&&e.className||void 0!==e.getAttribute&&e.getAttribute("class")||"")})},ATTR:function(e,n,r){return function(i){var o=t.attr(i,e) +return null==o?"!="===n:!n||(o+="","="===n?o===r:"!="===n?o!==r:"^="===n?r&&0===o.indexOf(r):"*="===n?r&&o.indexOf(r)>-1:"$="===n?r&&o.slice(-r.length)===r:"~="===n?(" "+o.replace(ie," ")+" ").indexOf(r)>-1:"|="===n&&(o===r||o.slice(0,r.length+1)===r+"-"))}},CHILD:function(e,t,n,r,i){var o="nth"!==e.slice(0,3),s="last"!==e.slice(-4),a="of-type"===t +return 1===r&&0===i?function(e){return!!e.parentNode}:function(t,n,u){var c,l,p,f,h,d,m=o!==s?"nextSibling":"previousSibling",g=t.parentNode,y=a&&t.nodeName.toLowerCase(),v=!u&&!a,b=!1 +if(g){if(o){for(;m;){for(f=t;f=f[m];)if(a?f.nodeName.toLowerCase()===y:1===f.nodeType)return!1 +d=m="only"===e&&!d&&"nextSibling"}return!0}if(d=[s?g.firstChild:g.lastChild],s&&v){for(f=g,p=f[F]||(f[F]={}),l=p[f.uniqueID]||(p[f.uniqueID]={}),c=l[e]||[],h=c[0]===B&&c[1],b=h&&c[2],f=h&&g.childNodes[h];f=++h&&f&&f[m]||(b=h=0)||d.pop();)if(1===f.nodeType&&++b&&f===t){l[e]=[B,h,b] +break}}else if(v&&(f=t,p=f[F]||(f[F]={}),l=p[f.uniqueID]||(p[f.uniqueID]={}),c=l[e]||[],h=c[0]===B&&c[1],b=h),!1===b)for(;(f=++h&&f&&f[m]||(b=h=0)||d.pop())&&((a?f.nodeName.toLowerCase()!==y:1!==f.nodeType)||!++b||(v&&(p=f[F]||(f[F]={}),l=p[f.uniqueID]||(p[f.uniqueID]={}),l[e]=[B,b]),f!==t)););return(b-=i)===r||b%r==0&&b/r>=0}}},PSEUDO:function(e,n){var i,o=w.pseudos[e]||w.setFilters[e.toLowerCase()]||t.error("unsupported pseudo: "+e) +return o[F]?o(n):o.length>1?(i=[e,e,"",n],w.setFilters.hasOwnProperty(e.toLowerCase())?r(function(e,t){for(var r,i=o(e,n),s=i.length;s--;)r=J(e,i[s]),e[r]=!(t[r]=i[s])}):function(e){return o(e,0,i)}):o}},pseudos:{not:r(function(e){var t=[],n=[],i=C(e.replace(oe,"$1")) +return i[F]?r(function(e,t,n,r){for(var o,s=i(e,null,r,[]),a=e.length;a--;)(o=s[a])&&(e[a]=!(t[a]=o))}):function(e,r,o){return t[0]=e,i(t,null,o,n),t[0]=null,!n.pop()}}),has:r(function(e){return function(n){return t(e,n).length>0}}),contains:r(function(e){return e=e.replace(ye,ve),function(t){return(t.textContent||t.innerText||E(t)).indexOf(e)>-1}}),lang:r(function(e){return le.test(e||"")||t.error("unsupported lang: "+e),e=e.replace(ye,ve).toLowerCase(),function(t){var n +do{if(n=N?t.lang:t.getAttribute("xml:lang")||t.getAttribute("lang"))return(n=n.toLowerCase())===e||0===n.indexOf(e+"-")}while((t=t.parentNode)&&1===t.nodeType) +return!1}}),target:function(t){var n=e.location&&e.location.hash +return n&&n.slice(1)===t.id},root:function(e){return e===P},focus:function(e){return e===j.activeElement&&(!j.hasFocus||j.hasFocus())&&!!(e.type||e.href||~e.tabIndex)},enabled:a(!1),disabled:a(!0),checked:function(e){var t=e.nodeName.toLowerCase() +return"input"===t&&!!e.checked||"option"===t&&!!e.selected},selected:function(e){return e.parentNode&&e.parentNode.selectedIndex,!0===e.selected},empty:function(e){for(e=e.firstChild;e;e=e.nextSibling)if(e.nodeType<6)return!1 +return!0},parent:function(e){return!w.pseudos.empty(e)},header:function(e){return he.test(e.nodeName)},input:function(e){return fe.test(e.nodeName)},button:function(e){var t=e.nodeName.toLowerCase() +return"input"===t&&"button"===e.type||"button"===t},text:function(e){var t +return"input"===e.nodeName.toLowerCase()&&"text"===e.type&&(null==(t=e.getAttribute("type"))||"text"===t.toLowerCase())},first:u(function(){return[0]}),last:u(function(e,t){return[t-1]}),eq:u(function(e,t,n){return[n<0?n+t:n]}),even:u(function(e,t){for(var n=0;n=0;)e.push(r) +return e}),gt:u(function(e,t,n){for(var r=n<0?n+t:n;++r2&&"ID"===(s=o[0]).type&&9===t.nodeType&&N&&w.relative[o[1].type]){if(!(t=(w.find.ID(s.matches[0].replace(ye,ve),t)||[])[0]))return n +l&&(t=t.parentNode),e=e.slice(o.shift().value.length)}for(i=pe.needsContext.test(e)?0:o.length;i--&&(s=o[i],!w.relative[a=s.type]);)if((u=w.find[a])&&(r=u(s.matches[0].replace(ye,ve),ge.test(o[0].type)&&c(t.parentNode)||t))){if(o.splice(i,1),!(e=r.length&&p(o)))return Y.apply(n,r),n +break}}return(l||C(e,f))(r,t,!N,n,!t||ge.test(e)&&c(t.parentNode)||t),n},_.sortStable=F.split("").sort(W).join("")===F,_.detectDuplicates=!!R,k(),_.sortDetached=i(function(e){return 1&e.compareDocumentPosition(j.createElement("fieldset"))}),i(function(e){return e.innerHTML="","#"===e.firstChild.getAttribute("href")})||o("type|href|height|width",function(e,t,n){if(!n)return e.getAttribute(t,"type"===t.toLowerCase()?1:2)}),_.attributes&&i(function(e){return e.innerHTML="",e.firstChild.setAttribute("value",""),""===e.firstChild.getAttribute("value")})||o("value",function(e,t,n){if(!n&&"input"===e.nodeName.toLowerCase())return e.defaultValue}),i(function(e){return null==e.getAttribute("disabled")})||o(Z,function(e,t,n){var r +if(!n)return!0===e[t]?t.toLowerCase():(r=e.getAttributeNode(t))&&r.specified?r.value:null}),t}(e) +de.find=be,de.expr=be.selectors,de.expr[":"]=de.expr.pseudos,de.uniqueSort=de.unique=be.uniqueSort,de.text=be.getText,de.isXMLDoc=be.isXML,de.contains=be.contains,de.escapeSelector=be.escape +var _e=function(e,t,n){for(var r=[],i=void 0!==n;(e=e[t])&&9!==e.nodeType;)if(1===e.nodeType){if(i&&de(e).is(n))break +r.push(e)}return r},we=function(e,t){for(var n=[];e;e=e.nextSibling)1===e.nodeType&&e!==t&&n.push(e) +return n},Ee=de.expr.match.needsContext,xe=/^<([a-z][^\/\0>:\x20\t\r\n\f]*)[\x20\t\r\n\f]*\/?>(?:<\/\1>|)$/i,Oe=/^.[^:#\[\.,]*$/ +de.filter=function(e,t,n){var r=t[0] +return n&&(e=":not("+e+")"),1===t.length&&1===r.nodeType?de.find.matchesSelector(r,e)?[r]:[]:de.find.matches(e,de.grep(t,function(e){return 1===e.nodeType}))},de.fn.extend({find:function(e){var t,n,r=this.length,i=this +if("string"!=typeof e)return this.pushStack(de(e).filter(function(){for(t=0;t1?de.uniqueSort(n):n},filter:function(e){return this.pushStack(o(this,e||[],!1))},not:function(e){return this.pushStack(o(this,e||[],!0))},is:function(e){return!!o(this,"string"==typeof e&&Ee.test(e)?de(e):e||[],!1).length}}) +var Ce,Se=/^(?:\s*(<[\w\W]+>)[^>]*|#([\w-]+))$/;(de.fn.init=function(e,t,n){var r,i +if(!e)return this +if(n=n||Ce,"string"==typeof e){if(!(r="<"===e[0]&&">"===e[e.length-1]&&e.length>=3?[null,e,null]:Se.exec(e))||!r[1]&&t)return!t||t.jquery?(t||n).find(e):this.constructor(t).find(e) +if(r[1]){if(t=t instanceof de?t[0]:t,de.merge(this,de.parseHTML(r[1],t&&t.nodeType?t.ownerDocument||t:ne,!0)),xe.test(r[1])&&de.isPlainObject(t))for(r in t)de.isFunction(this[r])?this[r](t[r]):this.attr(r,t[r]) +return this}return i=ne.getElementById(r[2]),i&&(this[0]=i,this.length=1),this}return e.nodeType?(this[0]=e,this.length=1,this):de.isFunction(e)?void 0!==n.ready?n.ready(e):e(de):de.makeArray(e,this)}).prototype=de.fn,Ce=de(ne) +var Te=/^(?:parents|prev(?:Until|All))/,Ae={children:!0,contents:!0,next:!0,prev:!0} +de.fn.extend({has:function(e){var t=de(e,this),n=t.length +return this.filter(function(){for(var e=0;e-1:1===n.nodeType&&de.find.matchesSelector(n,e))){o.push(n) +break}return this.pushStack(o.length>1?de.uniqueSort(o):o)},index:function(e){return e?"string"==typeof e?ae.call(de(e),this[0]):ae.call(this,e.jquery?e[0]:e):this[0]&&this[0].parentNode?this.first().prevAll().length:-1},add:function(e,t){return this.pushStack(de.uniqueSort(de.merge(this.get(),de(e,t))))},addBack:function(e){return this.add(null==e?this.prevObject:this.prevObject.filter(e))}}),de.each({parent:function(e){var t=e.parentNode +return t&&11!==t.nodeType?t:null},parents:function(e){return _e(e,"parentNode")},parentsUntil:function(e,t,n){return _e(e,"parentNode",n)},next:function(e){return s(e,"nextSibling")},prev:function(e){return s(e,"previousSibling")},nextAll:function(e){return _e(e,"nextSibling")},prevAll:function(e){return _e(e,"previousSibling")},nextUntil:function(e,t,n){return _e(e,"nextSibling",n)},prevUntil:function(e,t,n){return _e(e,"previousSibling",n)},siblings:function(e){return we((e.parentNode||{}).firstChild,e)},children:function(e){return we(e.firstChild)},contents:function(e){return i(e,"iframe")?e.contentDocument:(i(e,"template")&&(e=e.content||e),de.merge([],e.childNodes))}},function(e,t){de.fn[e]=function(n,r){var i=de.map(this,t,n) +return"Until"!==e.slice(-5)&&(r=n),r&&"string"==typeof r&&(i=de.filter(r,i)),this.length>1&&(Ae[e]||de.uniqueSort(i),Te.test(e)&&i.reverse()),this.pushStack(i)}}) +var Re=/[^\x20\t\r\n\f]+/g +de.Callbacks=function(e){e="string"==typeof e?a(e):de.extend({},e) +var t,n,r,i,o=[],s=[],u=-1,c=function(){for(i=i||e.once,r=t=!0;s.length;u=-1)for(n=s.shift();++u-1;)o.splice(n,1),n<=u&&u--}),this},has:function(e){return e?de.inArray(e,o)>-1:o.length>0},empty:function(){return o&&(o=[]),this},disable:function(){return i=s=[],o=n="",this},disabled:function(){return!o},lock:function(){return i=s=[],n||t||(o=n=""),this},locked:function(){return!!i},fireWith:function(e,n){return i||(n=n||[],n=[e,n.slice?n.slice():n],s.push(n),t||c()),this},fire:function(){return l.fireWith(this,arguments),this},fired:function(){return!!r}} +return l},de.extend({Deferred:function(t){var n=[["notify","progress",de.Callbacks("memory"),de.Callbacks("memory"),2],["resolve","done",de.Callbacks("once memory"),de.Callbacks("once memory"),0,"resolved"],["reject","fail",de.Callbacks("once memory"),de.Callbacks("once memory"),1,"rejected"]],r="pending",i={state:function(){return r},always:function(){return o.done(arguments).fail(arguments),this},catch:function(e){return i.then(null,e)},pipe:function(){var e=arguments +return de.Deferred(function(t){de.each(n,function(n,r){var i=de.isFunction(e[r[4]])&&e[r[4]] +o[r[1]](function(){var e=i&&i.apply(this,arguments) +e&&de.isFunction(e.promise)?e.promise().progress(t.notify).done(t.resolve).fail(t.reject):t[r[0]+"With"](this,i?[e]:arguments)})}),e=null}).promise()},then:function(t,r,i){function o(t,n,r,i){return function(){var a=this,l=arguments,p=function(){var e,p +if(!(t=s&&(r!==c&&(a=void 0,l=[e]),n.rejectWith(a,l))}} +t?f():(de.Deferred.getStackHook&&(f.stackTrace=de.Deferred.getStackHook()),e.setTimeout(f))}}var s=0 +return de.Deferred(function(e){n[0][3].add(o(0,e,de.isFunction(i)?i:u,e.notifyWith)),n[1][3].add(o(0,e,de.isFunction(t)?t:u)),n[2][3].add(o(0,e,de.isFunction(r)?r:c))}).promise()},promise:function(e){return null!=e?de.extend(e,i):i}},o={} +return de.each(n,function(e,t){var s=t[2],a=t[5] +i[t[1]]=s.add,a&&s.add(function(){r=a},n[3-e][2].disable,n[0][2].lock),s.add(t[3].fire),o[t[0]]=function(){return o[t[0]+"With"](this===o?void 0:this,arguments),this},o[t[0]+"With"]=s.fireWith}),i.promise(o),t&&t.call(o,o),o},when:function(e){var t=arguments.length,n=t,r=Array(n),i=ie.call(arguments),o=de.Deferred(),s=function(e){return function(n){r[e]=this,i[e]=arguments.length>1?ie.call(arguments):n,--t||o.resolveWith(r,i)}} +if(t<=1&&(l(e,o.done(s(n)).resolve,o.reject,!t),"pending"===o.state()||de.isFunction(i[n]&&i[n].then)))return o.then() +for(;n--;)l(i[n],s(n),o.reject) +return o.promise()}}) +var ke=/^(Eval|Internal|Range|Reference|Syntax|Type|URI)Error$/ +de.Deferred.exceptionHook=function(t,n){e.console&&e.console.warn&&t&&ke.test(t.name)&&e.console.warn("jQuery.Deferred exception: "+t.message,t.stack,n)},de.readyException=function(t){e.setTimeout(function(){throw t})} +var je=de.Deferred() +de.fn.ready=function(e){return je.then(e).catch(function(e){de.readyException(e)}),this},de.extend({isReady:!1,readyWait:1,ready:function(e){(!0===e?--de.readyWait:de.isReady)||(de.isReady=!0,!0!==e&&--de.readyWait>0||je.resolveWith(ne,[de]))}}),de.ready.then=je.then,"complete"===ne.readyState||"loading"!==ne.readyState&&!ne.documentElement.doScroll?e.setTimeout(de.ready):(ne.addEventListener("DOMContentLoaded",p),e.addEventListener("load",p)) +var Pe=function(e,t,n,r,i,o,s){var a=0,u=e.length,c=null==n +if("object"===de.type(n)){i=!0 +for(a in n)Pe(e,t,a,n[a],!0,o,s)}else if(void 0!==r&&(i=!0,de.isFunction(r)||(s=!0),c&&(s?(t.call(e,r),t=null):(c=t,t=function(e,t,n){return c.call(de(e),n)})),t))for(;a1,null,!0)},removeData:function(e){return this.each(function(){Me.remove(this,e)})}}),de.extend({queue:function(e,t,n){var r +if(e)return t=(t||"fx")+"queue",r=De.get(e,t),n&&(!r||Array.isArray(n)?r=De.access(e,t,de.makeArray(n)):r.push(n)),r||[]},dequeue:function(e,t){t=t||"fx" +var n=de.queue(e,t),r=n.length,i=n.shift(),o=de._queueHooks(e,t),s=function(){de.dequeue(e,t)} +"inprogress"===i&&(i=n.shift(),r--),i&&("fx"===t&&n.unshift("inprogress"),delete o.stop,i.call(e,s,o)),!r&&o&&o.empty.fire()},_queueHooks:function(e,t){var n=t+"queueHooks" +return De.get(e,n)||De.access(e,n,{empty:de.Callbacks("once memory").add(function(){De.remove(e,[t+"queue",n])})})}}),de.fn.extend({queue:function(e,t){var n=2 +return"string"!=typeof e&&(t=e,e="fx",n--),arguments.length\x20\t\r\n\f]+)/i,Ge=/^$|\/(?:java|ecma)script/i,Ke={option:[1,""],thead:[1,"","
    "],col:[2,"","
    "],tr:[2,"","
    "],td:[3,"","
    "],_default:[0,"",""]} +Ke.optgroup=Ke.option,Ke.tbody=Ke.tfoot=Ke.colgroup=Ke.caption=Ke.thead,Ke.th=Ke.td +var Qe=/<|&#?\w+;/;(function(){var e=ne.createDocumentFragment(),t=e.appendChild(ne.createElement("div")),n=ne.createElement("input") +n.setAttribute("type","radio"),n.setAttribute("checked","checked"),n.setAttribute("name","t"),t.appendChild(n),he.checkClone=t.cloneNode(!0).cloneNode(!0).lastChild.checked,t.innerHTML="",he.noCloneChecked=!!t.cloneNode(!0).lastChild.defaultValue})() +var $e=ne.documentElement,Ye=/^key/,Xe=/^(?:mouse|pointer|contextmenu|drag|drop)|click/,Je=/^([^.]*)(?:\.(.+)|)/ +de.event={global:{},add:function(e,t,n,r,i){var o,s,a,u,c,l,p,f,h,d,m,g=De.get(e) +if(g)for(n.handler&&(o=n,n=o.handler,i=o.selector),i&&de.find.matchesSelector($e,i),n.guid||(n.guid=de.guid++),(u=g.events)||(u=g.events={}),(s=g.handle)||(s=g.handle=function(t){return void 0!==de&&de.event.triggered!==t.type?de.event.dispatch.apply(e,arguments):void 0}),t=(t||"").match(Re)||[""],c=t.length;c--;)a=Je.exec(t[c])||[],h=m=a[1],d=(a[2]||"").split(".").sort(),h&&(p=de.event.special[h]||{},h=(i?p.delegateType:p.bindType)||h,p=de.event.special[h]||{},l=de.extend({type:h,origType:m,data:r,handler:n,guid:n.guid,selector:i,needsContext:i&&de.expr.match.needsContext.test(i),namespace:d.join(".")},o),(f=u[h])||(f=u[h]=[],f.delegateCount=0,p.setup&&!1!==p.setup.call(e,r,d,s)||e.addEventListener&&e.addEventListener(h,s)),p.add&&(p.add.call(e,l),l.handler.guid||(l.handler.guid=n.guid)),i?f.splice(f.delegateCount++,0,l):f.push(l),de.event.global[h]=!0)},remove:function(e,t,n,r,i){var o,s,a,u,c,l,p,f,h,d,m,g=De.hasData(e)&&De.get(e) +if(g&&(u=g.events)){for(t=(t||"").match(Re)||[""],c=t.length;c--;)if(a=Je.exec(t[c])||[],h=m=a[1],d=(a[2]||"").split(".").sort(),h){for(p=de.event.special[h]||{},h=(r?p.delegateType:p.bindType)||h,f=u[h]||[],a=a[2]&&new RegExp("(^|\\.)"+d.join("\\.(?:.*\\.|)")+"(\\.|$)"),s=o=f.length;o--;)l=f[o],!i&&m!==l.origType||n&&n.guid!==l.guid||a&&!a.test(l.namespace)||r&&r!==l.selector&&("**"!==r||!l.selector)||(f.splice(o,1),l.selector&&f.delegateCount--,p.remove&&p.remove.call(e,l)) +s&&!f.length&&(p.teardown&&!1!==p.teardown.call(e,d,g.handle)||de.removeEvent(e,h,g.handle),delete u[h])}else for(h in u)de.event.remove(e,h+t[c],n,r,!0) +de.isEmptyObject(u)&&De.remove(e,"handle events")}},dispatch:function(e){var t,n,r,i,o,s,a=de.event.fix(e),u=new Array(arguments.length),c=(De.get(this,"events")||{})[a.type]||[],l=de.event.special[a.type]||{} +for(u[0]=a,t=1;t=1))for(;c!==this;c=c.parentNode||this)if(1===c.nodeType&&("click"!==e.type||!0!==c.disabled)){for(o=[],s={},n=0;n-1:de.find(i,this,null,[c]).length),s[i]&&o.push(r) +o.length&&a.push({elem:c,handlers:o})}return c=this,u\x20\t\r\n\f]*)[^>]*)\/>/gi,et=/\s*$/g +de.extend({htmlPrefilter:function(e){return e.replace(Ze,"<$1>")},clone:function(e,t,n){var r,i,o,s,a=e.cloneNode(!0),u=de.contains(e.ownerDocument,e) +if(!(he.noCloneChecked||1!==e.nodeType&&11!==e.nodeType||de.isXMLDoc(e)))for(s=v(a),o=v(e),r=0,i=o.length;r0&&b(s,!u&&v(e,"script")),a},cleanData:function(e){for(var t,n,r,i=de.event.special,o=0;void 0!==(n=e[o]);o++)if(Ne(n)){if(t=n[De.expando]){if(t.events)for(r in t.events)i[r]?de.event.remove(n,r):de.removeEvent(n,r,t.handle) +n[De.expando]=void 0}n[Me.expando]&&(n[Me.expando]=void 0)}}}),de.fn.extend({detach:function(e){return j(this,e,!0)},remove:function(e){return j(this,e)},text:function(e){return Pe(this,function(e){return void 0===e?de.text(this):this.empty().each(function(){1!==this.nodeType&&11!==this.nodeType&&9!==this.nodeType||(this.textContent=e)})},null,e,arguments.length)},append:function(){return k(this,arguments,function(e){if(1===this.nodeType||11===this.nodeType||9===this.nodeType){C(this,e).appendChild(e)}})},prepend:function(){return k(this,arguments,function(e){if(1===this.nodeType||11===this.nodeType||9===this.nodeType){var t=C(this,e) +t.insertBefore(e,t.firstChild)}})},before:function(){return k(this,arguments,function(e){this.parentNode&&this.parentNode.insertBefore(e,this)})},after:function(){return k(this,arguments,function(e){this.parentNode&&this.parentNode.insertBefore(e,this.nextSibling)})},empty:function(){for(var e,t=0;null!=(e=this[t]);t++)1===e.nodeType&&(de.cleanData(v(e,!1)),e.textContent="") +return this},clone:function(e,t){return e=null!=e&&e,t=null==t?e:t,this.map(function(){return de.clone(this,e,t)})},html:function(e){return Pe(this,function(e){var t=this[0]||{},n=0,r=this.length +if(void 0===e&&1===t.nodeType)return t.innerHTML +if("string"==typeof e&&!et.test(e)&&!Ke[(We.exec(e)||["",""])[1].toLowerCase()]){e=de.htmlPrefilter(e) +try{for(;n1)}}),de.Tween=q,q.prototype={constructor:q,init:function(e,t,n,r,i,o){this.elem=e,this.prop=n,this.easing=i||de.easing._default,this.options=t,this.start=this.now=this.cur(),this.end=r,this.unit=o||(de.cssNumber[n]?"":"px")},cur:function(){var e=q.propHooks[this.prop] +return e&&e.get?e.get(this):q.propHooks._default.get(this)},run:function(e){var t,n=q.propHooks[this.prop] +return this.options.duration?this.pos=t=de.easing[this.easing](e,this.options.duration*e,0,1,this.options.duration):this.pos=t=e,this.now=(this.end-this.start)*t+this.start,this.options.step&&this.options.step.call(this.elem,this.now,this),n&&n.set?n.set(this):q.propHooks._default.set(this),this}},q.prototype.init.prototype=q.prototype,q.propHooks={_default:{get:function(e){var t +return 1!==e.elem.nodeType||null!=e.elem[e.prop]&&null==e.elem.style[e.prop]?e.elem[e.prop]:(t=de.css(e.elem,e.prop,""),t&&"auto"!==t?t:0)},set:function(e){de.fx.step[e.prop]?de.fx.step[e.prop](e):1!==e.elem.nodeType||null==e.elem.style[de.cssProps[e.prop]]&&!de.cssHooks[e.prop]?e.elem[e.prop]=e.now:de.style(e.elem,e.prop,e.now+e.unit)}}},q.propHooks.scrollTop=q.propHooks.scrollLeft={set:function(e){e.elem.nodeType&&e.elem.parentNode&&(e.elem[e.prop]=e.now)}},de.easing={linear:function(e){return e},swing:function(e){return.5-Math.cos(e*Math.PI)/2},_default:"swing"},de.fx=q.prototype.init,de.fx.step={} +var ht,dt,mt=/^(?:toggle|show|hide)$/,gt=/queueHooks$/ +de.Animation=de.extend(G,{tweeners:{"*":[function(e,t){var n=this.createTween(e,t) +return m(n.elem,e,qe.exec(t),n),n}]},tweener:function(e,t){de.isFunction(e)?(t=e,e=["*"]):e=e.match(Re) +for(var n,r=0,i=e.length;r1)},removeAttr:function(e){return this.each(function(){de.removeAttr(this,e)})}}),de.extend({attr:function(e,t,n){var r,i,o=e.nodeType +if(3!==o&&8!==o&&2!==o)return void 0===e.getAttribute?de.prop(e,t,n):(1===o&&de.isXMLDoc(e)||(i=de.attrHooks[t.toLowerCase()]||(de.expr.match.bool.test(t)?yt:void 0)),void 0!==n?null===n?void de.removeAttr(e,t):i&&"set"in i&&void 0!==(r=i.set(e,n,t))?r:(e.setAttribute(t,n+""),n):i&&"get"in i&&null!==(r=i.get(e,t))?r:(r=de.find.attr(e,t),null==r?void 0:r))},attrHooks:{type:{set:function(e,t){if(!he.radioValue&&"radio"===t&&i(e,"input")){var n=e.value +return e.setAttribute("type",t),n&&(e.value=n),t}}}},removeAttr:function(e,t){var n,r=0,i=t&&t.match(Re) +if(i&&1===e.nodeType)for(;n=i[r++];)e.removeAttribute(n)}}),yt={set:function(e,t,n){return!1===t?de.removeAttr(e,n):e.setAttribute(n,n),n}},de.each(de.expr.match.bool.source.match(/\w+/g),function(e,t){var n=vt[t]||de.find.attr +vt[t]=function(e,t,r){var i,o,s=t.toLowerCase() +return r||(o=vt[s],vt[s]=i,i=null!=n(e,t,r)?s:null,vt[s]=o),i}}) +var bt=/^(?:input|select|textarea|button)$/i,_t=/^(?:a|area)$/i +de.fn.extend({prop:function(e,t){return Pe(this,de.prop,e,t,arguments.length>1)},removeProp:function(e){return this.each(function(){delete this[de.propFix[e]||e]})}}),de.extend({prop:function(e,t,n){var r,i,o=e.nodeType +if(3!==o&&8!==o&&2!==o)return 1===o&&de.isXMLDoc(e)||(t=de.propFix[t]||t,i=de.propHooks[t]),void 0!==n?i&&"set"in i&&void 0!==(r=i.set(e,n,t))?r:e[t]=n:i&&"get"in i&&null!==(r=i.get(e,t))?r:e[t]},propHooks:{tabIndex:{get:function(e){var t=de.find.attr(e,"tabindex") +return t?parseInt(t,10):bt.test(e.nodeName)||_t.test(e.nodeName)&&e.href?0:-1}}},propFix:{for:"htmlFor",class:"className"}}),he.optSelected||(de.propHooks.selected={get:function(e){var t=e.parentNode +return t&&t.parentNode&&t.parentNode.selectedIndex,null},set:function(e){var t=e.parentNode +t&&(t.selectedIndex,t.parentNode&&t.parentNode.selectedIndex)}}),de.each(["tabIndex","readOnly","maxLength","cellSpacing","cellPadding","rowSpan","colSpan","useMap","frameBorder","contentEditable"],function(){de.propFix[this.toLowerCase()]=this}),de.fn.extend({addClass:function(e){var t,n,r,i,o,s,a,u=0 +if(de.isFunction(e))return this.each(function(t){de(this).addClass(e.call(this,t,Q(this)))}) +if("string"==typeof e&&e)for(t=e.match(Re)||[];n=this[u++];)if(i=Q(n),r=1===n.nodeType&&" "+K(i)+" "){for(s=0;o=t[s++];)r.indexOf(" "+o+" ")<0&&(r+=o+" ") +a=K(r),i!==a&&n.setAttribute("class",a)}return this},removeClass:function(e){var t,n,r,i,o,s,a,u=0 +if(de.isFunction(e))return this.each(function(t){de(this).removeClass(e.call(this,t,Q(this)))}) +if(!arguments.length)return this.attr("class","") +if("string"==typeof e&&e)for(t=e.match(Re)||[];n=this[u++];)if(i=Q(n),r=1===n.nodeType&&" "+K(i)+" "){for(s=0;o=t[s++];)for(;r.indexOf(" "+o+" ")>-1;)r=r.replace(" "+o+" "," ") +a=K(r),i!==a&&n.setAttribute("class",a)}return this},toggleClass:function(e,t){var n=typeof e +return"boolean"==typeof t&&"string"===n?t?this.addClass(e):this.removeClass(e):de.isFunction(e)?this.each(function(n){de(this).toggleClass(e.call(this,n,Q(this),t),t)}):this.each(function(){var t,r,i,o +if("string"===n)for(r=0,i=de(this),o=e.match(Re)||[];t=o[r++];)i.hasClass(t)?i.removeClass(t):i.addClass(t) +else void 0!==e&&"boolean"!==n||(t=Q(this),t&&De.set(this,"__className__",t),this.setAttribute&&this.setAttribute("class",t||!1===e?"":De.get(this,"__className__")||""))})},hasClass:function(e){var t,n,r=0 +for(t=" "+e+" ";n=this[r++];)if(1===n.nodeType&&(" "+K(Q(n))+" ").indexOf(t)>-1)return!0 +return!1}}) +var wt=/\r/g +de.fn.extend({val:function(e){var t,n,r,i=this[0] +{if(arguments.length)return r=de.isFunction(e),this.each(function(n){var i +1===this.nodeType&&(i=r?e.call(this,n,de(this).val()):e,null==i?i="":"number"==typeof i?i+="":Array.isArray(i)&&(i=de.map(i,function(e){return null==e?"":e+""})),(t=de.valHooks[this.type]||de.valHooks[this.nodeName.toLowerCase()])&&"set"in t&&void 0!==t.set(this,i,"value")||(this.value=i))}) +if(i)return(t=de.valHooks[i.type]||de.valHooks[i.nodeName.toLowerCase()])&&"get"in t&&void 0!==(n=t.get(i,"value"))?n:(n=i.value,"string"==typeof n?n.replace(wt,""):null==n?"":n)}}}),de.extend({valHooks:{option:{get:function(e){var t=de.find.attr(e,"value") +return null!=t?t:K(de.text(e))}},select:{get:function(e){var t,n,r,o=e.options,s=e.selectedIndex,a="select-one"===e.type,u=a?null:[],c=a?s+1:o.length +for(r=s<0?c:a?s:0;r-1)&&(n=!0) +return n||(e.selectedIndex=-1),o}}}}),de.each(["radio","checkbox"],function(){de.valHooks[this]={set:function(e,t){if(Array.isArray(t))return e.checked=de.inArray(de(e).val(),t)>-1}},he.checkOn||(de.valHooks[this].get=function(e){return null===e.getAttribute("value")?"on":e.value})}) +var Et=/^(?:focusinfocus|focusoutblur)$/ +de.extend(de.event,{trigger:function(t,n,r,i){var o,s,a,u,c,l,p,f=[r||ne],h=le.call(t,"type")?t.type:t,d=le.call(t,"namespace")?t.namespace.split("."):[] +if(s=a=r=r||ne,3!==r.nodeType&&8!==r.nodeType&&!Et.test(h+de.event.triggered)&&(h.indexOf(".")>-1&&(d=h.split("."),h=d.shift(),d.sort()),c=h.indexOf(":")<0&&"on"+h,t=t[de.expando]?t:new de.Event(h,"object"==typeof t&&t),t.isTrigger=i?2:3,t.namespace=d.join("."),t.rnamespace=t.namespace?new RegExp("(^|\\.)"+d.join("\\.(?:.*\\.|)")+"(\\.|$)"):null,t.result=void 0,t.target||(t.target=r),n=null==n?[t]:de.makeArray(n,[t]),p=de.event.special[h]||{},i||!p.trigger||!1!==p.trigger.apply(r,n))){if(!i&&!p.noBubble&&!de.isWindow(r)){for(u=p.delegateType||h,Et.test(u+h)||(s=s.parentNode);s;s=s.parentNode)f.push(s),a=s +a===(r.ownerDocument||ne)&&f.push(a.defaultView||a.parentWindow||e)}for(o=0;(s=f[o++])&&!t.isPropagationStopped();)t.type=o>1?u:p.bindType||h,l=(De.get(s,"events")||{})[t.type]&&De.get(s,"handle"),l&&l.apply(s,n),(l=c&&s[c])&&l.apply&&Ne(s)&&(t.result=l.apply(s,n),!1===t.result&&t.preventDefault()) +return t.type=h,i||t.isDefaultPrevented()||p._default&&!1!==p._default.apply(f.pop(),n)||!Ne(r)||c&&de.isFunction(r[h])&&!de.isWindow(r)&&(a=r[c],a&&(r[c]=null),de.event.triggered=h,r[h](),de.event.triggered=void 0,a&&(r[c]=a)),t.result}},simulate:function(e,t,n){var r=de.extend(new de.Event,n,{type:e,isSimulated:!0}) +de.event.trigger(r,null,t)}}),de.fn.extend({trigger:function(e,t){return this.each(function(){de.event.trigger(e,t,this)})},triggerHandler:function(e,t){var n=this[0] +if(n)return de.event.trigger(e,t,n,!0)}}),de.each("blur focus focusin focusout resize scroll click dblclick mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave change select submit keydown keypress keyup contextmenu".split(" "),function(e,t){de.fn[t]=function(e,n){return arguments.length>0?this.on(t,null,e,n):this.trigger(t)}}),de.fn.extend({hover:function(e,t){return this.mouseenter(e).mouseleave(t||e)}}),he.focusin="onfocusin"in e,he.focusin||de.each({focus:"focusin",blur:"focusout"},function(e,t){var n=function(e){de.event.simulate(t,e.target,de.event.fix(e))} +de.event.special[t]={setup:function(){var r=this.ownerDocument||this,i=De.access(r,t) +i||r.addEventListener(e,n,!0),De.access(r,t,(i||0)+1)},teardown:function(){var r=this.ownerDocument||this,i=De.access(r,t)-1 +i?De.access(r,t,i):(r.removeEventListener(e,n,!0),De.remove(r,t))}}}) +var xt=e.location,Ot=de.now(),Ct=/\?/ +de.parseXML=function(t){var n +if(!t||"string"!=typeof t)return null +try{n=(new e.DOMParser).parseFromString(t,"text/xml")}catch(e){n=void 0}return n&&!n.getElementsByTagName("parsererror").length||de.error("Invalid XML: "+t),n} +var St=/\[\]$/,Tt=/\r?\n/g,At=/^(?:submit|button|image|reset|file)$/i,Rt=/^(?:input|select|textarea|keygen)/i +de.param=function(e,t){var n,r=[],i=function(e,t){var n=de.isFunction(t)?t():t +r[r.length]=encodeURIComponent(e)+"="+encodeURIComponent(null==n?"":n)} +if(Array.isArray(e)||e.jquery&&!de.isPlainObject(e))de.each(e,function(){i(this.name,this.value)}) +else for(n in e)$(n,e[n],t,i) +return r.join("&")},de.fn.extend({serialize:function(){return de.param(this.serializeArray())},serializeArray:function(){return this.map(function(){var e=de.prop(this,"elements") +return e?de.makeArray(e):this}).filter(function(){var e=this.type +return this.name&&!de(this).is(":disabled")&&Rt.test(this.nodeName)&&!At.test(e)&&(this.checked||!Ve.test(e))}).map(function(e,t){var n=de(this).val() +return null==n?null:Array.isArray(n)?de.map(n,function(e){return{name:t.name,value:e.replace(Tt,"\r\n")}}):{name:t.name,value:n.replace(Tt,"\r\n")}}).get()}}) +var kt=/%20/g,jt=/#.*$/,Pt=/([?&])_=[^&]*/,Nt=/^(.*?):[ \t]*([^\r\n]*)$/gm,Dt=/^(?:about|app|app-storage|.+-extension|file|res|widget):$/,Mt=/^(?:GET|HEAD)$/,Lt=/^\/\//,It={},Ft={},qt="*/".concat("*"),Bt=ne.createElement("a") +Bt.href=xt.href,de.extend({active:0,lastModified:{},etag:{},ajaxSettings:{url:xt.href,type:"GET",isLocal:Dt.test(xt.protocol),global:!0,processData:!0,async:!0,contentType:"application/x-www-form-urlencoded; charset=UTF-8",accepts:{"*":qt,text:"text/plain",html:"text/html",xml:"application/xml, text/xml",json:"application/json, text/javascript"},contents:{xml:/\bxml\b/,html:/\bhtml/,json:/\bjson\b/},responseFields:{xml:"responseXML",text:"responseText",json:"responseJSON"},converters:{"* text":String,"text html":!0,"text json":JSON.parse,"text xml":de.parseXML},flatOptions:{url:!0,context:!0}},ajaxSetup:function(e,t){return t?J(J(e,de.ajaxSettings),t):J(de.ajaxSettings,e)},ajaxPrefilter:Y(It),ajaxTransport:Y(Ft),ajax:function(t,n){function r(t,n,r,a){var c,f,h,_,w,E=n +l||(l=!0,u&&e.clearTimeout(u),i=void 0,s=a||"",x.readyState=t>0?4:0,c=t>=200&&t<300||304===t,r&&(_=Z(d,x,r)),_=ee(d,_,x,c),c?(d.ifModified&&(w=x.getResponseHeader("Last-Modified"),w&&(de.lastModified[o]=w),(w=x.getResponseHeader("etag"))&&(de.etag[o]=w)),204===t||"HEAD"===d.type?E="nocontent":304===t?E="notmodified":(E=_.state,f=_.data,h=_.error,c=!h)):(h=E,!t&&E||(E="error",t<0&&(t=0))),x.status=t,x.statusText=(n||E)+"",c?y.resolveWith(m,[f,E,x]):y.rejectWith(m,[x,E,h]),x.statusCode(b),b=void 0,p&&g.trigger(c?"ajaxSuccess":"ajaxError",[x,d,c?f:h]),v.fireWith(m,[x,E]),p&&(g.trigger("ajaxComplete",[x,d]),--de.active||de.event.trigger("ajaxStop")))}"object"==typeof t&&(n=t,t=void 0),n=n||{} +var i,o,s,a,u,c,l,p,f,h,d=de.ajaxSetup({},n),m=d.context||d,g=d.context&&(m.nodeType||m.jquery)?de(m):de.event,y=de.Deferred(),v=de.Callbacks("once memory"),b=d.statusCode||{},_={},w={},E="canceled",x={readyState:0,getResponseHeader:function(e){var t +if(l){if(!a)for(a={};t=Nt.exec(s);)a[t[1].toLowerCase()]=t[2] +t=a[e.toLowerCase()]}return null==t?null:t},getAllResponseHeaders:function(){return l?s:null},setRequestHeader:function(e,t){return null==l&&(e=w[e.toLowerCase()]=w[e.toLowerCase()]||e,_[e]=t),this},overrideMimeType:function(e){return null==l&&(d.mimeType=e),this},statusCode:function(e){var t +if(e)if(l)x.always(e[x.status]) +else for(t in e)b[t]=[b[t],e[t]] +return this},abort:function(e){var t=e||E +return i&&i.abort(t),r(0,t),this}} +if(y.promise(x),d.url=((t||d.url||xt.href)+"").replace(Lt,xt.protocol+"//"),d.type=n.method||n.type||d.method||d.type,d.dataTypes=(d.dataType||"*").toLowerCase().match(Re)||[""],null==d.crossDomain){c=ne.createElement("a") +try{c.href=d.url,c.href=c.href,d.crossDomain=Bt.protocol+"//"+Bt.host!=c.protocol+"//"+c.host}catch(e){d.crossDomain=!0}}if(d.data&&d.processData&&"string"!=typeof d.data&&(d.data=de.param(d.data,d.traditional)),X(It,d,n,x),l)return x +p=de.event&&d.global,p&&0==de.active++&&de.event.trigger("ajaxStart"),d.type=d.type.toUpperCase(),d.hasContent=!Mt.test(d.type),o=d.url.replace(jt,""),d.hasContent?d.data&&d.processData&&0===(d.contentType||"").indexOf("application/x-www-form-urlencoded")&&(d.data=d.data.replace(kt,"+")):(h=d.url.slice(o.length),d.data&&(o+=(Ct.test(o)?"&":"?")+d.data,delete d.data),!1===d.cache&&(o=o.replace(Pt,"$1"),h=(Ct.test(o)?"&":"?")+"_="+Ot+++h),d.url=o+h),d.ifModified&&(de.lastModified[o]&&x.setRequestHeader("If-Modified-Since",de.lastModified[o]),de.etag[o]&&x.setRequestHeader("If-None-Match",de.etag[o])),(d.data&&d.hasContent&&!1!==d.contentType||n.contentType)&&x.setRequestHeader("Content-Type",d.contentType),x.setRequestHeader("Accept",d.dataTypes[0]&&d.accepts[d.dataTypes[0]]?d.accepts[d.dataTypes[0]]+("*"!==d.dataTypes[0]?", "+qt+"; q=0.01":""):d.accepts["*"]) +for(f in d.headers)x.setRequestHeader(f,d.headers[f]) +if(d.beforeSend&&(!1===d.beforeSend.call(m,x,d)||l))return x.abort() +if(E="abort",v.add(d.complete),x.done(d.success),x.fail(d.error),i=X(Ft,d,n,x)){if(x.readyState=1,p&&g.trigger("ajaxSend",[x,d]),l)return x +d.async&&d.timeout>0&&(u=e.setTimeout(function(){x.abort("timeout")},d.timeout)) +try{l=!1,i.send(_,r)}catch(e){if(l)throw e +r(-1,e)}}else r(-1,"No Transport") +return x},getJSON:function(e,t,n){return de.get(e,t,n,"json")},getScript:function(e,t){return de.get(e,void 0,t,"script")}}),de.each(["get","post"],function(e,t){de[t]=function(e,n,r,i){return de.isFunction(n)&&(i=i||r,r=n,n=void 0),de.ajax(de.extend({url:e,type:t,dataType:i,data:n,success:r},de.isPlainObject(e)&&e))}}),de._evalUrl=function(e){return de.ajax({url:e,type:"GET",dataType:"script",cache:!0,async:!1,global:!1,throws:!0})},de.fn.extend({wrapAll:function(e){var t +return this[0]&&(de.isFunction(e)&&(e=e.call(this[0])),t=de(e,this[0].ownerDocument).eq(0).clone(!0),this[0].parentNode&&t.insertBefore(this[0]),t.map(function(){for(var e=this;e.firstElementChild;)e=e.firstElementChild +return e}).append(this)),this},wrapInner:function(e){return de.isFunction(e)?this.each(function(t){de(this).wrapInner(e.call(this,t))}):this.each(function(){var t=de(this),n=t.contents() +n.length?n.wrapAll(e):t.append(e)})},wrap:function(e){var t=de.isFunction(e) +return this.each(function(n){de(this).wrapAll(t?e.call(this,n):e)})},unwrap:function(e){return this.parent(e).not("body").each(function(){de(this).replaceWith(this.childNodes)}),this}}),de.expr.pseudos.hidden=function(e){return!de.expr.pseudos.visible(e)},de.expr.pseudos.visible=function(e){return!!(e.offsetWidth||e.offsetHeight||e.getClientRects().length)},de.ajaxSettings.xhr=function(){try{return new e.XMLHttpRequest}catch(e){}} +var Ut={0:200,1223:204},zt=de.ajaxSettings.xhr() +he.cors=!!zt&&"withCredentials"in zt,he.ajax=zt=!!zt,de.ajaxTransport(function(t){var n,r +if(he.cors||zt&&!t.crossDomain)return{send:function(i,o){var s,a=t.xhr() +if(a.open(t.type,t.url,t.async,t.username,t.password),t.xhrFields)for(s in t.xhrFields)a[s]=t.xhrFields[s] +t.mimeType&&a.overrideMimeType&&a.overrideMimeType(t.mimeType),t.crossDomain||i["X-Requested-With"]||(i["X-Requested-With"]="XMLHttpRequest") +for(s in i)a.setRequestHeader(s,i[s]) +n=function(e){return function(){n&&(n=r=a.onload=a.onerror=a.onabort=a.onreadystatechange=null,"abort"===e?a.abort():"error"===e?"number"!=typeof a.status?o(0,"error"):o(a.status,a.statusText):o(Ut[a.status]||a.status,a.statusText,"text"!==(a.responseType||"text")||"string"!=typeof a.responseText?{binary:a.response}:{text:a.responseText},a.getAllResponseHeaders()))}},a.onload=n(),r=a.onerror=n("error"),void 0!==a.onabort?a.onabort=r:a.onreadystatechange=function(){4===a.readyState&&e.setTimeout(function(){n&&r()})},n=n("abort") +try{a.send(t.hasContent&&t.data||null)}catch(e){if(n)throw e}},abort:function(){n&&n()}}}),de.ajaxPrefilter(function(e){e.crossDomain&&(e.contents.script=!1)}),de.ajaxSetup({accepts:{script:"text/javascript, application/javascript, application/ecmascript, application/x-ecmascript"},contents:{script:/\b(?:java|ecma)script\b/},converters:{"text script":function(e){return de.globalEval(e),e}}}),de.ajaxPrefilter("script",function(e){void 0===e.cache&&(e.cache=!1),e.crossDomain&&(e.type="GET")}),de.ajaxTransport("script",function(e){if(e.crossDomain){var t,n +return{send:function(r,i){t=de(" + + + + + diff --git a/blog/2016-01-22-functional-templating-in-ember/demo/dist/robots.txt b/blog/2016-01-22-functional-templating-in-ember/demo/dist/robots.txt new file mode 100644 index 00000000..f5916452 --- /dev/null +++ b/blog/2016-01-22-functional-templating-in-ember/demo/dist/robots.txt @@ -0,0 +1,3 @@ +# http://www.robotstxt.org +User-agent: * +Disallow: diff --git a/blog/2016-01-22-functional-templating-in-ember/demo/ember-cli-build.js b/blog/2016-01-22-functional-templating-in-ember/demo/ember-cli-build.js new file mode 100644 index 00000000..e8659291 --- /dev/null +++ b/blog/2016-01-22-functional-templating-in-ember/demo/ember-cli-build.js @@ -0,0 +1,25 @@ +/* eslint-env node */ +'use strict'; + +const EmberApp = require('ember-cli/lib/broccoli/ember-app'); + +module.exports = function(defaults) { + let app = new EmberApp(defaults, { + storeConfigInMeta: false + }); + + // Use `app.import` to add additional libraries to the generated + // output files. + // + // If you need to use different assets in different + // environments, specify an object as the first parameter. That + // object's keys should be the environment name and the values + // should be the asset to use in that environment. + // + // If the library that you are including contains AMD or ES6 + // modules that you would like to import into your application + // please specify an object with the list of modules as keys + // along with the exports of each module as its value. + + return app.toTree(); +}; diff --git a/blog/2016-01-22-functional-templating-in-ember/demo/package.json b/blog/2016-01-22-functional-templating-in-ember/demo/package.json new file mode 100644 index 00000000..e772387e --- /dev/null +++ b/blog/2016-01-22-functional-templating-in-ember/demo/package.json @@ -0,0 +1,51 @@ +{ + "name": "file-upload-demo", + "version": "0.0.0", + "private": true, + "description": "Small description for file-upload-demo goes here", + "license": "MIT", + "author": "", + "directories": { + "doc": "doc", + "test": "tests" + }, + "repository": "", + "scripts": { + "build": "ember build", + "start": "ember server", + "test": "ember test" + }, + "devDependencies": { + "broccoli-asset-rev": "^2.4.5", + "ember-ajax": "^3.0.0", + "ember-cli": "~2.15.1", + "ember-cli-app-version": "^3.0.0", + "ember-cli-babel": "^6.3.0", + "ember-cli-dependency-checker": "^2.0.0", + "ember-cli-eslint": "^4.0.0", + "ember-cli-htmlbars": "^2.0.1", + "ember-cli-htmlbars-inline-precompile": "^1.0.0", + "ember-cli-inject-live-reload": "^1.4.1", + "ember-cli-qunit": "^4.0.0", + "ember-cli-shims": "^1.1.0", + "ember-cli-sri": "^2.1.0", + "ember-cli-uglify": "^1.2.0", + "ember-code-snippet": "^2.0.0", + "ember-export-application-global": "^2.0.0", + "ember-islands": "^1.3.0", + "ember-load-initializers": "^1.0.0", + "ember-resolver": "^4.0.0", + "ember-source": "~2.15.0", + "emberx-file-input": "git+https://github.com/thefrontside/emberx-file-input.git#3eb72ecd8b151b804a7718d9f241f752cc8c206e", + "emberx-file-reader": "git+https://github.com/thefrontside/emberx-file-reader.git#ceee2b6837c033e5668ee531db0526fca32ba767", + "emberx-xml-http-request": "git+https://github.com/thefrontside/emberx-xml-http-request.git#master", + "loader.js": "^4.2.3" + }, + "engines": { + "node": "^4.5 || 6.* || >= 7.*" + }, + "volta": { + "node": "6.17.1", + "yarn": "1.22.22" + } +} diff --git a/blog/2016-01-22-functional-templating-in-ember/demo/public/crossdomain.xml b/blog/2016-01-22-functional-templating-in-ember/demo/public/crossdomain.xml new file mode 100644 index 00000000..0c16a7a0 --- /dev/null +++ b/blog/2016-01-22-functional-templating-in-ember/demo/public/crossdomain.xml @@ -0,0 +1,15 @@ + + + + + + + + + + + diff --git a/blog/2016-01-22-functional-templating-in-ember/demo/public/robots.txt b/blog/2016-01-22-functional-templating-in-ember/demo/public/robots.txt new file mode 100644 index 00000000..f5916452 --- /dev/null +++ b/blog/2016-01-22-functional-templating-in-ember/demo/public/robots.txt @@ -0,0 +1,3 @@ +# http://www.robotstxt.org +User-agent: * +Disallow: diff --git a/blog/2016-01-22-functional-templating-in-ember/demo/testem.js b/blog/2016-01-22-functional-templating-in-ember/demo/testem.js new file mode 100644 index 00000000..a40a5305 --- /dev/null +++ b/blog/2016-01-22-functional-templating-in-ember/demo/testem.js @@ -0,0 +1,19 @@ +/* eslint-env node */ +module.exports = { + test_page: 'tests/index.html?hidepassed', + disable_watching: true, + launch_in_ci: [ + 'Chrome' + ], + launch_in_dev: [ + 'Chrome' + ], + browser_args: { + Chrome: [ + '--disable-gpu', + '--headless', + '--remote-debugging-port=9222', + '--window-size=1440,900' + ] + } +}; diff --git a/blog/2016-01-22-functional-templating-in-ember/demo/tests/.eslintrc.js b/blog/2016-01-22-functional-templating-in-ember/demo/tests/.eslintrc.js new file mode 100644 index 00000000..fbf25552 --- /dev/null +++ b/blog/2016-01-22-functional-templating-in-ember/demo/tests/.eslintrc.js @@ -0,0 +1,5 @@ +module.exports = { + env: { + embertest: true + } +}; diff --git a/blog/2016-01-22-functional-templating-in-ember/demo/tests/.jshintrc b/blog/2016-01-22-functional-templating-in-ember/demo/tests/.jshintrc new file mode 100644 index 00000000..6ec0b7c1 --- /dev/null +++ b/blog/2016-01-22-functional-templating-in-ember/demo/tests/.jshintrc @@ -0,0 +1,52 @@ +{ + "predef": [ + "document", + "window", + "location", + "setTimeout", + "$", + "-Promise", + "define", + "console", + "visit", + "exists", + "fillIn", + "click", + "keyEvent", + "triggerEvent", + "find", + "findWithAssert", + "wait", + "DS", + "andThen", + "currentURL", + "currentPath", + "currentRouteName" + ], + "node": false, + "browser": false, + "boss": true, + "curly": true, + "debug": false, + "devel": false, + "eqeqeq": true, + "evil": true, + "forin": false, + "immed": false, + "laxbreak": false, + "newcap": true, + "noarg": true, + "noempty": false, + "nonew": false, + "nomen": false, + "onevar": false, + "plusplus": false, + "regexp": false, + "undef": true, + "sub": true, + "strict": false, + "white": false, + "eqnull": true, + "esnext": true, + "unused": true +} diff --git a/blog/2016-01-22-functional-templating-in-ember/demo/tests/helpers/destroy-app.js b/blog/2016-01-22-functional-templating-in-ember/demo/tests/helpers/destroy-app.js new file mode 100644 index 00000000..c3d4d1ab --- /dev/null +++ b/blog/2016-01-22-functional-templating-in-ember/demo/tests/helpers/destroy-app.js @@ -0,0 +1,5 @@ +import Ember from 'ember'; + +export default function destroyApp(application) { + Ember.run(application, 'destroy'); +} diff --git a/blog/2016-01-22-functional-templating-in-ember/demo/tests/helpers/module-for-acceptance.js b/blog/2016-01-22-functional-templating-in-ember/demo/tests/helpers/module-for-acceptance.js new file mode 100644 index 00000000..5a1a583a --- /dev/null +++ b/blog/2016-01-22-functional-templating-in-ember/demo/tests/helpers/module-for-acceptance.js @@ -0,0 +1,23 @@ +import { module } from 'qunit'; +import Ember from 'ember'; +import startApp from '../helpers/start-app'; +import destroyApp from '../helpers/destroy-app'; + +const { RSVP: { resolve } } = Ember; + +export default function(name, options = {}) { + module(name, { + beforeEach() { + this.application = startApp(); + + if (options.beforeEach) { + return options.beforeEach.apply(this, arguments); + } + }, + + afterEach() { + let afterEach = options.afterEach && options.afterEach.apply(this, arguments); + return resolve(afterEach).then(() => destroyApp(this.application)); + } + }); +} diff --git a/blog/2016-01-22-functional-templating-in-ember/demo/tests/helpers/resolver.js b/blog/2016-01-22-functional-templating-in-ember/demo/tests/helpers/resolver.js new file mode 100644 index 00000000..b208d38d --- /dev/null +++ b/blog/2016-01-22-functional-templating-in-ember/demo/tests/helpers/resolver.js @@ -0,0 +1,11 @@ +import Resolver from '../../resolver'; +import config from '../../config/environment'; + +const resolver = Resolver.create(); + +resolver.namespace = { + modulePrefix: config.modulePrefix, + podModulePrefix: config.podModulePrefix +}; + +export default resolver; diff --git a/blog/2016-01-22-functional-templating-in-ember/demo/tests/helpers/start-app.js b/blog/2016-01-22-functional-templating-in-ember/demo/tests/helpers/start-app.js new file mode 100644 index 00000000..9a605eb8 --- /dev/null +++ b/blog/2016-01-22-functional-templating-in-ember/demo/tests/helpers/start-app.js @@ -0,0 +1,15 @@ +import Ember from 'ember'; +import Application from '../../app'; +import config from '../../config/environment'; + +export default function startApp(attrs) { + let attributes = Ember.merge({}, config.APP); + attributes = Ember.merge(attributes, attrs); // use defaults, but you can override; + + return Ember.run(() => { + let application = Application.create(attributes); + application.setupForTesting(); + application.injectTestHelpers(); + return application; + }); +} diff --git a/blog/2016-01-22-functional-templating-in-ember/demo/tests/index.html b/blog/2016-01-22-functional-templating-in-ember/demo/tests/index.html new file mode 100644 index 00000000..c88f82fe --- /dev/null +++ b/blog/2016-01-22-functional-templating-in-ember/demo/tests/index.html @@ -0,0 +1,33 @@ + + + + + + FileUploadDemo Tests + + + + {{content-for "head"}} + {{content-for "test-head"}} + + + + + + {{content-for "head-footer"}} + {{content-for "test-head-footer"}} + + + {{content-for "body"}} + {{content-for "test-body"}} + + + + + + + + {{content-for "body-footer"}} + {{content-for "test-body-footer"}} + + diff --git a/blog/2016-01-22-functional-templating-in-ember/demo/tests/integration/.gitkeep b/blog/2016-01-22-functional-templating-in-ember/demo/tests/integration/.gitkeep new file mode 100644 index 00000000..e69de29b diff --git a/blog/2016-01-22-functional-templating-in-ember/demo/tests/integration/components/demo-pane-test.js b/blog/2016-01-22-functional-templating-in-ember/demo/tests/integration/components/demo-pane-test.js new file mode 100644 index 00000000..5ece7d3a --- /dev/null +++ b/blog/2016-01-22-functional-templating-in-ember/demo/tests/integration/components/demo-pane-test.js @@ -0,0 +1,17 @@ +import { moduleForComponent, test } from 'ember-qunit'; +import hbs from 'htmlbars-inline-precompile'; + +moduleForComponent('demo-pane', 'Integration | Component | demo pane', { + integration: true +}); + +test('it renders', function(assert) { + + // Set any properties with this.set('myProperty', 'value'); + // Handle any actions with this.on('myAction', function(val) { ... });" + EOL + EOL + + + this.render(hbs`{{demo-pane name="file-chooser-only" title="Choose Files"}}`); + + assert.equal(this.$('.demo-pane__title').text().trim(), 'Demo: Choose Files'); + assert.equal(this.$('.demo-pane__content').text().trim(), '⇧ Choose Files'); +}); diff --git a/blog/2016-01-22-functional-templating-in-ember/demo/tests/test-helper.js b/blog/2016-01-22-functional-templating-in-ember/demo/tests/test-helper.js new file mode 100644 index 00000000..f219659e --- /dev/null +++ b/blog/2016-01-22-functional-templating-in-ember/demo/tests/test-helper.js @@ -0,0 +1,8 @@ +import resolver from './helpers/resolver'; +import { + setResolver +} from 'ember-qunit'; +import { start } from 'ember-cli-qunit'; + +setResolver(resolver); +start(); diff --git a/blog/2016-01-22-functional-templating-in-ember/demo/tests/unit/.gitkeep b/blog/2016-01-22-functional-templating-in-ember/demo/tests/unit/.gitkeep new file mode 100644 index 00000000..e69de29b diff --git a/blog/2016-01-22-functional-templating-in-ember/demo/tests/unit/routes/file-chooser-only-test.js b/blog/2016-01-22-functional-templating-in-ember/demo/tests/unit/routes/file-chooser-only-test.js new file mode 100644 index 00000000..d68ef530 --- /dev/null +++ b/blog/2016-01-22-functional-templating-in-ember/demo/tests/unit/routes/file-chooser-only-test.js @@ -0,0 +1,11 @@ +import { moduleFor, test } from 'ember-qunit'; + +moduleFor('route:file-chooser-only', 'Unit | Route | file chooser only', { + // Specify the other units that are required for this test. + // needs: ['controller:foo'] +}); + +test('it exists', function(assert) { + let route = this.subject(); + assert.ok(route); +}); diff --git a/blog/2016-01-22-functional-templating-in-ember/demo/tests/unit/routes/full-demo-test.js b/blog/2016-01-22-functional-templating-in-ember/demo/tests/unit/routes/full-demo-test.js new file mode 100644 index 00000000..5ca8f4ac --- /dev/null +++ b/blog/2016-01-22-functional-templating-in-ember/demo/tests/unit/routes/full-demo-test.js @@ -0,0 +1,11 @@ +import { moduleFor, test } from 'ember-qunit'; + +moduleFor('route:full-demo', 'Unit | Route | full demo', { + // Specify the other units that are required for this test. + // needs: ['controller:foo'] +}); + +test('it exists', function(assert) { + let route = this.subject(); + assert.ok(route); +}); diff --git a/blog/2016-01-22-functional-templating-in-ember/demo/vendor/.gitkeep b/blog/2016-01-22-functional-templating-in-ember/demo/vendor/.gitkeep new file mode 100644 index 00000000..e69de29b diff --git a/blog/2016-01-22-functional-templating-in-ember/demo/yarn.lock b/blog/2016-01-22-functional-templating-in-ember/demo/yarn.lock new file mode 100644 index 00000000..3746f92e --- /dev/null +++ b/blog/2016-01-22-functional-templating-in-ember/demo/yarn.lock @@ -0,0 +1,5882 @@ +# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. +# yarn lockfile v1 + + +"@glimmer/compiler@^0.25.3": + version "0.25.3" + resolved "https://registry.yarnpkg.com/@glimmer/compiler/-/compiler-0.25.3.tgz#25eb06394f3ba1c1fae5af25c9cf7deb2c11ef4e" + dependencies: + "@glimmer/interfaces" "^0.25.3" + "@glimmer/syntax" "^0.25.3" + "@glimmer/util" "^0.25.3" + "@glimmer/wire-format" "^0.25.3" + simple-html-tokenizer "^0.3.0" + +"@glimmer/di@^0.2.0": + version "0.2.0" + resolved "https://registry.yarnpkg.com/@glimmer/di/-/di-0.2.0.tgz#73bfd4a6ee4148a80bf092e8a5d29bcac9d4ce7e" + +"@glimmer/interfaces@^0.25.3": + version "0.25.3" + resolved "https://registry.yarnpkg.com/@glimmer/interfaces/-/interfaces-0.25.3.tgz#8c460b28ad5a17eaa1712e6aa7b8ebb49738c38f" + dependencies: + "@glimmer/wire-format" "^0.25.3" + +"@glimmer/node@^0.25.3": + version "0.25.3" + resolved "https://registry.yarnpkg.com/@glimmer/node/-/node-0.25.3.tgz#301828e8455be141d5384b01980ed9be02984059" + dependencies: + "@glimmer/runtime" "^0.25.3" + simple-dom "^0.3.0" + +"@glimmer/object-reference@^0.25.3": + version "0.25.3" + resolved "https://registry.yarnpkg.com/@glimmer/object-reference/-/object-reference-0.25.3.tgz#e0d1fa874f912e7d1232d487fcd2096e6b31b620" + dependencies: + "@glimmer/reference" "^0.25.3" + "@glimmer/util" "^0.25.3" + +"@glimmer/object@^0.25.3": + version "0.25.3" + resolved "https://registry.yarnpkg.com/@glimmer/object/-/object-0.25.3.tgz#451eb208dadba1ede9c0c038a90dfe32637493fe" + dependencies: + "@glimmer/object-reference" "^0.25.3" + "@glimmer/util" "^0.25.3" + +"@glimmer/reference@^0.25.3": + version "0.25.3" + resolved "https://registry.yarnpkg.com/@glimmer/reference/-/reference-0.25.3.tgz#a09ddc397bee0223de73ea5044a304a30935104f" + dependencies: + "@glimmer/util" "^0.25.3" + +"@glimmer/resolver@^0.4.1": + version "0.4.1" + resolved "https://registry.yarnpkg.com/@glimmer/resolver/-/resolver-0.4.1.tgz#cd9644572c556e7e799de1cf8eff2b999cf5b878" + dependencies: + "@glimmer/di" "^0.2.0" + +"@glimmer/runtime@^0.25.3": + version "0.25.3" + resolved "https://registry.yarnpkg.com/@glimmer/runtime/-/runtime-0.25.3.tgz#ae2101a1e4de3330d08f20806c18327dbfa86d78" + dependencies: + "@glimmer/interfaces" "^0.25.3" + "@glimmer/object" "^0.25.3" + "@glimmer/object-reference" "^0.25.3" + "@glimmer/reference" "^0.25.3" + "@glimmer/util" "^0.25.3" + "@glimmer/wire-format" "^0.25.3" + +"@glimmer/syntax@^0.25.3": + version "0.25.3" + resolved "https://registry.yarnpkg.com/@glimmer/syntax/-/syntax-0.25.3.tgz#b3f8a59bee616fd600301d778de3b649bf77036e" + dependencies: + "@glimmer/interfaces" "^0.25.3" + "@glimmer/util" "^0.25.3" + handlebars "^4.0.6" + simple-html-tokenizer "^0.3.0" + +"@glimmer/util@^0.25.3": + version "0.25.3" + resolved "https://registry.yarnpkg.com/@glimmer/util/-/util-0.25.3.tgz#7cedf72947137b519658c8be34d0d5965cebe3a1" + +"@glimmer/wire-format@^0.25.3": + version "0.25.3" + resolved "https://registry.yarnpkg.com/@glimmer/wire-format/-/wire-format-0.25.3.tgz#046692b3a26a30a498712266cd0bdb47d7710f37" + dependencies: + "@glimmer/util" "^0.25.3" + +abbrev@1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/abbrev/-/abbrev-1.1.1.tgz#f8f2c887ad10bf67f634f005b6987fed3179aac8" + +accepts@1.3.3: + version "1.3.3" + resolved "https://registry.yarnpkg.com/accepts/-/accepts-1.3.3.tgz#c3ca7434938648c3e0d9c1e328dd68b622c284ca" + dependencies: + mime-types "~2.1.11" + negotiator "0.6.1" + +accepts@~1.3.4: + version "1.3.4" + resolved "https://registry.yarnpkg.com/accepts/-/accepts-1.3.4.tgz#86246758c7dd6d21a6474ff084a4740ec05eb21f" + dependencies: + mime-types "~2.1.16" + negotiator "0.6.1" + +acorn-jsx@^3.0.0: + version "3.0.1" + resolved "https://registry.yarnpkg.com/acorn-jsx/-/acorn-jsx-3.0.1.tgz#afdf9488fb1ecefc8348f6fb22f464e32a58b36b" + dependencies: + acorn "^3.0.4" + +acorn@^3.0.4: + version "3.3.0" + resolved "https://registry.yarnpkg.com/acorn/-/acorn-3.3.0.tgz#45e37fb39e8da3f25baee3ff5369e2bb5f22017a" + +acorn@^4.0.3: + version "4.0.13" + resolved "https://registry.yarnpkg.com/acorn/-/acorn-4.0.13.tgz#105495ae5361d697bd195c825192e1ad7f253787" + +acorn@^5.1.1: + version "5.1.2" + resolved "https://registry.yarnpkg.com/acorn/-/acorn-5.1.2.tgz#911cb53e036807cf0fa778dc5d370fbd864246d7" + +after@0.8.1: + version "0.8.1" + resolved "https://registry.yarnpkg.com/after/-/after-0.8.1.tgz#ab5d4fb883f596816d3515f8f791c0af486dd627" + +ajv-keywords@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/ajv-keywords/-/ajv-keywords-2.1.0.tgz#a296e17f7bfae7c1ce4f7e0de53d29cb32162df0" + +ajv@^4.9.1: + version "4.11.8" + resolved "https://registry.yarnpkg.com/ajv/-/ajv-4.11.8.tgz#82ffb02b29e662ae53bdc20af15947706739c536" + dependencies: + co "^4.6.0" + json-stable-stringify "^1.0.1" + +ajv@^5.2.0, ajv@^5.2.3: + version "5.2.3" + resolved "https://registry.yarnpkg.com/ajv/-/ajv-5.2.3.tgz#c06f598778c44c6b161abafe3466b81ad1814ed2" + dependencies: + co "^4.6.0" + fast-deep-equal "^1.0.0" + json-schema-traverse "^0.3.0" + json-stable-stringify "^1.0.1" + +align-text@^0.1.1, align-text@^0.1.3: + version "0.1.4" + resolved "https://registry.yarnpkg.com/align-text/-/align-text-0.1.4.tgz#0cd90a561093f35d0a99256c22b7069433fad117" + dependencies: + kind-of "^3.0.2" + longest "^1.0.1" + repeat-string "^1.5.2" + +alter@~0.2.0: + version "0.2.0" + resolved "https://registry.yarnpkg.com/alter/-/alter-0.2.0.tgz#c7588808617572034aae62480af26b1d4d1cb3cd" + dependencies: + stable "~0.1.3" + +amd-name-resolver@0.0.7: + version "0.0.7" + resolved "https://registry.yarnpkg.com/amd-name-resolver/-/amd-name-resolver-0.0.7.tgz#814301adfe8a2f109f6e84d5e935196efb669615" + dependencies: + ensure-posix-path "^1.0.1" + +amdefine@>=0.0.4: + version "1.0.1" + resolved "https://registry.yarnpkg.com/amdefine/-/amdefine-1.0.1.tgz#4a5282ac164729e93619bcfd3ad151f817ce91f5" + +ansi-escapes@^1.1.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/ansi-escapes/-/ansi-escapes-1.4.0.tgz#d3a8a83b319aa67793662b13e761c7911422306e" + +ansi-escapes@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/ansi-escapes/-/ansi-escapes-3.0.0.tgz#ec3e8b4e9f8064fc02c3ac9b65f1c275bda8ef92" + +ansi-regex@^0.2.0, ansi-regex@^0.2.1: + version "0.2.1" + resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-0.2.1.tgz#0d8e946967a3d8143f93e24e298525fc1b2235f9" + +ansi-regex@^2.0.0: + version "2.1.1" + resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-2.1.1.tgz#c3b33ab5ee360d86e0e628f0468ae7ef27d654df" + +ansi-regex@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-3.0.0.tgz#ed0317c322064f79466c02966bddb605ab37d998" + +ansi-styles@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-1.1.0.tgz#eaecbf66cd706882760b2f4691582b8f55d7a7de" + +ansi-styles@^2.2.1: + version "2.2.1" + resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-2.2.1.tgz#b432dd3358b634cf75e1e4664368240533c1ddbe" + +ansi-styles@^3.0.0, ansi-styles@^3.1.0: + version "3.2.0" + resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-3.2.0.tgz#c159b8d5be0f9e5a6f346dab94f16ce022161b88" + dependencies: + color-convert "^1.9.0" + +ansicolors@~0.2.1: + version "0.2.1" + resolved "https://registry.yarnpkg.com/ansicolors/-/ansicolors-0.2.1.tgz#be089599097b74a5c9c4a84a0cdbcdb62bd87aef" + +anymatch@^1.3.0: + version "1.3.2" + resolved "https://registry.yarnpkg.com/anymatch/-/anymatch-1.3.2.tgz#553dcb8f91e3c889845dfdba34c77721b90b9d7a" + dependencies: + micromatch "^2.1.5" + normalize-path "^2.0.0" + +aot-test-generators@^0.1.0: + version "0.1.0" + resolved "https://registry.yarnpkg.com/aot-test-generators/-/aot-test-generators-0.1.0.tgz#43f0f615f97cb298d7919c1b0b4e6b7310b03cd0" + dependencies: + jsesc "^2.5.0" + +aproba@^1.0.3: + version "1.2.0" + resolved "https://registry.yarnpkg.com/aproba/-/aproba-1.2.0.tgz#6802e6264efd18c790a1b0d517f0f2627bf2c94a" + +are-we-there-yet@~1.1.2: + version "1.1.4" + resolved "https://registry.yarnpkg.com/are-we-there-yet/-/are-we-there-yet-1.1.4.tgz#bb5dca382bb94f05e15194373d16fd3ba1ca110d" + dependencies: + delegates "^1.0.0" + readable-stream "^2.0.6" + +argparse@^1.0.7: + version "1.0.9" + resolved "https://registry.yarnpkg.com/argparse/-/argparse-1.0.9.tgz#73d83bc263f86e97f8cc4f6bae1b0e90a7d22c86" + dependencies: + sprintf-js "~1.0.2" + +arr-diff@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/arr-diff/-/arr-diff-2.0.0.tgz#8f3b827f955a8bd669697e4a4256ac3ceae356cf" + dependencies: + arr-flatten "^1.0.1" + +arr-flatten@^1.0.1: + version "1.1.0" + resolved "https://registry.yarnpkg.com/arr-flatten/-/arr-flatten-1.1.0.tgz#36048bbff4e7b47e136644316c99669ea5ae91f1" + +array-equal@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/array-equal/-/array-equal-1.0.0.tgz#8c2a5ef2472fd9ea742b04c77a75093ba2757c93" + +array-flatten@1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/array-flatten/-/array-flatten-1.1.1.tgz#9a5f699051b1e7073328f2a008968b64ea2955d2" + +array-to-error@^1.0.0: + version "1.1.1" + resolved "https://registry.yarnpkg.com/array-to-error/-/array-to-error-1.1.1.tgz#d68812926d14097a205579a667eeaf1856a44c07" + dependencies: + array-to-sentence "^1.1.0" + +array-to-sentence@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/array-to-sentence/-/array-to-sentence-1.1.0.tgz#c804956dafa53232495b205a9452753a258d39fc" + +array-union@^1.0.1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/array-union/-/array-union-1.0.2.tgz#9a34410e4f4e3da23dea375be5be70f24778ec39" + dependencies: + array-uniq "^1.0.1" + +array-uniq@^1.0.1: + version "1.0.3" + resolved "https://registry.yarnpkg.com/array-uniq/-/array-uniq-1.0.3.tgz#af6ac877a25cc7f74e058894753858dfdb24fdb6" + +array-unique@^0.2.1: + version "0.2.1" + resolved "https://registry.yarnpkg.com/array-unique/-/array-unique-0.2.1.tgz#a1d97ccafcbc2625cc70fadceb36a50c58b01a53" + +arraybuffer.slice@0.0.6: + version "0.0.6" + resolved "https://registry.yarnpkg.com/arraybuffer.slice/-/arraybuffer.slice-0.0.6.tgz#f33b2159f0532a3f3107a272c0ccfbd1ad2979ca" + +arrify@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/arrify/-/arrify-1.0.1.tgz#898508da2226f380df904728456849c1501a4b0d" + +asn1@~0.2.3: + version "0.2.3" + resolved "https://registry.yarnpkg.com/asn1/-/asn1-0.2.3.tgz#dac8787713c9966849fc8180777ebe9c1ddf3b86" + +assert-plus@1.0.0, assert-plus@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/assert-plus/-/assert-plus-1.0.0.tgz#f12e0f3c5d77b0b1cdd9146942e4e96c1e4dd525" + +assert-plus@^0.2.0: + version "0.2.0" + resolved "https://registry.yarnpkg.com/assert-plus/-/assert-plus-0.2.0.tgz#d74e1b87e7affc0db8aadb7021f3fe48101ab234" + +ast-traverse@~0.1.1: + version "0.1.1" + resolved "https://registry.yarnpkg.com/ast-traverse/-/ast-traverse-0.1.1.tgz#69cf2b8386f19dcda1bb1e05d68fe359d8897de6" + +ast-types@0.8.12: + version "0.8.12" + resolved "https://registry.yarnpkg.com/ast-types/-/ast-types-0.8.12.tgz#a0d90e4351bb887716c83fd637ebf818af4adfcc" + +ast-types@0.8.15: + version "0.8.15" + resolved "https://registry.yarnpkg.com/ast-types/-/ast-types-0.8.15.tgz#8eef0827f04dff0ec8857ba925abe3fea6194e52" + +ast-types@0.9.6: + version "0.9.6" + resolved "https://registry.yarnpkg.com/ast-types/-/ast-types-0.9.6.tgz#102c9e9e9005d3e7e3829bf0c4fa24ee862ee9b9" + +async-disk-cache@^1.2.1: + version "1.3.3" + resolved "https://registry.yarnpkg.com/async-disk-cache/-/async-disk-cache-1.3.3.tgz#6040486660b370e4051cd9fa9fee275e1fae3728" + dependencies: + debug "^2.1.3" + heimdalljs "^0.2.3" + istextorbinary "2.1.0" + mkdirp "^0.5.0" + rimraf "^2.5.3" + rsvp "^3.0.18" + username-sync "1.0.1" + +async-each@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/async-each/-/async-each-1.0.1.tgz#19d386a1d9edc6e7c1c85d388aedbcc56d33602d" + +async-promise-queue@^1.0.3: + version "1.0.4" + resolved "https://registry.yarnpkg.com/async-promise-queue/-/async-promise-queue-1.0.4.tgz#308baafbc74aff66a0bb6e7f4a18d4fe8434440c" + dependencies: + async "^2.4.1" + debug "^2.6.8" + +async@^1.4.0, async@^1.5.2: + version "1.5.2" + resolved "https://registry.yarnpkg.com/async/-/async-1.5.2.tgz#ec6a61ae56480c0c3cb241c95618e20892f9672a" + +async@^2.4.1: + version "2.5.0" + resolved "https://registry.yarnpkg.com/async/-/async-2.5.0.tgz#843190fd6b7357a0b9e1c956edddd5ec8462b54d" + dependencies: + lodash "^4.14.0" + +async@~0.2.9: + version "0.2.10" + resolved "https://registry.yarnpkg.com/async/-/async-0.2.10.tgz#b6bbe0b0674b9d719708ca38de8c237cb526c3d1" + +asynckit@^0.4.0: + version "0.4.0" + resolved "https://registry.yarnpkg.com/asynckit/-/asynckit-0.4.0.tgz#c79ed97f7f34cb8f2ba1bc9790bcc366474b4b79" + +aws-sign2@~0.6.0: + version "0.6.0" + resolved "https://registry.yarnpkg.com/aws-sign2/-/aws-sign2-0.6.0.tgz#14342dd38dbcc94d0e5b87d763cd63612c0e794f" + +aws4@^1.2.1: + version "1.6.0" + resolved "https://registry.yarnpkg.com/aws4/-/aws4-1.6.0.tgz#83ef5ca860b2b32e4a0deedee8c771b9db57471e" + +babel-code-frame@^6.22.0, babel-code-frame@^6.26.0: + version "6.26.0" + resolved "https://registry.yarnpkg.com/babel-code-frame/-/babel-code-frame-6.26.0.tgz#63fd43f7dc1e3bb7ce35947db8fe369a3f58c74b" + dependencies: + chalk "^1.1.3" + esutils "^2.0.2" + js-tokens "^3.0.2" + +babel-core@^5.0.0: + version "5.8.38" + resolved "https://registry.yarnpkg.com/babel-core/-/babel-core-5.8.38.tgz#1fcaee79d7e61b750b00b8e54f6dfc9d0af86558" + dependencies: + babel-plugin-constant-folding "^1.0.1" + babel-plugin-dead-code-elimination "^1.0.2" + babel-plugin-eval "^1.0.1" + babel-plugin-inline-environment-variables "^1.0.1" + babel-plugin-jscript "^1.0.4" + babel-plugin-member-expression-literals "^1.0.1" + babel-plugin-property-literals "^1.0.1" + babel-plugin-proto-to-assign "^1.0.3" + babel-plugin-react-constant-elements "^1.0.3" + babel-plugin-react-display-name "^1.0.3" + babel-plugin-remove-console "^1.0.1" + babel-plugin-remove-debugger "^1.0.1" + babel-plugin-runtime "^1.0.7" + babel-plugin-undeclared-variables-check "^1.0.2" + babel-plugin-undefined-to-void "^1.1.6" + babylon "^5.8.38" + bluebird "^2.9.33" + chalk "^1.0.0" + convert-source-map "^1.1.0" + core-js "^1.0.0" + debug "^2.1.1" + detect-indent "^3.0.0" + esutils "^2.0.0" + fs-readdir-recursive "^0.1.0" + globals "^6.4.0" + home-or-tmp "^1.0.0" + is-integer "^1.0.4" + js-tokens "1.0.1" + json5 "^0.4.0" + lodash "^3.10.0" + minimatch "^2.0.3" + output-file-sync "^1.1.0" + path-exists "^1.0.0" + path-is-absolute "^1.0.0" + private "^0.1.6" + regenerator "0.8.40" + regexpu "^1.3.0" + repeating "^1.1.2" + resolve "^1.1.6" + shebang-regex "^1.0.0" + slash "^1.0.0" + source-map "^0.5.0" + source-map-support "^0.2.10" + to-fast-properties "^1.0.0" + trim-right "^1.0.0" + try-resolve "^1.0.0" + +babel-core@^6.14.0, babel-core@^6.26.0: + version "6.26.0" + resolved "https://registry.yarnpkg.com/babel-core/-/babel-core-6.26.0.tgz#af32f78b31a6fcef119c87b0fd8d9753f03a0bb8" + dependencies: + babel-code-frame "^6.26.0" + babel-generator "^6.26.0" + babel-helpers "^6.24.1" + babel-messages "^6.23.0" + babel-register "^6.26.0" + babel-runtime "^6.26.0" + babel-template "^6.26.0" + babel-traverse "^6.26.0" + babel-types "^6.26.0" + babylon "^6.18.0" + convert-source-map "^1.5.0" + debug "^2.6.8" + json5 "^0.5.1" + lodash "^4.17.4" + minimatch "^3.0.4" + path-is-absolute "^1.0.1" + private "^0.1.7" + slash "^1.0.0" + source-map "^0.5.6" + +babel-generator@^6.26.0: + version "6.26.0" + resolved "https://registry.yarnpkg.com/babel-generator/-/babel-generator-6.26.0.tgz#ac1ae20070b79f6e3ca1d3269613053774f20dc5" + dependencies: + babel-messages "^6.23.0" + babel-runtime "^6.26.0" + babel-types "^6.26.0" + detect-indent "^4.0.0" + jsesc "^1.3.0" + lodash "^4.17.4" + source-map "^0.5.6" + trim-right "^1.0.1" + +babel-helper-builder-binary-assignment-operator-visitor@^6.24.1: + version "6.24.1" + resolved "https://registry.yarnpkg.com/babel-helper-builder-binary-assignment-operator-visitor/-/babel-helper-builder-binary-assignment-operator-visitor-6.24.1.tgz#cce4517ada356f4220bcae8a02c2b346f9a56664" + dependencies: + babel-helper-explode-assignable-expression "^6.24.1" + babel-runtime "^6.22.0" + babel-types "^6.24.1" + +babel-helper-call-delegate@^6.24.1: + version "6.24.1" + resolved "https://registry.yarnpkg.com/babel-helper-call-delegate/-/babel-helper-call-delegate-6.24.1.tgz#ece6aacddc76e41c3461f88bfc575bd0daa2df8d" + dependencies: + babel-helper-hoist-variables "^6.24.1" + babel-runtime "^6.22.0" + babel-traverse "^6.24.1" + babel-types "^6.24.1" + +babel-helper-define-map@^6.24.1: + version "6.26.0" + resolved "https://registry.yarnpkg.com/babel-helper-define-map/-/babel-helper-define-map-6.26.0.tgz#a5f56dab41a25f97ecb498c7ebaca9819f95be5f" + dependencies: + babel-helper-function-name "^6.24.1" + babel-runtime "^6.26.0" + babel-types "^6.26.0" + lodash "^4.17.4" + +babel-helper-explode-assignable-expression@^6.24.1: + version "6.24.1" + resolved "https://registry.yarnpkg.com/babel-helper-explode-assignable-expression/-/babel-helper-explode-assignable-expression-6.24.1.tgz#f25b82cf7dc10433c55f70592d5746400ac22caa" + dependencies: + babel-runtime "^6.22.0" + babel-traverse "^6.24.1" + babel-types "^6.24.1" + +babel-helper-function-name@^6.24.1: + version "6.24.1" + resolved "https://registry.yarnpkg.com/babel-helper-function-name/-/babel-helper-function-name-6.24.1.tgz#d3475b8c03ed98242a25b48351ab18399d3580a9" + dependencies: + babel-helper-get-function-arity "^6.24.1" + babel-runtime "^6.22.0" + babel-template "^6.24.1" + babel-traverse "^6.24.1" + babel-types "^6.24.1" + +babel-helper-get-function-arity@^6.24.1: + version "6.24.1" + resolved "https://registry.yarnpkg.com/babel-helper-get-function-arity/-/babel-helper-get-function-arity-6.24.1.tgz#8f7782aa93407c41d3aa50908f89b031b1b6853d" + dependencies: + babel-runtime "^6.22.0" + babel-types "^6.24.1" + +babel-helper-hoist-variables@^6.24.1: + version "6.24.1" + resolved "https://registry.yarnpkg.com/babel-helper-hoist-variables/-/babel-helper-hoist-variables-6.24.1.tgz#1ecb27689c9d25513eadbc9914a73f5408be7a76" + dependencies: + babel-runtime "^6.22.0" + babel-types "^6.24.1" + +babel-helper-optimise-call-expression@^6.24.1: + version "6.24.1" + resolved "https://registry.yarnpkg.com/babel-helper-optimise-call-expression/-/babel-helper-optimise-call-expression-6.24.1.tgz#f7a13427ba9f73f8f4fa993c54a97882d1244257" + dependencies: + babel-runtime "^6.22.0" + babel-types "^6.24.1" + +babel-helper-regex@^6.24.1: + version "6.26.0" + resolved "https://registry.yarnpkg.com/babel-helper-regex/-/babel-helper-regex-6.26.0.tgz#325c59f902f82f24b74faceed0363954f6495e72" + dependencies: + babel-runtime "^6.26.0" + babel-types "^6.26.0" + lodash "^4.17.4" + +babel-helper-remap-async-to-generator@^6.24.1: + version "6.24.1" + resolved "https://registry.yarnpkg.com/babel-helper-remap-async-to-generator/-/babel-helper-remap-async-to-generator-6.24.1.tgz#5ec581827ad723fecdd381f1c928390676e4551b" + dependencies: + babel-helper-function-name "^6.24.1" + babel-runtime "^6.22.0" + babel-template "^6.24.1" + babel-traverse "^6.24.1" + babel-types "^6.24.1" + +babel-helper-replace-supers@^6.24.1: + version "6.24.1" + resolved "https://registry.yarnpkg.com/babel-helper-replace-supers/-/babel-helper-replace-supers-6.24.1.tgz#bf6dbfe43938d17369a213ca8a8bf74b6a90ab1a" + dependencies: + babel-helper-optimise-call-expression "^6.24.1" + babel-messages "^6.23.0" + babel-runtime "^6.22.0" + babel-template "^6.24.1" + babel-traverse "^6.24.1" + babel-types "^6.24.1" + +babel-helpers@^6.24.1: + version "6.24.1" + resolved "https://registry.yarnpkg.com/babel-helpers/-/babel-helpers-6.24.1.tgz#3471de9caec388e5c850e597e58a26ddf37602b2" + dependencies: + babel-runtime "^6.22.0" + babel-template "^6.24.1" + +babel-messages@^6.23.0: + version "6.23.0" + resolved "https://registry.yarnpkg.com/babel-messages/-/babel-messages-6.23.0.tgz#f3cdf4703858035b2a2951c6ec5edf6c62f2630e" + dependencies: + babel-runtime "^6.22.0" + +babel-plugin-check-es2015-constants@^6.22.0: + version "6.22.0" + resolved "https://registry.yarnpkg.com/babel-plugin-check-es2015-constants/-/babel-plugin-check-es2015-constants-6.22.0.tgz#35157b101426fd2ffd3da3f75c7d1e91835bbf8a" + dependencies: + babel-runtime "^6.22.0" + +babel-plugin-constant-folding@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/babel-plugin-constant-folding/-/babel-plugin-constant-folding-1.0.1.tgz#8361d364c98e449c3692bdba51eff0844290aa8e" + +babel-plugin-dead-code-elimination@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/babel-plugin-dead-code-elimination/-/babel-plugin-dead-code-elimination-1.0.2.tgz#5f7c451274dcd7cccdbfbb3e0b85dd28121f0f65" + +babel-plugin-debug-macros@^0.1.10, babel-plugin-debug-macros@^0.1.11: + version "0.1.11" + resolved "https://registry.yarnpkg.com/babel-plugin-debug-macros/-/babel-plugin-debug-macros-0.1.11.tgz#6c562bf561fccd406ce14ab04f42c218cf956605" + dependencies: + semver "^5.3.0" + +babel-plugin-ember-modules-api-polyfill@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/babel-plugin-ember-modules-api-polyfill/-/babel-plugin-ember-modules-api-polyfill-2.0.1.tgz#baaf26dcebe2ed1de120021bc42be29f520497b3" + dependencies: + ember-rfc176-data "^0.2.7" + +babel-plugin-eval@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/babel-plugin-eval/-/babel-plugin-eval-1.0.1.tgz#a2faed25ce6be69ade4bfec263f70169195950da" + +babel-plugin-htmlbars-inline-precompile@^0.2.3: + version "0.2.3" + resolved "https://registry.yarnpkg.com/babel-plugin-htmlbars-inline-precompile/-/babel-plugin-htmlbars-inline-precompile-0.2.3.tgz#cd365e278af409bfa6be7704c4354beee742446b" + +babel-plugin-inline-environment-variables@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/babel-plugin-inline-environment-variables/-/babel-plugin-inline-environment-variables-1.0.1.tgz#1f58ce91207ad6a826a8bf645fafe68ff5fe3ffe" + +babel-plugin-jscript@^1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/babel-plugin-jscript/-/babel-plugin-jscript-1.0.4.tgz#8f342c38276e87a47d5fa0a8bd3d5eb6ccad8fcc" + +babel-plugin-member-expression-literals@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/babel-plugin-member-expression-literals/-/babel-plugin-member-expression-literals-1.0.1.tgz#cc5edb0faa8dc927170e74d6d1c02440021624d3" + +babel-plugin-property-literals@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/babel-plugin-property-literals/-/babel-plugin-property-literals-1.0.1.tgz#0252301900192980b1c118efea48ce93aab83336" + +babel-plugin-proto-to-assign@^1.0.3: + version "1.0.4" + resolved "https://registry.yarnpkg.com/babel-plugin-proto-to-assign/-/babel-plugin-proto-to-assign-1.0.4.tgz#c49e7afd02f577bc4da05ea2df002250cf7cd123" + dependencies: + lodash "^3.9.3" + +babel-plugin-react-constant-elements@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/babel-plugin-react-constant-elements/-/babel-plugin-react-constant-elements-1.0.3.tgz#946736e8378429cbc349dcff62f51c143b34e35a" + +babel-plugin-react-display-name@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/babel-plugin-react-display-name/-/babel-plugin-react-display-name-1.0.3.tgz#754fe38926e8424a4e7b15ab6ea6139dee0514fc" + +babel-plugin-remove-console@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/babel-plugin-remove-console/-/babel-plugin-remove-console-1.0.1.tgz#d8f24556c3a05005d42aaaafd27787f53ff013a7" + +babel-plugin-remove-debugger@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/babel-plugin-remove-debugger/-/babel-plugin-remove-debugger-1.0.1.tgz#fd2ea3cd61a428ad1f3b9c89882ff4293e8c14c7" + +babel-plugin-runtime@^1.0.7: + version "1.0.7" + resolved "https://registry.yarnpkg.com/babel-plugin-runtime/-/babel-plugin-runtime-1.0.7.tgz#bf7c7d966dd56ecd5c17fa1cb253c9acb7e54aaf" + +babel-plugin-syntax-async-functions@^6.8.0: + version "6.13.0" + resolved "https://registry.yarnpkg.com/babel-plugin-syntax-async-functions/-/babel-plugin-syntax-async-functions-6.13.0.tgz#cad9cad1191b5ad634bf30ae0872391e0647be95" + +babel-plugin-syntax-exponentiation-operator@^6.8.0: + version "6.13.0" + resolved "https://registry.yarnpkg.com/babel-plugin-syntax-exponentiation-operator/-/babel-plugin-syntax-exponentiation-operator-6.13.0.tgz#9ee7e8337290da95288201a6a57f4170317830de" + +babel-plugin-syntax-trailing-function-commas@^6.22.0: + version "6.22.0" + resolved "https://registry.yarnpkg.com/babel-plugin-syntax-trailing-function-commas/-/babel-plugin-syntax-trailing-function-commas-6.22.0.tgz#ba0360937f8d06e40180a43fe0d5616fff532cf3" + +babel-plugin-transform-async-to-generator@^6.22.0: + version "6.24.1" + resolved "https://registry.yarnpkg.com/babel-plugin-transform-async-to-generator/-/babel-plugin-transform-async-to-generator-6.24.1.tgz#6536e378aff6cb1d5517ac0e40eb3e9fc8d08761" + dependencies: + babel-helper-remap-async-to-generator "^6.24.1" + babel-plugin-syntax-async-functions "^6.8.0" + babel-runtime "^6.22.0" + +babel-plugin-transform-es2015-arrow-functions@^6.22.0: + version "6.22.0" + resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-arrow-functions/-/babel-plugin-transform-es2015-arrow-functions-6.22.0.tgz#452692cb711d5f79dc7f85e440ce41b9f244d221" + dependencies: + babel-runtime "^6.22.0" + +babel-plugin-transform-es2015-block-scoped-functions@^6.22.0: + version "6.22.0" + resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-block-scoped-functions/-/babel-plugin-transform-es2015-block-scoped-functions-6.22.0.tgz#bbc51b49f964d70cb8d8e0b94e820246ce3a6141" + dependencies: + babel-runtime "^6.22.0" + +babel-plugin-transform-es2015-block-scoping@^6.23.0: + version "6.26.0" + resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-block-scoping/-/babel-plugin-transform-es2015-block-scoping-6.26.0.tgz#d70f5299c1308d05c12f463813b0a09e73b1895f" + dependencies: + babel-runtime "^6.26.0" + babel-template "^6.26.0" + babel-traverse "^6.26.0" + babel-types "^6.26.0" + lodash "^4.17.4" + +babel-plugin-transform-es2015-classes@^6.23.0: + version "6.24.1" + resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-classes/-/babel-plugin-transform-es2015-classes-6.24.1.tgz#5a4c58a50c9c9461e564b4b2a3bfabc97a2584db" + dependencies: + babel-helper-define-map "^6.24.1" + babel-helper-function-name "^6.24.1" + babel-helper-optimise-call-expression "^6.24.1" + babel-helper-replace-supers "^6.24.1" + babel-messages "^6.23.0" + babel-runtime "^6.22.0" + babel-template "^6.24.1" + babel-traverse "^6.24.1" + babel-types "^6.24.1" + +babel-plugin-transform-es2015-computed-properties@^6.22.0: + version "6.24.1" + resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-computed-properties/-/babel-plugin-transform-es2015-computed-properties-6.24.1.tgz#6fe2a8d16895d5634f4cd999b6d3480a308159b3" + dependencies: + babel-runtime "^6.22.0" + babel-template "^6.24.1" + +babel-plugin-transform-es2015-destructuring@^6.23.0: + version "6.23.0" + resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-destructuring/-/babel-plugin-transform-es2015-destructuring-6.23.0.tgz#997bb1f1ab967f682d2b0876fe358d60e765c56d" + dependencies: + babel-runtime "^6.22.0" + +babel-plugin-transform-es2015-duplicate-keys@^6.22.0: + version "6.24.1" + resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-duplicate-keys/-/babel-plugin-transform-es2015-duplicate-keys-6.24.1.tgz#73eb3d310ca969e3ef9ec91c53741a6f1576423e" + dependencies: + babel-runtime "^6.22.0" + babel-types "^6.24.1" + +babel-plugin-transform-es2015-for-of@^6.23.0: + version "6.23.0" + resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-for-of/-/babel-plugin-transform-es2015-for-of-6.23.0.tgz#f47c95b2b613df1d3ecc2fdb7573623c75248691" + dependencies: + babel-runtime "^6.22.0" + +babel-plugin-transform-es2015-function-name@^6.22.0: + version "6.24.1" + resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-function-name/-/babel-plugin-transform-es2015-function-name-6.24.1.tgz#834c89853bc36b1af0f3a4c5dbaa94fd8eacaa8b" + dependencies: + babel-helper-function-name "^6.24.1" + babel-runtime "^6.22.0" + babel-types "^6.24.1" + +babel-plugin-transform-es2015-literals@^6.22.0: + version "6.22.0" + resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-literals/-/babel-plugin-transform-es2015-literals-6.22.0.tgz#4f54a02d6cd66cf915280019a31d31925377ca2e" + dependencies: + babel-runtime "^6.22.0" + +babel-plugin-transform-es2015-modules-amd@^6.22.0, babel-plugin-transform-es2015-modules-amd@^6.24.0, babel-plugin-transform-es2015-modules-amd@^6.24.1: + version "6.24.1" + resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-modules-amd/-/babel-plugin-transform-es2015-modules-amd-6.24.1.tgz#3b3e54017239842d6d19c3011c4bd2f00a00d154" + dependencies: + babel-plugin-transform-es2015-modules-commonjs "^6.24.1" + babel-runtime "^6.22.0" + babel-template "^6.24.1" + +babel-plugin-transform-es2015-modules-commonjs@^6.23.0, babel-plugin-transform-es2015-modules-commonjs@^6.24.1: + version "6.26.0" + resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-modules-commonjs/-/babel-plugin-transform-es2015-modules-commonjs-6.26.0.tgz#0d8394029b7dc6abe1a97ef181e00758dd2e5d8a" + dependencies: + babel-plugin-transform-strict-mode "^6.24.1" + babel-runtime "^6.26.0" + babel-template "^6.26.0" + babel-types "^6.26.0" + +babel-plugin-transform-es2015-modules-systemjs@^6.23.0: + version "6.24.1" + resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-modules-systemjs/-/babel-plugin-transform-es2015-modules-systemjs-6.24.1.tgz#ff89a142b9119a906195f5f106ecf305d9407d23" + dependencies: + babel-helper-hoist-variables "^6.24.1" + babel-runtime "^6.22.0" + babel-template "^6.24.1" + +babel-plugin-transform-es2015-modules-umd@^6.23.0: + version "6.24.1" + resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-modules-umd/-/babel-plugin-transform-es2015-modules-umd-6.24.1.tgz#ac997e6285cd18ed6176adb607d602344ad38468" + dependencies: + babel-plugin-transform-es2015-modules-amd "^6.24.1" + babel-runtime "^6.22.0" + babel-template "^6.24.1" + +babel-plugin-transform-es2015-object-super@^6.22.0: + version "6.24.1" + resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-object-super/-/babel-plugin-transform-es2015-object-super-6.24.1.tgz#24cef69ae21cb83a7f8603dad021f572eb278f8d" + dependencies: + babel-helper-replace-supers "^6.24.1" + babel-runtime "^6.22.0" + +babel-plugin-transform-es2015-parameters@^6.23.0: + version "6.24.1" + resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-parameters/-/babel-plugin-transform-es2015-parameters-6.24.1.tgz#57ac351ab49caf14a97cd13b09f66fdf0a625f2b" + dependencies: + babel-helper-call-delegate "^6.24.1" + babel-helper-get-function-arity "^6.24.1" + babel-runtime "^6.22.0" + babel-template "^6.24.1" + babel-traverse "^6.24.1" + babel-types "^6.24.1" + +babel-plugin-transform-es2015-shorthand-properties@^6.22.0: + version "6.24.1" + resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-shorthand-properties/-/babel-plugin-transform-es2015-shorthand-properties-6.24.1.tgz#24f875d6721c87661bbd99a4622e51f14de38aa0" + dependencies: + babel-runtime "^6.22.0" + babel-types "^6.24.1" + +babel-plugin-transform-es2015-spread@^6.22.0: + version "6.22.0" + resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-spread/-/babel-plugin-transform-es2015-spread-6.22.0.tgz#d6d68a99f89aedc4536c81a542e8dd9f1746f8d1" + dependencies: + babel-runtime "^6.22.0" + +babel-plugin-transform-es2015-sticky-regex@^6.22.0: + version "6.24.1" + resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-sticky-regex/-/babel-plugin-transform-es2015-sticky-regex-6.24.1.tgz#00c1cdb1aca71112cdf0cf6126c2ed6b457ccdbc" + dependencies: + babel-helper-regex "^6.24.1" + babel-runtime "^6.22.0" + babel-types "^6.24.1" + +babel-plugin-transform-es2015-template-literals@^6.22.0: + version "6.22.0" + resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-template-literals/-/babel-plugin-transform-es2015-template-literals-6.22.0.tgz#a84b3450f7e9f8f1f6839d6d687da84bb1236d8d" + dependencies: + babel-runtime "^6.22.0" + +babel-plugin-transform-es2015-typeof-symbol@^6.23.0: + version "6.23.0" + resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-typeof-symbol/-/babel-plugin-transform-es2015-typeof-symbol-6.23.0.tgz#dec09f1cddff94b52ac73d505c84df59dcceb372" + dependencies: + babel-runtime "^6.22.0" + +babel-plugin-transform-es2015-unicode-regex@^6.22.0: + version "6.24.1" + resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-unicode-regex/-/babel-plugin-transform-es2015-unicode-regex-6.24.1.tgz#d38b12f42ea7323f729387f18a7c5ae1faeb35e9" + dependencies: + babel-helper-regex "^6.24.1" + babel-runtime "^6.22.0" + regexpu-core "^2.0.0" + +babel-plugin-transform-exponentiation-operator@^6.22.0: + version "6.24.1" + resolved "https://registry.yarnpkg.com/babel-plugin-transform-exponentiation-operator/-/babel-plugin-transform-exponentiation-operator-6.24.1.tgz#2ab0c9c7f3098fa48907772bb813fe41e8de3a0e" + dependencies: + babel-helper-builder-binary-assignment-operator-visitor "^6.24.1" + babel-plugin-syntax-exponentiation-operator "^6.8.0" + babel-runtime "^6.22.0" + +babel-plugin-transform-regenerator@^6.22.0: + version "6.26.0" + resolved "https://registry.yarnpkg.com/babel-plugin-transform-regenerator/-/babel-plugin-transform-regenerator-6.26.0.tgz#e0703696fbde27f0a3efcacf8b4dca2f7b3a8f2f" + dependencies: + regenerator-transform "^0.10.0" + +babel-plugin-transform-strict-mode@^6.24.1: + version "6.24.1" + resolved "https://registry.yarnpkg.com/babel-plugin-transform-strict-mode/-/babel-plugin-transform-strict-mode-6.24.1.tgz#d5faf7aa578a65bbe591cf5edae04a0c67020758" + dependencies: + babel-runtime "^6.22.0" + babel-types "^6.24.1" + +babel-plugin-undeclared-variables-check@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/babel-plugin-undeclared-variables-check/-/babel-plugin-undeclared-variables-check-1.0.2.tgz#5cf1aa539d813ff64e99641290af620965f65dee" + dependencies: + leven "^1.0.2" + +babel-plugin-undefined-to-void@^1.1.6: + version "1.1.6" + resolved "https://registry.yarnpkg.com/babel-plugin-undefined-to-void/-/babel-plugin-undefined-to-void-1.1.6.tgz#7f578ef8b78dfae6003385d8417a61eda06e2f81" + +babel-polyfill@^6.16.0: + version "6.26.0" + resolved "https://registry.yarnpkg.com/babel-polyfill/-/babel-polyfill-6.26.0.tgz#379937abc67d7895970adc621f284cd966cf2153" + dependencies: + babel-runtime "^6.26.0" + core-js "^2.5.0" + regenerator-runtime "^0.10.5" + +babel-preset-env@^1.5.1: + version "1.6.0" + resolved "https://registry.yarnpkg.com/babel-preset-env/-/babel-preset-env-1.6.0.tgz#2de1c782a780a0a5d605d199c957596da43c44e4" + dependencies: + babel-plugin-check-es2015-constants "^6.22.0" + babel-plugin-syntax-trailing-function-commas "^6.22.0" + babel-plugin-transform-async-to-generator "^6.22.0" + babel-plugin-transform-es2015-arrow-functions "^6.22.0" + babel-plugin-transform-es2015-block-scoped-functions "^6.22.0" + babel-plugin-transform-es2015-block-scoping "^6.23.0" + babel-plugin-transform-es2015-classes "^6.23.0" + babel-plugin-transform-es2015-computed-properties "^6.22.0" + babel-plugin-transform-es2015-destructuring "^6.23.0" + babel-plugin-transform-es2015-duplicate-keys "^6.22.0" + babel-plugin-transform-es2015-for-of "^6.23.0" + babel-plugin-transform-es2015-function-name "^6.22.0" + babel-plugin-transform-es2015-literals "^6.22.0" + babel-plugin-transform-es2015-modules-amd "^6.22.0" + babel-plugin-transform-es2015-modules-commonjs "^6.23.0" + babel-plugin-transform-es2015-modules-systemjs "^6.23.0" + babel-plugin-transform-es2015-modules-umd "^6.23.0" + babel-plugin-transform-es2015-object-super "^6.22.0" + babel-plugin-transform-es2015-parameters "^6.23.0" + babel-plugin-transform-es2015-shorthand-properties "^6.22.0" + babel-plugin-transform-es2015-spread "^6.22.0" + babel-plugin-transform-es2015-sticky-regex "^6.22.0" + babel-plugin-transform-es2015-template-literals "^6.22.0" + babel-plugin-transform-es2015-typeof-symbol "^6.23.0" + babel-plugin-transform-es2015-unicode-regex "^6.22.0" + babel-plugin-transform-exponentiation-operator "^6.22.0" + babel-plugin-transform-regenerator "^6.22.0" + browserslist "^2.1.2" + invariant "^2.2.2" + semver "^5.3.0" + +babel-register@^6.26.0: + version "6.26.0" + resolved "https://registry.yarnpkg.com/babel-register/-/babel-register-6.26.0.tgz#6ed021173e2fcb486d7acb45c6009a856f647071" + dependencies: + babel-core "^6.26.0" + babel-runtime "^6.26.0" + core-js "^2.5.0" + home-or-tmp "^2.0.0" + lodash "^4.17.4" + mkdirp "^0.5.1" + source-map-support "^0.4.15" + +babel-runtime@^5.8.25: + version "5.8.38" + resolved "https://registry.yarnpkg.com/babel-runtime/-/babel-runtime-5.8.38.tgz#1c0b02eb63312f5f087ff20450827b425c9d4c19" + dependencies: + core-js "^1.0.0" + +babel-runtime@^6.18.0, babel-runtime@^6.22.0, babel-runtime@^6.26.0: + version "6.26.0" + resolved "https://registry.yarnpkg.com/babel-runtime/-/babel-runtime-6.26.0.tgz#965c7058668e82b55d7bfe04ff2337bc8b5647fe" + dependencies: + core-js "^2.4.0" + regenerator-runtime "^0.11.0" + +babel-template@^6.24.1, babel-template@^6.26.0: + version "6.26.0" + resolved "https://registry.yarnpkg.com/babel-template/-/babel-template-6.26.0.tgz#de03e2d16396b069f46dd9fff8521fb1a0e35e02" + dependencies: + babel-runtime "^6.26.0" + babel-traverse "^6.26.0" + babel-types "^6.26.0" + babylon "^6.18.0" + lodash "^4.17.4" + +babel-traverse@^6.24.1, babel-traverse@^6.26.0: + version "6.26.0" + resolved "https://registry.yarnpkg.com/babel-traverse/-/babel-traverse-6.26.0.tgz#46a9cbd7edcc62c8e5c064e2d2d8d0f4035766ee" + dependencies: + babel-code-frame "^6.26.0" + babel-messages "^6.23.0" + babel-runtime "^6.26.0" + babel-types "^6.26.0" + babylon "^6.18.0" + debug "^2.6.8" + globals "^9.18.0" + invariant "^2.2.2" + lodash "^4.17.4" + +babel-types@^6.19.0, babel-types@^6.24.1, babel-types@^6.26.0: + version "6.26.0" + resolved "https://registry.yarnpkg.com/babel-types/-/babel-types-6.26.0.tgz#a3b073f94ab49eb6fa55cd65227a334380632497" + dependencies: + babel-runtime "^6.26.0" + esutils "^2.0.2" + lodash "^4.17.4" + to-fast-properties "^1.0.3" + +babylon@^5.8.38: + version "5.8.38" + resolved "https://registry.yarnpkg.com/babylon/-/babylon-5.8.38.tgz#ec9b120b11bf6ccd4173a18bf217e60b79859ffd" + +babylon@^6.18.0: + version "6.18.0" + resolved "https://registry.yarnpkg.com/babylon/-/babylon-6.18.0.tgz#af2f3b88fa6f5c1e4c634d1a0f8eac4f55b395e3" + +backbone@^1.1.2: + version "1.3.3" + resolved "https://registry.yarnpkg.com/backbone/-/backbone-1.3.3.tgz#4cc80ea7cb1631ac474889ce40f2f8bc683b2999" + dependencies: + underscore ">=1.8.3" + +backo2@1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/backo2/-/backo2-1.0.2.tgz#31ab1ac8b129363463e35b3ebb69f4dfcfba7947" + +balanced-match@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.0.tgz#89b4d199ab2bee49de164ea02b89ce462d71b767" + +base64-arraybuffer@0.1.5: + version "0.1.5" + resolved "https://registry.yarnpkg.com/base64-arraybuffer/-/base64-arraybuffer-0.1.5.tgz#73926771923b5a19747ad666aa5cd4bf9c6e9ce8" + +base64id@0.1.0: + version "0.1.0" + resolved "https://registry.yarnpkg.com/base64id/-/base64id-0.1.0.tgz#02ce0fdeee0cef4f40080e1e73e834f0b1bfce3f" + +basic-auth@~2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/basic-auth/-/basic-auth-2.0.0.tgz#015db3f353e02e56377755f962742e8981e7bbba" + dependencies: + safe-buffer "5.1.1" + +bcrypt-pbkdf@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.1.tgz#63bc5dcb61331b92bc05fd528953c33462a06f8d" + dependencies: + tweetnacl "^0.14.3" + +better-assert@~1.0.0: + version "1.0.2" + resolved "https://registry.yarnpkg.com/better-assert/-/better-assert-1.0.2.tgz#40866b9e1b9e0b55b481894311e68faffaebc522" + dependencies: + callsite "1.0.0" + +binary-extensions@^1.0.0: + version "1.10.0" + resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-1.10.0.tgz#9aeb9a6c5e88638aad171e167f5900abe24835d0" + +"binaryextensions@1 || 2": + version "2.0.0" + resolved "https://registry.yarnpkg.com/binaryextensions/-/binaryextensions-2.0.0.tgz#e597d1a7a6a3558a2d1c7241a16c99965e6aa40f" + +blank-object@^1.0.1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/blank-object/-/blank-object-1.0.2.tgz#f990793fbe9a8c8dd013fb3219420bec81d5f4b9" + +blob@0.0.4: + version "0.0.4" + resolved "https://registry.yarnpkg.com/blob/-/blob-0.0.4.tgz#bcf13052ca54463f30f9fc7e95b9a47630a94921" + +block-stream@*: + version "0.0.9" + resolved "https://registry.yarnpkg.com/block-stream/-/block-stream-0.0.9.tgz#13ebfe778a03205cfe03751481ebb4b3300c126a" + dependencies: + inherits "~2.0.0" + +bluebird@^2.9.33: + version "2.11.0" + resolved "https://registry.yarnpkg.com/bluebird/-/bluebird-2.11.0.tgz#534b9033c022c9579c56ba3b3e5a5caafbb650e1" + +bluebird@^3.1.1, bluebird@^3.4.6: + version "3.5.0" + resolved "https://registry.yarnpkg.com/bluebird/-/bluebird-3.5.0.tgz#791420d7f551eea2897453a8a77653f96606d67c" + +body-parser@1.18.2: + version "1.18.2" + resolved "https://registry.yarnpkg.com/body-parser/-/body-parser-1.18.2.tgz#87678a19d84b47d859b83199bd59bce222b10454" + dependencies: + bytes "3.0.0" + content-type "~1.0.4" + debug "2.6.9" + depd "~1.1.1" + http-errors "~1.6.2" + iconv-lite "0.4.19" + on-finished "~2.3.0" + qs "6.5.1" + raw-body "2.3.2" + type-is "~1.6.15" + +body@^5.1.0: + version "5.1.0" + resolved "https://registry.yarnpkg.com/body/-/body-5.1.0.tgz#e4ba0ce410a46936323367609ecb4e6553125069" + dependencies: + continuable-cache "^0.3.1" + error "^7.0.0" + raw-body "~1.1.0" + safe-json-parse "~1.0.1" + +boom@2.x.x: + version "2.10.1" + resolved "https://registry.yarnpkg.com/boom/-/boom-2.10.1.tgz#39c8918ceff5799f83f9492a848f625add0c766f" + dependencies: + hoek "2.x.x" + +bower-config@^1.3.0: + version "1.4.1" + resolved "https://registry.yarnpkg.com/bower-config/-/bower-config-1.4.1.tgz#85fd9df367c2b8dbbd0caa4c5f2bad40cd84c2cc" + dependencies: + graceful-fs "^4.1.3" + mout "^1.0.0" + optimist "^0.6.1" + osenv "^0.1.3" + untildify "^2.1.0" + +bower-endpoint-parser@0.2.2: + version "0.2.2" + resolved "https://registry.yarnpkg.com/bower-endpoint-parser/-/bower-endpoint-parser-0.2.2.tgz#00b565adbfab6f2d35addde977e97962acbcb3f6" + +brace-expansion@^1.0.0, brace-expansion@^1.1.7: + version "1.1.8" + resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.8.tgz#c07b211c7c952ec1f8efd51a77ef0d1d3990a292" + dependencies: + balanced-match "^1.0.0" + concat-map "0.0.1" + +braces@^1.8.2: + version "1.8.5" + resolved "https://registry.yarnpkg.com/braces/-/braces-1.8.5.tgz#ba77962e12dff969d6b76711e914b737857bf6a7" + dependencies: + expand-range "^1.8.1" + preserve "^0.2.0" + repeat-element "^1.1.2" + +breakable@~1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/breakable/-/breakable-1.0.0.tgz#784a797915a38ead27bad456b5572cb4bbaa78c1" + +broccoli-asset-rev@^2.4.5: + version "2.6.0" + resolved "https://registry.yarnpkg.com/broccoli-asset-rev/-/broccoli-asset-rev-2.6.0.tgz#0633fc3a0b2ba0c2c1d56fa9feb7b331fc83be6d" + dependencies: + broccoli-asset-rewrite "^1.1.0" + broccoli-filter "^1.2.2" + json-stable-stringify "^1.0.0" + minimatch "^3.0.4" + rsvp "^3.0.6" + +broccoli-asset-rewrite@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/broccoli-asset-rewrite/-/broccoli-asset-rewrite-1.1.0.tgz#77a5da56157aa318c59113245e8bafb4617f8830" + dependencies: + broccoli-filter "^1.2.3" + +broccoli-babel-transpiler@^5.6.2: + version "5.7.2" + resolved "https://registry.yarnpkg.com/broccoli-babel-transpiler/-/broccoli-babel-transpiler-5.7.2.tgz#756c30544775144e984333b7115f42c916ba08e0" + dependencies: + babel-core "^5.0.0" + broccoli-funnel "^1.0.0" + broccoli-merge-trees "^1.0.0" + broccoli-persistent-filter "^1.4.2" + clone "^0.2.0" + hash-for-dep "^1.0.2" + heimdalljs-logger "^0.1.7" + json-stable-stringify "^1.0.0" + rsvp "^3.5.0" + workerpool "^2.2.1" + +broccoli-babel-transpiler@^6.0.0, broccoli-babel-transpiler@^6.1.2: + version "6.1.2" + resolved "https://registry.yarnpkg.com/broccoli-babel-transpiler/-/broccoli-babel-transpiler-6.1.2.tgz#26019c045b5ea3e44cfef62821302f9bd483cabd" + dependencies: + babel-core "^6.14.0" + broccoli-funnel "^1.0.0" + broccoli-merge-trees "^1.0.0" + broccoli-persistent-filter "^1.4.0" + clone "^2.0.0" + hash-for-dep "^1.0.2" + heimdalljs-logger "^0.1.7" + json-stable-stringify "^1.0.0" + rsvp "^3.5.0" + workerpool "^2.2.1" + +broccoli-brocfile-loader@^0.18.0: + version "0.18.0" + resolved "https://registry.yarnpkg.com/broccoli-brocfile-loader/-/broccoli-brocfile-loader-0.18.0.tgz#2e86021c805c34ffc8d29a2fb721cf273e819e4b" + dependencies: + findup-sync "^0.4.2" + +broccoli-builder@^0.18.8: + version "0.18.8" + resolved "https://registry.yarnpkg.com/broccoli-builder/-/broccoli-builder-0.18.8.tgz#fe54694d544c3cdfdb01028e802eeca65749a879" + dependencies: + heimdalljs "^0.2.0" + promise-map-series "^0.2.1" + quick-temp "^0.1.2" + rimraf "^2.2.8" + rsvp "^3.0.17" + silent-error "^1.0.1" + +broccoli-caching-writer@^2.2.0: + version "2.3.1" + resolved "https://registry.yarnpkg.com/broccoli-caching-writer/-/broccoli-caching-writer-2.3.1.tgz#b93cf58f9264f003075868db05774f4e7f25bd07" + dependencies: + broccoli-kitchen-sink-helpers "^0.2.5" + broccoli-plugin "1.1.0" + debug "^2.1.1" + rimraf "^2.2.8" + rsvp "^3.0.17" + walk-sync "^0.2.5" + +broccoli-caching-writer@^3.0.3: + version "3.0.3" + resolved "https://registry.yarnpkg.com/broccoli-caching-writer/-/broccoli-caching-writer-3.0.3.tgz#0bd2c96a9738d6a6ab590f07ba35c5157d7db476" + dependencies: + broccoli-kitchen-sink-helpers "^0.3.1" + broccoli-plugin "^1.2.1" + debug "^2.1.1" + rimraf "^2.2.8" + rsvp "^3.0.17" + walk-sync "^0.3.0" + +broccoli-clean-css@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/broccoli-clean-css/-/broccoli-clean-css-1.1.0.tgz#9db143d9af7e0ae79c26e3ac5a9bb2d720ea19fa" + dependencies: + broccoli-persistent-filter "^1.1.6" + clean-css-promise "^0.1.0" + inline-source-map-comment "^1.0.5" + json-stable-stringify "^1.0.0" + +broccoli-concat@^3.2.2: + version "3.2.2" + resolved "https://registry.yarnpkg.com/broccoli-concat/-/broccoli-concat-3.2.2.tgz#86ffdc52606eb590ba9f6b894c5ec7a016f5b7b9" + dependencies: + broccoli-kitchen-sink-helpers "^0.3.1" + broccoli-plugin "^1.3.0" + broccoli-stew "^1.3.3" + ensure-posix-path "^1.0.2" + fast-sourcemap-concat "^1.0.1" + find-index "^1.1.0" + fs-extra "^1.0.0" + fs-tree-diff "^0.5.6" + lodash.merge "^4.3.0" + lodash.omit "^4.1.0" + lodash.uniq "^4.2.0" + walk-sync "^0.3.1" + +broccoli-config-loader@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/broccoli-config-loader/-/broccoli-config-loader-1.0.1.tgz#d10aaf8ebc0cb45c1da5baa82720e1d88d28c80a" + dependencies: + broccoli-caching-writer "^3.0.3" + +broccoli-config-replace@^1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/broccoli-config-replace/-/broccoli-config-replace-1.1.2.tgz#6ea879d92a5bad634d11329b51fc5f4aafda9c00" + dependencies: + broccoli-kitchen-sink-helpers "^0.3.1" + broccoli-plugin "^1.2.0" + debug "^2.2.0" + fs-extra "^0.24.0" + +broccoli-debug@^0.6.1, broccoli-debug@^0.6.2: + version "0.6.3" + resolved "https://registry.yarnpkg.com/broccoli-debug/-/broccoli-debug-0.6.3.tgz#1f33bb0eacb5db81366f0492524c82b1217eb578" + dependencies: + broccoli-plugin "^1.2.1" + fs-tree-diff "^0.5.2" + heimdalljs "^0.2.1" + heimdalljs-logger "^0.1.7" + minimatch "^3.0.3" + symlink-or-copy "^1.1.8" + tree-sync "^1.2.2" + +broccoli-filter@^1.2.2, broccoli-filter@^1.2.3: + version "1.2.4" + resolved "https://registry.yarnpkg.com/broccoli-filter/-/broccoli-filter-1.2.4.tgz#409afb94b9a3a6da9fac8134e91e205f40cc7330" + dependencies: + broccoli-kitchen-sink-helpers "^0.3.1" + broccoli-plugin "^1.0.0" + copy-dereference "^1.0.0" + debug "^2.2.0" + mkdirp "^0.5.1" + promise-map-series "^0.2.1" + rsvp "^3.0.18" + symlink-or-copy "^1.0.1" + walk-sync "^0.3.1" + +broccoli-flatiron@^0.0.0: + version "0.0.0" + resolved "https://registry.yarnpkg.com/broccoli-flatiron/-/broccoli-flatiron-0.0.0.tgz#e97504016b56eea04813b5d862fda18b6f11a77f" + dependencies: + broccoli-kitchen-sink-helpers "~0.2.4" + broccoli-writer "~0.1.1" + mkdirp "^0.3.5" + rsvp "~3.0.6" + +broccoli-funnel-reducer@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/broccoli-funnel-reducer/-/broccoli-funnel-reducer-1.0.0.tgz#11365b2a785aec9b17972a36df87eef24c5cc0ea" + +broccoli-funnel@^1.0.0, broccoli-funnel@^1.0.1, broccoli-funnel@^1.0.6, broccoli-funnel@^1.1.0, broccoli-funnel@^1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/broccoli-funnel/-/broccoli-funnel-1.2.0.tgz#cddc3afc5ff1685a8023488fff74ce6fb5a51296" + dependencies: + array-equal "^1.0.0" + blank-object "^1.0.1" + broccoli-plugin "^1.3.0" + debug "^2.2.0" + exists-sync "0.0.4" + fast-ordered-set "^1.0.0" + fs-tree-diff "^0.5.3" + heimdalljs "^0.2.0" + minimatch "^3.0.0" + mkdirp "^0.5.0" + path-posix "^1.0.0" + rimraf "^2.4.3" + symlink-or-copy "^1.0.0" + walk-sync "^0.3.1" + +broccoli-funnel@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/broccoli-funnel/-/broccoli-funnel-2.0.1.tgz#6823c73b675ef78fffa7ab800f083e768b51d449" + dependencies: + array-equal "^1.0.0" + blank-object "^1.0.1" + broccoli-plugin "^1.3.0" + debug "^2.2.0" + fast-ordered-set "^1.0.0" + fs-tree-diff "^0.5.3" + heimdalljs "^0.2.0" + minimatch "^3.0.0" + mkdirp "^0.5.0" + path-posix "^1.0.0" + rimraf "^2.4.3" + symlink-or-copy "^1.0.0" + walk-sync "^0.3.1" + +broccoli-kitchen-sink-helpers@^0.2.0, broccoli-kitchen-sink-helpers@^0.2.5, broccoli-kitchen-sink-helpers@~0.2.4: + version "0.2.9" + resolved "https://registry.yarnpkg.com/broccoli-kitchen-sink-helpers/-/broccoli-kitchen-sink-helpers-0.2.9.tgz#a5e0986ed8d76fb5984b68c3f0450d3a96e36ecc" + dependencies: + glob "^5.0.10" + mkdirp "^0.5.1" + +broccoli-kitchen-sink-helpers@^0.3.1: + version "0.3.1" + resolved "https://registry.yarnpkg.com/broccoli-kitchen-sink-helpers/-/broccoli-kitchen-sink-helpers-0.3.1.tgz#77c7c18194b9664163ec4fcee2793444926e0c06" + dependencies: + glob "^5.0.10" + mkdirp "^0.5.1" + +broccoli-lint-eslint@^4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/broccoli-lint-eslint/-/broccoli-lint-eslint-4.1.0.tgz#dccfa1150dc62407cd66fd56a619273c5479a10e" + dependencies: + aot-test-generators "^0.1.0" + broccoli-concat "^3.2.2" + broccoli-persistent-filter "^1.2.0" + eslint "^4.0.0" + json-stable-stringify "^1.0.1" + lodash.defaultsdeep "^4.6.0" + md5-hex "^2.0.0" + +broccoli-merge-trees@^1.0.0: + version "1.2.4" + resolved "https://registry.yarnpkg.com/broccoli-merge-trees/-/broccoli-merge-trees-1.2.4.tgz#a001519bb5067f06589d91afa2942445a2d0fdb5" + dependencies: + broccoli-plugin "^1.3.0" + can-symlink "^1.0.0" + fast-ordered-set "^1.0.2" + fs-tree-diff "^0.5.4" + heimdalljs "^0.2.1" + heimdalljs-logger "^0.1.7" + rimraf "^2.4.3" + symlink-or-copy "^1.0.0" + +broccoli-merge-trees@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/broccoli-merge-trees/-/broccoli-merge-trees-2.0.0.tgz#10aea46dd5cebcc8b8f7d5a54f0a84a4f0bb90b9" + dependencies: + broccoli-plugin "^1.3.0" + merge-trees "^1.0.1" + +broccoli-middleware@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/broccoli-middleware/-/broccoli-middleware-1.0.0.tgz#92f4e1fb9a791ea986245a7077f35cc648dab097" + dependencies: + handlebars "^4.0.4" + mime "^1.2.11" + +broccoli-persistent-filter@^1.0.3, broccoli-persistent-filter@^1.1.6, broccoli-persistent-filter@^1.2.0, broccoli-persistent-filter@^1.4.0, broccoli-persistent-filter@^1.4.2: + version "1.4.3" + resolved "https://registry.yarnpkg.com/broccoli-persistent-filter/-/broccoli-persistent-filter-1.4.3.tgz#3511bc52fc53740cda51621f58a28152d9911bc1" + dependencies: + async-disk-cache "^1.2.1" + async-promise-queue "^1.0.3" + broccoli-plugin "^1.0.0" + fs-tree-diff "^0.5.2" + hash-for-dep "^1.0.2" + heimdalljs "^0.2.1" + heimdalljs-logger "^0.1.7" + mkdirp "^0.5.1" + promise-map-series "^0.2.1" + rimraf "^2.6.1" + rsvp "^3.0.18" + symlink-or-copy "^1.0.1" + walk-sync "^0.3.1" + +broccoli-plugin@1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/broccoli-plugin/-/broccoli-plugin-1.1.0.tgz#73e2cfa05f8ea1e3fc1420c40c3d9e7dc724bf02" + dependencies: + promise-map-series "^0.2.1" + quick-temp "^0.1.3" + rimraf "^2.3.4" + symlink-or-copy "^1.0.1" + +broccoli-plugin@^1.0.0, broccoli-plugin@^1.2.0, broccoli-plugin@^1.2.1, broccoli-plugin@^1.3.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/broccoli-plugin/-/broccoli-plugin-1.3.0.tgz#bee704a8e42da08cb58e513aaa436efb7f0ef1ee" + dependencies: + promise-map-series "^0.2.1" + quick-temp "^0.1.3" + rimraf "^2.3.4" + symlink-or-copy "^1.1.8" + +broccoli-slow-trees@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/broccoli-slow-trees/-/broccoli-slow-trees-3.0.1.tgz#9bf2a9e2f8eb3ed3a3f2abdde988da437ccdc9b4" + dependencies: + heimdalljs "^0.2.1" + +broccoli-source@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/broccoli-source/-/broccoli-source-1.1.0.tgz#54f0e82c8b73f46580cbbc4f578f0b32fca8f809" + +broccoli-sri-hash@^2.1.0: + version "2.1.2" + resolved "https://registry.yarnpkg.com/broccoli-sri-hash/-/broccoli-sri-hash-2.1.2.tgz#bc69905ed7a381ad325cc0d02ded071328ebf3f3" + dependencies: + broccoli-caching-writer "^2.2.0" + mkdirp "^0.5.1" + rsvp "^3.1.0" + sri-toolbox "^0.2.0" + symlink-or-copy "^1.0.1" + +broccoli-static-compiler@^0.1.4: + version "0.1.4" + resolved "https://registry.yarnpkg.com/broccoli-static-compiler/-/broccoli-static-compiler-0.1.4.tgz#713d18f08eb3131530575a0c5ad2951bba10af41" + dependencies: + broccoli-kitchen-sink-helpers "^0.2.0" + broccoli-writer "^0.1.1" + mkdirp "^0.3.5" + +broccoli-stew@^1.2.0, broccoli-stew@^1.3.3: + version "1.5.0" + resolved "https://registry.yarnpkg.com/broccoli-stew/-/broccoli-stew-1.5.0.tgz#d7af8c18511dce510e49d308a62e5977f461883c" + dependencies: + broccoli-debug "^0.6.1" + broccoli-funnel "^1.0.1" + broccoli-merge-trees "^1.0.0" + broccoli-persistent-filter "^1.1.6" + broccoli-plugin "^1.3.0" + chalk "^1.1.3" + debug "^2.4.0" + ensure-posix-path "^1.0.1" + fs-extra "^2.0.0" + minimatch "^3.0.2" + resolve "^1.1.6" + rsvp "^3.0.16" + symlink-or-copy "^1.1.8" + walk-sync "^0.3.0" + +broccoli-uglify-sourcemap@^1.0.0: + version "1.5.2" + resolved "https://registry.yarnpkg.com/broccoli-uglify-sourcemap/-/broccoli-uglify-sourcemap-1.5.2.tgz#04f84ab0db539031fa868ccfa563c9932d50cedb" + dependencies: + broccoli-plugin "^1.2.1" + debug "^2.2.0" + lodash.merge "^4.5.1" + matcher-collection "^1.0.0" + mkdirp "^0.5.0" + source-map-url "^0.3.0" + symlink-or-copy "^1.0.1" + uglify-js "^2.7.0" + walk-sync "^0.1.3" + +broccoli-writer@^0.1.1, broccoli-writer@~0.1.1: + version "0.1.1" + resolved "https://registry.yarnpkg.com/broccoli-writer/-/broccoli-writer-0.1.1.tgz#d4d71aa8f2afbc67a3866b91a2da79084b96ab2d" + dependencies: + quick-temp "^0.1.0" + rsvp "^3.0.6" + +browserslist@^2.1.2: + version "2.4.0" + resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-2.4.0.tgz#693ee93d01e66468a6348da5498e011f578f87f8" + dependencies: + caniuse-lite "^1.0.30000718" + electron-to-chromium "^1.3.18" + +bser@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/bser/-/bser-2.0.0.tgz#9ac78d3ed5d915804fd87acb158bc797147a1719" + dependencies: + node-int64 "^0.4.0" + +builtins@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/builtins/-/builtins-1.0.3.tgz#cb94faeb61c8696451db36534e1422f94f0aee88" + +bytes@1: + version "1.0.0" + resolved "https://registry.yarnpkg.com/bytes/-/bytes-1.0.0.tgz#3569ede8ba34315fab99c3e92cb04c7220de1fa8" + +bytes@3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/bytes/-/bytes-3.0.0.tgz#d32815404d689699f85a4ea4fa8755dd13a96048" + +calculate-cache-key-for-tree@^1.0.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/calculate-cache-key-for-tree/-/calculate-cache-key-for-tree-1.1.0.tgz#0c3e42c9c134f3c9de5358c0f16793627ea976d6" + dependencies: + json-stable-stringify "^1.0.1" + +caller-path@^0.1.0: + version "0.1.0" + resolved "https://registry.yarnpkg.com/caller-path/-/caller-path-0.1.0.tgz#94085ef63581ecd3daa92444a8fe94e82577751f" + dependencies: + callsites "^0.2.0" + +callsite@1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/callsite/-/callsite-1.0.0.tgz#280398e5d664bd74038b6f0905153e6e8af1bc20" + +callsites@^0.2.0: + version "0.2.0" + resolved "https://registry.yarnpkg.com/callsites/-/callsites-0.2.0.tgz#afab96262910a7f33c19a5775825c69f34e350ca" + +camelcase@^1.0.2, camelcase@^1.2.1: + version "1.2.1" + resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-1.2.1.tgz#9bb5304d2e0b56698b2c758b08a3eaa9daa58a39" + +can-symlink@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/can-symlink/-/can-symlink-1.0.0.tgz#97b607d8a84bb6c6e228b902d864ecb594b9d219" + dependencies: + tmp "0.0.28" + +caniuse-lite@^1.0.30000718: + version "1.0.30000740" + resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30000740.tgz#f2c4c04d6564eb812e61006841700ad557f6f973" + +capture-exit@^1.1.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/capture-exit/-/capture-exit-1.2.0.tgz#1c5fcc489fd0ab00d4f1ac7ae1072e3173fbab6f" + dependencies: + rsvp "^3.3.3" + +cardinal@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/cardinal/-/cardinal-1.0.0.tgz#50e21c1b0aa37729f9377def196b5a9cec932ee9" + dependencies: + ansicolors "~0.2.1" + redeyed "~1.0.0" + +caseless@~0.12.0: + version "0.12.0" + resolved "https://registry.yarnpkg.com/caseless/-/caseless-0.12.0.tgz#1b681c21ff84033c826543090689420d187151dc" + +center-align@^0.1.1: + version "0.1.3" + resolved "https://registry.yarnpkg.com/center-align/-/center-align-0.1.3.tgz#aa0d32629b6ee972200411cbd4461c907bc2b7ad" + dependencies: + align-text "^0.1.3" + lazy-cache "^1.0.3" + +chai-jquery@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/chai-jquery/-/chai-jquery-2.0.0.tgz#0f43042308dd746332bd98164aaef4a4f45ba167" + +chalk@^0.5.1: + version "0.5.1" + resolved "https://registry.yarnpkg.com/chalk/-/chalk-0.5.1.tgz#663b3a648b68b55d04690d49167aa837858f2174" + dependencies: + ansi-styles "^1.1.0" + escape-string-regexp "^1.0.0" + has-ansi "^0.1.0" + strip-ansi "^0.3.0" + supports-color "^0.2.0" + +chalk@^1.0.0, chalk@^1.1.1, chalk@^1.1.3: + version "1.1.3" + resolved "https://registry.yarnpkg.com/chalk/-/chalk-1.1.3.tgz#a8115c55e4a702fe4d150abd3872822a7e09fc98" + dependencies: + ansi-styles "^2.2.1" + escape-string-regexp "^1.0.2" + has-ansi "^2.0.0" + strip-ansi "^3.0.0" + supports-color "^2.0.0" + +chalk@^2.0.0, chalk@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.1.0.tgz#ac5becf14fa21b99c6c92ca7a7d7cfd5b17e743e" + dependencies: + ansi-styles "^3.1.0" + escape-string-regexp "^1.0.5" + supports-color "^4.0.0" + +charm@^1.0.0: + version "1.0.2" + resolved "https://registry.yarnpkg.com/charm/-/charm-1.0.2.tgz#8add367153a6d9a581331052c4090991da995e35" + dependencies: + inherits "^2.0.1" + +chokidar@1.6.1: + version "1.6.1" + resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-1.6.1.tgz#2f4447ab5e96e50fb3d789fd90d4c72e0e4c70c2" + dependencies: + anymatch "^1.3.0" + async-each "^1.0.0" + glob-parent "^2.0.0" + inherits "^2.0.1" + is-binary-path "^1.0.0" + is-glob "^2.0.0" + path-is-absolute "^1.0.0" + readdirp "^2.0.0" + optionalDependencies: + fsevents "^1.0.0" + +circular-json@^0.3.1: + version "0.3.3" + resolved "https://registry.yarnpkg.com/circular-json/-/circular-json-0.3.3.tgz#815c99ea84f6809529d2f45791bdf82711352d66" + +clean-base-url@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/clean-base-url/-/clean-base-url-1.0.0.tgz#c901cf0a20b972435b0eccd52d056824a4351b7b" + +clean-css-promise@^0.1.0: + version "0.1.1" + resolved "https://registry.yarnpkg.com/clean-css-promise/-/clean-css-promise-0.1.1.tgz#43f3d2c8dfcb2bf071481252cd9b76433c08eecb" + dependencies: + array-to-error "^1.0.0" + clean-css "^3.4.5" + pinkie-promise "^2.0.0" + +clean-css@^3.4.5: + version "3.4.28" + resolved "https://registry.yarnpkg.com/clean-css/-/clean-css-3.4.28.tgz#bf1945e82fc808f55695e6ddeaec01400efd03ff" + dependencies: + commander "2.8.x" + source-map "0.4.x" + +cli-cursor@^1.0.1, cli-cursor@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/cli-cursor/-/cli-cursor-1.0.2.tgz#64da3f7d56a54412e59794bd62dc35295e8f2987" + dependencies: + restore-cursor "^1.0.1" + +cli-cursor@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/cli-cursor/-/cli-cursor-2.1.0.tgz#b35dac376479facc3e94747d41d0d0f5238ffcb5" + dependencies: + restore-cursor "^2.0.0" + +cli-spinners@^0.1.2: + version "0.1.2" + resolved "https://registry.yarnpkg.com/cli-spinners/-/cli-spinners-0.1.2.tgz#bb764d88e185fb9e1e6a2a1f19772318f605e31c" + +cli-table2@^0.2.0: + version "0.2.0" + resolved "https://registry.yarnpkg.com/cli-table2/-/cli-table2-0.2.0.tgz#2d1ef7f218a0e786e214540562d4bd177fe32d97" + dependencies: + lodash "^3.10.1" + string-width "^1.0.1" + optionalDependencies: + colors "^1.1.2" + +cli-table@^0.3.1: + version "0.3.1" + resolved "https://registry.yarnpkg.com/cli-table/-/cli-table-0.3.1.tgz#f53b05266a8b1a0b934b3d0821e6e2dc5914ae23" + dependencies: + colors "1.0.3" + +cli-width@^2.0.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/cli-width/-/cli-width-2.2.0.tgz#ff19ede8a9a5e579324147b0c11f0fbcbabed639" + +cliui@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/cliui/-/cliui-2.1.0.tgz#4b475760ff80264c762c3a1719032e91c7fea0d1" + dependencies: + center-align "^0.1.1" + right-align "^0.1.1" + wordwrap "0.0.2" + +clone@^0.2.0: + version "0.2.0" + resolved "https://registry.yarnpkg.com/clone/-/clone-0.2.0.tgz#c6126a90ad4f72dbf5acdb243cc37724fe93fc1f" + +clone@^2.0.0: + version "2.1.1" + resolved "https://registry.yarnpkg.com/clone/-/clone-2.1.1.tgz#d217d1e961118e3ac9a4b8bba3285553bf647cdb" + +co@^4.6.0: + version "4.6.0" + resolved "https://registry.yarnpkg.com/co/-/co-4.6.0.tgz#6ea6bdf3d853ae54ccb8e47bfa0bf3f9031fb184" + +code-point-at@^1.0.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/code-point-at/-/code-point-at-1.1.0.tgz#0d070b4d043a5bea33a2f1a40e2edb3d9a4ccf77" + +color-convert@^1.9.0: + version "1.9.0" + resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-1.9.0.tgz#1accf97dd739b983bf994d56fec8f95853641b7a" + dependencies: + color-name "^1.1.1" + +color-name@^1.1.1: + version "1.1.3" + resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.3.tgz#a7d0558bd89c42f795dd42328f740831ca53bc25" + +colors@1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/colors/-/colors-1.0.3.tgz#0433f44d809680fdeb60ed260f1b0c262e82a40b" + +colors@^1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/colors/-/colors-1.1.2.tgz#168a4701756b6a7f51a12ce0c97bfa28c084ed63" + +combined-stream@^1.0.5, combined-stream@~1.0.5: + version "1.0.5" + resolved "https://registry.yarnpkg.com/combined-stream/-/combined-stream-1.0.5.tgz#938370a57b4a51dea2c77c15d5c5fdf895164009" + dependencies: + delayed-stream "~1.0.0" + +commander@2.8.x: + version "2.8.1" + resolved "https://registry.yarnpkg.com/commander/-/commander-2.8.1.tgz#06be367febfda0c330aa1e2a072d3dc9762425d4" + dependencies: + graceful-readlink ">= 1.0.0" + +commander@2.9.0: + version "2.9.0" + resolved "https://registry.yarnpkg.com/commander/-/commander-2.9.0.tgz#9c99094176e12240cb22d6c5146098400fe0f7d4" + dependencies: + graceful-readlink ">= 1.0.0" + +commander@^2.5.0, commander@^2.6.0: + version "2.11.0" + resolved "https://registry.yarnpkg.com/commander/-/commander-2.11.0.tgz#157152fd1e7a6c8d98a5b715cf376df928004563" + +commoner@~0.10.3: + version "0.10.8" + resolved "https://registry.yarnpkg.com/commoner/-/commoner-0.10.8.tgz#34fc3672cd24393e8bb47e70caa0293811f4f2c5" + dependencies: + commander "^2.5.0" + detective "^4.3.1" + glob "^5.0.15" + graceful-fs "^4.1.2" + iconv-lite "^0.4.5" + mkdirp "^0.5.0" + private "^0.1.6" + q "^1.1.2" + recast "^0.11.17" + +component-bind@1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/component-bind/-/component-bind-1.0.0.tgz#00c608ab7dcd93897c0009651b1d3a8e1e73bbd1" + +component-emitter@1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/component-emitter/-/component-emitter-1.1.2.tgz#296594f2753daa63996d2af08d15a95116c9aec3" + +component-emitter@1.2.1: + version "1.2.1" + resolved "https://registry.yarnpkg.com/component-emitter/-/component-emitter-1.2.1.tgz#137918d6d78283f7df7a6b7c5a63e140e69425e6" + +component-inherit@0.0.3: + version "0.0.3" + resolved "https://registry.yarnpkg.com/component-inherit/-/component-inherit-0.0.3.tgz#645fc4adf58b72b649d5cae65135619db26ff143" + +compressible@~2.0.11: + version "2.0.11" + resolved "https://registry.yarnpkg.com/compressible/-/compressible-2.0.11.tgz#16718a75de283ed8e604041625a2064586797d8a" + dependencies: + mime-db ">= 1.29.0 < 2" + +compression@^1.4.4: + version "1.7.1" + resolved "https://registry.yarnpkg.com/compression/-/compression-1.7.1.tgz#eff2603efc2e22cf86f35d2eb93589f9875373db" + dependencies: + accepts "~1.3.4" + bytes "3.0.0" + compressible "~2.0.11" + debug "2.6.9" + on-headers "~1.0.1" + safe-buffer "5.1.1" + vary "~1.1.2" + +concat-map@0.0.1: + version "0.0.1" + resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" + +concat-stream@^1.4.7, concat-stream@^1.6.0: + version "1.6.0" + resolved "https://registry.yarnpkg.com/concat-stream/-/concat-stream-1.6.0.tgz#0aac662fd52be78964d5532f694784e70110acf7" + dependencies: + inherits "^2.0.3" + readable-stream "^2.2.2" + typedarray "^0.0.6" + +configstore@^3.0.0: + version "3.1.1" + resolved "https://registry.yarnpkg.com/configstore/-/configstore-3.1.1.tgz#094ee662ab83fad9917678de114faaea8fcdca90" + dependencies: + dot-prop "^4.1.0" + graceful-fs "^4.1.2" + make-dir "^1.0.0" + unique-string "^1.0.0" + write-file-atomic "^2.0.0" + xdg-basedir "^3.0.0" + +console-control-strings@^1.0.0, console-control-strings@~1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/console-control-strings/-/console-control-strings-1.1.0.tgz#3d7cf4464db6446ea644bf4b39507f9851008e8e" + +console-ui@^1.0.2: + version "1.0.3" + resolved "https://registry.yarnpkg.com/console-ui/-/console-ui-1.0.3.tgz#31c524461b63422769f9e89c173495d91393721c" + dependencies: + chalk "^1.1.3" + inquirer "^1.2.3" + ora "^0.2.0" + through "^2.3.8" + +consolidate@^0.14.0: + version "0.14.5" + resolved "https://registry.yarnpkg.com/consolidate/-/consolidate-0.14.5.tgz#5a25047bc76f73072667c8cb52c989888f494c63" + dependencies: + bluebird "^3.1.1" + +content-disposition@0.5.2: + version "0.5.2" + resolved "https://registry.yarnpkg.com/content-disposition/-/content-disposition-0.5.2.tgz#0cf68bb9ddf5f2be7961c3a85178cb85dba78cb4" + +content-type@~1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/content-type/-/content-type-1.0.4.tgz#e138cc75e040c727b1966fe5e5f8c9aee256fe3b" + +continuable-cache@^0.3.1: + version "0.3.1" + resolved "https://registry.yarnpkg.com/continuable-cache/-/continuable-cache-0.3.1.tgz#bd727a7faed77e71ff3985ac93351a912733ad0f" + +convert-source-map@^1.1.0, convert-source-map@^1.5.0: + version "1.5.0" + resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-1.5.0.tgz#9acd70851c6d5dfdd93d9282e5edf94a03ff46b5" + +cookie-signature@1.0.6: + version "1.0.6" + resolved "https://registry.yarnpkg.com/cookie-signature/-/cookie-signature-1.0.6.tgz#e303a882b342cc3ee8ca513a79999734dab3ae2c" + +cookie@0.3.1: + version "0.3.1" + resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.3.1.tgz#e7e0a1f9ef43b4c8ba925c5c5a96e806d16873bb" + +copy-dereference@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/copy-dereference/-/copy-dereference-1.0.0.tgz#6b131865420fd81b413ba994b44d3655311152b6" + +core-js@^1.0.0: + version "1.2.7" + resolved "https://registry.yarnpkg.com/core-js/-/core-js-1.2.7.tgz#652294c14651db28fa93bd2d5ff2983a4f08c636" + +core-js@^2.4.0, core-js@^2.5.0: + version "2.5.1" + resolved "https://registry.yarnpkg.com/core-js/-/core-js-2.5.1.tgz#ae6874dc66937789b80754ff5428df66819ca50b" + +core-object@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/core-object/-/core-object-1.1.0.tgz#86d63918733cf9da1a5aae729e62c0a88e66ad0a" + +core-object@^3.1.3: + version "3.1.5" + resolved "https://registry.yarnpkg.com/core-object/-/core-object-3.1.5.tgz#fa627b87502adc98045e44678e9a8ec3b9c0d2a9" + dependencies: + chalk "^2.0.0" + +core-util-is@1.0.2, core-util-is@~1.0.0: + version "1.0.2" + resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.2.tgz#b5fd54220aa2bc5ab57aab7140c940754503c1a7" + +cross-spawn@^5.0.1, cross-spawn@^5.1.0: + version "5.1.0" + resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-5.1.0.tgz#e8bd0efee58fcff6f8f94510a0a554bbfa235449" + dependencies: + lru-cache "^4.0.1" + shebang-command "^1.2.0" + which "^1.2.9" + +cryptiles@2.x.x: + version "2.0.5" + resolved "https://registry.yarnpkg.com/cryptiles/-/cryptiles-2.0.5.tgz#3bdfecdc608147c1c67202fa291e7dca59eaa3b8" + dependencies: + boom "2.x.x" + +crypto-random-string@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/crypto-random-string/-/crypto-random-string-1.0.0.tgz#a230f64f568310e1498009940790ec99545bca7e" + +dag-map@^2.0.2: + version "2.0.2" + resolved "https://registry.yarnpkg.com/dag-map/-/dag-map-2.0.2.tgz#9714b472de82a1843de2fba9b6876938cab44c68" + +dashdash@^1.12.0: + version "1.14.1" + resolved "https://registry.yarnpkg.com/dashdash/-/dashdash-1.14.1.tgz#853cfa0f7cbe2fed5de20326b8dd581035f6e2f0" + dependencies: + assert-plus "^1.0.0" + +debug@2.2.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/debug/-/debug-2.2.0.tgz#f87057e995b1a1f6ae6a4960664137bc56f039da" + dependencies: + ms "0.7.1" + +debug@2.3.3: + version "2.3.3" + resolved "https://registry.yarnpkg.com/debug/-/debug-2.3.3.tgz#40c453e67e6e13c901ddec317af8986cda9eff8c" + dependencies: + ms "0.7.2" + +debug@2.6.9, debug@^2.1.0, debug@^2.1.1, debug@^2.1.3, debug@^2.2.0, debug@^2.4.0, debug@^2.6.8, debug@~2.6.7: + version "2.6.9" + resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f" + dependencies: + ms "2.0.0" + +debug@^3.0.1: + version "3.1.0" + resolved "https://registry.yarnpkg.com/debug/-/debug-3.1.0.tgz#5bb5a0672628b64149566ba16819e61518c67261" + dependencies: + ms "2.0.0" + +decamelize@^1.0.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/decamelize/-/decamelize-1.2.0.tgz#f6534d15148269b20352e7bee26f501f9a191290" + +deep-extend@~0.4.0: + version "0.4.2" + resolved "https://registry.yarnpkg.com/deep-extend/-/deep-extend-0.4.2.tgz#48b699c27e334bf89f10892be432f6e4c7d34a7f" + +deep-freeze@^0.0.1: + version "0.0.1" + resolved "https://registry.yarnpkg.com/deep-freeze/-/deep-freeze-0.0.1.tgz#3a0b0005de18672819dfd38cd31f91179c893e84" + +deep-is@~0.1.3: + version "0.1.3" + resolved "https://registry.yarnpkg.com/deep-is/-/deep-is-0.1.3.tgz#b369d6fb5dbc13eecf524f91b070feedc357cf34" + +defined@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/defined/-/defined-1.0.0.tgz#c98d9bcef75674188e110969151199e39b1fa693" + +defs@~1.1.0: + version "1.1.1" + resolved "https://registry.yarnpkg.com/defs/-/defs-1.1.1.tgz#b22609f2c7a11ba7a3db116805c139b1caffa9d2" + dependencies: + alter "~0.2.0" + ast-traverse "~0.1.1" + breakable "~1.0.0" + esprima-fb "~15001.1001.0-dev-harmony-fb" + simple-fmt "~0.1.0" + simple-is "~0.2.0" + stringmap "~0.2.2" + stringset "~0.2.1" + tryor "~0.1.2" + yargs "~3.27.0" + +del@^2.0.2: + version "2.2.2" + resolved "https://registry.yarnpkg.com/del/-/del-2.2.2.tgz#c12c981d067846c84bcaf862cff930d907ffd1a8" + dependencies: + globby "^5.0.0" + is-path-cwd "^1.0.0" + is-path-in-cwd "^1.0.0" + object-assign "^4.0.1" + pify "^2.0.0" + pinkie-promise "^2.0.0" + rimraf "^2.2.8" + +delayed-stream@~1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/delayed-stream/-/delayed-stream-1.0.0.tgz#df3ae199acadfb7d440aaae0b29e2272b24ec619" + +delegates@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/delegates/-/delegates-1.0.0.tgz#84c6e159b81904fdca59a0ef44cd870d31250f9a" + +depd@1.1.1, depd@~1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/depd/-/depd-1.1.1.tgz#5783b4e1c459f06fa5ca27f991f3d06e7a310359" + +destroy@~1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/destroy/-/destroy-1.0.4.tgz#978857442c44749e4206613e37946205826abd80" + +detect-file@^0.1.0: + version "0.1.0" + resolved "https://registry.yarnpkg.com/detect-file/-/detect-file-0.1.0.tgz#4935dedfd9488648e006b0129566e9386711ea63" + dependencies: + fs-exists-sync "^0.1.0" + +detect-indent@^3.0.0: + version "3.0.1" + resolved "https://registry.yarnpkg.com/detect-indent/-/detect-indent-3.0.1.tgz#9dc5e5ddbceef8325764b9451b02bc6d54084f75" + dependencies: + get-stdin "^4.0.1" + minimist "^1.1.0" + repeating "^1.1.0" + +detect-indent@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/detect-indent/-/detect-indent-4.0.0.tgz#f76d064352cdf43a1cb6ce619c4ee3a9475de208" + dependencies: + repeating "^2.0.0" + +detective@^4.3.1: + version "4.5.0" + resolved "https://registry.yarnpkg.com/detective/-/detective-4.5.0.tgz#6e5a8c6b26e6c7a254b1c6b6d7490d98ec91edd1" + dependencies: + acorn "^4.0.3" + defined "^1.0.0" + +diff@^3.2.0: + version "3.3.1" + resolved "https://registry.yarnpkg.com/diff/-/diff-3.3.1.tgz#aa8567a6eed03c531fc89d3f711cd0e5259dec75" + +doctrine@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/doctrine/-/doctrine-2.0.0.tgz#c73d8d2909d22291e1a007a395804da8b665fe63" + dependencies: + esutils "^2.0.2" + isarray "^1.0.0" + +dot-prop@^4.1.0: + version "4.2.0" + resolved "https://registry.yarnpkg.com/dot-prop/-/dot-prop-4.2.0.tgz#1f19e0c2e1aa0e32797c49799f2837ac6af69c57" + dependencies: + is-obj "^1.0.0" + +ecc-jsbn@~0.1.1: + version "0.1.1" + resolved "https://registry.yarnpkg.com/ecc-jsbn/-/ecc-jsbn-0.1.1.tgz#0fc73a9ed5f0d53c38193398523ef7e543777505" + dependencies: + jsbn "~0.1.0" + +editions@^1.1.1: + version "1.3.3" + resolved "https://registry.yarnpkg.com/editions/-/editions-1.3.3.tgz#0907101bdda20fac3cbe334c27cbd0688dc99a5b" + +ee-first@1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/ee-first/-/ee-first-1.1.1.tgz#590c61156b0ae2f4f0255732a158b266bc56b21d" + +electron-to-chromium@^1.3.18: + version "1.3.24" + resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.24.tgz#9b7b88bb05ceb9fa016a177833cc2dde388f21b6" + +ember-ajax@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/ember-ajax/-/ember-ajax-3.0.0.tgz#8f21e9da0c1d433cf879aa855fce464d517e9ab5" + dependencies: + ember-cli-babel "^6.0.0" + +ember-cli-app-version@^3.0.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/ember-cli-app-version/-/ember-cli-app-version-3.1.0.tgz#074b0330581a7b8ab094ff07fefb34e0d0254bfd" + dependencies: + ember-cli-babel "^6.8.0" + git-repo-version "0.4.1" + +ember-cli-babel@^5.1.7: + version "5.2.4" + resolved "https://registry.yarnpkg.com/ember-cli-babel/-/ember-cli-babel-5.2.4.tgz#5ce4f46b08ed6f6d21e878619fb689719d6e8e13" + dependencies: + broccoli-babel-transpiler "^5.6.2" + broccoli-funnel "^1.0.0" + clone "^2.0.0" + ember-cli-version-checker "^1.0.2" + resolve "^1.1.2" + +ember-cli-babel@^6.0.0, ember-cli-babel@^6.0.0-beta.7, ember-cli-babel@^6.3.0, ember-cli-babel@^6.8.0, ember-cli-babel@^6.8.1: + version "6.8.2" + resolved "https://registry.yarnpkg.com/ember-cli-babel/-/ember-cli-babel-6.8.2.tgz#eac2785964f4743f4c815cd53c6288f00cc087d7" + dependencies: + amd-name-resolver "0.0.7" + babel-plugin-debug-macros "^0.1.11" + babel-plugin-ember-modules-api-polyfill "^2.0.1" + babel-plugin-transform-es2015-modules-amd "^6.24.0" + babel-polyfill "^6.16.0" + babel-preset-env "^1.5.1" + broccoli-babel-transpiler "^6.1.2" + broccoli-debug "^0.6.2" + broccoli-funnel "^1.0.0" + broccoli-source "^1.1.0" + clone "^2.0.0" + ember-cli-version-checker "^2.0.0" + +ember-cli-broccoli-sane-watcher@^2.0.4: + version "2.0.4" + resolved "https://registry.yarnpkg.com/ember-cli-broccoli-sane-watcher/-/ember-cli-broccoli-sane-watcher-2.0.4.tgz#f43f42f75b7509c212fb926cd9aea86ae19264c6" + dependencies: + broccoli-slow-trees "^3.0.1" + heimdalljs "^0.2.1" + heimdalljs-logger "^0.1.7" + rsvp "^3.0.18" + sane "^1.1.1" + +ember-cli-dependency-checker@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/ember-cli-dependency-checker/-/ember-cli-dependency-checker-2.0.1.tgz#e44cd2f8cdbf6a1043092de1ebfd62e7b8c00dd1" + dependencies: + chalk "^1.1.3" + is-git-url "^1.0.0" + semver "^5.3.0" + +ember-cli-eslint@^4.0.0: + version "4.2.0" + resolved "https://registry.yarnpkg.com/ember-cli-eslint/-/ember-cli-eslint-4.2.0.tgz#3ecb7b0e67fdadd1e68eaba05fbaffb03bcd7864" + dependencies: + broccoli-lint-eslint "^4.1.0" + ember-cli-version-checker "^2.0.0" + rsvp "^3.2.1" + walk-sync "^0.3.0" + +ember-cli-get-component-path-option@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/ember-cli-get-component-path-option/-/ember-cli-get-component-path-option-1.0.0.tgz#0d7b595559e2f9050abed804f1d8eff1b08bc771" + +ember-cli-get-dependency-depth@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/ember-cli-get-dependency-depth/-/ember-cli-get-dependency-depth-1.0.0.tgz#e0afecf82a2d52f00f28ab468295281aec368d11" + +ember-cli-htmlbars-inline-precompile@^1.0.0: + version "1.0.2" + resolved "https://registry.yarnpkg.com/ember-cli-htmlbars-inline-precompile/-/ember-cli-htmlbars-inline-precompile-1.0.2.tgz#5b544f664d5d9911f08cd979c5f70d8cb0ca2add" + dependencies: + babel-plugin-htmlbars-inline-precompile "^0.2.3" + ember-cli-version-checker "^2.0.0" + hash-for-dep "^1.0.2" + heimdalljs-logger "^0.1.7" + silent-error "^1.1.0" + +ember-cli-htmlbars@^2.0.1: + version "2.0.3" + resolved "https://registry.yarnpkg.com/ember-cli-htmlbars/-/ember-cli-htmlbars-2.0.3.tgz#e116e1500dba12f29c94b05b9ec90f52cb8bb042" + dependencies: + broccoli-persistent-filter "^1.0.3" + hash-for-dep "^1.0.2" + json-stable-stringify "^1.0.0" + strip-bom "^3.0.0" + +ember-cli-inject-live-reload@^1.4.1: + version "1.7.0" + resolved "https://registry.yarnpkg.com/ember-cli-inject-live-reload/-/ember-cli-inject-live-reload-1.7.0.tgz#af94336e015336127dfb98080ad442bb233e37ed" + +ember-cli-is-package-missing@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/ember-cli-is-package-missing/-/ember-cli-is-package-missing-1.0.0.tgz#6e6184cafb92635dd93ca6c946b104292d4e3390" + +ember-cli-legacy-blueprints@^0.1.2: + version "0.1.5" + resolved "https://registry.yarnpkg.com/ember-cli-legacy-blueprints/-/ember-cli-legacy-blueprints-0.1.5.tgz#93c15ca242ec5107d62a8af7ec30f6ac538f3ad9" + dependencies: + chalk "^1.1.1" + ember-cli-get-component-path-option "^1.0.0" + ember-cli-get-dependency-depth "^1.0.0" + ember-cli-is-package-missing "^1.0.0" + ember-cli-lodash-subset "^1.0.7" + ember-cli-normalize-entity-name "^1.0.0" + ember-cli-path-utils "^1.0.0" + ember-cli-string-utils "^1.0.0" + ember-cli-test-info "^1.0.0" + ember-cli-valid-component-name "^1.0.0" + ember-cli-version-checker "^1.1.7" + ember-router-generator "^1.0.0" + exists-sync "0.0.3" + fs-extra "^0.24.0" + inflection "^1.7.1" + rsvp "^3.0.17" + silent-error "^1.0.0" + +ember-cli-lodash-subset@^1.0.11, ember-cli-lodash-subset@^1.0.7: + version "1.0.12" + resolved "https://registry.yarnpkg.com/ember-cli-lodash-subset/-/ember-cli-lodash-subset-1.0.12.tgz#af2e77eba5dcb0d77f3308d3a6fd7d3450f6e537" + +ember-cli-normalize-entity-name@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/ember-cli-normalize-entity-name/-/ember-cli-normalize-entity-name-1.0.0.tgz#0b14f7bcbc599aa117b5fddc81e4fd03c4bad5b7" + dependencies: + silent-error "^1.0.0" + +ember-cli-path-utils@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/ember-cli-path-utils/-/ember-cli-path-utils-1.0.0.tgz#4e39af8b55301cddc5017739b77a804fba2071ed" + +ember-cli-preprocess-registry@^3.1.0: + version "3.1.1" + resolved "https://registry.yarnpkg.com/ember-cli-preprocess-registry/-/ember-cli-preprocess-registry-3.1.1.tgz#38456c21c4d2b64945850cf9ec68db6ba769288a" + dependencies: + broccoli-clean-css "^1.1.0" + broccoli-funnel "^1.0.0" + broccoli-merge-trees "^1.0.0" + debug "^2.2.0" + ember-cli-lodash-subset "^1.0.7" + exists-sync "0.0.3" + process-relative-require "^1.0.0" + silent-error "^1.0.0" + +ember-cli-qunit@^4.0.0: + version "4.0.1" + resolved "https://registry.yarnpkg.com/ember-cli-qunit/-/ember-cli-qunit-4.0.1.tgz#905aa07620ae9fdb417c7e48d45bd2277b62f864" + dependencies: + broccoli-funnel "^2.0.0" + broccoli-merge-trees "^2.0.0" + ember-cli-babel "^6.8.1" + ember-cli-test-loader "^2.2.0" + ember-cli-version-checker "^2.0.0" + ember-qunit "^2.2.0" + qunit-notifications "^0.1.1" + qunitjs "^2.4.0" + resolve "^1.4.0" + silent-error "^1.1.0" + +ember-cli-shims@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/ember-cli-shims/-/ember-cli-shims-1.1.0.tgz#0e3b8a048be865b4f81cc81d397ff1eeb13f75b6" + dependencies: + ember-cli-babel "^6.0.0-beta.7" + ember-cli-version-checker "^1.2.0" + silent-error "^1.0.1" + +ember-cli-sri@^2.1.0: + version "2.1.1" + resolved "https://registry.yarnpkg.com/ember-cli-sri/-/ember-cli-sri-2.1.1.tgz#971620934a4b9183cf7923cc03e178b83aa907fd" + dependencies: + broccoli-sri-hash "^2.1.0" + +ember-cli-string-utils@^1.0.0, ember-cli-string-utils@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/ember-cli-string-utils/-/ember-cli-string-utils-1.1.0.tgz#39b677fc2805f55173735376fcef278eaa4452a1" + +ember-cli-test-info@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/ember-cli-test-info/-/ember-cli-test-info-1.0.0.tgz#ed4e960f249e97523cf891e4aed2072ce84577b4" + dependencies: + ember-cli-string-utils "^1.0.0" + +ember-cli-test-loader@^2.2.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/ember-cli-test-loader/-/ember-cli-test-loader-2.2.0.tgz#3fb8d5d1357e4460d3f0a092f5375e71b6f7c243" + dependencies: + ember-cli-babel "^6.8.1" + +ember-cli-uglify@^1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/ember-cli-uglify/-/ember-cli-uglify-1.2.0.tgz#3208c32b54bc2783056e8bb0d5cfe9bbaf17ffb2" + dependencies: + broccoli-uglify-sourcemap "^1.0.0" + +ember-cli-valid-component-name@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/ember-cli-valid-component-name/-/ember-cli-valid-component-name-1.0.0.tgz#71550ce387e0233065f30b30b1510aa2dfbe87ef" + dependencies: + silent-error "^1.0.0" + +ember-cli-version-checker@^1.0.2, ember-cli-version-checker@^1.1.6, ember-cli-version-checker@^1.1.7, ember-cli-version-checker@^1.2.0, ember-cli-version-checker@^1.3.1: + version "1.3.1" + resolved "https://registry.yarnpkg.com/ember-cli-version-checker/-/ember-cli-version-checker-1.3.1.tgz#0bc2d134c830142da64bf9627a0eded10b61ae72" + dependencies: + semver "^5.3.0" + +ember-cli-version-checker@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/ember-cli-version-checker/-/ember-cli-version-checker-2.0.0.tgz#e1f7d8e4cdcd752ac35f1611e4daa8836db4c4c7" + dependencies: + resolve "^1.3.3" + semver "^5.3.0" + +ember-cli@~2.15.1: + version "2.15.1" + resolved "https://registry.yarnpkg.com/ember-cli/-/ember-cli-2.15.1.tgz#773add3cc18e5068f1c5f43a77544efa2712e47b" + dependencies: + amd-name-resolver "0.0.7" + babel-plugin-transform-es2015-modules-amd "^6.24.0" + bower-config "^1.3.0" + bower-endpoint-parser "0.2.2" + broccoli-babel-transpiler "^6.0.0" + broccoli-brocfile-loader "^0.18.0" + broccoli-builder "^0.18.8" + broccoli-concat "^3.2.2" + broccoli-config-loader "^1.0.0" + broccoli-config-replace "^1.1.2" + broccoli-funnel "^1.0.6" + broccoli-funnel-reducer "^1.0.0" + broccoli-merge-trees "^2.0.0" + broccoli-middleware "^1.0.0" + broccoli-source "^1.1.0" + broccoli-stew "^1.2.0" + calculate-cache-key-for-tree "^1.0.0" + capture-exit "^1.1.0" + chalk "^1.1.3" + clean-base-url "^1.0.0" + compression "^1.4.4" + configstore "^3.0.0" + console-ui "^1.0.2" + core-object "^3.1.3" + dag-map "^2.0.2" + deep-freeze "^0.0.1" + diff "^3.2.0" + ember-cli-broccoli-sane-watcher "^2.0.4" + ember-cli-is-package-missing "^1.0.0" + ember-cli-legacy-blueprints "^0.1.2" + ember-cli-lodash-subset "^1.0.11" + ember-cli-normalize-entity-name "^1.0.0" + ember-cli-preprocess-registry "^3.1.0" + ember-cli-string-utils "^1.0.0" + ember-try "^0.2.15" + ensure-posix-path "^1.0.2" + execa "^0.7.0" + exists-sync "0.0.4" + exit "^0.1.2" + express "^4.12.3" + filesize "^3.1.3" + find-up "^2.1.0" + fs-extra "^3.0.0" + fs-tree-diff "^0.5.2" + get-caller-file "^1.0.0" + git-repo-info "^1.4.1" + glob "7.1.1" + heimdalljs "^0.2.3" + heimdalljs-fs-monitor "^0.1.0" + heimdalljs-graph "^0.3.1" + heimdalljs-logger "^0.1.7" + http-proxy "^1.9.0" + inflection "^1.7.0" + is-git-url "^1.0.0" + isbinaryfile "^3.0.0" + js-yaml "^3.6.1" + json-stable-stringify "^1.0.1" + leek "0.0.24" + lodash.template "^4.2.5" + markdown-it "^8.3.0" + markdown-it-terminal "0.1.0" + minimatch "^3.0.0" + morgan "^1.8.1" + node-modules-path "^1.0.0" + nopt "^3.0.6" + npm-package-arg "^4.1.1" + portfinder "^1.0.7" + promise-map-series "^0.2.1" + quick-temp "^0.1.8" + resolve "^1.3.0" + rsvp "^3.3.3" + sane "^1.6.0" + semver "^5.1.1" + silent-error "^1.0.0" + sort-package-json "^1.4.0" + symlink-or-copy "^1.1.8" + temp "0.8.3" + testem "^1.18.0" + tiny-lr "^1.0.3" + tree-sync "^1.2.1" + uuid "^3.0.0" + validate-npm-package-name "^3.0.0" + walk-sync "^0.3.0" + yam "0.0.22" + +ember-code-snippet@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/ember-code-snippet/-/ember-code-snippet-2.0.0.tgz#79e006c982411dc7eb21bf5a51feb66cd36d7f3b" + dependencies: + broccoli-flatiron "^0.0.0" + broccoli-merge-trees "^1.0.0" + broccoli-static-compiler "^0.1.4" + broccoli-writer "^0.1.1" + es6-promise "^1.0.0" + glob "^4.0.4" + +ember-export-application-global@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/ember-export-application-global/-/ember-export-application-global-2.0.0.tgz#8d6d7619ac8a1a3f8c43003549eb21ebed685bd2" + dependencies: + ember-cli-babel "^6.0.0-beta.7" + +ember-factory-for-polyfill@^1.1.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/ember-factory-for-polyfill/-/ember-factory-for-polyfill-1.2.0.tgz#e27752a7d9dbd5336e8b470341bc1c55bbe3e4d2" + dependencies: + ember-cli-version-checker "^1.2.0" + +ember-getowner-polyfill@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/ember-getowner-polyfill/-/ember-getowner-polyfill-2.0.1.tgz#9bfe2b4d527ed174e76fef2c8f30937d77cb66fb" + dependencies: + ember-cli-version-checker "^1.2.0" + ember-factory-for-polyfill "^1.1.0" + +ember-islands@^1.3.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/ember-islands/-/ember-islands-1.3.0.tgz#7914d7a638710b6a884b7e990ef0269c5f359059" + dependencies: + ember-cli-babel "^5.1.7" + ember-getowner-polyfill "^2.0.1" + +ember-load-initializers@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/ember-load-initializers/-/ember-load-initializers-1.0.0.tgz#4919eaf06f6dfeca7e134633d8c05a6c9921e6e7" + dependencies: + ember-cli-babel "^6.0.0-beta.7" + +ember-qunit@^2.2.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/ember-qunit/-/ember-qunit-2.2.0.tgz#3cdf400031c93a38de781a7304819738753b7f99" + dependencies: + ember-test-helpers "^0.6.3" + +ember-resolver@^4.0.0: + version "4.5.0" + resolved "https://registry.yarnpkg.com/ember-resolver/-/ember-resolver-4.5.0.tgz#9248bf534dfc197fafe3118fff538d436078bf99" + dependencies: + "@glimmer/resolver" "^0.4.1" + babel-plugin-debug-macros "^0.1.10" + broccoli-funnel "^1.1.0" + broccoli-merge-trees "^2.0.0" + ember-cli-babel "^6.8.1" + ember-cli-version-checker "^2.0.0" + resolve "^1.3.3" + +ember-rfc176-data@^0.2.7: + version "0.2.7" + resolved "https://registry.yarnpkg.com/ember-rfc176-data/-/ember-rfc176-data-0.2.7.tgz#bd355bc9b473e08096b518784170a23388bc973b" + +ember-router-generator@^1.0.0, ember-router-generator@^1.2.3: + version "1.2.3" + resolved "https://registry.yarnpkg.com/ember-router-generator/-/ember-router-generator-1.2.3.tgz#8ed2ca86ff323363120fc14278191e9e8f1315ee" + dependencies: + recast "^0.11.3" + +ember-source@~2.15.0: + version "2.15.0" + resolved "https://registry.yarnpkg.com/ember-source/-/ember-source-2.15.0.tgz#901cbe3abee09292372b06f6aa8dd342683be2d5" + dependencies: + "@glimmer/compiler" "^0.25.3" + "@glimmer/node" "^0.25.3" + "@glimmer/reference" "^0.25.3" + "@glimmer/runtime" "^0.25.3" + "@glimmer/util" "^0.25.3" + broccoli-funnel "^1.2.0" + broccoli-merge-trees "^2.0.0" + ember-cli-get-component-path-option "^1.0.0" + ember-cli-is-package-missing "^1.0.0" + ember-cli-normalize-entity-name "^1.0.0" + ember-cli-path-utils "^1.0.0" + ember-cli-string-utils "^1.1.0" + ember-cli-test-info "^1.0.0" + ember-cli-valid-component-name "^1.0.0" + ember-cli-version-checker "^1.3.1" + ember-router-generator "^1.2.3" + handlebars "^4.0.6" + jquery "^3.2.1" + resolve "^1.3.3" + rsvp "^3.6.1" + simple-dom "^0.3.0" + simple-html-tokenizer "^0.4.1" + +ember-test-helpers@^0.6.3: + version "0.6.3" + resolved "https://registry.yarnpkg.com/ember-test-helpers/-/ember-test-helpers-0.6.3.tgz#f864cdf6f4e75f3f8768d6537785b5ab6e82d907" + +ember-try-config@^2.0.1: + version "2.1.0" + resolved "https://registry.yarnpkg.com/ember-try-config/-/ember-try-config-2.1.0.tgz#e0e156229a542346a58ee6f6ad605104c98edfe0" + dependencies: + lodash "^4.6.1" + node-fetch "^1.3.3" + rsvp "^3.2.1" + semver "^5.1.0" + +ember-try@^0.2.15: + version "0.2.17" + resolved "https://registry.yarnpkg.com/ember-try/-/ember-try-0.2.17.tgz#0ffff687630291b4cf94f5b196e728c1a92d8aec" + dependencies: + chalk "^1.0.0" + cli-table2 "^0.2.0" + core-object "^1.1.0" + debug "^2.2.0" + ember-cli-version-checker "^1.1.6" + ember-try-config "^2.0.1" + extend "^3.0.0" + fs-extra "^0.26.0" + promise-map-series "^0.2.1" + resolve "^1.1.6" + rimraf "^2.3.2" + rsvp "^3.0.17" + semver "^5.1.0" + +"emberx-file-input@git+https://github.com/thefrontside/emberx-file-input.git#3eb72ecd8b151b804a7718d9f241f752cc8c206e": + version "1.1.2" + resolved "git+https://github.com/thefrontside/emberx-file-input.git#3eb72ecd8b151b804a7718d9f241f752cc8c206e" + dependencies: + chai-jquery "^2.0.0" + ember-cli-babel "^6.3.0" + ember-cli-htmlbars "^2.0.1" + +"emberx-file-reader@git+https://github.com/thefrontside/emberx-file-reader.git#ceee2b6837c033e5668ee531db0526fca32ba767": + version "0.0.0" + resolved "git+https://github.com/thefrontside/emberx-file-reader.git#ceee2b6837c033e5668ee531db0526fca32ba767" + dependencies: + ember-cli-babel "^6.3.0" + ember-cli-htmlbars "^2.0.1" + +"emberx-xml-http-request@git+https://github.com/thefrontside/emberx-xml-http-request.git#master": + version "0.2.3" + resolved "git+https://github.com/thefrontside/emberx-xml-http-request.git#bc3c04875fd9ddcad2e3ee1daded65f17032b2a3" + dependencies: + ember-cli-babel "^6.3.0" + ember-cli-htmlbars "^2.0.1" + x-request "^0.2.2" + +encodeurl@~1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/encodeurl/-/encodeurl-1.0.1.tgz#79e3d58655346909fe6f0f45a5de68103b294d20" + +encoding@^0.1.11: + version "0.1.12" + resolved "https://registry.yarnpkg.com/encoding/-/encoding-0.1.12.tgz#538b66f3ee62cd1ab51ec323829d1f9480c74beb" + dependencies: + iconv-lite "~0.4.13" + +engine.io-client@1.8.0: + version "1.8.0" + resolved "https://registry.yarnpkg.com/engine.io-client/-/engine.io-client-1.8.0.tgz#7b730e4127414087596d9be3c88d2bc5fdb6cf5c" + dependencies: + component-emitter "1.2.1" + component-inherit "0.0.3" + debug "2.3.3" + engine.io-parser "1.3.1" + has-cors "1.1.0" + indexof "0.0.1" + parsejson "0.0.3" + parseqs "0.0.5" + parseuri "0.0.5" + ws "1.1.1" + xmlhttprequest-ssl "1.5.3" + yeast "0.1.2" + +engine.io-parser@1.3.1: + version "1.3.1" + resolved "https://registry.yarnpkg.com/engine.io-parser/-/engine.io-parser-1.3.1.tgz#9554f1ae33107d6fbd170ca5466d2f833f6a07cf" + dependencies: + after "0.8.1" + arraybuffer.slice "0.0.6" + base64-arraybuffer "0.1.5" + blob "0.0.4" + has-binary "0.1.6" + wtf-8 "1.0.0" + +engine.io@1.8.0: + version "1.8.0" + resolved "https://registry.yarnpkg.com/engine.io/-/engine.io-1.8.0.tgz#3eeb5f264cb75dbbec1baaea26d61f5a4eace2aa" + dependencies: + accepts "1.3.3" + base64id "0.1.0" + cookie "0.3.1" + debug "2.3.3" + engine.io-parser "1.3.1" + ws "1.1.1" + +ensure-posix-path@^1.0.0, ensure-posix-path@^1.0.1, ensure-posix-path@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/ensure-posix-path/-/ensure-posix-path-1.0.2.tgz#a65b3e42d0b71cfc585eb774f9943c8d9b91b0c2" + +entities@~1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/entities/-/entities-1.1.1.tgz#6e5c2d0a5621b5dadaecef80b90edfb5cd7772f0" + +error@^7.0.0: + version "7.0.2" + resolved "https://registry.yarnpkg.com/error/-/error-7.0.2.tgz#a5f75fff4d9926126ddac0ea5dc38e689153cb02" + dependencies: + string-template "~0.2.1" + xtend "~4.0.0" + +es6-promise@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/es6-promise/-/es6-promise-1.0.0.tgz#f90d3629faa7c26166ae4df77c89bacdeb8dca7f" + +escape-html@~1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/escape-html/-/escape-html-1.0.3.tgz#0258eae4d3d0c0974de1c169188ef0051d1d1988" + +escape-string-regexp@^1.0.0, escape-string-regexp@^1.0.2, escape-string-regexp@^1.0.5: + version "1.0.5" + resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4" + +eslint-scope@^3.7.1: + version "3.7.1" + resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-3.7.1.tgz#3d63c3edfda02e06e01a452ad88caacc7cdcb6e8" + dependencies: + esrecurse "^4.1.0" + estraverse "^4.1.1" + +eslint@^4.0.0: + version "4.7.2" + resolved "https://registry.yarnpkg.com/eslint/-/eslint-4.7.2.tgz#ff6f5f5193848a27ee9b627be3e73fb9cb5e662e" + dependencies: + ajv "^5.2.0" + babel-code-frame "^6.22.0" + chalk "^2.1.0" + concat-stream "^1.6.0" + cross-spawn "^5.1.0" + debug "^3.0.1" + doctrine "^2.0.0" + eslint-scope "^3.7.1" + espree "^3.5.1" + esquery "^1.0.0" + estraverse "^4.2.0" + esutils "^2.0.2" + file-entry-cache "^2.0.0" + functional-red-black-tree "^1.0.1" + glob "^7.1.2" + globals "^9.17.0" + ignore "^3.3.3" + imurmurhash "^0.1.4" + inquirer "^3.0.6" + is-resolvable "^1.0.0" + js-yaml "^3.9.1" + json-stable-stringify "^1.0.1" + levn "^0.3.0" + lodash "^4.17.4" + minimatch "^3.0.2" + mkdirp "^0.5.1" + natural-compare "^1.4.0" + optionator "^0.8.2" + path-is-inside "^1.0.2" + pluralize "^7.0.0" + progress "^2.0.0" + require-uncached "^1.0.3" + semver "^5.3.0" + strip-ansi "^4.0.0" + strip-json-comments "~2.0.1" + table "^4.0.1" + text-table "~0.2.0" + +espree@^3.5.1: + version "3.5.1" + resolved "https://registry.yarnpkg.com/espree/-/espree-3.5.1.tgz#0c988b8ab46db53100a1954ae4ba995ddd27d87e" + dependencies: + acorn "^5.1.1" + acorn-jsx "^3.0.0" + +esprima-fb@~15001.1001.0-dev-harmony-fb: + version "15001.1001.0-dev-harmony-fb" + resolved "https://registry.yarnpkg.com/esprima-fb/-/esprima-fb-15001.1001.0-dev-harmony-fb.tgz#43beb57ec26e8cf237d3dd8b33e42533577f2659" + +esprima@^2.6.0: + version "2.7.3" + resolved "https://registry.yarnpkg.com/esprima/-/esprima-2.7.3.tgz#96e3b70d5779f6ad49cd032673d1c312767ba581" + +esprima@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/esprima/-/esprima-4.0.0.tgz#4499eddcd1110e0b218bacf2fa7f7f59f55ca804" + +esprima@~3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/esprima/-/esprima-3.0.0.tgz#53cf247acda77313e551c3aa2e73342d3fb4f7d9" + +esprima@~3.1.0: + version "3.1.3" + resolved "https://registry.yarnpkg.com/esprima/-/esprima-3.1.3.tgz#fdca51cee6133895e3c88d535ce49dbff62a4633" + +esquery@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/esquery/-/esquery-1.0.0.tgz#cfba8b57d7fba93f17298a8a006a04cda13d80fa" + dependencies: + estraverse "^4.0.0" + +esrecurse@^4.1.0: + version "4.2.0" + resolved "https://registry.yarnpkg.com/esrecurse/-/esrecurse-4.2.0.tgz#fa9568d98d3823f9a41d91e902dcab9ea6e5b163" + dependencies: + estraverse "^4.1.0" + object-assign "^4.0.1" + +estraverse@^4.0.0, estraverse@^4.1.0, estraverse@^4.1.1, estraverse@^4.2.0: + version "4.2.0" + resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-4.2.0.tgz#0dee3fed31fcd469618ce7342099fc1afa0bdb13" + +esutils@^2.0.0, esutils@^2.0.2: + version "2.0.2" + resolved "https://registry.yarnpkg.com/esutils/-/esutils-2.0.2.tgz#0abf4f1caa5bcb1f7a9d8acc6dea4faaa04bac9b" + +etag@~1.8.1: + version "1.8.1" + resolved "https://registry.yarnpkg.com/etag/-/etag-1.8.1.tgz#41ae2eeb65efa62268aebfea83ac7d79299b0887" + +eventemitter3@1.x.x: + version "1.2.0" + resolved "https://registry.yarnpkg.com/eventemitter3/-/eventemitter3-1.2.0.tgz#1c86991d816ad1e504750e73874224ecf3bec508" + +events-to-array@^1.0.1: + version "1.1.2" + resolved "https://registry.yarnpkg.com/events-to-array/-/events-to-array-1.1.2.tgz#2d41f563e1fe400ed4962fe1a4d5c6a7539df7f6" + +exec-sh@^0.2.0: + version "0.2.1" + resolved "https://registry.yarnpkg.com/exec-sh/-/exec-sh-0.2.1.tgz#163b98a6e89e6b65b47c2a28d215bc1f63989c38" + dependencies: + merge "^1.1.3" + +execa@^0.7.0: + version "0.7.0" + resolved "https://registry.yarnpkg.com/execa/-/execa-0.7.0.tgz#944becd34cc41ee32a63a9faf27ad5a65fc59777" + dependencies: + cross-spawn "^5.0.1" + get-stream "^3.0.0" + is-stream "^1.1.0" + npm-run-path "^2.0.0" + p-finally "^1.0.0" + signal-exit "^3.0.0" + strip-eof "^1.0.0" + +exists-stat@1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/exists-stat/-/exists-stat-1.0.0.tgz#0660e3525a2e89d9e446129440c272edfa24b529" + +exists-sync@0.0.3: + version "0.0.3" + resolved "https://registry.yarnpkg.com/exists-sync/-/exists-sync-0.0.3.tgz#b910000bedbb113b378b82f5f5a7638107622dcf" + +exists-sync@0.0.4: + version "0.0.4" + resolved "https://registry.yarnpkg.com/exists-sync/-/exists-sync-0.0.4.tgz#9744c2c428cc03b01060db454d4b12f0ef3c8879" + +exit-hook@^1.0.0: + version "1.1.1" + resolved "https://registry.yarnpkg.com/exit-hook/-/exit-hook-1.1.1.tgz#f05ca233b48c05d54fff07765df8507e95c02ff8" + +exit@^0.1.2: + version "0.1.2" + resolved "https://registry.yarnpkg.com/exit/-/exit-0.1.2.tgz#0632638f8d877cc82107d30a0fff1a17cba1cd0c" + +expand-brackets@^0.1.4: + version "0.1.5" + resolved "https://registry.yarnpkg.com/expand-brackets/-/expand-brackets-0.1.5.tgz#df07284e342a807cd733ac5af72411e581d1177b" + dependencies: + is-posix-bracket "^0.1.0" + +expand-range@^1.8.1: + version "1.8.2" + resolved "https://registry.yarnpkg.com/expand-range/-/expand-range-1.8.2.tgz#a299effd335fe2721ebae8e257ec79644fc85337" + dependencies: + fill-range "^2.1.0" + +expand-tilde@^1.2.2: + version "1.2.2" + resolved "https://registry.yarnpkg.com/expand-tilde/-/expand-tilde-1.2.2.tgz#0b81eba897e5a3d31d1c3d102f8f01441e559449" + dependencies: + os-homedir "^1.0.1" + +express@^4.10.7, express@^4.12.3: + version "4.16.0" + resolved "https://registry.yarnpkg.com/express/-/express-4.16.0.tgz#b519638e4eb58e7178c81b498ef22f798cb2e255" + dependencies: + accepts "~1.3.4" + array-flatten "1.1.1" + body-parser "1.18.2" + content-disposition "0.5.2" + content-type "~1.0.4" + cookie "0.3.1" + cookie-signature "1.0.6" + debug "2.6.9" + depd "~1.1.1" + encodeurl "~1.0.1" + escape-html "~1.0.3" + etag "~1.8.1" + finalhandler "1.1.0" + fresh "0.5.2" + merge-descriptors "1.0.1" + methods "~1.1.2" + on-finished "~2.3.0" + parseurl "~1.3.2" + path-to-regexp "0.1.7" + proxy-addr "~2.0.2" + qs "6.5.1" + range-parser "~1.2.0" + safe-buffer "5.1.1" + send "0.16.0" + serve-static "1.13.0" + setprototypeof "1.1.0" + statuses "~1.3.1" + type-is "~1.6.15" + utils-merge "1.0.1" + vary "~1.1.2" + +extend@^3.0.0, extend@~3.0.0: + version "3.0.1" + resolved "https://registry.yarnpkg.com/extend/-/extend-3.0.1.tgz#a755ea7bc1adfcc5a31ce7e762dbaadc5e636444" + +external-editor@^1.1.0: + version "1.1.1" + resolved "https://registry.yarnpkg.com/external-editor/-/external-editor-1.1.1.tgz#12d7b0db850f7ff7e7081baf4005700060c4600b" + dependencies: + extend "^3.0.0" + spawn-sync "^1.0.15" + tmp "^0.0.29" + +external-editor@^2.0.4: + version "2.0.5" + resolved "https://registry.yarnpkg.com/external-editor/-/external-editor-2.0.5.tgz#52c249a3981b9ba187c7cacf5beb50bf1d91a6bc" + dependencies: + iconv-lite "^0.4.17" + jschardet "^1.4.2" + tmp "^0.0.33" + +extglob@^0.3.1: + version "0.3.2" + resolved "https://registry.yarnpkg.com/extglob/-/extglob-0.3.2.tgz#2e18ff3d2f49ab2765cec9023f011daa8d8349a1" + dependencies: + is-extglob "^1.0.0" + +extsprintf@1.3.0, extsprintf@^1.2.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/extsprintf/-/extsprintf-1.3.0.tgz#96918440e3041a7a414f8c52e3c574eb3c3e1e05" + +fast-deep-equal@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-1.0.0.tgz#96256a3bc975595eb36d82e9929d060d893439ff" + +fast-levenshtein@~2.0.4: + version "2.0.6" + resolved "https://registry.yarnpkg.com/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz#3d8a5c66883a16a30ca8643e851f19baa7797917" + +fast-ordered-set@^1.0.0, fast-ordered-set@^1.0.2: + version "1.0.3" + resolved "https://registry.yarnpkg.com/fast-ordered-set/-/fast-ordered-set-1.0.3.tgz#3fbb36634f7be79e4f7edbdb4a357dee25d184eb" + dependencies: + blank-object "^1.0.1" + +fast-sourcemap-concat@^1.0.1: + version "1.2.3" + resolved "https://registry.yarnpkg.com/fast-sourcemap-concat/-/fast-sourcemap-concat-1.2.3.tgz#22f14e92d739e37920334376ec8433bf675eaa04" + dependencies: + chalk "^0.5.1" + fs-extra "^0.30.0" + heimdalljs-logger "^0.1.7" + memory-streams "^0.1.0" + mkdirp "^0.5.0" + rsvp "^3.0.14" + source-map "^0.4.2" + source-map-url "^0.3.0" + sourcemap-validator "^1.0.5" + +faye-websocket@~0.10.0: + version "0.10.0" + resolved "https://registry.yarnpkg.com/faye-websocket/-/faye-websocket-0.10.0.tgz#4e492f8d04dfb6f89003507f6edbf2d501e7c6f4" + dependencies: + websocket-driver ">=0.5.1" + +fb-watchman@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/fb-watchman/-/fb-watchman-2.0.0.tgz#54e9abf7dfa2f26cd9b1636c588c1afc05de5d58" + dependencies: + bser "^2.0.0" + +figures@^1.3.5: + version "1.7.0" + resolved "https://registry.yarnpkg.com/figures/-/figures-1.7.0.tgz#cbe1e3affcf1cd44b80cadfed28dc793a9701d2e" + dependencies: + escape-string-regexp "^1.0.5" + object-assign "^4.1.0" + +figures@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/figures/-/figures-2.0.0.tgz#3ab1a2d2a62c8bfb431a0c94cb797a2fce27c962" + dependencies: + escape-string-regexp "^1.0.5" + +file-entry-cache@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/file-entry-cache/-/file-entry-cache-2.0.0.tgz#c392990c3e684783d838b8c84a45d8a048458361" + dependencies: + flat-cache "^1.2.1" + object-assign "^4.0.1" + +filename-regex@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/filename-regex/-/filename-regex-2.0.1.tgz#c1c4b9bee3e09725ddb106b75c1e301fe2f18b26" + +filesize@^3.1.3: + version "3.5.10" + resolved "https://registry.yarnpkg.com/filesize/-/filesize-3.5.10.tgz#fc8fa23ddb4ef9e5e0ab6e1e64f679a24a56761f" + +fill-range@^2.1.0: + version "2.2.3" + resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-2.2.3.tgz#50b77dfd7e469bc7492470963699fe7a8485a723" + dependencies: + is-number "^2.1.0" + isobject "^2.0.0" + randomatic "^1.1.3" + repeat-element "^1.1.2" + repeat-string "^1.5.2" + +finalhandler@1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/finalhandler/-/finalhandler-1.1.0.tgz#ce0b6855b45853e791b2fcc680046d88253dd7f5" + dependencies: + debug "2.6.9" + encodeurl "~1.0.1" + escape-html "~1.0.3" + on-finished "~2.3.0" + parseurl "~1.3.2" + statuses "~1.3.1" + unpipe "~1.0.0" + +find-index@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/find-index/-/find-index-1.1.0.tgz#53007c79cd30040d6816d79458e8837d5c5705ef" + +find-up@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/find-up/-/find-up-2.1.0.tgz#45d1b7e506c717ddd482775a2b77920a3c0c57a7" + dependencies: + locate-path "^2.0.0" + +findup-sync@0.4.3, findup-sync@^0.4.2: + version "0.4.3" + resolved "https://registry.yarnpkg.com/findup-sync/-/findup-sync-0.4.3.tgz#40043929e7bc60adf0b7f4827c4c6e75a0deca12" + dependencies: + detect-file "^0.1.0" + is-glob "^2.0.1" + micromatch "^2.3.7" + resolve-dir "^0.1.0" + +fireworm@^0.7.0: + version "0.7.1" + resolved "https://registry.yarnpkg.com/fireworm/-/fireworm-0.7.1.tgz#ccf20f7941f108883fcddb99383dbe6e1861c758" + dependencies: + async "~0.2.9" + is-type "0.0.1" + lodash.debounce "^3.1.1" + lodash.flatten "^3.0.2" + minimatch "^3.0.2" + +flat-cache@^1.2.1: + version "1.3.0" + resolved "https://registry.yarnpkg.com/flat-cache/-/flat-cache-1.3.0.tgz#d3030b32b38154f4e3b7e9c709f490f7ef97c481" + dependencies: + circular-json "^0.3.1" + del "^2.0.2" + graceful-fs "^4.1.2" + write "^0.2.1" + +for-in@^1.0.1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/for-in/-/for-in-1.0.2.tgz#81068d295a8142ec0ac726c6e2200c30fb6d5e80" + +for-own@^0.1.4: + version "0.1.5" + resolved "https://registry.yarnpkg.com/for-own/-/for-own-0.1.5.tgz#5265c681a4f294dabbf17c9509b6763aa84510ce" + dependencies: + for-in "^1.0.1" + +forever-agent@~0.6.1: + version "0.6.1" + resolved "https://registry.yarnpkg.com/forever-agent/-/forever-agent-0.6.1.tgz#fbc71f0c41adeb37f96c577ad1ed42d8fdacca91" + +form-data@~2.1.1: + version "2.1.4" + resolved "https://registry.yarnpkg.com/form-data/-/form-data-2.1.4.tgz#33c183acf193276ecaa98143a69e94bfee1750d1" + dependencies: + asynckit "^0.4.0" + combined-stream "^1.0.5" + mime-types "^2.1.12" + +forwarded@~0.1.2: + version "0.1.2" + resolved "https://registry.yarnpkg.com/forwarded/-/forwarded-0.1.2.tgz#98c23dab1175657b8c0573e8ceccd91b0ff18c84" + +fresh@0.5.2: + version "0.5.2" + resolved "https://registry.yarnpkg.com/fresh/-/fresh-0.5.2.tgz#3d8cadd90d976569fa835ab1f8e4b23a105605a7" + +fs-exists-sync@^0.1.0: + version "0.1.0" + resolved "https://registry.yarnpkg.com/fs-exists-sync/-/fs-exists-sync-0.1.0.tgz#982d6893af918e72d08dec9e8673ff2b5a8d6add" + +fs-extra@^0.24.0: + version "0.24.0" + resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-0.24.0.tgz#d4e4342a96675cb7846633a6099249332b539952" + dependencies: + graceful-fs "^4.1.2" + jsonfile "^2.1.0" + path-is-absolute "^1.0.0" + rimraf "^2.2.8" + +fs-extra@^0.26.0: + version "0.26.7" + resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-0.26.7.tgz#9ae1fdd94897798edab76d0918cf42d0c3184fa9" + dependencies: + graceful-fs "^4.1.2" + jsonfile "^2.1.0" + klaw "^1.0.0" + path-is-absolute "^1.0.0" + rimraf "^2.2.8" + +fs-extra@^0.30.0: + version "0.30.0" + resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-0.30.0.tgz#f233ffcc08d4da7d432daa449776989db1df93f0" + dependencies: + graceful-fs "^4.1.2" + jsonfile "^2.1.0" + klaw "^1.0.0" + path-is-absolute "^1.0.0" + rimraf "^2.2.8" + +fs-extra@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-1.0.0.tgz#cd3ce5f7e7cb6145883fcae3191e9877f8587950" + dependencies: + graceful-fs "^4.1.2" + jsonfile "^2.1.0" + klaw "^1.0.0" + +fs-extra@^2.0.0: + version "2.1.2" + resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-2.1.2.tgz#046c70163cef9aad46b0e4a7fa467fb22d71de35" + dependencies: + graceful-fs "^4.1.2" + jsonfile "^2.1.0" + +fs-extra@^3.0.0: + version "3.0.1" + resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-3.0.1.tgz#3794f378c58b342ea7dbbb23095109c4b3b62291" + dependencies: + graceful-fs "^4.1.2" + jsonfile "^3.0.0" + universalify "^0.1.0" + +fs-readdir-recursive@^0.1.0: + version "0.1.2" + resolved "https://registry.yarnpkg.com/fs-readdir-recursive/-/fs-readdir-recursive-0.1.2.tgz#315b4fb8c1ca5b8c47defef319d073dad3568059" + +fs-tree-diff@^0.5.2, fs-tree-diff@^0.5.3, fs-tree-diff@^0.5.4, fs-tree-diff@^0.5.6: + version "0.5.6" + resolved "https://registry.yarnpkg.com/fs-tree-diff/-/fs-tree-diff-0.5.6.tgz#342665749e8dca406800b672268c8f5073f3e623" + dependencies: + heimdalljs-logger "^0.1.7" + object-assign "^4.1.0" + path-posix "^1.0.0" + symlink-or-copy "^1.1.8" + +fs.realpath@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f" + +fsevents@^1.0.0: + version "1.1.2" + resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-1.1.2.tgz#3282b713fb3ad80ede0e9fcf4611b5aa6fc033f4" + dependencies: + nan "^2.3.0" + node-pre-gyp "^0.6.36" + +fstream-ignore@^1.0.5: + version "1.0.5" + resolved "https://registry.yarnpkg.com/fstream-ignore/-/fstream-ignore-1.0.5.tgz#9c31dae34767018fe1d249b24dada67d092da105" + dependencies: + fstream "^1.0.0" + inherits "2" + minimatch "^3.0.0" + +fstream@^1.0.0, fstream@^1.0.10, fstream@^1.0.2: + version "1.0.11" + resolved "https://registry.yarnpkg.com/fstream/-/fstream-1.0.11.tgz#5c1fb1f117477114f0632a0eb4b71b3cb0fd3171" + dependencies: + graceful-fs "^4.1.2" + inherits "~2.0.0" + mkdirp ">=0.5 0" + rimraf "2" + +functional-red-black-tree@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz#1b0ab3bd553b2a0d6399d29c0e3ea0b252078327" + +gauge@~2.7.3: + version "2.7.4" + resolved "https://registry.yarnpkg.com/gauge/-/gauge-2.7.4.tgz#2c03405c7538c39d7eb37b317022e325fb018bf7" + dependencies: + aproba "^1.0.3" + console-control-strings "^1.0.0" + has-unicode "^2.0.0" + object-assign "^4.1.0" + signal-exit "^3.0.0" + string-width "^1.0.1" + strip-ansi "^3.0.1" + wide-align "^1.1.0" + +get-caller-file@^1.0.0: + version "1.0.2" + resolved "https://registry.yarnpkg.com/get-caller-file/-/get-caller-file-1.0.2.tgz#f702e63127e7e231c160a80c1554acb70d5047e5" + +get-stdin@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/get-stdin/-/get-stdin-4.0.1.tgz#b968c6b0a04384324902e8bf1a5df32579a450fe" + +get-stream@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-3.0.0.tgz#8e943d1358dc37555054ecbe2edb05aa174ede14" + +getpass@^0.1.1: + version "0.1.7" + resolved "https://registry.yarnpkg.com/getpass/-/getpass-0.1.7.tgz#5eff8e3e684d569ae4cb2b1282604e8ba62149fa" + dependencies: + assert-plus "^1.0.0" + +git-repo-info@^1.4.1: + version "1.4.1" + resolved "https://registry.yarnpkg.com/git-repo-info/-/git-repo-info-1.4.1.tgz#2a072823254aaf62fcf0766007d7b6651bd41943" + +git-repo-info@~1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/git-repo-info/-/git-repo-info-1.2.0.tgz#43d8513e04a24dd441330a2f7c6655a709fdbaf2" + +git-repo-version@0.4.1: + version "0.4.1" + resolved "https://registry.yarnpkg.com/git-repo-version/-/git-repo-version-0.4.1.tgz#75fab9a0a4ec8470755b0eea7fdaa6f9d41453bf" + dependencies: + git-repo-info "~1.2.0" + +glob-base@^0.3.0: + version "0.3.0" + resolved "https://registry.yarnpkg.com/glob-base/-/glob-base-0.3.0.tgz#dbb164f6221b1c0b1ccf82aea328b497df0ea3c4" + dependencies: + glob-parent "^2.0.0" + is-glob "^2.0.0" + +glob-parent@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-2.0.0.tgz#81383d72db054fcccf5336daa902f182f6edbb28" + dependencies: + is-glob "^2.0.0" + +glob@7.1.1: + version "7.1.1" + resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.1.tgz#805211df04faaf1c63a3600306cdf5ade50b2ec8" + dependencies: + fs.realpath "^1.0.0" + inflight "^1.0.4" + inherits "2" + minimatch "^3.0.2" + once "^1.3.0" + path-is-absolute "^1.0.0" + +glob@^4.0.4: + version "4.5.3" + resolved "https://registry.yarnpkg.com/glob/-/glob-4.5.3.tgz#c6cb73d3226c1efef04de3c56d012f03377ee15f" + dependencies: + inflight "^1.0.4" + inherits "2" + minimatch "^2.0.1" + once "^1.3.0" + +glob@^5.0.10, glob@^5.0.15: + version "5.0.15" + resolved "https://registry.yarnpkg.com/glob/-/glob-5.0.15.tgz#1bc936b9e02f4a603fcc222ecf7633d30b8b93b1" + dependencies: + inflight "^1.0.4" + inherits "2" + minimatch "2 || 3" + once "^1.3.0" + path-is-absolute "^1.0.0" + +glob@^7.0.3, glob@^7.0.4, glob@^7.0.5, glob@^7.1.2: + version "7.1.2" + resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.2.tgz#c19c9df9a028702d678612384a6552404c636d15" + dependencies: + fs.realpath "^1.0.0" + inflight "^1.0.4" + inherits "2" + minimatch "^3.0.4" + once "^1.3.0" + path-is-absolute "^1.0.0" + +global-modules@^0.2.3: + version "0.2.3" + resolved "https://registry.yarnpkg.com/global-modules/-/global-modules-0.2.3.tgz#ea5a3bed42c6d6ce995a4f8a1269b5dae223828d" + dependencies: + global-prefix "^0.1.4" + is-windows "^0.2.0" + +global-prefix@^0.1.4: + version "0.1.5" + resolved "https://registry.yarnpkg.com/global-prefix/-/global-prefix-0.1.5.tgz#8d3bc6b8da3ca8112a160d8d496ff0462bfef78f" + dependencies: + homedir-polyfill "^1.0.0" + ini "^1.3.4" + is-windows "^0.2.0" + which "^1.2.12" + +globals@^6.4.0: + version "6.4.1" + resolved "https://registry.yarnpkg.com/globals/-/globals-6.4.1.tgz#8498032b3b6d1cc81eebc5f79690d8fe29fabf4f" + +globals@^9.17.0, globals@^9.18.0: + version "9.18.0" + resolved "https://registry.yarnpkg.com/globals/-/globals-9.18.0.tgz#aa3896b3e69b487f17e31ed2143d69a8e30c2d8a" + +globby@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/globby/-/globby-5.0.0.tgz#ebd84667ca0dbb330b99bcfc68eac2bc54370e0d" + dependencies: + array-union "^1.0.1" + arrify "^1.0.0" + glob "^7.0.3" + object-assign "^4.0.1" + pify "^2.0.0" + pinkie-promise "^2.0.0" + +graceful-fs@^4.1.11, graceful-fs@^4.1.2, graceful-fs@^4.1.3, graceful-fs@^4.1.4, graceful-fs@^4.1.6, graceful-fs@^4.1.9: + version "4.1.11" + resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.1.11.tgz#0e8bdfe4d1ddb8854d64e04ea7c00e2a026e5658" + +"graceful-readlink@>= 1.0.0": + version "1.0.1" + resolved "https://registry.yarnpkg.com/graceful-readlink/-/graceful-readlink-1.0.1.tgz#4cafad76bc62f02fa039b2f94e9a3dd3a391a725" + +growly@^1.3.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/growly/-/growly-1.3.0.tgz#f10748cbe76af964b7c96c93c6bcc28af120c081" + +handlebars@^4.0.4, handlebars@^4.0.6: + version "4.0.10" + resolved "https://registry.yarnpkg.com/handlebars/-/handlebars-4.0.10.tgz#3d30c718b09a3d96f23ea4cc1f403c4d3ba9ff4f" + dependencies: + async "^1.4.0" + optimist "^0.6.1" + source-map "^0.4.4" + optionalDependencies: + uglify-js "^2.6" + +har-schema@^1.0.5: + version "1.0.5" + resolved "https://registry.yarnpkg.com/har-schema/-/har-schema-1.0.5.tgz#d263135f43307c02c602afc8fe95970c0151369e" + +har-validator@~4.2.1: + version "4.2.1" + resolved "https://registry.yarnpkg.com/har-validator/-/har-validator-4.2.1.tgz#33481d0f1bbff600dd203d75812a6a5fba002e2a" + dependencies: + ajv "^4.9.1" + har-schema "^1.0.5" + +has-ansi@^0.1.0: + version "0.1.0" + resolved "https://registry.yarnpkg.com/has-ansi/-/has-ansi-0.1.0.tgz#84f265aae8c0e6a88a12d7022894b7568894c62e" + dependencies: + ansi-regex "^0.2.0" + +has-ansi@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/has-ansi/-/has-ansi-2.0.0.tgz#34f5049ce1ecdf2b0649af3ef24e45ed35416d91" + dependencies: + ansi-regex "^2.0.0" + +has-binary@0.1.6: + version "0.1.6" + resolved "https://registry.yarnpkg.com/has-binary/-/has-binary-0.1.6.tgz#25326f39cfa4f616ad8787894e3af2cfbc7b6e10" + dependencies: + isarray "0.0.1" + +has-binary@0.1.7: + version "0.1.7" + resolved "https://registry.yarnpkg.com/has-binary/-/has-binary-0.1.7.tgz#68e61eb16210c9545a0a5cce06a873912fe1e68c" + dependencies: + isarray "0.0.1" + +has-cors@1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/has-cors/-/has-cors-1.1.0.tgz#5e474793f7ea9843d1bb99c23eef49ff126fff39" + +has-flag@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-2.0.0.tgz#e8207af1cc7b30d446cc70b734b5e8be18f88d51" + +has-unicode@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/has-unicode/-/has-unicode-2.0.1.tgz#e0e6fe6a28cf51138855e086d1691e771de2a8b9" + +hash-for-dep@^1.0.2: + version "1.2.0" + resolved "https://registry.yarnpkg.com/hash-for-dep/-/hash-for-dep-1.2.0.tgz#3bdb883aef0d34e82097ef2f7109b1b401cada6b" + dependencies: + broccoli-kitchen-sink-helpers "^0.3.1" + heimdalljs "^0.2.3" + heimdalljs-logger "^0.1.7" + resolve "^1.4.0" + +hawk@3.1.3, hawk@~3.1.3: + version "3.1.3" + resolved "https://registry.yarnpkg.com/hawk/-/hawk-3.1.3.tgz#078444bd7c1640b0fe540d2c9b73d59678e8e1c4" + dependencies: + boom "2.x.x" + cryptiles "2.x.x" + hoek "2.x.x" + sntp "1.x.x" + +heimdalljs-fs-monitor@^0.1.0: + version "0.1.0" + resolved "https://registry.yarnpkg.com/heimdalljs-fs-monitor/-/heimdalljs-fs-monitor-0.1.0.tgz#d404a65688c6714c485469ed3495da4853440272" + dependencies: + heimdalljs "^0.2.0" + heimdalljs-logger "^0.1.7" + +heimdalljs-graph@^0.3.1: + version "0.3.3" + resolved "https://registry.yarnpkg.com/heimdalljs-graph/-/heimdalljs-graph-0.3.3.tgz#ea801dbba659c8d522fe1cb83b2d605726e4918f" + +heimdalljs-logger@^0.1.7: + version "0.1.9" + resolved "https://registry.yarnpkg.com/heimdalljs-logger/-/heimdalljs-logger-0.1.9.tgz#d76ada4e45b7bb6f786fc9c010a68eb2e2faf176" + dependencies: + debug "^2.2.0" + heimdalljs "^0.2.0" + +heimdalljs@^0.2.0, heimdalljs@^0.2.1, heimdalljs@^0.2.3: + version "0.2.5" + resolved "https://registry.yarnpkg.com/heimdalljs/-/heimdalljs-0.2.5.tgz#6aa54308eee793b642cff9cf94781445f37730ac" + dependencies: + rsvp "~3.2.1" + +hoek@2.x.x: + version "2.16.3" + resolved "https://registry.yarnpkg.com/hoek/-/hoek-2.16.3.tgz#20bb7403d3cea398e91dc4710a8ff1b8274a25ed" + +home-or-tmp@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/home-or-tmp/-/home-or-tmp-1.0.0.tgz#4b9f1e40800c3e50c6c27f781676afcce71f3985" + dependencies: + os-tmpdir "^1.0.1" + user-home "^1.1.1" + +home-or-tmp@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/home-or-tmp/-/home-or-tmp-2.0.0.tgz#e36c3f2d2cae7d746a857e38d18d5f32a7882db8" + dependencies: + os-homedir "^1.0.0" + os-tmpdir "^1.0.1" + +homedir-polyfill@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/homedir-polyfill/-/homedir-polyfill-1.0.1.tgz#4c2bbc8a758998feebf5ed68580f76d46768b4bc" + dependencies: + parse-passwd "^1.0.0" + +hosted-git-info@^2.1.5: + version "2.5.0" + resolved "https://registry.yarnpkg.com/hosted-git-info/-/hosted-git-info-2.5.0.tgz#6d60e34b3abbc8313062c3b798ef8d901a07af3c" + +http-errors@1.6.2, http-errors@~1.6.2: + version "1.6.2" + resolved "https://registry.yarnpkg.com/http-errors/-/http-errors-1.6.2.tgz#0a002cc85707192a7e7946ceedc11155f60ec736" + dependencies: + depd "1.1.1" + inherits "2.0.3" + setprototypeof "1.0.3" + statuses ">= 1.3.1 < 2" + +http-parser-js@>=0.4.0: + version "0.4.8" + resolved "https://registry.yarnpkg.com/http-parser-js/-/http-parser-js-0.4.8.tgz#763f75c4b771a0bb44653b07070bff6ca7bc5561" + +http-proxy@^1.13.1, http-proxy@^1.9.0: + version "1.16.2" + resolved "https://registry.yarnpkg.com/http-proxy/-/http-proxy-1.16.2.tgz#06dff292952bf64dbe8471fa9df73066d4f37742" + dependencies: + eventemitter3 "1.x.x" + requires-port "1.x.x" + +http-signature@~1.1.0: + version "1.1.1" + resolved "https://registry.yarnpkg.com/http-signature/-/http-signature-1.1.1.tgz#df72e267066cd0ac67fb76adf8e134a8fbcf91bf" + dependencies: + assert-plus "^0.2.0" + jsprim "^1.2.2" + sshpk "^1.7.0" + +iconv-lite@0.4.19, iconv-lite@^0.4.17, iconv-lite@^0.4.5, iconv-lite@~0.4.13: + version "0.4.19" + resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.19.tgz#f7468f60135f5e5dad3399c0a81be9a1603a082b" + +ignore@^3.3.3: + version "3.3.5" + resolved "https://registry.yarnpkg.com/ignore/-/ignore-3.3.5.tgz#c4e715455f6073a8d7e5dae72d2fc9d71663dba6" + +imurmurhash@^0.1.4: + version "0.1.4" + resolved "https://registry.yarnpkg.com/imurmurhash/-/imurmurhash-0.1.4.tgz#9218b9b2b928a238b13dc4fb6b6d576f231453ea" + +indexof@0.0.1: + version "0.0.1" + resolved "https://registry.yarnpkg.com/indexof/-/indexof-0.0.1.tgz#82dc336d232b9062179d05ab3293a66059fd435d" + +inflection@^1.7.0, inflection@^1.7.1: + version "1.12.0" + resolved "https://registry.yarnpkg.com/inflection/-/inflection-1.12.0.tgz#a200935656d6f5f6bc4dc7502e1aecb703228416" + +inflight@^1.0.4: + version "1.0.6" + resolved "https://registry.yarnpkg.com/inflight/-/inflight-1.0.6.tgz#49bd6331d7d02d0c09bc910a1075ba8165b56df9" + dependencies: + once "^1.3.0" + wrappy "1" + +inherits@2, inherits@2.0.3, inherits@^2.0.1, inherits@^2.0.3, inherits@~2.0.0, inherits@~2.0.1, inherits@~2.0.3: + version "2.0.3" + resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.3.tgz#633c2c83e3da42a502f52466022480f4208261de" + +ini@^1.3.4, ini@~1.3.0: + version "1.3.4" + resolved "https://registry.yarnpkg.com/ini/-/ini-1.3.4.tgz#0537cb79daf59b59a1a517dff706c86ec039162e" + +inline-source-map-comment@^1.0.5: + version "1.0.5" + resolved "https://registry.yarnpkg.com/inline-source-map-comment/-/inline-source-map-comment-1.0.5.tgz#50a8a44c2a790dfac441b5c94eccd5462635faf6" + dependencies: + chalk "^1.0.0" + get-stdin "^4.0.1" + minimist "^1.1.1" + sum-up "^1.0.1" + xtend "^4.0.0" + +inquirer@^1.2.3: + version "1.2.3" + resolved "https://registry.yarnpkg.com/inquirer/-/inquirer-1.2.3.tgz#4dec6f32f37ef7bb0b2ed3f1d1a5c3f545074918" + dependencies: + ansi-escapes "^1.1.0" + chalk "^1.0.0" + cli-cursor "^1.0.1" + cli-width "^2.0.0" + external-editor "^1.1.0" + figures "^1.3.5" + lodash "^4.3.0" + mute-stream "0.0.6" + pinkie-promise "^2.0.0" + run-async "^2.2.0" + rx "^4.1.0" + string-width "^1.0.1" + strip-ansi "^3.0.0" + through "^2.3.6" + +inquirer@^3.0.6: + version "3.3.0" + resolved "https://registry.yarnpkg.com/inquirer/-/inquirer-3.3.0.tgz#9dd2f2ad765dcab1ff0443b491442a20ba227dc9" + dependencies: + ansi-escapes "^3.0.0" + chalk "^2.0.0" + cli-cursor "^2.1.0" + cli-width "^2.0.0" + external-editor "^2.0.4" + figures "^2.0.0" + lodash "^4.3.0" + mute-stream "0.0.7" + run-async "^2.2.0" + rx-lite "^4.0.8" + rx-lite-aggregates "^4.0.8" + string-width "^2.1.0" + strip-ansi "^4.0.0" + through "^2.3.6" + +invariant@^2.2.2: + version "2.2.2" + resolved "https://registry.yarnpkg.com/invariant/-/invariant-2.2.2.tgz#9e1f56ac0acdb6bf303306f338be3b204ae60360" + dependencies: + loose-envify "^1.0.0" + +invert-kv@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/invert-kv/-/invert-kv-1.0.0.tgz#104a8e4aaca6d3d8cd157a8ef8bfab2d7a3ffdb6" + +ipaddr.js@1.5.2: + version "1.5.2" + resolved "https://registry.yarnpkg.com/ipaddr.js/-/ipaddr.js-1.5.2.tgz#d4b505bde9946987ccf0fc58d9010ff9607e3fa0" + +is-binary-path@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/is-binary-path/-/is-binary-path-1.0.1.tgz#75f16642b480f187a711c814161fd3a4a7655898" + dependencies: + binary-extensions "^1.0.0" + +is-buffer@^1.1.5: + version "1.1.5" + resolved "https://registry.yarnpkg.com/is-buffer/-/is-buffer-1.1.5.tgz#1f3b26ef613b214b88cbca23cc6c01d87961eecc" + +is-dotfile@^1.0.0: + version "1.0.3" + resolved "https://registry.yarnpkg.com/is-dotfile/-/is-dotfile-1.0.3.tgz#a6a2f32ffd2dfb04f5ca25ecd0f6b83cf798a1e1" + +is-equal-shallow@^0.1.3: + version "0.1.3" + resolved "https://registry.yarnpkg.com/is-equal-shallow/-/is-equal-shallow-0.1.3.tgz#2238098fc221de0bcfa5d9eac4c45d638aa1c534" + dependencies: + is-primitive "^2.0.0" + +is-extendable@^0.1.1: + version "0.1.1" + resolved "https://registry.yarnpkg.com/is-extendable/-/is-extendable-0.1.1.tgz#62b110e289a471418e3ec36a617d472e301dfc89" + +is-extglob@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/is-extglob/-/is-extglob-1.0.0.tgz#ac468177c4943405a092fc8f29760c6ffc6206c0" + +is-finite@^1.0.0: + version "1.0.2" + resolved "https://registry.yarnpkg.com/is-finite/-/is-finite-1.0.2.tgz#cc6677695602be550ef11e8b4aa6305342b6d0aa" + dependencies: + number-is-nan "^1.0.0" + +is-fullwidth-code-point@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz#ef9e31386f031a7f0d643af82fde50c457ef00cb" + dependencies: + number-is-nan "^1.0.0" + +is-fullwidth-code-point@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz#a3b30a5c4f199183167aaab93beefae3ddfb654f" + +is-git-url@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/is-git-url/-/is-git-url-1.0.0.tgz#53f684cd143285b52c3244b4e6f28253527af66b" + +is-glob@^2.0.0, is-glob@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-2.0.1.tgz#d096f926a3ded5600f3fdfd91198cb0888c2d863" + dependencies: + is-extglob "^1.0.0" + +is-integer@^1.0.4: + version "1.0.7" + resolved "https://registry.yarnpkg.com/is-integer/-/is-integer-1.0.7.tgz#6bde81aacddf78b659b6629d629cadc51a886d5c" + dependencies: + is-finite "^1.0.0" + +is-number@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/is-number/-/is-number-2.1.0.tgz#01fcbbb393463a548f2f466cce16dece49db908f" + dependencies: + kind-of "^3.0.2" + +is-number@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/is-number/-/is-number-3.0.0.tgz#24fd6201a4782cf50561c810276afc7d12d71195" + dependencies: + kind-of "^3.0.2" + +is-obj@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/is-obj/-/is-obj-1.0.1.tgz#3e4729ac1f5fde025cd7d83a896dab9f4f67db0f" + +is-path-cwd@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/is-path-cwd/-/is-path-cwd-1.0.0.tgz#d225ec23132e89edd38fda767472e62e65f1106d" + +is-path-in-cwd@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/is-path-in-cwd/-/is-path-in-cwd-1.0.0.tgz#6477582b8214d602346094567003be8a9eac04dc" + dependencies: + is-path-inside "^1.0.0" + +is-path-inside@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/is-path-inside/-/is-path-inside-1.0.0.tgz#fc06e5a1683fbda13de667aff717bbc10a48f37f" + dependencies: + path-is-inside "^1.0.1" + +is-posix-bracket@^0.1.0: + version "0.1.1" + resolved "https://registry.yarnpkg.com/is-posix-bracket/-/is-posix-bracket-0.1.1.tgz#3334dc79774368e92f016e6fbc0a88f5cd6e6bc4" + +is-primitive@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/is-primitive/-/is-primitive-2.0.0.tgz#207bab91638499c07b2adf240a41a87210034575" + +is-promise@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/is-promise/-/is-promise-2.1.0.tgz#79a2a9ece7f096e80f36d2b2f3bc16c1ff4bf3fa" + +is-resolvable@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/is-resolvable/-/is-resolvable-1.0.0.tgz#8df57c61ea2e3c501408d100fb013cf8d6e0cc62" + dependencies: + tryit "^1.0.1" + +is-stream@^1.0.1, is-stream@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-1.1.0.tgz#12d4a3dd4e68e0b79ceb8dbc84173ae80d91ca44" + +is-type@0.0.1: + version "0.0.1" + resolved "https://registry.yarnpkg.com/is-type/-/is-type-0.0.1.tgz#f651d85c365d44955d14a51d8d7061f3f6b4779c" + dependencies: + core-util-is "~1.0.0" + +is-typedarray@~1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/is-typedarray/-/is-typedarray-1.0.0.tgz#e479c80858df0c1b11ddda6940f96011fcda4a9a" + +is-windows@^0.2.0: + version "0.2.0" + resolved "https://registry.yarnpkg.com/is-windows/-/is-windows-0.2.0.tgz#de1aa6d63ea29dd248737b69f1ff8b8002d2108c" + +isarray@0.0.1: + version "0.0.1" + resolved "https://registry.yarnpkg.com/isarray/-/isarray-0.0.1.tgz#8a18acfca9a8f4177e09abfc6038939b05d1eedf" + +isarray@1.0.0, isarray@^1.0.0, isarray@~1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/isarray/-/isarray-1.0.0.tgz#bb935d48582cba168c06834957a54a3e07124f11" + +isbinaryfile@^3.0.0: + version "3.0.2" + resolved "https://registry.yarnpkg.com/isbinaryfile/-/isbinaryfile-3.0.2.tgz#4a3e974ec0cba9004d3fc6cde7209ea69368a621" + +isexe@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10" + +isobject@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/isobject/-/isobject-2.1.0.tgz#f065561096a3f1da2ef46272f815c840d87e0c89" + dependencies: + isarray "1.0.0" + +isstream@~0.1.2: + version "0.1.2" + resolved "https://registry.yarnpkg.com/isstream/-/isstream-0.1.2.tgz#47e63f7af55afa6f92e1500e690eb8b8529c099a" + +istextorbinary@2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/istextorbinary/-/istextorbinary-2.1.0.tgz#dbed2a6f51be2f7475b68f89465811141b758874" + dependencies: + binaryextensions "1 || 2" + editions "^1.1.1" + textextensions "1 || 2" + +jquery@^3.2.1: + version "3.2.1" + resolved "https://registry.yarnpkg.com/jquery/-/jquery-3.2.1.tgz#5c4d9de652af6cd0a770154a631bba12b015c787" + +js-reporters@1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/js-reporters/-/js-reporters-1.2.0.tgz#7cf2cb698196684790350d0c4ca07f4aed9ec17e" + +js-tokens@1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-1.0.1.tgz#cc435a5c8b94ad15acb7983140fc80182c89aeae" + +js-tokens@^3.0.0, js-tokens@^3.0.2: + version "3.0.2" + resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-3.0.2.tgz#9866df395102130e38f7f996bceb65443209c25b" + +js-yaml@^3.2.5, js-yaml@^3.2.7, js-yaml@^3.6.1, js-yaml@^3.9.1: + version "3.10.0" + resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.10.0.tgz#2e78441646bd4682e963f22b6e92823c309c62dc" + dependencies: + argparse "^1.0.7" + esprima "^4.0.0" + +jsbn@~0.1.0: + version "0.1.1" + resolved "https://registry.yarnpkg.com/jsbn/-/jsbn-0.1.1.tgz#a5e654c2e5a2deb5f201d96cefbca80c0ef2f513" + +jschardet@^1.4.2: + version "1.5.1" + resolved "https://registry.yarnpkg.com/jschardet/-/jschardet-1.5.1.tgz#c519f629f86b3a5bedba58a88d311309eec097f9" + +jsesc@^1.3.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-1.3.0.tgz#46c3fec8c1892b12b0833db9bc7622176dbab34b" + +jsesc@^2.5.0: + version "2.5.1" + resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-2.5.1.tgz#e421a2a8e20d6b0819df28908f782526b96dd1fe" + +jsesc@~0.3.x: + version "0.3.0" + resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-0.3.0.tgz#1bf5ee63b4539fe2e26d0c1e99c240b97a457972" + +jsesc@~0.5.0: + version "0.5.0" + resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-0.5.0.tgz#e7dee66e35d6fc16f710fe91d5cf69f70f08911d" + +json-schema-traverse@^0.3.0: + version "0.3.1" + resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-0.3.1.tgz#349a6d44c53a51de89b40805c5d5e59b417d3340" + +json-schema@0.2.3: + version "0.2.3" + resolved "https://registry.yarnpkg.com/json-schema/-/json-schema-0.2.3.tgz#b480c892e59a2f05954ce727bd3f2a4e882f9e13" + +json-stable-stringify@^1.0.0, json-stable-stringify@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/json-stable-stringify/-/json-stable-stringify-1.0.1.tgz#9a759d39c5f2ff503fd5300646ed445f88c4f9af" + dependencies: + jsonify "~0.0.0" + +json-stringify-safe@~5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz#1296a2d58fd45f19a0f6ce01d65701e2c735b6eb" + +json3@3.3.2: + version "3.3.2" + resolved "https://registry.yarnpkg.com/json3/-/json3-3.3.2.tgz#3c0434743df93e2f5c42aee7b19bcb483575f4e1" + +json5@^0.4.0: + version "0.4.0" + resolved "https://registry.yarnpkg.com/json5/-/json5-0.4.0.tgz#054352e4c4c80c86c0923877d449de176a732c8d" + +json5@^0.5.1: + version "0.5.1" + resolved "https://registry.yarnpkg.com/json5/-/json5-0.5.1.tgz#1eade7acc012034ad84e2396767ead9fa5495821" + +jsonfile@^2.1.0: + version "2.4.0" + resolved "https://registry.yarnpkg.com/jsonfile/-/jsonfile-2.4.0.tgz#3736a2b428b87bbda0cc83b53fa3d633a35c2ae8" + optionalDependencies: + graceful-fs "^4.1.6" + +jsonfile@^3.0.0: + version "3.0.1" + resolved "https://registry.yarnpkg.com/jsonfile/-/jsonfile-3.0.1.tgz#a5ecc6f65f53f662c4415c7675a0331d0992ec66" + optionalDependencies: + graceful-fs "^4.1.6" + +jsonify@~0.0.0: + version "0.0.0" + resolved "https://registry.yarnpkg.com/jsonify/-/jsonify-0.0.0.tgz#2c74b6ee41d93ca51b7b5aaee8f503631d252a73" + +jsprim@^1.2.2: + version "1.4.1" + resolved "https://registry.yarnpkg.com/jsprim/-/jsprim-1.4.1.tgz#313e66bc1e5cc06e438bc1b7499c2e5c56acb6a2" + dependencies: + assert-plus "1.0.0" + extsprintf "1.3.0" + json-schema "0.2.3" + verror "1.10.0" + +kind-of@^3.0.2: + version "3.2.2" + resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-3.2.2.tgz#31ea21a734bab9bbb0f32466d893aea51e4a3c64" + dependencies: + is-buffer "^1.1.5" + +kind-of@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-4.0.0.tgz#20813df3d712928b207378691a45066fae72dd57" + dependencies: + is-buffer "^1.1.5" + +klaw@^1.0.0: + version "1.3.1" + resolved "https://registry.yarnpkg.com/klaw/-/klaw-1.3.1.tgz#4088433b46b3b1ba259d78785d8e96f73ba02439" + optionalDependencies: + graceful-fs "^4.1.9" + +lazy-cache@^1.0.3: + version "1.0.4" + resolved "https://registry.yarnpkg.com/lazy-cache/-/lazy-cache-1.0.4.tgz#a1d78fc3a50474cb80845d3b3b6e1da49a446e8e" + +lcid@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/lcid/-/lcid-1.0.0.tgz#308accafa0bc483a3867b4b6f2b9506251d1b835" + dependencies: + invert-kv "^1.0.0" + +leek@0.0.24: + version "0.0.24" + resolved "https://registry.yarnpkg.com/leek/-/leek-0.0.24.tgz#e400e57f0e60d8ef2bd4d068dc428a54345dbcda" + dependencies: + debug "^2.1.0" + lodash.assign "^3.2.0" + rsvp "^3.0.21" + +leven@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/leven/-/leven-1.0.2.tgz#9144b6eebca5f1d0680169f1a6770dcea60b75c3" + +levn@^0.3.0, levn@~0.3.0: + version "0.3.0" + resolved "https://registry.yarnpkg.com/levn/-/levn-0.3.0.tgz#3b09924edf9f083c0490fdd4c0bc4421e04764ee" + dependencies: + prelude-ls "~1.1.2" + type-check "~0.3.2" + +linkify-it@^2.0.0: + version "2.0.3" + resolved "https://registry.yarnpkg.com/linkify-it/-/linkify-it-2.0.3.tgz#d94a4648f9b1c179d64fa97291268bdb6ce9434f" + dependencies: + uc.micro "^1.0.1" + +livereload-js@^2.2.2: + version "2.2.2" + resolved "https://registry.yarnpkg.com/livereload-js/-/livereload-js-2.2.2.tgz#6c87257e648ab475bc24ea257457edcc1f8d0bc2" + +loader.js@^4.2.3: + version "4.6.0" + resolved "https://registry.yarnpkg.com/loader.js/-/loader.js-4.6.0.tgz#b965663ddbe2d80da482454cb865efe496e93e22" + +locate-path@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-2.0.0.tgz#2b568b265eec944c6d9c0de9c3dbbbca0354cd8e" + dependencies: + p-locate "^2.0.0" + path-exists "^3.0.0" + +lodash._baseassign@^3.0.0: + version "3.2.0" + resolved "https://registry.yarnpkg.com/lodash._baseassign/-/lodash._baseassign-3.2.0.tgz#8c38a099500f215ad09e59f1722fd0c52bfe0a4e" + dependencies: + lodash._basecopy "^3.0.0" + lodash.keys "^3.0.0" + +lodash._basebind@~2.3.0: + version "2.3.0" + resolved "https://registry.yarnpkg.com/lodash._basebind/-/lodash._basebind-2.3.0.tgz#2b5bc452a0e106143b21869f233bdb587417d248" + dependencies: + lodash._basecreate "~2.3.0" + lodash._setbinddata "~2.3.0" + lodash.isobject "~2.3.0" + +lodash._basecopy@^3.0.0: + version "3.0.1" + resolved "https://registry.yarnpkg.com/lodash._basecopy/-/lodash._basecopy-3.0.1.tgz#8da0e6a876cf344c0ad8a54882111dd3c5c7ca36" + +lodash._basecreate@~2.3.0: + version "2.3.0" + resolved "https://registry.yarnpkg.com/lodash._basecreate/-/lodash._basecreate-2.3.0.tgz#9b88a86a4dcff7b7f3c61d83a2fcfc0671ec9de0" + dependencies: + lodash._renative "~2.3.0" + lodash.isobject "~2.3.0" + lodash.noop "~2.3.0" + +lodash._basecreatecallback@~2.3.0: + version "2.3.0" + resolved "https://registry.yarnpkg.com/lodash._basecreatecallback/-/lodash._basecreatecallback-2.3.0.tgz#37b2ab17591a339e988db3259fcd46019d7ac362" + dependencies: + lodash._setbinddata "~2.3.0" + lodash.bind "~2.3.0" + lodash.identity "~2.3.0" + lodash.support "~2.3.0" + +lodash._basecreatewrapper@~2.3.0: + version "2.3.0" + resolved "https://registry.yarnpkg.com/lodash._basecreatewrapper/-/lodash._basecreatewrapper-2.3.0.tgz#aa0c61ad96044c3933376131483a9759c3651247" + dependencies: + lodash._basecreate "~2.3.0" + lodash._setbinddata "~2.3.0" + lodash._slice "~2.3.0" + lodash.isobject "~2.3.0" + +lodash._baseflatten@^3.0.0: + version "3.1.4" + resolved "https://registry.yarnpkg.com/lodash._baseflatten/-/lodash._baseflatten-3.1.4.tgz#0770ff80131af6e34f3b511796a7ba5214e65ff7" + dependencies: + lodash.isarguments "^3.0.0" + lodash.isarray "^3.0.0" + +lodash._bindcallback@^3.0.0: + version "3.0.1" + resolved "https://registry.yarnpkg.com/lodash._bindcallback/-/lodash._bindcallback-3.0.1.tgz#e531c27644cf8b57a99e17ed95b35c748789392e" + +lodash._createassigner@^3.0.0: + version "3.1.1" + resolved "https://registry.yarnpkg.com/lodash._createassigner/-/lodash._createassigner-3.1.1.tgz#838a5bae2fdaca63ac22dee8e19fa4e6d6970b11" + dependencies: + lodash._bindcallback "^3.0.0" + lodash._isiterateecall "^3.0.0" + lodash.restparam "^3.0.0" + +lodash._createwrapper@~2.3.0: + version "2.3.0" + resolved "https://registry.yarnpkg.com/lodash._createwrapper/-/lodash._createwrapper-2.3.0.tgz#d1aae1102dadf440e8e06fc133a6edd7fe146075" + dependencies: + lodash._basebind "~2.3.0" + lodash._basecreatewrapper "~2.3.0" + lodash.isfunction "~2.3.0" + +lodash._escapehtmlchar@~2.3.0: + version "2.3.0" + resolved "https://registry.yarnpkg.com/lodash._escapehtmlchar/-/lodash._escapehtmlchar-2.3.0.tgz#d03da6bd82eedf38dc0a5b503d740ecd0e894592" + dependencies: + lodash._htmlescapes "~2.3.0" + +lodash._escapestringchar@~2.3.0: + version "2.3.0" + resolved "https://registry.yarnpkg.com/lodash._escapestringchar/-/lodash._escapestringchar-2.3.0.tgz#cce73ae60fc6da55d2bf8a0679c23ca2bab149fc" + +lodash._getnative@^3.0.0: + version "3.9.1" + resolved "https://registry.yarnpkg.com/lodash._getnative/-/lodash._getnative-3.9.1.tgz#570bc7dede46d61cdcde687d65d3eecbaa3aaff5" + +lodash._htmlescapes@~2.3.0: + version "2.3.0" + resolved "https://registry.yarnpkg.com/lodash._htmlescapes/-/lodash._htmlescapes-2.3.0.tgz#1ca98863cadf1fa1d82c84f35f31e40556a04f3a" + +lodash._isiterateecall@^3.0.0: + version "3.0.9" + resolved "https://registry.yarnpkg.com/lodash._isiterateecall/-/lodash._isiterateecall-3.0.9.tgz#5203ad7ba425fae842460e696db9cf3e6aac057c" + +lodash._objecttypes@~2.3.0: + version "2.3.0" + resolved "https://registry.yarnpkg.com/lodash._objecttypes/-/lodash._objecttypes-2.3.0.tgz#6a3ea3987dd6eeb8021b2d5c9c303549cc2bae1e" + +lodash._reinterpolate@~2.3.0: + version "2.3.0" + resolved "https://registry.yarnpkg.com/lodash._reinterpolate/-/lodash._reinterpolate-2.3.0.tgz#03ee9d85c0e55cbd590d71608a295bdda51128ec" + +lodash._reinterpolate@~3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/lodash._reinterpolate/-/lodash._reinterpolate-3.0.0.tgz#0ccf2d89166af03b3663c796538b75ac6e114d9d" + +lodash._renative@~2.3.0: + version "2.3.0" + resolved "https://registry.yarnpkg.com/lodash._renative/-/lodash._renative-2.3.0.tgz#77d8edd4ced26dd5971f9e15a5f772e4e317fbd3" + +lodash._reunescapedhtml@~2.3.0: + version "2.3.0" + resolved "https://registry.yarnpkg.com/lodash._reunescapedhtml/-/lodash._reunescapedhtml-2.3.0.tgz#db920b55ac7f3ff825939aceb9ba2c231713d24d" + dependencies: + lodash._htmlescapes "~2.3.0" + lodash.keys "~2.3.0" + +lodash._setbinddata@~2.3.0: + version "2.3.0" + resolved "https://registry.yarnpkg.com/lodash._setbinddata/-/lodash._setbinddata-2.3.0.tgz#e5610490acd13277d59858d95b5f2727f1508f04" + dependencies: + lodash._renative "~2.3.0" + lodash.noop "~2.3.0" + +lodash._shimkeys@~2.3.0: + version "2.3.0" + resolved "https://registry.yarnpkg.com/lodash._shimkeys/-/lodash._shimkeys-2.3.0.tgz#611f93149e3e6c721096b48769ef29537ada8ba9" + dependencies: + lodash._objecttypes "~2.3.0" + +lodash._slice@~2.3.0: + version "2.3.0" + resolved "https://registry.yarnpkg.com/lodash._slice/-/lodash._slice-2.3.0.tgz#147198132859972e4680ca29a5992c855669aa5c" + +lodash.assign@^3.2.0: + version "3.2.0" + resolved "https://registry.yarnpkg.com/lodash.assign/-/lodash.assign-3.2.0.tgz#3ce9f0234b4b2223e296b8fa0ac1fee8ebca64fa" + dependencies: + lodash._baseassign "^3.0.0" + lodash._createassigner "^3.0.0" + lodash.keys "^3.0.0" + +lodash.assignin@^4.1.0: + version "4.2.0" + resolved "https://registry.yarnpkg.com/lodash.assignin/-/lodash.assignin-4.2.0.tgz#ba8df5fb841eb0a3e8044232b0e263a8dc6a28a2" + +lodash.bind@~2.3.0: + version "2.3.0" + resolved "https://registry.yarnpkg.com/lodash.bind/-/lodash.bind-2.3.0.tgz#c2a8e18b68e5ecc152e2b168266116fea5b016cc" + dependencies: + lodash._createwrapper "~2.3.0" + lodash._renative "~2.3.0" + lodash._slice "~2.3.0" + +lodash.clonedeep@^4.4.1: + version "4.5.0" + resolved "https://registry.yarnpkg.com/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz#e23f3f9c4f8fbdde872529c1071857a086e5ccef" + +lodash.debounce@^3.1.1: + version "3.1.1" + resolved "https://registry.yarnpkg.com/lodash.debounce/-/lodash.debounce-3.1.1.tgz#812211c378a94cc29d5aa4e3346cf0bfce3a7df5" + dependencies: + lodash._getnative "^3.0.0" + +lodash.defaults@~2.3.0: + version "2.3.0" + resolved "https://registry.yarnpkg.com/lodash.defaults/-/lodash.defaults-2.3.0.tgz#a832b001f138f3bb9721c2819a2a7cc5ae21ed25" + dependencies: + lodash._objecttypes "~2.3.0" + lodash.keys "~2.3.0" + +lodash.defaultsdeep@^4.6.0: + version "4.6.0" + resolved "https://registry.yarnpkg.com/lodash.defaultsdeep/-/lodash.defaultsdeep-4.6.0.tgz#bec1024f85b1bd96cbea405b23c14ad6443a6f81" + +lodash.escape@~2.3.0: + version "2.3.0" + resolved "https://registry.yarnpkg.com/lodash.escape/-/lodash.escape-2.3.0.tgz#844c38c58f844e1362ebe96726159b62cf5f2a58" + dependencies: + lodash._escapehtmlchar "~2.3.0" + lodash._reunescapedhtml "~2.3.0" + lodash.keys "~2.3.0" + +lodash.find@^4.5.1: + version "4.6.0" + resolved "https://registry.yarnpkg.com/lodash.find/-/lodash.find-4.6.0.tgz#cb0704d47ab71789ffa0de8b97dd926fb88b13b1" + +lodash.flatten@^3.0.2: + version "3.0.2" + resolved "https://registry.yarnpkg.com/lodash.flatten/-/lodash.flatten-3.0.2.tgz#de1cf57758f8f4479319d35c3e9cc60c4501938c" + dependencies: + lodash._baseflatten "^3.0.0" + lodash._isiterateecall "^3.0.0" + +lodash.foreach@~2.3.x: + version "2.3.0" + resolved "https://registry.yarnpkg.com/lodash.foreach/-/lodash.foreach-2.3.0.tgz#083404c91e846ee77245fdf9d76519c68b2af168" + dependencies: + lodash._basecreatecallback "~2.3.0" + lodash.forown "~2.3.0" + +lodash.forown@~2.3.0: + version "2.3.0" + resolved "https://registry.yarnpkg.com/lodash.forown/-/lodash.forown-2.3.0.tgz#24fb4aaf800d45fc2dc60bfec3ce04c836a3ad7f" + dependencies: + lodash._basecreatecallback "~2.3.0" + lodash._objecttypes "~2.3.0" + lodash.keys "~2.3.0" + +lodash.identity@~2.3.0: + version "2.3.0" + resolved "https://registry.yarnpkg.com/lodash.identity/-/lodash.identity-2.3.0.tgz#6b01a210c9485355c2a913b48b6711219a173ded" + +lodash.isarguments@^3.0.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/lodash.isarguments/-/lodash.isarguments-3.1.0.tgz#2f573d85c6a24289ff00663b491c1d338ff3458a" + +lodash.isarray@^3.0.0: + version "3.0.4" + resolved "https://registry.yarnpkg.com/lodash.isarray/-/lodash.isarray-3.0.4.tgz#79e4eb88c36a8122af86f844aa9bcd851b5fbb55" + +lodash.isfunction@~2.3.0: + version "2.3.0" + resolved "https://registry.yarnpkg.com/lodash.isfunction/-/lodash.isfunction-2.3.0.tgz#6b2973e47a647cf12e70d676aea13643706e5267" + +lodash.isobject@~2.3.0: + version "2.3.0" + resolved "https://registry.yarnpkg.com/lodash.isobject/-/lodash.isobject-2.3.0.tgz#2e16d3fc583da9831968953f2d8e6d73434f6799" + dependencies: + lodash._objecttypes "~2.3.0" + +lodash.keys@^3.0.0: + version "3.1.2" + resolved "https://registry.yarnpkg.com/lodash.keys/-/lodash.keys-3.1.2.tgz#4dbc0472b156be50a0b286855d1bd0b0c656098a" + dependencies: + lodash._getnative "^3.0.0" + lodash.isarguments "^3.0.0" + lodash.isarray "^3.0.0" + +lodash.keys@~2.3.0: + version "2.3.0" + resolved "https://registry.yarnpkg.com/lodash.keys/-/lodash.keys-2.3.0.tgz#b350f4f92caa9f45a4a2ecf018454cf2f28ae253" + dependencies: + lodash._renative "~2.3.0" + lodash._shimkeys "~2.3.0" + lodash.isobject "~2.3.0" + +lodash.merge@^4.3.0, lodash.merge@^4.4.0, lodash.merge@^4.5.1, lodash.merge@^4.6.0: + version "4.6.0" + resolved "https://registry.yarnpkg.com/lodash.merge/-/lodash.merge-4.6.0.tgz#69884ba144ac33fe699737a6086deffadd0f89c5" + +lodash.noop@~2.3.0: + version "2.3.0" + resolved "https://registry.yarnpkg.com/lodash.noop/-/lodash.noop-2.3.0.tgz#3059d628d51bbf937cd2a0b6fc3a7f212a669c2c" + +lodash.omit@^4.1.0: + version "4.5.0" + resolved "https://registry.yarnpkg.com/lodash.omit/-/lodash.omit-4.5.0.tgz#6eb19ae5a1ee1dd9df0b969e66ce0b7fa30b5e60" + +lodash.restparam@^3.0.0: + version "3.6.1" + resolved "https://registry.yarnpkg.com/lodash.restparam/-/lodash.restparam-3.6.1.tgz#936a4e309ef330a7645ed4145986c85ae5b20805" + +lodash.support@~2.3.0: + version "2.3.0" + resolved "https://registry.yarnpkg.com/lodash.support/-/lodash.support-2.3.0.tgz#7eaf038af4f0d6aab776b44aa6dcfc80334c9bfd" + dependencies: + lodash._renative "~2.3.0" + +lodash.template@^4.2.5: + version "4.4.0" + resolved "https://registry.yarnpkg.com/lodash.template/-/lodash.template-4.4.0.tgz#e73a0385c8355591746e020b99679c690e68fba0" + dependencies: + lodash._reinterpolate "~3.0.0" + lodash.templatesettings "^4.0.0" + +lodash.template@~2.3.x: + version "2.3.0" + resolved "https://registry.yarnpkg.com/lodash.template/-/lodash.template-2.3.0.tgz#4e3e29c433b4cfea675ec835e6f12391c61fd22b" + dependencies: + lodash._escapestringchar "~2.3.0" + lodash._reinterpolate "~2.3.0" + lodash.defaults "~2.3.0" + lodash.escape "~2.3.0" + lodash.keys "~2.3.0" + lodash.templatesettings "~2.3.0" + lodash.values "~2.3.0" + +lodash.templatesettings@^4.0.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/lodash.templatesettings/-/lodash.templatesettings-4.1.0.tgz#2b4d4e95ba440d915ff08bc899e4553666713316" + dependencies: + lodash._reinterpolate "~3.0.0" + +lodash.templatesettings@~2.3.0: + version "2.3.0" + resolved "https://registry.yarnpkg.com/lodash.templatesettings/-/lodash.templatesettings-2.3.0.tgz#303d132c342710040d5a18efaa2d572fd03f8cdc" + dependencies: + lodash._reinterpolate "~2.3.0" + lodash.escape "~2.3.0" + +lodash.uniq@^4.2.0: + version "4.5.0" + resolved "https://registry.yarnpkg.com/lodash.uniq/-/lodash.uniq-4.5.0.tgz#d0225373aeb652adc1bc82e4945339a842754773" + +lodash.uniqby@^4.7.0: + version "4.7.0" + resolved "https://registry.yarnpkg.com/lodash.uniqby/-/lodash.uniqby-4.7.0.tgz#d99c07a669e9e6d24e1362dfe266c67616af1302" + +lodash.values@~2.3.0: + version "2.3.0" + resolved "https://registry.yarnpkg.com/lodash.values/-/lodash.values-2.3.0.tgz#ca96fbe60a20b0b0ec2ba2ba5fc6a765bd14a3ba" + dependencies: + lodash.keys "~2.3.0" + +lodash@^3.10.0, lodash@^3.10.1, lodash@^3.9.3: + version "3.10.1" + resolved "https://registry.yarnpkg.com/lodash/-/lodash-3.10.1.tgz#5bf45e8e49ba4189e17d482789dfd15bd140b7b6" + +lodash@^4.14.0, lodash@^4.17.4, lodash@^4.3.0, lodash@^4.6.1: + version "4.17.4" + resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.4.tgz#78203a4d1c328ae1d86dca6460e369b57f4055ae" + +longest@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/longest/-/longest-1.0.1.tgz#30a0b2da38f73770e8294a0d22e6625ed77d0097" + +loose-envify@^1.0.0: + version "1.3.1" + resolved "https://registry.yarnpkg.com/loose-envify/-/loose-envify-1.3.1.tgz#d1a8ad33fa9ce0e713d65fdd0ac8b748d478c848" + dependencies: + js-tokens "^3.0.0" + +lru-cache@^4.0.1: + version "4.1.1" + resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-4.1.1.tgz#622e32e82488b49279114a4f9ecf45e7cd6bba55" + dependencies: + pseudomap "^1.0.2" + yallist "^2.1.2" + +make-dir@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/make-dir/-/make-dir-1.0.0.tgz#97a011751e91dd87cfadef58832ebb04936de978" + dependencies: + pify "^2.3.0" + +makeerror@1.0.x: + version "1.0.11" + resolved "https://registry.yarnpkg.com/makeerror/-/makeerror-1.0.11.tgz#e01a5c9109f2af79660e4e8b9587790184f5a96c" + dependencies: + tmpl "1.0.x" + +markdown-it-terminal@0.1.0: + version "0.1.0" + resolved "https://registry.yarnpkg.com/markdown-it-terminal/-/markdown-it-terminal-0.1.0.tgz#545abd8dd01c3d62353bfcea71db580b51d22bd9" + dependencies: + ansi-styles "^3.0.0" + cardinal "^1.0.0" + cli-table "^0.3.1" + lodash.merge "^4.6.0" + markdown-it "^8.3.1" + +markdown-it@^8.3.0, markdown-it@^8.3.1: + version "8.4.0" + resolved "https://registry.yarnpkg.com/markdown-it/-/markdown-it-8.4.0.tgz#e2400881bf171f7018ed1bd9da441dac8af6306d" + dependencies: + argparse "^1.0.7" + entities "~1.1.1" + linkify-it "^2.0.0" + mdurl "^1.0.1" + uc.micro "^1.0.3" + +matcher-collection@^1.0.0: + version "1.0.5" + resolved "https://registry.yarnpkg.com/matcher-collection/-/matcher-collection-1.0.5.tgz#2ee095438372cb8884f058234138c05c644ec339" + dependencies: + minimatch "^3.0.2" + +md5-hex@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/md5-hex/-/md5-hex-2.0.0.tgz#d0588e9f1c74954492ecd24ac0ac6ce997d92e33" + dependencies: + md5-o-matic "^0.1.1" + +md5-o-matic@^0.1.1: + version "0.1.1" + resolved "https://registry.yarnpkg.com/md5-o-matic/-/md5-o-matic-0.1.1.tgz#822bccd65e117c514fab176b25945d54100a03c3" + +mdurl@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/mdurl/-/mdurl-1.0.1.tgz#fe85b2ec75a59037f2adfec100fd6c601761152e" + +media-typer@0.3.0: + version "0.3.0" + resolved "https://registry.yarnpkg.com/media-typer/-/media-typer-0.3.0.tgz#8710d7af0aa626f8fffa1ce00168545263255748" + +memory-streams@^0.1.0: + version "0.1.2" + resolved "https://registry.yarnpkg.com/memory-streams/-/memory-streams-0.1.2.tgz#273ff777ab60fec599b116355255282cca2c50c2" + dependencies: + readable-stream "~1.0.2" + +merge-descriptors@1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/merge-descriptors/-/merge-descriptors-1.0.1.tgz#b00aaa556dd8b44568150ec9d1b953f3f90cbb61" + +merge-trees@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/merge-trees/-/merge-trees-1.0.1.tgz#ccbe674569787f9def17fd46e6525f5700bbd23e" + dependencies: + can-symlink "^1.0.0" + fs-tree-diff "^0.5.4" + heimdalljs "^0.2.1" + heimdalljs-logger "^0.1.7" + rimraf "^2.4.3" + symlink-or-copy "^1.0.0" + +merge@^1.1.3: + version "1.2.0" + resolved "https://registry.yarnpkg.com/merge/-/merge-1.2.0.tgz#7531e39d4949c281a66b8c5a6e0265e8b05894da" + +methods@~1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/methods/-/methods-1.1.2.tgz#5529a4d67654134edcc5266656835b0f851afcee" + +micromatch@^2.1.5, micromatch@^2.3.7: + version "2.3.11" + resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-2.3.11.tgz#86677c97d1720b363431d04d0d15293bd38c1565" + dependencies: + arr-diff "^2.0.0" + array-unique "^0.2.1" + braces "^1.8.2" + expand-brackets "^0.1.4" + extglob "^0.3.1" + filename-regex "^2.0.0" + is-extglob "^1.0.0" + is-glob "^2.0.1" + kind-of "^3.0.2" + normalize-path "^2.0.1" + object.omit "^2.0.0" + parse-glob "^3.0.4" + regex-cache "^0.4.2" + +"mime-db@>= 1.29.0 < 2", mime-db@~1.30.0: + version "1.30.0" + resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.30.0.tgz#74c643da2dd9d6a45399963465b26d5ca7d71f01" + +mime-types@^2.1.12, mime-types@~2.1.11, mime-types@~2.1.15, mime-types@~2.1.16, mime-types@~2.1.7: + version "2.1.17" + resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.17.tgz#09d7a393f03e995a79f8af857b70a9e0ab16557a" + dependencies: + mime-db "~1.30.0" + +mime@1.4.1, mime@^1.2.11: + version "1.4.1" + resolved "https://registry.yarnpkg.com/mime/-/mime-1.4.1.tgz#121f9ebc49e3766f311a76e1fa1c8003c4b03aa6" + +mimic-fn@^1.0.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-1.1.0.tgz#e667783d92e89dbd342818b5230b9d62a672ad18" + +"minimatch@2 || 3", minimatch@^3.0.0, minimatch@^3.0.2, minimatch@^3.0.3, minimatch@^3.0.4: + version "3.0.4" + resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.0.4.tgz#5166e286457f03306064be5497e8dbb0c3d32083" + dependencies: + brace-expansion "^1.1.7" + +minimatch@^2.0.1, minimatch@^2.0.3: + version "2.0.10" + resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-2.0.10.tgz#8d087c39c6b38c001b97fca7ce6d0e1e80afbac7" + dependencies: + brace-expansion "^1.0.0" + +minimist@0.0.8: + version "0.0.8" + resolved "https://registry.yarnpkg.com/minimist/-/minimist-0.0.8.tgz#857fcabfc3397d2625b8228262e86aa7a011b05d" + +minimist@^1.1.0, minimist@^1.1.1, minimist@^1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.0.tgz#a35008b20f41383eec1fb914f4cd5df79a264284" + +minimist@~0.0.1: + version "0.0.10" + resolved "https://registry.yarnpkg.com/minimist/-/minimist-0.0.10.tgz#de3f98543dbf96082be48ad1a0c7cda836301dcf" + +mkdirp@0.5.x, "mkdirp@>=0.5 0", mkdirp@^0.5.0, mkdirp@^0.5.1: + version "0.5.1" + resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.1.tgz#30057438eac6cf7f8c4767f38648d6697d75c903" + dependencies: + minimist "0.0.8" + +mkdirp@^0.3.5: + version "0.3.5" + resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.3.5.tgz#de3e5f8961c88c787ee1368df849ac4413eca8d7" + +mktemp@~0.4.0: + version "0.4.0" + resolved "https://registry.yarnpkg.com/mktemp/-/mktemp-0.4.0.tgz#6d0515611c8a8c84e484aa2000129b98e981ff0b" + +morgan@^1.8.1: + version "1.9.0" + resolved "https://registry.yarnpkg.com/morgan/-/morgan-1.9.0.tgz#d01fa6c65859b76fcf31b3cb53a3821a311d8051" + dependencies: + basic-auth "~2.0.0" + debug "2.6.9" + depd "~1.1.1" + on-finished "~2.3.0" + on-headers "~1.0.1" + +mout@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/mout/-/mout-1.0.0.tgz#9bdf1d4af57d66d47cb353a6335a3281098e1501" + +ms@0.7.1: + version "0.7.1" + resolved "https://registry.yarnpkg.com/ms/-/ms-0.7.1.tgz#9cd13c03adbff25b65effde7ce864ee952017098" + +ms@0.7.2: + version "0.7.2" + resolved "https://registry.yarnpkg.com/ms/-/ms-0.7.2.tgz#ae25cf2512b3885a1d95d7f037868d8431124765" + +ms@2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8" + +mustache@^2.2.1: + version "2.3.0" + resolved "https://registry.yarnpkg.com/mustache/-/mustache-2.3.0.tgz#4028f7778b17708a489930a6e52ac3bca0da41d0" + +mute-stream@0.0.6: + version "0.0.6" + resolved "https://registry.yarnpkg.com/mute-stream/-/mute-stream-0.0.6.tgz#48962b19e169fd1dfc240b3f1e7317627bbc47db" + +mute-stream@0.0.7: + version "0.0.7" + resolved "https://registry.yarnpkg.com/mute-stream/-/mute-stream-0.0.7.tgz#3075ce93bc21b8fab43e1bc4da7e8115ed1e7bab" + +nan@^2.3.0: + version "2.7.0" + resolved "https://registry.yarnpkg.com/nan/-/nan-2.7.0.tgz#d95bf721ec877e08db276ed3fc6eb78f9083ad46" + +natural-compare@^1.4.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/natural-compare/-/natural-compare-1.4.0.tgz#4abebfeed7541f2c27acfb29bdbbd15c8d5ba4f7" + +negotiator@0.6.1: + version "0.6.1" + resolved "https://registry.yarnpkg.com/negotiator/-/negotiator-0.6.1.tgz#2b327184e8992101177b28563fb5e7102acd0ca9" + +node-fetch@^1.3.3: + version "1.7.3" + resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-1.7.3.tgz#980f6f72d85211a5347c6b2bc18c5b84c3eb47ef" + dependencies: + encoding "^0.1.11" + is-stream "^1.0.1" + +node-int64@^0.4.0: + version "0.4.0" + resolved "https://registry.yarnpkg.com/node-int64/-/node-int64-0.4.0.tgz#87a9065cdb355d3182d8f94ce11188b825c68a3b" + +node-modules-path@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/node-modules-path/-/node-modules-path-1.0.1.tgz#40096b08ce7ad0ea14680863af449c7c75a5d1c8" + +node-notifier@^5.0.1: + version "5.1.2" + resolved "https://registry.yarnpkg.com/node-notifier/-/node-notifier-5.1.2.tgz#2fa9e12605fa10009d44549d6fcd8a63dde0e4ff" + dependencies: + growly "^1.3.0" + semver "^5.3.0" + shellwords "^0.1.0" + which "^1.2.12" + +node-pre-gyp@^0.6.36: + version "0.6.38" + resolved "https://registry.yarnpkg.com/node-pre-gyp/-/node-pre-gyp-0.6.38.tgz#e92a20f83416415bb4086f6d1fb78b3da73d113d" + dependencies: + hawk "3.1.3" + mkdirp "^0.5.1" + nopt "^4.0.1" + npmlog "^4.0.2" + rc "^1.1.7" + request "2.81.0" + rimraf "^2.6.1" + semver "^5.3.0" + tar "^2.2.1" + tar-pack "^3.4.0" + +nopt@^3.0.6: + version "3.0.6" + resolved "https://registry.yarnpkg.com/nopt/-/nopt-3.0.6.tgz#c6465dbf08abcd4db359317f79ac68a646b28ff9" + dependencies: + abbrev "1" + +nopt@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/nopt/-/nopt-4.0.1.tgz#d0d4685afd5415193c8c7505602d0d17cd64474d" + dependencies: + abbrev "1" + osenv "^0.1.4" + +normalize-path@^2.0.0, normalize-path@^2.0.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/normalize-path/-/normalize-path-2.1.1.tgz#1ab28b556e198363a8c1a6f7e6fa20137fe6aed9" + dependencies: + remove-trailing-separator "^1.0.1" + +npm-package-arg@^4.1.1: + version "4.2.1" + resolved "https://registry.yarnpkg.com/npm-package-arg/-/npm-package-arg-4.2.1.tgz#593303fdea85f7c422775f17f9eb7670f680e3ec" + dependencies: + hosted-git-info "^2.1.5" + semver "^5.1.0" + +npm-run-path@^2.0.0: + version "2.0.2" + resolved "https://registry.yarnpkg.com/npm-run-path/-/npm-run-path-2.0.2.tgz#35a9232dfa35d7067b4cb2ddf2357b1871536c5f" + dependencies: + path-key "^2.0.0" + +npmlog@^4.0.0, npmlog@^4.0.2: + version "4.1.2" + resolved "https://registry.yarnpkg.com/npmlog/-/npmlog-4.1.2.tgz#08a7f2a8bf734604779a9efa4ad5cc717abb954b" + dependencies: + are-we-there-yet "~1.1.2" + console-control-strings "~1.1.0" + gauge "~2.7.3" + set-blocking "~2.0.0" + +number-is-nan@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/number-is-nan/-/number-is-nan-1.0.1.tgz#097b602b53422a522c1afb8790318336941a011d" + +oauth-sign@~0.8.1: + version "0.8.2" + resolved "https://registry.yarnpkg.com/oauth-sign/-/oauth-sign-0.8.2.tgz#46a6ab7f0aead8deae9ec0565780b7d4efeb9d43" + +object-assign@4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.0.tgz#7a3b3d0e98063d43f4c03f2e8ae6cd51a86883a0" + +object-assign@4.1.1, object-assign@^4.0.1, object-assign@^4.1.0: + version "4.1.1" + resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863" + +object-component@0.0.3: + version "0.0.3" + resolved "https://registry.yarnpkg.com/object-component/-/object-component-0.0.3.tgz#f0c69aa50efc95b866c186f400a33769cb2f1291" + +object.omit@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/object.omit/-/object.omit-2.0.1.tgz#1a9c744829f39dbb858c76ca3579ae2a54ebd1fa" + dependencies: + for-own "^0.1.4" + is-extendable "^0.1.1" + +on-finished@~2.3.0: + version "2.3.0" + resolved "https://registry.yarnpkg.com/on-finished/-/on-finished-2.3.0.tgz#20f1336481b083cd75337992a16971aa2d906947" + dependencies: + ee-first "1.1.1" + +on-headers@~1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/on-headers/-/on-headers-1.0.1.tgz#928f5d0f470d49342651ea6794b0857c100693f7" + +once@^1.3.0, once@^1.3.3: + version "1.4.0" + resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1" + dependencies: + wrappy "1" + +onetime@^1.0.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/onetime/-/onetime-1.1.0.tgz#a1f7838f8314c516f05ecefcbc4ccfe04b4ed789" + +onetime@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/onetime/-/onetime-2.0.1.tgz#067428230fd67443b2794b22bba528b6867962d4" + dependencies: + mimic-fn "^1.0.0" + +optimist@^0.6.1: + version "0.6.1" + resolved "https://registry.yarnpkg.com/optimist/-/optimist-0.6.1.tgz#da3ea74686fa21a19a111c326e90eb15a0196686" + dependencies: + minimist "~0.0.1" + wordwrap "~0.0.2" + +optionator@^0.8.2: + version "0.8.2" + resolved "https://registry.yarnpkg.com/optionator/-/optionator-0.8.2.tgz#364c5e409d3f4d6301d6c0b4c05bba50180aeb64" + dependencies: + deep-is "~0.1.3" + fast-levenshtein "~2.0.4" + levn "~0.3.0" + prelude-ls "~1.1.2" + type-check "~0.3.2" + wordwrap "~1.0.0" + +options@>=0.0.5: + version "0.0.6" + resolved "https://registry.yarnpkg.com/options/-/options-0.0.6.tgz#ec22d312806bb53e731773e7cdaefcf1c643128f" + +ora@^0.2.0: + version "0.2.3" + resolved "https://registry.yarnpkg.com/ora/-/ora-0.2.3.tgz#37527d220adcd53c39b73571d754156d5db657a4" + dependencies: + chalk "^1.1.1" + cli-cursor "^1.0.2" + cli-spinners "^0.1.2" + object-assign "^4.0.1" + +os-homedir@^1.0.0, os-homedir@^1.0.1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/os-homedir/-/os-homedir-1.0.2.tgz#ffbc4988336e0e833de0c168c7ef152121aa7fb3" + +os-locale@^1.4.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/os-locale/-/os-locale-1.4.0.tgz#20f9f17ae29ed345e8bde583b13d2009803c14d9" + dependencies: + lcid "^1.0.0" + +os-shim@^0.1.2: + version "0.1.3" + resolved "https://registry.yarnpkg.com/os-shim/-/os-shim-0.1.3.tgz#6b62c3791cf7909ea35ed46e17658bb417cb3917" + +os-tmpdir@^1.0.0, os-tmpdir@^1.0.1, os-tmpdir@~1.0.1, os-tmpdir@~1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/os-tmpdir/-/os-tmpdir-1.0.2.tgz#bbe67406c79aa85c5cfec766fe5734555dfa1274" + +osenv@^0.1.3, osenv@^0.1.4: + version "0.1.4" + resolved "https://registry.yarnpkg.com/osenv/-/osenv-0.1.4.tgz#42fe6d5953df06c8064be6f176c3d05aaaa34644" + dependencies: + os-homedir "^1.0.0" + os-tmpdir "^1.0.0" + +output-file-sync@^1.1.0: + version "1.1.2" + resolved "https://registry.yarnpkg.com/output-file-sync/-/output-file-sync-1.1.2.tgz#d0a33eefe61a205facb90092e826598d5245ce76" + dependencies: + graceful-fs "^4.1.4" + mkdirp "^0.5.1" + object-assign "^4.1.0" + +p-finally@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/p-finally/-/p-finally-1.0.0.tgz#3fbcfb15b899a44123b34b6dcc18b724336a2cae" + +p-limit@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-1.1.0.tgz#b07ff2d9a5d88bec806035895a2bab66a27988bc" + +p-locate@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-2.0.0.tgz#20a0103b222a70c8fd39cc2e580680f3dde5ec43" + dependencies: + p-limit "^1.1.0" + +parse-glob@^3.0.4: + version "3.0.4" + resolved "https://registry.yarnpkg.com/parse-glob/-/parse-glob-3.0.4.tgz#b2c376cfb11f35513badd173ef0bb6e3a388391c" + dependencies: + glob-base "^0.3.0" + is-dotfile "^1.0.0" + is-extglob "^1.0.0" + is-glob "^2.0.0" + +parse-passwd@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/parse-passwd/-/parse-passwd-1.0.0.tgz#6d5b934a456993b23d37f40a382d6f1666a8e5c6" + +parsejson@0.0.3: + version "0.0.3" + resolved "https://registry.yarnpkg.com/parsejson/-/parsejson-0.0.3.tgz#ab7e3759f209ece99437973f7d0f1f64ae0e64ab" + dependencies: + better-assert "~1.0.0" + +parseqs@0.0.5: + version "0.0.5" + resolved "https://registry.yarnpkg.com/parseqs/-/parseqs-0.0.5.tgz#d5208a3738e46766e291ba2ea173684921a8b89d" + dependencies: + better-assert "~1.0.0" + +parseuri@0.0.5: + version "0.0.5" + resolved "https://registry.yarnpkg.com/parseuri/-/parseuri-0.0.5.tgz#80204a50d4dbb779bfdc6ebe2778d90e4bce320a" + dependencies: + better-assert "~1.0.0" + +parseurl@~1.3.2: + version "1.3.2" + resolved "https://registry.yarnpkg.com/parseurl/-/parseurl-1.3.2.tgz#fc289d4ed8993119460c156253262cdc8de65bf3" + +path-exists@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-1.0.0.tgz#d5a8998eb71ef37a74c34eb0d9eba6e878eea081" + +path-exists@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-3.0.0.tgz#ce0ebeaa5f78cb18925ea7d810d7b59b010fd515" + +path-is-absolute@^1.0.0, path-is-absolute@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f" + +path-is-inside@^1.0.1, path-is-inside@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/path-is-inside/-/path-is-inside-1.0.2.tgz#365417dede44430d1c11af61027facf074bdfc53" + +path-key@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/path-key/-/path-key-2.0.1.tgz#411cadb574c5a140d3a4b1910d40d80cc9f40b40" + +path-parse@^1.0.5: + version "1.0.5" + resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.5.tgz#3c1adf871ea9cd6c9431b6ea2bd74a0ff055c4c1" + +path-posix@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/path-posix/-/path-posix-1.0.0.tgz#06b26113f56beab042545a23bfa88003ccac260f" + +path-to-regexp@0.1.7: + version "0.1.7" + resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-0.1.7.tgz#df604178005f522f15eb4490e7247a1bfaa67f8c" + +performance-now@^0.2.0: + version "0.2.0" + resolved "https://registry.yarnpkg.com/performance-now/-/performance-now-0.2.0.tgz#33ef30c5c77d4ea21c5a53869d91b56d8f2555e5" + +pify@^2.0.0, pify@^2.3.0: + version "2.3.0" + resolved "https://registry.yarnpkg.com/pify/-/pify-2.3.0.tgz#ed141a6ac043a849ea588498e7dca8b15330e90c" + +pinkie-promise@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/pinkie-promise/-/pinkie-promise-2.0.1.tgz#2135d6dfa7a358c069ac9b178776288228450ffa" + dependencies: + pinkie "^2.0.0" + +pinkie@^2.0.0: + version "2.0.4" + resolved "https://registry.yarnpkg.com/pinkie/-/pinkie-2.0.4.tgz#72556b80cfa0d48a974e80e77248e80ed4f7f870" + +pluralize@^7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/pluralize/-/pluralize-7.0.0.tgz#298b89df8b93b0221dbf421ad2b1b1ea23fc6777" + +portfinder@^1.0.7: + version "1.0.13" + resolved "https://registry.yarnpkg.com/portfinder/-/portfinder-1.0.13.tgz#bb32ecd87c27104ae6ee44b5a3ccbf0ebb1aede9" + dependencies: + async "^1.5.2" + debug "^2.2.0" + mkdirp "0.5.x" + +prelude-ls@~1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.1.2.tgz#21932a549f5e52ffd9a827f570e04be62a97da54" + +preserve@^0.2.0: + version "0.2.0" + resolved "https://registry.yarnpkg.com/preserve/-/preserve-0.2.0.tgz#815ed1f6ebc65926f865b310c0713bcb3315ce4b" + +printf@^0.2.3: + version "0.2.5" + resolved "https://registry.yarnpkg.com/printf/-/printf-0.2.5.tgz#c438ca2ca33e3927671db4ab69c0e52f936a4f0f" + +private@^0.1.6, private@^0.1.7, private@~0.1.5: + version "0.1.7" + resolved "https://registry.yarnpkg.com/private/-/private-0.1.7.tgz#68ce5e8a1ef0a23bb570cc28537b5332aba63ef1" + +process-nextick-args@~1.0.6: + version "1.0.7" + resolved "https://registry.yarnpkg.com/process-nextick-args/-/process-nextick-args-1.0.7.tgz#150e20b756590ad3f91093f25a4f2ad8bff30ba3" + +process-relative-require@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/process-relative-require/-/process-relative-require-1.0.0.tgz#1590dfcf5b8f2983ba53e398446b68240b4cc68a" + dependencies: + node-modules-path "^1.0.0" + +progress@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/progress/-/progress-2.0.0.tgz#8a1be366bf8fc23db2bd23f10c6fe920b4389d1f" + +promise-map-series@^0.2.1: + version "0.2.3" + resolved "https://registry.yarnpkg.com/promise-map-series/-/promise-map-series-0.2.3.tgz#c2d377afc93253f6bd03dbb77755eb88ab20a847" + dependencies: + rsvp "^3.0.14" + +proxy-addr@~2.0.2: + version "2.0.2" + resolved "https://registry.yarnpkg.com/proxy-addr/-/proxy-addr-2.0.2.tgz#6571504f47bb988ec8180253f85dd7e14952bdec" + dependencies: + forwarded "~0.1.2" + ipaddr.js "1.5.2" + +pseudomap@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/pseudomap/-/pseudomap-1.0.2.tgz#f052a28da70e618917ef0a8ac34c1ae5a68286b3" + +punycode@^1.4.1: + version "1.4.1" + resolved "https://registry.yarnpkg.com/punycode/-/punycode-1.4.1.tgz#c0d5a63b2718800ad8e1eb0fa5269c84dd41845e" + +q@^1.1.2: + version "1.5.0" + resolved "https://registry.yarnpkg.com/q/-/q-1.5.0.tgz#dd01bac9d06d30e6f219aecb8253ee9ebdc308f1" + +qs@6.5.1, qs@^6.4.0: + version "6.5.1" + resolved "https://registry.yarnpkg.com/qs/-/qs-6.5.1.tgz#349cdf6eef89ec45c12d7d5eb3fc0c870343a6d8" + +qs@~6.4.0: + version "6.4.0" + resolved "https://registry.yarnpkg.com/qs/-/qs-6.4.0.tgz#13e26d28ad6b0ffaa91312cd3bf708ed351e7233" + +quick-temp@^0.1.0, quick-temp@^0.1.2, quick-temp@^0.1.3, quick-temp@^0.1.5, quick-temp@^0.1.8: + version "0.1.8" + resolved "https://registry.yarnpkg.com/quick-temp/-/quick-temp-0.1.8.tgz#bab02a242ab8fb0dd758a3c9776b32f9a5d94408" + dependencies: + mktemp "~0.4.0" + rimraf "^2.5.4" + underscore.string "~3.3.4" + +qunit-notifications@^0.1.1: + version "0.1.1" + resolved "https://registry.yarnpkg.com/qunit-notifications/-/qunit-notifications-0.1.1.tgz#3001afc6a6a77dfbd962ccbcddde12dec5286c09" + +qunitjs@^2.4.0: + version "2.4.0" + resolved "https://registry.yarnpkg.com/qunitjs/-/qunitjs-2.4.0.tgz#58f3a81e846687f2e7f637c5bedc9c267f887261" + dependencies: + chokidar "1.6.1" + commander "2.9.0" + exists-stat "1.0.0" + findup-sync "0.4.3" + js-reporters "1.2.0" + resolve "1.3.2" + walk-sync "0.3.1" + +randomatic@^1.1.3: + version "1.1.7" + resolved "https://registry.yarnpkg.com/randomatic/-/randomatic-1.1.7.tgz#c7abe9cc8b87c0baa876b19fde83fd464797e38c" + dependencies: + is-number "^3.0.0" + kind-of "^4.0.0" + +range-parser@~1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/range-parser/-/range-parser-1.2.0.tgz#f49be6b487894ddc40dcc94a322f611092e00d5e" + +raw-body@2.3.2: + version "2.3.2" + resolved "https://registry.yarnpkg.com/raw-body/-/raw-body-2.3.2.tgz#bcd60c77d3eb93cde0050295c3f379389bc88f89" + dependencies: + bytes "3.0.0" + http-errors "1.6.2" + iconv-lite "0.4.19" + unpipe "1.0.0" + +raw-body@~1.1.0: + version "1.1.7" + resolved "https://registry.yarnpkg.com/raw-body/-/raw-body-1.1.7.tgz#1d027c2bfa116acc6623bca8f00016572a87d425" + dependencies: + bytes "1" + string_decoder "0.10" + +rc@^1.1.7: + version "1.2.1" + resolved "https://registry.yarnpkg.com/rc/-/rc-1.2.1.tgz#2e03e8e42ee450b8cb3dce65be1bf8974e1dfd95" + dependencies: + deep-extend "~0.4.0" + ini "~1.3.0" + minimist "^1.2.0" + strip-json-comments "~2.0.1" + +readable-stream@^2, readable-stream@^2.0.2, readable-stream@^2.0.6, readable-stream@^2.1.4, readable-stream@^2.2.2: + version "2.3.3" + resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.3.3.tgz#368f2512d79f9d46fdfc71349ae7878bbc1eb95c" + dependencies: + core-util-is "~1.0.0" + inherits "~2.0.3" + isarray "~1.0.0" + process-nextick-args "~1.0.6" + safe-buffer "~5.1.1" + string_decoder "~1.0.3" + util-deprecate "~1.0.1" + +readable-stream@~1.0.2: + version "1.0.34" + resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-1.0.34.tgz#125820e34bc842d2f2aaafafe4c2916ee32c157c" + dependencies: + core-util-is "~1.0.0" + inherits "~2.0.1" + isarray "0.0.1" + string_decoder "~0.10.x" + +readdirp@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/readdirp/-/readdirp-2.1.0.tgz#4ed0ad060df3073300c48440373f72d1cc642d78" + dependencies: + graceful-fs "^4.1.2" + minimatch "^3.0.2" + readable-stream "^2.0.2" + set-immediate-shim "^1.0.1" + +recast@0.10.33: + version "0.10.33" + resolved "https://registry.yarnpkg.com/recast/-/recast-0.10.33.tgz#942808f7aa016f1fa7142c461d7e5704aaa8d697" + dependencies: + ast-types "0.8.12" + esprima-fb "~15001.1001.0-dev-harmony-fb" + private "~0.1.5" + source-map "~0.5.0" + +recast@^0.10.10: + version "0.10.43" + resolved "https://registry.yarnpkg.com/recast/-/recast-0.10.43.tgz#b95d50f6d60761a5f6252e15d80678168491ce7f" + dependencies: + ast-types "0.8.15" + esprima-fb "~15001.1001.0-dev-harmony-fb" + private "~0.1.5" + source-map "~0.5.0" + +recast@^0.11.17, recast@^0.11.3: + version "0.11.23" + resolved "https://registry.yarnpkg.com/recast/-/recast-0.11.23.tgz#451fd3004ab1e4df9b4e4b66376b2a21912462d3" + dependencies: + ast-types "0.9.6" + esprima "~3.1.0" + private "~0.1.5" + source-map "~0.5.0" + +redeyed@~1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/redeyed/-/redeyed-1.0.1.tgz#e96c193b40c0816b00aec842698e61185e55498a" + dependencies: + esprima "~3.0.0" + +regenerate@^1.2.1: + version "1.3.3" + resolved "https://registry.yarnpkg.com/regenerate/-/regenerate-1.3.3.tgz#0c336d3980553d755c39b586ae3b20aa49c82b7f" + +regenerator-runtime@^0.10.5: + version "0.10.5" + resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.10.5.tgz#336c3efc1220adcedda2c9fab67b5a7955a33658" + +regenerator-runtime@^0.11.0: + version "0.11.0" + resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.11.0.tgz#7e54fe5b5ccd5d6624ea6255c3473be090b802e1" + +regenerator-transform@^0.10.0: + version "0.10.1" + resolved "https://registry.yarnpkg.com/regenerator-transform/-/regenerator-transform-0.10.1.tgz#1e4996837231da8b7f3cf4114d71b5691a0680dd" + dependencies: + babel-runtime "^6.18.0" + babel-types "^6.19.0" + private "^0.1.6" + +regenerator@0.8.40: + version "0.8.40" + resolved "https://registry.yarnpkg.com/regenerator/-/regenerator-0.8.40.tgz#a0e457c58ebdbae575c9f8cd75127e93756435d8" + dependencies: + commoner "~0.10.3" + defs "~1.1.0" + esprima-fb "~15001.1001.0-dev-harmony-fb" + private "~0.1.5" + recast "0.10.33" + through "~2.3.8" + +regex-cache@^0.4.2: + version "0.4.4" + resolved "https://registry.yarnpkg.com/regex-cache/-/regex-cache-0.4.4.tgz#75bdc58a2a1496cec48a12835bc54c8d562336dd" + dependencies: + is-equal-shallow "^0.1.3" + +regexpu-core@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/regexpu-core/-/regexpu-core-2.0.0.tgz#49d038837b8dcf8bfa5b9a42139938e6ea2ae240" + dependencies: + regenerate "^1.2.1" + regjsgen "^0.2.0" + regjsparser "^0.1.4" + +regexpu@^1.3.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/regexpu/-/regexpu-1.3.0.tgz#e534dc991a9e5846050c98de6d7dd4a55c9ea16d" + dependencies: + esprima "^2.6.0" + recast "^0.10.10" + regenerate "^1.2.1" + regjsgen "^0.2.0" + regjsparser "^0.1.4" + +regjsgen@^0.2.0: + version "0.2.0" + resolved "https://registry.yarnpkg.com/regjsgen/-/regjsgen-0.2.0.tgz#6c016adeac554f75823fe37ac05b92d5a4edb1f7" + +regjsparser@^0.1.4: + version "0.1.5" + resolved "https://registry.yarnpkg.com/regjsparser/-/regjsparser-0.1.5.tgz#7ee8f84dc6fa792d3fd0ae228d24bd949ead205c" + dependencies: + jsesc "~0.5.0" + +remove-trailing-separator@^1.0.1: + version "1.1.0" + resolved "https://registry.yarnpkg.com/remove-trailing-separator/-/remove-trailing-separator-1.1.0.tgz#c24bce2a283adad5bc3f58e0d48249b92379d8ef" + +repeat-element@^1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/repeat-element/-/repeat-element-1.1.2.tgz#ef089a178d1483baae4d93eb98b4f9e4e11d990a" + +repeat-string@^1.5.2: + version "1.6.1" + resolved "https://registry.yarnpkg.com/repeat-string/-/repeat-string-1.6.1.tgz#8dcae470e1c88abc2d600fff4a776286da75e637" + +repeating@^1.1.0, repeating@^1.1.2: + version "1.1.3" + resolved "https://registry.yarnpkg.com/repeating/-/repeating-1.1.3.tgz#3d4114218877537494f97f77f9785fab810fa4ac" + dependencies: + is-finite "^1.0.0" + +repeating@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/repeating/-/repeating-2.0.1.tgz#5214c53a926d3552707527fbab415dbc08d06dda" + dependencies: + is-finite "^1.0.0" + +request@2.81.0: + version "2.81.0" + resolved "https://registry.yarnpkg.com/request/-/request-2.81.0.tgz#c6928946a0e06c5f8d6f8a9333469ffda46298a0" + dependencies: + aws-sign2 "~0.6.0" + aws4 "^1.2.1" + caseless "~0.12.0" + combined-stream "~1.0.5" + extend "~3.0.0" + forever-agent "~0.6.1" + form-data "~2.1.1" + har-validator "~4.2.1" + hawk "~3.1.3" + http-signature "~1.1.0" + is-typedarray "~1.0.0" + isstream "~0.1.2" + json-stringify-safe "~5.0.1" + mime-types "~2.1.7" + oauth-sign "~0.8.1" + performance-now "^0.2.0" + qs "~6.4.0" + safe-buffer "^5.0.1" + stringstream "~0.0.4" + tough-cookie "~2.3.0" + tunnel-agent "^0.6.0" + uuid "^3.0.0" + +require-uncached@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/require-uncached/-/require-uncached-1.0.3.tgz#4e0d56d6c9662fd31e43011c4b95aa49955421d3" + dependencies: + caller-path "^0.1.0" + resolve-from "^1.0.0" + +requires-port@1.x.x: + version "1.0.0" + resolved "https://registry.yarnpkg.com/requires-port/-/requires-port-1.0.0.tgz#925d2601d39ac485e091cf0da5c6e694dc3dcaff" + +resolve-dir@^0.1.0: + version "0.1.1" + resolved "https://registry.yarnpkg.com/resolve-dir/-/resolve-dir-0.1.1.tgz#b219259a5602fac5c5c496ad894a6e8cc430261e" + dependencies: + expand-tilde "^1.2.2" + global-modules "^0.2.3" + +resolve-from@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-1.0.1.tgz#26cbfe935d1aeeeabb29bc3fe5aeb01e93d44226" + +resolve@1.3.2: + version "1.3.2" + resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.3.2.tgz#1f0442c9e0cbb8136e87b9305f932f46c7f28235" + dependencies: + path-parse "^1.0.5" + +resolve@^1.1.2, resolve@^1.1.6, resolve@^1.3.0, resolve@^1.3.3, resolve@^1.4.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.4.0.tgz#a75be01c53da25d934a98ebd0e4c4a7312f92a86" + dependencies: + path-parse "^1.0.5" + +restore-cursor@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/restore-cursor/-/restore-cursor-1.0.1.tgz#34661f46886327fed2991479152252df92daa541" + dependencies: + exit-hook "^1.0.0" + onetime "^1.0.0" + +restore-cursor@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/restore-cursor/-/restore-cursor-2.0.0.tgz#9f7ee287f82fd326d4fd162923d62129eee0dfaf" + dependencies: + onetime "^2.0.0" + signal-exit "^3.0.2" + +right-align@^0.1.1: + version "0.1.3" + resolved "https://registry.yarnpkg.com/right-align/-/right-align-0.1.3.tgz#61339b722fe6a3515689210d24e14c96148613ef" + dependencies: + align-text "^0.1.1" + +rimraf@2, rimraf@^2.2.8, rimraf@^2.3.2, rimraf@^2.3.4, rimraf@^2.4.3, rimraf@^2.4.4, rimraf@^2.5.1, rimraf@^2.5.3, rimraf@^2.5.4, rimraf@^2.6.1: + version "2.6.2" + resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.6.2.tgz#2ed8150d24a16ea8651e6d6ef0f47c4158ce7a36" + dependencies: + glob "^7.0.5" + +rimraf@~2.2.6: + version "2.2.8" + resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.2.8.tgz#e439be2aaee327321952730f99a8929e4fc50582" + +rsvp@^3.0.14, rsvp@^3.0.16, rsvp@^3.0.17, rsvp@^3.0.18, rsvp@^3.0.21, rsvp@^3.0.6, rsvp@^3.1.0, rsvp@^3.2.1, rsvp@^3.3.3, rsvp@^3.5.0, rsvp@^3.6.1: + version "3.6.2" + resolved "https://registry.yarnpkg.com/rsvp/-/rsvp-3.6.2.tgz#2e96491599a96cde1b515d5674a8f7a91452926a" + +rsvp@~3.0.6: + version "3.0.21" + resolved "https://registry.yarnpkg.com/rsvp/-/rsvp-3.0.21.tgz#49c588fe18ef293bcd0ab9f4e6756e6ac433359f" + +rsvp@~3.2.1: + version "3.2.1" + resolved "https://registry.yarnpkg.com/rsvp/-/rsvp-3.2.1.tgz#07cb4a5df25add9e826ebc67dcc9fd89db27d84a" + +run-async@^2.2.0: + version "2.3.0" + resolved "https://registry.yarnpkg.com/run-async/-/run-async-2.3.0.tgz#0371ab4ae0bdd720d4166d7dfda64ff7a445a6c0" + dependencies: + is-promise "^2.1.0" + +rx-lite-aggregates@^4.0.8: + version "4.0.8" + resolved "https://registry.yarnpkg.com/rx-lite-aggregates/-/rx-lite-aggregates-4.0.8.tgz#753b87a89a11c95467c4ac1626c4efc4e05c67be" + dependencies: + rx-lite "*" + +rx-lite@*, rx-lite@^4.0.8: + version "4.0.8" + resolved "https://registry.yarnpkg.com/rx-lite/-/rx-lite-4.0.8.tgz#0b1e11af8bc44836f04a6407e92da42467b79444" + +rx@^4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/rx/-/rx-4.1.0.tgz#a5f13ff79ef3b740fe30aa803fb09f98805d4782" + +safe-buffer@5.1.1, safe-buffer@^5.0.1, safe-buffer@~5.1.0, safe-buffer@~5.1.1: + version "5.1.1" + resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.1.tgz#893312af69b2123def71f57889001671eeb2c853" + +safe-json-parse@~1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/safe-json-parse/-/safe-json-parse-1.0.1.tgz#3e76723e38dfdda13c9b1d29a1e07ffee4b30b57" + +sane@^1.1.1, sane@^1.6.0: + version "1.7.0" + resolved "https://registry.yarnpkg.com/sane/-/sane-1.7.0.tgz#b3579bccb45c94cf20355cc81124990dfd346e30" + dependencies: + anymatch "^1.3.0" + exec-sh "^0.2.0" + fb-watchman "^2.0.0" + minimatch "^3.0.2" + minimist "^1.1.1" + walker "~1.0.5" + watch "~0.10.0" + +semver@^5.1.0, semver@^5.1.1, semver@^5.3.0: + version "5.4.1" + resolved "https://registry.yarnpkg.com/semver/-/semver-5.4.1.tgz#e059c09d8571f0540823733433505d3a2f00b18e" + +send@0.16.0: + version "0.16.0" + resolved "https://registry.yarnpkg.com/send/-/send-0.16.0.tgz#16338dbb9a2ede4ad57b48420ec3b82d8e80a57b" + dependencies: + debug "2.6.9" + depd "~1.1.1" + destroy "~1.0.4" + encodeurl "~1.0.1" + escape-html "~1.0.3" + etag "~1.8.1" + fresh "0.5.2" + http-errors "~1.6.2" + mime "1.4.1" + ms "2.0.0" + on-finished "~2.3.0" + range-parser "~1.2.0" + statuses "~1.3.1" + +serve-static@1.13.0: + version "1.13.0" + resolved "https://registry.yarnpkg.com/serve-static/-/serve-static-1.13.0.tgz#810c91db800e94ba287eae6b4e06caab9fdc16f1" + dependencies: + encodeurl "~1.0.1" + escape-html "~1.0.3" + parseurl "~1.3.2" + send "0.16.0" + +set-blocking@~2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/set-blocking/-/set-blocking-2.0.0.tgz#045f9782d011ae9a6803ddd382b24392b3d890f7" + +set-immediate-shim@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/set-immediate-shim/-/set-immediate-shim-1.0.1.tgz#4b2b1b27eb808a9f8dcc481a58e5e56f599f3f61" + +setprototypeof@1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/setprototypeof/-/setprototypeof-1.0.3.tgz#66567e37043eeb4f04d91bd658c0cbefb55b8e04" + +setprototypeof@1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/setprototypeof/-/setprototypeof-1.1.0.tgz#d0bd85536887b6fe7c0d818cb962d9d91c54e656" + +shebang-command@^1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/shebang-command/-/shebang-command-1.2.0.tgz#44aac65b695b03398968c39f363fee5deafdf1ea" + dependencies: + shebang-regex "^1.0.0" + +shebang-regex@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/shebang-regex/-/shebang-regex-1.0.0.tgz#da42f49740c0b42db2ca9728571cb190c98efea3" + +shellwords@^0.1.0: + version "0.1.1" + resolved "https://registry.yarnpkg.com/shellwords/-/shellwords-0.1.1.tgz#d6b9181c1a48d397324c84871efbcfc73fc0654b" + +signal-exit@^3.0.0, signal-exit@^3.0.2: + version "3.0.2" + resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.2.tgz#b5fdc08f1287ea1178628e415e25132b73646c6d" + +silent-error@^1.0.0, silent-error@^1.0.1, silent-error@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/silent-error/-/silent-error-1.1.0.tgz#2209706f1c850a9f1d10d0d840918b46f26e1bc9" + dependencies: + debug "^2.2.0" + +simple-dom@^0.3.0: + version "0.3.2" + resolved "https://registry.yarnpkg.com/simple-dom/-/simple-dom-0.3.2.tgz#0663d10f1556f1500551d518f56e3aba0871371d" + +simple-fmt@~0.1.0: + version "0.1.0" + resolved "https://registry.yarnpkg.com/simple-fmt/-/simple-fmt-0.1.0.tgz#191bf566a59e6530482cb25ab53b4a8dc85c3a6b" + +simple-html-tokenizer@^0.3.0: + version "0.3.0" + resolved "https://registry.yarnpkg.com/simple-html-tokenizer/-/simple-html-tokenizer-0.3.0.tgz#9b8b5559d80e331a544dd13dd59382e5d0d94411" + +simple-html-tokenizer@^0.4.1: + version "0.4.1" + resolved "https://registry.yarnpkg.com/simple-html-tokenizer/-/simple-html-tokenizer-0.4.1.tgz#028988bb7fe8b2e6645676d82052587d440b02d3" + +simple-is@~0.2.0: + version "0.2.0" + resolved "https://registry.yarnpkg.com/simple-is/-/simple-is-0.2.0.tgz#2abb75aade39deb5cc815ce10e6191164850baf0" + +slash@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/slash/-/slash-1.0.0.tgz#c41f2f6c39fc16d1cd17ad4b5d896114ae470d55" + +slice-ansi@1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/slice-ansi/-/slice-ansi-1.0.0.tgz#044f1a49d8842ff307aad6b505ed178bd950134d" + dependencies: + is-fullwidth-code-point "^2.0.0" + +sntp@1.x.x: + version "1.0.9" + resolved "https://registry.yarnpkg.com/sntp/-/sntp-1.0.9.tgz#6541184cc90aeea6c6e7b35e2659082443c66198" + dependencies: + hoek "2.x.x" + +socket.io-adapter@0.5.0: + version "0.5.0" + resolved "https://registry.yarnpkg.com/socket.io-adapter/-/socket.io-adapter-0.5.0.tgz#cb6d4bb8bec81e1078b99677f9ced0046066bb8b" + dependencies: + debug "2.3.3" + socket.io-parser "2.3.1" + +socket.io-client@1.6.0: + version "1.6.0" + resolved "https://registry.yarnpkg.com/socket.io-client/-/socket.io-client-1.6.0.tgz#5b668f4f771304dfeed179064708386fa6717853" + dependencies: + backo2 "1.0.2" + component-bind "1.0.0" + component-emitter "1.2.1" + debug "2.3.3" + engine.io-client "1.8.0" + has-binary "0.1.7" + indexof "0.0.1" + object-component "0.0.3" + parseuri "0.0.5" + socket.io-parser "2.3.1" + to-array "0.1.4" + +socket.io-parser@2.3.1: + version "2.3.1" + resolved "https://registry.yarnpkg.com/socket.io-parser/-/socket.io-parser-2.3.1.tgz#dd532025103ce429697326befd64005fcfe5b4a0" + dependencies: + component-emitter "1.1.2" + debug "2.2.0" + isarray "0.0.1" + json3 "3.3.2" + +socket.io@1.6.0: + version "1.6.0" + resolved "https://registry.yarnpkg.com/socket.io/-/socket.io-1.6.0.tgz#3e40d932637e6bd923981b25caf7c53e83b6e2e1" + dependencies: + debug "2.3.3" + engine.io "1.8.0" + has-binary "0.1.7" + object-assign "4.1.0" + socket.io-adapter "0.5.0" + socket.io-client "1.6.0" + socket.io-parser "2.3.1" + +sort-object-keys@^1.1.1: + version "1.1.2" + resolved "https://registry.yarnpkg.com/sort-object-keys/-/sort-object-keys-1.1.2.tgz#d3a6c48dc2ac97e6bc94367696e03f6d09d37952" + +sort-package-json@^1.4.0: + version "1.7.1" + resolved "https://registry.yarnpkg.com/sort-package-json/-/sort-package-json-1.7.1.tgz#f2e5fbffe8420cc1bb04485f4509f05e73b4c0f2" + dependencies: + sort-object-keys "^1.1.1" + +source-map-support@^0.2.10: + version "0.2.10" + resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.2.10.tgz#ea5a3900a1c1cb25096a0ae8cc5c2b4b10ded3dc" + dependencies: + source-map "0.1.32" + +source-map-support@^0.4.15: + version "0.4.18" + resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.4.18.tgz#0286a6de8be42641338594e97ccea75f0a2c585f" + dependencies: + source-map "^0.5.6" + +source-map-url@^0.3.0: + version "0.3.0" + resolved "https://registry.yarnpkg.com/source-map-url/-/source-map-url-0.3.0.tgz#7ecaf13b57bcd09da8a40c5d269db33799d4aaf9" + +source-map@0.1.32: + version "0.1.32" + resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.1.32.tgz#c8b6c167797ba4740a8ea33252162ff08591b266" + dependencies: + amdefine ">=0.0.4" + +source-map@0.4.x, source-map@^0.4.2, source-map@^0.4.4: + version "0.4.4" + resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.4.4.tgz#eba4f5da9c0dc999de68032d8b4f76173652036b" + dependencies: + amdefine ">=0.0.4" + +source-map@^0.5.0, source-map@^0.5.6, source-map@~0.5.0, source-map@~0.5.1: + version "0.5.7" + resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.5.7.tgz#8a039d2d1021d22d1ea14c80d8ea468ba2ef3fcc" + +source-map@~0.1.x: + version "0.1.43" + resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.1.43.tgz#c24bc146ca517c1471f5dacbe2571b2b7f9e3346" + dependencies: + amdefine ">=0.0.4" + +sourcemap-validator@^1.0.5: + version "1.0.5" + resolved "https://registry.yarnpkg.com/sourcemap-validator/-/sourcemap-validator-1.0.5.tgz#f9b960f48c6469e288a19af305f005da3dc1df3a" + dependencies: + jsesc "~0.3.x" + lodash.foreach "~2.3.x" + lodash.template "~2.3.x" + source-map "~0.1.x" + +spawn-args@^0.2.0: + version "0.2.0" + resolved "https://registry.yarnpkg.com/spawn-args/-/spawn-args-0.2.0.tgz#fb7d0bd1d70fd4316bd9e3dec389e65f9d6361bb" + +spawn-sync@^1.0.15: + version "1.0.15" + resolved "https://registry.yarnpkg.com/spawn-sync/-/spawn-sync-1.0.15.tgz#b00799557eb7fb0c8376c29d44e8a1ea67e57476" + dependencies: + concat-stream "^1.4.7" + os-shim "^0.1.2" + +sprintf-js@^1.0.3: + version "1.1.1" + resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.1.1.tgz#36be78320afe5801f6cea3ee78b6e5aab940ea0c" + +sprintf-js@~1.0.2: + version "1.0.3" + resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.0.3.tgz#04e6926f662895354f3dd015203633b857297e2c" + +sri-toolbox@^0.2.0: + version "0.2.0" + resolved "https://registry.yarnpkg.com/sri-toolbox/-/sri-toolbox-0.2.0.tgz#a7fea5c3fde55e675cf1c8c06f3ebb5c2935835e" + +sshpk@^1.7.0: + version "1.13.1" + resolved "https://registry.yarnpkg.com/sshpk/-/sshpk-1.13.1.tgz#512df6da6287144316dc4c18fe1cf1d940739be3" + dependencies: + asn1 "~0.2.3" + assert-plus "^1.0.0" + dashdash "^1.12.0" + getpass "^0.1.1" + optionalDependencies: + bcrypt-pbkdf "^1.0.0" + ecc-jsbn "~0.1.1" + jsbn "~0.1.0" + tweetnacl "~0.14.0" + +stable@~0.1.3: + version "0.1.6" + resolved "https://registry.yarnpkg.com/stable/-/stable-0.1.6.tgz#910f5d2aed7b520c6e777499c1f32e139fdecb10" + +"statuses@>= 1.3.1 < 2", statuses@~1.3.1: + version "1.3.1" + resolved "https://registry.yarnpkg.com/statuses/-/statuses-1.3.1.tgz#faf51b9eb74aaef3b3acf4ad5f61abf24cb7b93e" + +string-template@~0.2.1: + version "0.2.1" + resolved "https://registry.yarnpkg.com/string-template/-/string-template-0.2.1.tgz#42932e598a352d01fc22ec3367d9d84eec6c9add" + +string-width@^1.0.1, string-width@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/string-width/-/string-width-1.0.2.tgz#118bdf5b8cdc51a2a7e70d211e07e2b0b9b107d3" + dependencies: + code-point-at "^1.0.0" + is-fullwidth-code-point "^1.0.0" + strip-ansi "^3.0.0" + +string-width@^2.1.0, string-width@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/string-width/-/string-width-2.1.1.tgz#ab93f27a8dc13d28cac815c462143a6d9012ae9e" + dependencies: + is-fullwidth-code-point "^2.0.0" + strip-ansi "^4.0.0" + +string_decoder@0.10, string_decoder@~0.10.x: + version "0.10.31" + resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-0.10.31.tgz#62e203bc41766c6c28c9fc84301dab1c5310fa94" + +string_decoder@~1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.0.3.tgz#0fc67d7c141825de94282dd536bec6b9bce860ab" + dependencies: + safe-buffer "~5.1.0" + +stringmap@~0.2.2: + version "0.2.2" + resolved "https://registry.yarnpkg.com/stringmap/-/stringmap-0.2.2.tgz#556c137b258f942b8776f5b2ef582aa069d7d1b1" + +stringset@~0.2.1: + version "0.2.1" + resolved "https://registry.yarnpkg.com/stringset/-/stringset-0.2.1.tgz#ef259c4e349344377fcd1c913dd2e848c9c042b5" + +stringstream@~0.0.4: + version "0.0.5" + resolved "https://registry.yarnpkg.com/stringstream/-/stringstream-0.0.5.tgz#4e484cd4de5a0bbbee18e46307710a8a81621878" + +strip-ansi@^0.3.0: + version "0.3.0" + resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-0.3.0.tgz#25f48ea22ca79187f3174a4db8759347bb126220" + dependencies: + ansi-regex "^0.2.1" + +strip-ansi@^3.0.0, strip-ansi@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-3.0.1.tgz#6a385fb8853d952d5ff05d0e8aaf94278dc63dcf" + dependencies: + ansi-regex "^2.0.0" + +strip-ansi@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-4.0.0.tgz#a8479022eb1ac368a871389b635262c505ee368f" + dependencies: + ansi-regex "^3.0.0" + +strip-bom@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/strip-bom/-/strip-bom-3.0.0.tgz#2334c18e9c759f7bdd56fdef7e9ae3d588e68ed3" + +strip-eof@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/strip-eof/-/strip-eof-1.0.0.tgz#bb43ff5598a6eb05d89b59fcd129c983313606bf" + +strip-json-comments@~2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-2.0.1.tgz#3c531942e908c2697c0ec344858c286c7ca0a60a" + +styled_string@0.0.1: + version "0.0.1" + resolved "https://registry.yarnpkg.com/styled_string/-/styled_string-0.0.1.tgz#d22782bd81295459bc4f1df18c4bad8e94dd124a" + +sum-up@^1.0.1: + version "1.0.3" + resolved "https://registry.yarnpkg.com/sum-up/-/sum-up-1.0.3.tgz#1c661f667057f63bcb7875aa1438bc162525156e" + dependencies: + chalk "^1.0.0" + +supports-color@^0.2.0: + version "0.2.0" + resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-0.2.0.tgz#d92de2694eb3f67323973d7ae3d8b55b4c22190a" + +supports-color@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-2.0.0.tgz#535d045ce6b6363fa40117084629995e9df324c7" + +supports-color@^4.0.0: + version "4.4.0" + resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-4.4.0.tgz#883f7ddabc165142b2a61427f3352ded195d1a3e" + dependencies: + has-flag "^2.0.0" + +symlink-or-copy@^1.0.0, symlink-or-copy@^1.0.1, symlink-or-copy@^1.1.8: + version "1.1.8" + resolved "https://registry.yarnpkg.com/symlink-or-copy/-/symlink-or-copy-1.1.8.tgz#cabe61e0010c1c023c173b25ee5108b37f4b4aa3" + +table@^4.0.1: + version "4.0.2" + resolved "https://registry.yarnpkg.com/table/-/table-4.0.2.tgz#a33447375391e766ad34d3486e6e2aedc84d2e36" + dependencies: + ajv "^5.2.3" + ajv-keywords "^2.1.0" + chalk "^2.1.0" + lodash "^4.17.4" + slice-ansi "1.0.0" + string-width "^2.1.1" + +tap-parser@^5.1.0: + version "5.4.0" + resolved "https://registry.yarnpkg.com/tap-parser/-/tap-parser-5.4.0.tgz#6907e89725d7b7fa6ae41ee2c464c3db43188aec" + dependencies: + events-to-array "^1.0.1" + js-yaml "^3.2.7" + optionalDependencies: + readable-stream "^2" + +tar-pack@^3.4.0: + version "3.4.0" + resolved "https://registry.yarnpkg.com/tar-pack/-/tar-pack-3.4.0.tgz#23be2d7f671a8339376cbdb0b8fe3fdebf317984" + dependencies: + debug "^2.2.0" + fstream "^1.0.10" + fstream-ignore "^1.0.5" + once "^1.3.3" + readable-stream "^2.1.4" + rimraf "^2.5.1" + tar "^2.2.1" + uid-number "^0.0.6" + +tar@^2.2.1: + version "2.2.1" + resolved "https://registry.yarnpkg.com/tar/-/tar-2.2.1.tgz#8e4d2a256c0e2185c6b18ad694aec968b83cb1d1" + dependencies: + block-stream "*" + fstream "^1.0.2" + inherits "2" + +temp@0.8.3: + version "0.8.3" + resolved "https://registry.yarnpkg.com/temp/-/temp-0.8.3.tgz#e0c6bc4d26b903124410e4fed81103014dfc1f59" + dependencies: + os-tmpdir "^1.0.0" + rimraf "~2.2.6" + +testem@^1.18.0: + version "1.18.4" + resolved "https://registry.yarnpkg.com/testem/-/testem-1.18.4.tgz#e45fed922bec2f54a616c43f11922598ac97eb41" + dependencies: + backbone "^1.1.2" + bluebird "^3.4.6" + charm "^1.0.0" + commander "^2.6.0" + consolidate "^0.14.0" + cross-spawn "^5.1.0" + express "^4.10.7" + fireworm "^0.7.0" + glob "^7.0.4" + http-proxy "^1.13.1" + js-yaml "^3.2.5" + lodash.assignin "^4.1.0" + lodash.clonedeep "^4.4.1" + lodash.find "^4.5.1" + lodash.uniqby "^4.7.0" + mkdirp "^0.5.1" + mustache "^2.2.1" + node-notifier "^5.0.1" + npmlog "^4.0.0" + printf "^0.2.3" + rimraf "^2.4.4" + socket.io "1.6.0" + spawn-args "^0.2.0" + styled_string "0.0.1" + tap-parser "^5.1.0" + xmldom "^0.1.19" + +text-table@~0.2.0: + version "0.2.0" + resolved "https://registry.yarnpkg.com/text-table/-/text-table-0.2.0.tgz#7f5ee823ae805207c00af2df4a84ec3fcfa570b4" + +"textextensions@1 || 2": + version "2.1.0" + resolved "https://registry.yarnpkg.com/textextensions/-/textextensions-2.1.0.tgz#1be0dc2a0dc244d44be8a09af6a85afb93c4dbc3" + +through@^2.3.6, through@^2.3.8, through@~2.3.8: + version "2.3.8" + resolved "https://registry.yarnpkg.com/through/-/through-2.3.8.tgz#0dd4c9ffaabc357960b1b724115d7e0e86a2e1f5" + +tiny-lr@^1.0.3: + version "1.0.5" + resolved "https://registry.yarnpkg.com/tiny-lr/-/tiny-lr-1.0.5.tgz#21f40bf84ebd1f853056680375eef1670c334112" + dependencies: + body "^5.1.0" + debug "~2.6.7" + faye-websocket "~0.10.0" + livereload-js "^2.2.2" + object-assign "^4.1.0" + qs "^6.4.0" + +tmp@0.0.28: + version "0.0.28" + resolved "https://registry.yarnpkg.com/tmp/-/tmp-0.0.28.tgz#172735b7f614ea7af39664fa84cf0de4e515d120" + dependencies: + os-tmpdir "~1.0.1" + +tmp@^0.0.29: + version "0.0.29" + resolved "https://registry.yarnpkg.com/tmp/-/tmp-0.0.29.tgz#f25125ff0dd9da3ccb0c2dd371ee1288bb9128c0" + dependencies: + os-tmpdir "~1.0.1" + +tmp@^0.0.33: + version "0.0.33" + resolved "https://registry.yarnpkg.com/tmp/-/tmp-0.0.33.tgz#6d34335889768d21b2bcda0aa277ced3b1bfadf9" + dependencies: + os-tmpdir "~1.0.2" + +tmpl@1.0.x: + version "1.0.4" + resolved "https://registry.yarnpkg.com/tmpl/-/tmpl-1.0.4.tgz#23640dd7b42d00433911140820e5cf440e521dd1" + +to-array@0.1.4: + version "0.1.4" + resolved "https://registry.yarnpkg.com/to-array/-/to-array-0.1.4.tgz#17e6c11f73dd4f3d74cda7a4ff3238e9ad9bf890" + +to-fast-properties@^1.0.0, to-fast-properties@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/to-fast-properties/-/to-fast-properties-1.0.3.tgz#b83571fa4d8c25b82e231b06e3a3055de4ca1a47" + +tough-cookie@~2.3.0: + version "2.3.3" + resolved "https://registry.yarnpkg.com/tough-cookie/-/tough-cookie-2.3.3.tgz#0b618a5565b6dea90bf3425d04d55edc475a7561" + dependencies: + punycode "^1.4.1" + +tree-sync@^1.2.1, tree-sync@^1.2.2: + version "1.2.2" + resolved "https://registry.yarnpkg.com/tree-sync/-/tree-sync-1.2.2.tgz#2cf76b8589f59ffedb58db5a3ac7cb013d0158b7" + dependencies: + debug "^2.2.0" + fs-tree-diff "^0.5.6" + mkdirp "^0.5.1" + quick-temp "^0.1.5" + walk-sync "^0.2.7" + +trim-right@^1.0.0, trim-right@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/trim-right/-/trim-right-1.0.1.tgz#cb2e1203067e0c8de1f614094b9fe45704ea6003" + +try-resolve@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/try-resolve/-/try-resolve-1.0.1.tgz#cfde6fabd72d63e5797cfaab873abbe8e700e912" + +tryit@^1.0.1: + version "1.0.3" + resolved "https://registry.yarnpkg.com/tryit/-/tryit-1.0.3.tgz#393be730a9446fd1ead6da59a014308f36c289cb" + +tryor@~0.1.2: + version "0.1.2" + resolved "https://registry.yarnpkg.com/tryor/-/tryor-0.1.2.tgz#8145e4ca7caff40acde3ccf946e8b8bb75b4172b" + +tunnel-agent@^0.6.0: + version "0.6.0" + resolved "https://registry.yarnpkg.com/tunnel-agent/-/tunnel-agent-0.6.0.tgz#27a5dea06b36b04a0a9966774b290868f0fc40fd" + dependencies: + safe-buffer "^5.0.1" + +tweetnacl@^0.14.3, tweetnacl@~0.14.0: + version "0.14.5" + resolved "https://registry.yarnpkg.com/tweetnacl/-/tweetnacl-0.14.5.tgz#5ae68177f192d4456269d108afa93ff8743f4f64" + +type-check@~0.3.2: + version "0.3.2" + resolved "https://registry.yarnpkg.com/type-check/-/type-check-0.3.2.tgz#5884cab512cf1d355e3fb784f30804b2b520db72" + dependencies: + prelude-ls "~1.1.2" + +type-is@~1.6.15: + version "1.6.15" + resolved "https://registry.yarnpkg.com/type-is/-/type-is-1.6.15.tgz#cab10fb4909e441c82842eafe1ad646c81804410" + dependencies: + media-typer "0.3.0" + mime-types "~2.1.15" + +typedarray@^0.0.6: + version "0.0.6" + resolved "https://registry.yarnpkg.com/typedarray/-/typedarray-0.0.6.tgz#867ac74e3864187b1d3d47d996a78ec5c8830777" + +uc.micro@^1.0.1, uc.micro@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/uc.micro/-/uc.micro-1.0.3.tgz#7ed50d5e0f9a9fb0a573379259f2a77458d50192" + +uglify-js@^2.6, uglify-js@^2.7.0: + version "2.8.29" + resolved "https://registry.yarnpkg.com/uglify-js/-/uglify-js-2.8.29.tgz#29c5733148057bb4e1f75df35b7a9cb72e6a59dd" + dependencies: + source-map "~0.5.1" + yargs "~3.10.0" + optionalDependencies: + uglify-to-browserify "~1.0.0" + +uglify-to-browserify@~1.0.0: + version "1.0.2" + resolved "https://registry.yarnpkg.com/uglify-to-browserify/-/uglify-to-browserify-1.0.2.tgz#6e0924d6bda6b5afe349e39a6d632850a0f882b7" + +uid-number@^0.0.6: + version "0.0.6" + resolved "https://registry.yarnpkg.com/uid-number/-/uid-number-0.0.6.tgz#0ea10e8035e8eb5b8e4449f06da1c730663baa81" + +ultron@1.0.x: + version "1.0.2" + resolved "https://registry.yarnpkg.com/ultron/-/ultron-1.0.2.tgz#ace116ab557cd197386a4e88f4685378c8b2e4fa" + +underscore.string@~3.3.4: + version "3.3.4" + resolved "https://registry.yarnpkg.com/underscore.string/-/underscore.string-3.3.4.tgz#2c2a3f9f83e64762fdc45e6ceac65142864213db" + dependencies: + sprintf-js "^1.0.3" + util-deprecate "^1.0.2" + +underscore@>=1.8.3: + version "1.8.3" + resolved "https://registry.yarnpkg.com/underscore/-/underscore-1.8.3.tgz#4f3fb53b106e6097fcf9cb4109f2a5e9bdfa5022" + +unique-string@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/unique-string/-/unique-string-1.0.0.tgz#9e1057cca851abb93398f8b33ae187b99caec11a" + dependencies: + crypto-random-string "^1.0.0" + +universalify@^0.1.0: + version "0.1.1" + resolved "https://registry.yarnpkg.com/universalify/-/universalify-0.1.1.tgz#fa71badd4437af4c148841e3b3b165f9e9e590b7" + +unpipe@1.0.0, unpipe@~1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/unpipe/-/unpipe-1.0.0.tgz#b2bf4ee8514aae6165b4817829d21b2ef49904ec" + +untildify@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/untildify/-/untildify-2.1.0.tgz#17eb2807987f76952e9c0485fc311d06a826a2e0" + dependencies: + os-homedir "^1.0.0" + +user-home@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/user-home/-/user-home-1.1.1.tgz#2b5be23a32b63a7c9deb8d0f28d485724a3df190" + +username-sync@1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/username-sync/-/username-sync-1.0.1.tgz#1cde87eefcf94b8822984d938ba2b797426dae1f" + +util-deprecate@^1.0.2, util-deprecate@~1.0.1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf" + +utils-merge@1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/utils-merge/-/utils-merge-1.0.1.tgz#9f95710f50a267947b2ccc124741c1028427e713" + +uuid@^3.0.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.1.0.tgz#3dd3d3e790abc24d7b0d3a034ffababe28ebbc04" + +validate-npm-package-name@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/validate-npm-package-name/-/validate-npm-package-name-3.0.0.tgz#5fa912d81eb7d0c74afc140de7317f0ca7df437e" + dependencies: + builtins "^1.0.3" + +vary@~1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/vary/-/vary-1.1.2.tgz#2299f02c6ded30d4a5961b0b9f74524a18f634fc" + +verror@1.10.0: + version "1.10.0" + resolved "https://registry.yarnpkg.com/verror/-/verror-1.10.0.tgz#3a105ca17053af55d6e270c1f8288682e18da400" + dependencies: + assert-plus "^1.0.0" + core-util-is "1.0.2" + extsprintf "^1.2.0" + +walk-sync@0.3.1: + version "0.3.1" + resolved "https://registry.yarnpkg.com/walk-sync/-/walk-sync-0.3.1.tgz#558a16aeac8c0db59c028b73c66f397684ece465" + dependencies: + ensure-posix-path "^1.0.0" + matcher-collection "^1.0.0" + +walk-sync@^0.1.3: + version "0.1.3" + resolved "https://registry.yarnpkg.com/walk-sync/-/walk-sync-0.1.3.tgz#8a07261a00bda6cfb1be25e9f100fad57546f583" + +walk-sync@^0.2.5, walk-sync@^0.2.7: + version "0.2.7" + resolved "https://registry.yarnpkg.com/walk-sync/-/walk-sync-0.2.7.tgz#b49be4ee6867657aeb736978b56a29d10fa39969" + dependencies: + ensure-posix-path "^1.0.0" + matcher-collection "^1.0.0" + +walk-sync@^0.3.0, walk-sync@^0.3.1: + version "0.3.2" + resolved "https://registry.yarnpkg.com/walk-sync/-/walk-sync-0.3.2.tgz#4827280afc42d0e035367c4a4e31eeac0d136f75" + dependencies: + ensure-posix-path "^1.0.0" + matcher-collection "^1.0.0" + +walker@~1.0.5: + version "1.0.7" + resolved "https://registry.yarnpkg.com/walker/-/walker-1.0.7.tgz#2f7f9b8fd10d677262b18a884e28d19618e028fb" + dependencies: + makeerror "1.0.x" + +watch@~0.10.0: + version "0.10.0" + resolved "https://registry.yarnpkg.com/watch/-/watch-0.10.0.tgz#77798b2da0f9910d595f1ace5b0c2258521f21dc" + +websocket-driver@>=0.5.1: + version "0.7.0" + resolved "https://registry.yarnpkg.com/websocket-driver/-/websocket-driver-0.7.0.tgz#0caf9d2d755d93aee049d4bdd0d3fe2cca2a24eb" + dependencies: + http-parser-js ">=0.4.0" + websocket-extensions ">=0.1.1" + +websocket-extensions@>=0.1.1: + version "0.1.2" + resolved "https://registry.yarnpkg.com/websocket-extensions/-/websocket-extensions-0.1.2.tgz#0e18781de629a18308ce1481650f67ffa2693a5d" + +which@^1.2.12, which@^1.2.9: + version "1.3.0" + resolved "https://registry.yarnpkg.com/which/-/which-1.3.0.tgz#ff04bdfc010ee547d780bec38e1ac1c2777d253a" + dependencies: + isexe "^2.0.0" + +wide-align@^1.1.0: + version "1.1.2" + resolved "https://registry.yarnpkg.com/wide-align/-/wide-align-1.1.2.tgz#571e0f1b0604636ebc0dfc21b0339bbe31341710" + dependencies: + string-width "^1.0.2" + +window-size@0.1.0: + version "0.1.0" + resolved "https://registry.yarnpkg.com/window-size/-/window-size-0.1.0.tgz#5438cd2ea93b202efa3a19fe8887aee7c94f9c9d" + +window-size@^0.1.2: + version "0.1.4" + resolved "https://registry.yarnpkg.com/window-size/-/window-size-0.1.4.tgz#f8e1aa1ee5a53ec5bf151ffa09742a6ad7697876" + +wordwrap@0.0.2: + version "0.0.2" + resolved "https://registry.yarnpkg.com/wordwrap/-/wordwrap-0.0.2.tgz#b79669bb42ecb409f83d583cad52ca17eaa1643f" + +wordwrap@~0.0.2: + version "0.0.3" + resolved "https://registry.yarnpkg.com/wordwrap/-/wordwrap-0.0.3.tgz#a3d5da6cd5c0bc0008d37234bbaf1bed63059107" + +wordwrap@~1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/wordwrap/-/wordwrap-1.0.0.tgz#27584810891456a4171c8d0226441ade90cbcaeb" + +workerpool@^2.2.1: + version "2.2.4" + resolved "https://registry.yarnpkg.com/workerpool/-/workerpool-2.2.4.tgz#c9dbe01e103e92df0e8f55356fc860135fbd43b0" + dependencies: + object-assign "4.1.1" + +wrappy@1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" + +write-file-atomic@^2.0.0: + version "2.3.0" + resolved "https://registry.yarnpkg.com/write-file-atomic/-/write-file-atomic-2.3.0.tgz#1ff61575c2e2a4e8e510d6fa4e243cce183999ab" + dependencies: + graceful-fs "^4.1.11" + imurmurhash "^0.1.4" + signal-exit "^3.0.2" + +write@^0.2.1: + version "0.2.1" + resolved "https://registry.yarnpkg.com/write/-/write-0.2.1.tgz#5fc03828e264cea3fe91455476f7a3c566cb0757" + dependencies: + mkdirp "^0.5.1" + +ws@1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/ws/-/ws-1.1.1.tgz#082ddb6c641e85d4bb451f03d52f06eabdb1f018" + dependencies: + options ">=0.0.5" + ultron "1.0.x" + +wtf-8@1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/wtf-8/-/wtf-8-1.0.0.tgz#392d8ba2d0f1c34d1ee2d630f15d0efb68e1048a" + +x-request@^0.2.2: + version "0.2.2" + resolved "https://registry.yarnpkg.com/x-request/-/x-request-0.2.2.tgz#ab3fcbd36c0721fef03d4f3239b9e2f720e4f7df" + dependencies: + babel-runtime "^5.8.25" + +xdg-basedir@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/xdg-basedir/-/xdg-basedir-3.0.0.tgz#496b2cc109eca8dbacfe2dc72b603c17c5870ad4" + +xmldom@^0.1.19: + version "0.1.27" + resolved "https://registry.yarnpkg.com/xmldom/-/xmldom-0.1.27.tgz#d501f97b3bdb403af8ef9ecc20573187aadac0e9" + +xmlhttprequest-ssl@1.5.3: + version "1.5.3" + resolved "https://registry.yarnpkg.com/xmlhttprequest-ssl/-/xmlhttprequest-ssl-1.5.3.tgz#185a888c04eca46c3e4070d99f7b49de3528992d" + +xtend@^4.0.0, xtend@~4.0.0: + version "4.0.1" + resolved "https://registry.yarnpkg.com/xtend/-/xtend-4.0.1.tgz#a5c6d532be656e23db820efb943a1f04998d63af" + +y18n@^3.2.0: + version "3.2.1" + resolved "https://registry.yarnpkg.com/y18n/-/y18n-3.2.1.tgz#6d15fba884c08679c0d77e88e7759e811e07fa41" + +yallist@^2.1.2: + version "2.1.2" + resolved "https://registry.yarnpkg.com/yallist/-/yallist-2.1.2.tgz#1c11f9218f076089a47dd512f93c6699a6a81d52" + +yam@0.0.22: + version "0.0.22" + resolved "https://registry.yarnpkg.com/yam/-/yam-0.0.22.tgz#38a76cb79a19284d9206ed49031e359a1340bd06" + dependencies: + fs-extra "^0.30.0" + lodash.merge "^4.4.0" + +yargs@~3.10.0: + version "3.10.0" + resolved "https://registry.yarnpkg.com/yargs/-/yargs-3.10.0.tgz#f7ee7bd857dd7c1d2d38c0e74efbd681d1431fd1" + dependencies: + camelcase "^1.0.2" + cliui "^2.1.0" + decamelize "^1.0.0" + window-size "0.1.0" + +yargs@~3.27.0: + version "3.27.0" + resolved "https://registry.yarnpkg.com/yargs/-/yargs-3.27.0.tgz#21205469316e939131d59f2da0c6d7f98221ea40" + dependencies: + camelcase "^1.2.1" + cliui "^2.1.0" + decamelize "^1.0.0" + os-locale "^1.4.0" + window-size "^0.1.2" + y18n "^3.2.0" + +yeast@0.1.2: + version "0.1.2" + resolved "https://registry.yarnpkg.com/yeast/-/yeast-0.1.2.tgz#008e06d8094320c372dbc2f8ed76a0ca6c8ac419" diff --git a/blog/2016-01-22-functional-templating-in-ember/file-chooser-only.svg b/blog/2016-01-22-functional-templating-in-ember/file-chooser-only.svg new file mode 100644 index 00000000..d4521bec --- /dev/null +++ b/blog/2016-01-22-functional-templating-in-ember/file-chooser-only.svg @@ -0,0 +1,3 @@ + + + Produced by OmniGraffle 6.4.1 2016-01-12 22:24:45 +0000Canvas 1Layer 1Top-Level Context files: FileListaction(action (mut files)){{x-file-input}} {{each}} file: File diff --git a/blog/2016-01-22-functional-templating-in-ember/full-demo.svg b/blog/2016-01-22-functional-templating-in-ember/full-demo.svg new file mode 100644 index 00000000..87c63af7 --- /dev/null +++ b/blog/2016-01-22-functional-templating-in-ember/full-demo.svg @@ -0,0 +1,3 @@ + + + Produced by OmniGraffle 6.4.1 2016-01-12 22:24:45 +0000Canvas 1Layer 1Top-Level Context files: FileListaction(action (mut files)){{x-file-input}} {{each}} file: File{{x-object-url}} url: String{{x-xml-http-request}} xhr: XMLHttpRequestState diff --git a/blog/2016-01-22-functional-templating-in-ember/index.md b/blog/2016-01-22-functional-templating-in-ember/index.md new file mode 100644 index 00000000..7c2bed8e --- /dev/null +++ b/blog/2016-01-22-functional-templating-in-ember/index.md @@ -0,0 +1,150 @@ +--- +title: "Functional Templating in Ember" +author: Charles Lowell +description: "See how easy it is to build a fully formed image upload widget complete with preview and progress bars when you apply functional programming techniques to your handlebars templates" +tags: + - ember + - javascript +image: lisp-all.png +--- + +> TL;DR -- Avoid using "magically bound" internal component properties in your templates at all costs. Instead be explicit about only passing values around through actions and block params. This will engender a new level of breeziness to your UI. + +Implementing a file upload in your web application is just the worst, am I right? + +I mean, once you get your server set up, it’s on to choosing which +off-the-shelf JavaScript Hairball ™ widget you’re going to use to +actually walk the user through the upload process. And no matter which +one you choose it never seems to fit quite right over the long-term +does it? Let’s forget for a moment that they usually come with their +own markup and style which is either difficult or impossible to +replace with your own. As irritating as that is though, it’s something +that we can perhaps live with, or at least wrestle into a submission +hold. But where one-size-fits-all solutions really start to fall down +is when you want to customize the actual file upload workflow. And +even the simplest file upload workflows are involved aren’t they? + +There’s not just the transfer of the bytes. First, there is the file selection process. If the file is an image, at the very least, you may want to show users a preview of that image. In some cases, you want to initiate the file upload immediately and without further action on the part of the user, but then again in others, you may need to stop and perform some intermediate work such as image resizing and cropping, and only then begin the transfer of the file to the server. If you’re uploading other MIME types like spreadsheets, there might be other intermediate preview-modify-and-confirm workflows altogether. + +And so if you’re like me up until about a year ago, your experience with it was a constant and simmering frustration of whacking the square peg of an upload widget into the round hole of your application’s unique workflow, and caulking around the inevitable gouges and gaps with healthy dollops of JavaScript. In a word: Exhausting. Exhausting to stand up in the first place, and exhausting to maintain over the long haul. + +So what would you say if I told you that with Ember components that are available on NPM right now you could build your own image upload widget, complete with preview and progress bars using nothing but about fifteen lines of Handlebars? Furthermore, what if I told you that you had complete freedom to use whatever markup and styling you saw fit, and beyond that, you could do it without a single line of JavaScript? + +With the technique I’m going to outline, you’ll see that these are not outlandish claims, but rather the happy, daily reality of working with what I like to call _functional templating_. What makes a template functional? It's simple really. All it means is that the only way a name becomes bound to a value is via function application. That is to say, as a block parameter, or via a `mut` action. + +A key construct underlying functional templating is the component that has little or no visual presence in terms of markup, but whose sole purpose is to manage a single piece of state and present it to a template. + +I'm going to be using three functional components today to build our file uploader, the first of which is the _file chooser_. The file chooser is a component whose sole responsibility is to capture a [FileList][1] object and present it to the templating context. What happens to the files is not the concern of the file chooser however. That can be left up to other components later on down the road. + +Let's see it in action! Click the "choose files" button below to select any number of files and have their metadata displayed in the area below the button. + + +
    + +In this example, the `x-file-input`'s action emits a `FileList` object every time that the user selects a group of files. By binding `(action (mut files))` to that action, we can inject that `FileList` into the handlebars scope, so that now it's available as a templating variable just the same as any other. Once it's in scope, the `{{each}}` iterates over the files and lists its metadata. + +Notice how the `x-file-input` component in our chooser places very little restriction on the markup that activates the dialog. In this case we chose a button, but we could have made it a link, or a label, or picture of a cat. + + + +
    + +
    Fig 1: Data flows from file input into template scope
    +
    + +Selecting the files and viewing all the metadata about them is great, but in our case, where we're headed is a full featured file upload complete with preview. In order to get there, we'll use two more functional components. + +### Preview All the Images + +In order to preview our image, we'll need to display it in the browser. Luckily, the [File API][5] provides a [createObjectURL][6] method to help you do just that. Call it with a [Blob][3] object, and it hands you back a url that you can use for links, images, or any other place a url might be prone to go. When you're done with it, you call [revokeObjectURL][7] so that the browser knows it doesn't need to hang onto that Blob anymore. + +And in order to present this url to our templating context? Yep, you guessed it. We'll use a functional component. We've written an `object-url` component which is responsible for managing an object url. It creates the url out of its `blob` attribute, and then revokes that url when either it receives a new `blob` attribute, or the componnent itself passes out of scope. Let's see it in action. + +> Note: In this example, the file input has been restricted to only image mime types by setting the `accept` attribute. + +
    + +All we do is allow the `file` attribute to flow right into the `object-url` component which yields the url we need into the template to make the image preview. + +Again, `object-url` has no markup of its own. Instead, its only job is to yield the model that makes the file preview markup possible. In this case, we bind the `url` value yielded by the `x-object-url` to the `background-image` property of a div and we're done. + +
    + +
    Fig 2: object-url :: Blob -> String
    +
    + +Finally, we arrive at our destination: the actual upload. To do this, we use a functional component that executes a real life [XMLHttpRequest][4]! + +This might seem totally nuts at first, but mostly because we’ve been trained over the years to think that a custom ajax request has always got to feel “heavy”. It turns out though that your plain vanilla, garden variety XHR is actually a well defined state machine, and as such, it maps pretty neatly onto a functional component. + +We'll just let the file object flow into an `x-xml-http-request` component just like it did the `x-object-url` + +
    + +
    Fig 3: x-xml-http-request :: File -> XHR
    +
    + +Again, the `x-xml-http-request` has absolutely no markup. Its only job is to continually yield a value into the template that represents the most up-to-date status of the request as it runs. Because of this, we can use it to very quickly model a progress bar! The only thing we need to do is leverage the `xhr.upload.percentage` property within the colorstops of a `linear-gradient`. + +> *Heads Up!* The following demo will actually upload images to [posttestserver.com][8] which is a _real_ server on the _real_ internet. Even though it’s just a bit bucket that periodically wipes all of its contents, you should still *never* upload anything sensitive to it. + +
    + +And there you have it. A fairly decent file upload widget in about 15 lines of handlebars. It's worth noting though, that if you don't like the way I've done it here, you have a free hand to change the markup and workflow to suit your liking since the components I used don't prescribe any of it. + +### Dropping the F-Bomb + +These demos were nearly trivial to put together because of the composability afforded by shifting to a more functional style. Values never just magically appear from some unknown location that could be inside your controller or your component. Instead, as in a functional language, variable names like `xhr` and `url` are only ever introduced as the formal parameters of a function. Because each component only interacts with its environment via its inputs and its outputs, it means that they can freely click together output-to-input; in as many ways as you can think of. + +The first time this technique really came together for me it felt impossibly light-weight. It felt like I must be missing something. Could it really be that easy to upload a file? Could I really style it any way that I wanted? Did I really have control over every aspect of the entire workflow from start to finish? And could I really do all of that while providing little or no custom JavaScript? The answer to all those questions turned out to be yes, and the resulting feeling was spectacular. + +
    + +I’m Charles Lowell ([@cowboyd][9] on twitter), and I build UI for a living at [The Frontside][10]. If you enjoyed this, I’d love to hear from you. + +Also, If you'd like to work with our team doing stuff like this, then please [get in touch](mailto:cowboyd@frontside.com). We're hiring. + + + + + + + + +[1]: https://developer.mozilla.org/en-US/docs/Web/API/FileList +[2]: https://developer.mozilla.org/en-US/docs/Web/API/URL/createObjectURL +[3]: https://developer.mozilla.org/en-US/docs/Web/API/Blob/Blob +[4]: https://developer.mozilla.org/en/docs/Web/API/XMLHttpRequest +[5]: https://w3c.github.io/FileAPI/ +[6]: https://developer.mozilla.org/en-US/docs/Web/API/URL/createObjectURL +[7]: https://developer.mozilla.org/en-US/docs/Web/API/URL/revokeObjectURL +[8]: http://posttestserver.com/ +[9]: https://twitter.com/cowboyd +[10]: https://frontside.com diff --git a/blog/2016-01-22-functional-templating-in-ember/lisp-all.png b/blog/2016-01-22-functional-templating-in-ember/lisp-all.png new file mode 100644 index 00000000..99bd5eab Binary files /dev/null and b/blog/2016-01-22-functional-templating-in-ember/lisp-all.png differ diff --git a/blog/2016-01-22-functional-templating-in-ember/upload-diagram.svg b/blog/2016-01-22-functional-templating-in-ember/upload-diagram.svg new file mode 100644 index 00000000..d4521bec --- /dev/null +++ b/blog/2016-01-22-functional-templating-in-ember/upload-diagram.svg @@ -0,0 +1,3 @@ + + + Produced by OmniGraffle 6.4.1 2016-01-12 22:24:45 +0000Canvas 1Layer 1Top-Level Context files: FileListaction(action (mut files)){{x-file-input}} {{each}} file: File diff --git a/blog/2016-03-18-the-insider-track/fridayhug.jpg b/blog/2016-03-18-the-insider-track/fridayhug.jpg new file mode 100644 index 00000000..b4f081e5 Binary files /dev/null and b/blog/2016-03-18-the-insider-track/fridayhug.jpg differ diff --git a/blog/2016-03-18-the-insider-track/fridayhugsquare.jpg b/blog/2016-03-18-the-insider-track/fridayhugsquare.jpg new file mode 100644 index 00000000..0e112ae0 Binary files /dev/null and b/blog/2016-03-18-the-insider-track/fridayhugsquare.jpg differ diff --git a/blog/2016-03-18-the-insider-track/index.md b/blog/2016-03-18-the-insider-track/index.md new file mode 100644 index 00000000..9c042a08 --- /dev/null +++ b/blog/2016-03-18-the-insider-track/index.md @@ -0,0 +1,318 @@ +--- +title: The Insider Track +author: Lydia Guarino +description: "A guide to using kindness to elevate your conference experience." +tags: + - conferences + - community +image: fridayhugsquare.jpg +--- + +A guide to using kindness to elevate your conference experience. + +Get your sticker collections, backup batteries and ironic nerd mashup +t-shirts ready, because it's tech conference season! + +We're gearing up for one of our [favorite conferences][7] of the year, and +we've been spending a lot of time discussing what it means to have an +amazing conference experience. We're incredibly stoked to see +our favorite thought leaders, community gurus and internet celebs BLOW +OUR MINDS with the new hotness and poignant epiphanies, but to +properly set expectations, *that* is not what this article is about. + +If you've got your ticket secured, you or your company +has invested in the opportunity to +create shared experiences with strangers from all over the world who +happen to be passionate about the same things you are. You've pressed +pause on your life and traveled far from home to connect, grow and +recharge your techie batteries. You are here to be inspired to +innovate and to uncover new [opportunities][8] for yourself or for your company. + +Conferences are about [community][9] and community is all about +[participation][10]. If you are attending a conference strictly for the +speaker line up, you will likely have a better experience streaming +the conference from home. You'll definitely get a better seat. But, if you +want to fly home feeling like you just had the best three days of your +life, after a whirlwind of fascinating interactions with people you +once called strangers and now call friends, you have the conference +spirit and it's time to *supercharge* your experience. + + +## Let's back up a bit. + +In a past life, before my development career, I was a sales rep for a +large tech company. Once or twice a year, the company's 700 or so +sales and vendor reps from all over the country would converge on +Austin for a three day Carnaval of hyper-charged networking events +that included extravagant parties, loud showboating at awards shows, +loads of vendor swag, and *lots and lots* of people who lived life at +a completely different pace than I did. + +I spent a good amount of time at these events hiding in the restroom, +recharging my introvert batteries so that I could put my extrovert +mask back on. I'd then wade back into the crowd with a crazed smile painted +on my face, feeling both overstimulated and exhausted at the same +time. Sometimes I just stayed in my hotel room instead of going +to the evening events. + +It was only after several of these conferences that I finally figured +out where my limits were, what my goals for the conference were, and +how I could be empowered to avoid the things that exhausted me, so I could +focus on the parts that were incredibly fun, inspiring and +rewarding. I learned that I needed to be much more intentional with my +time and energy. + + +## Participation Over Observation + +What worked for me was the decision to be a participant, not just an +observer. When I was intentional about participating, I felt in +control and was able to keep my energy up and my interactions +positive. Each positive interaction fed into the next, inoculating me +against the occasional not so awesome interaction. I left those events +feeling like I was on top of the world. I learned there was a formula +I could follow to dramatically enhance my experience and improve the +opportunities I came away with after the conference. + + +I've followed this formula at tech meet-ups and conferences for +the past three years. It's required very little tweaking. What +was once a survival mechanism for me and has now evolved into an +empowerment tool I use to forge strong connections with people who +might be my next client, hire or boss. It's required very little +tweaking because it's based on something pretty constant -- sales. And +sales is based on humans. + + +## You *are* a sales rep. + +Face it, when you attend a conference, you are selling. This truth is +scary for a lot of people in our industry. Sales sounds +like something reserved for the +pathologically extroverted among us. Luckily, it's a myth that you +have to be incredibly outgoing to be a successful salesperson. It may +help you get started, but it is not how you close a deal. You can't +get someone to buy something by *tricking them* or by intimidating +them into cracking open a checkbook. You've probably hung up on that +sales person. They probably get hung up on a lot. And that's because +sales is an exercise in [investigation][6]. It's the art of uncovering the +intersection between someone else's needs and something you can +offer. If you've ever had the opportunity to watch a skilled +salesperson work their magic, you may have discovered that they do +significantly less talking than you might expect. Most of their words +are likely leaving their mouth in the form of questions. + + +While you may not be pushing products at a conference, you *are* +peddling memorable experiences and positive impressions. Impressions +that may lead to your next hire, your next client, or your next job +opportunity. You're selling the idea that you are someone others would +like to work with. You're selling the idea that you would be great to +sit next to all day and someone who would be fun to collaborate with. + + +If this is not your first conference rodeo, you probably remember +*that guy* that seemed like he knew and was adored by *everyone* at +the conference. He was invited to do *everything* and was *everywhere* +you went. While it may be true that this person naturally has some +genetic defect that makes them fearless or has been sprinkled with +some kind of unicorn magic, it is far more likely that they have just +figured out the formula. It's a formula that will likely keep them +gainfully employed for the rest of their working life, regardless of +their objective level of technical skill, and it's surprisingly +straightforward. + + +## Focus your energy on making someone else's day better. Do that with every* person you meet. + +This sounds fluffy, and it is. I've distilled it down into a mantra +that I can repeat to myself when I walk into a big room filled with +strangers. It's just a rephrasing of exactly what sales is: +*Uncover what someone else needs and figure out what you have to +meet that need*. The implementation of this mantra is what I +call *The Formula*. Sometimes I use all of these tools and sometimes I +just choose pieces for a specific setting. It took me a while to +work up to using the entire list. I recommend trying out +one or two that feel most comfortable at first and see how your +experience changes. Then, try a few more. + + +## The Formula + +### Part 1 - Setting the Stage + +#### 1. Wave hello. + +The conference you are attending almost certainly has some [social +media channels][5] associated with it. A couple days before a conference, +start to make your presence known on social media by sending a message +about how excited you are to be going to the conference. Thank the +organizers or share helpful tips for getting to the venue or finding +parking. Share a good place to eat that you discovered. Mention people +you are excited to meet up with. Establish yourself as a friendly and +welcoming person and people will be significantly more likely to reach +out to you or be brave enough to walk up to you. Something that often +gets overlooked is that your social media picture should be an +*actual* recent picture of yourself. It's +how people find you in person or friend you after a positive +interaction. + + +#### 2. Make an itinerary. + +Come up with a list of things you want to do or see while you're at +the conference. Pick out the talks you want to be sure to see, and +pick some times to break off into the "Hallway Track." Some of the +best interactions you can have at a conference take place in the +quieter lulls when most people are watching talks. The Hallway Track +offers a smaller pool of people to converse with. Find several +restaurants, bars, after hours events, and recreation spots you'd like +to visit. When people are huddled together after talks trying to +decide on a place to go, offer an assertive choice. You'll get to do +what you wanted to do, and people will appreciate that they didn't +have to play the "I don't know, what do you want to do" game. It also +gives you an easy way to invite the people you've had good +interactions with to share specialized experiences with you. + + +#### 3. Wear a costume. + +This is an old sales trick. *The Formula* is about pushing past your +comfort zone. Being outside your comfort zone can be incredibly +exhausting. It's actually incredibly difficult for most humans not to +dwell on what people think of you. You are intentionally putting +yourself in a vulnerable position. I've had a lot of success with +[costumes][4]. This doesn't mean you should parade about in a feather boa +or a mask. It just has to be something that lets you be a little bit +of someone else while you're trying to overcome being more of +yourself. Sometimes, this is just a different hairstyle for me, or a new +pair of shoes, or wearing something that reminds me of someone I +love. It helps me get into my extrovert character, when my comfort +zone would be to quietly sit in the back row. + + +### Part 2 - Finding Positive Interactions + +#### 1. Ask, don't tell. + +Small talk is the single hardest and most exhausting part of meeting +new people. The trick to making it through this uncomfortable part of +an interaction is to power through it with questions. Remind yourself +that your goal is to figure out who this person is, what their goals +are and what stands between them and achieving those goals. Your goal +is *not* to impress them. You actually have very little control over +that part. What you *do* have control over is your level of engagement +with this new person. People are interested in people who are +interested in them. People like people who like them. Asking someone +about what's important to them is the best and fastest way to have a +positive interaction. In a good interaction, that person will +reciprocate and ask you about what's important to you. + + +Not everyone is going to be open to interacting with you, and that is +totally okay. Your goal is to give them the opportunity to engage with +you. If they don't reciprocate or you don't feel like it is going to +be a positive interaction, it is perfectly okay to move on to speaking +with someone else. Try not to take it personally. It's pretty +difficult to tease out why someone might not want to talk to you. The +best part about conferences is that there are a whole lot of +people. The next person you speak to is probably going to be open to a +conversation. + + +#### 2. Be the includer. + +This is my favorite tool. It's easier for some than others, and that's +okay. Give it a try and see how it goes. If you've been in a +particular industry for a while, it can be very tempting to spend most +of your time at a conference with people you already know. You should +definitely spend some time hanging out with the people you know and +like, but you should also be actively figuring out how to connect +those people with others you know -- or better yet, with nice people +you just met. Sit down at a table with people you don't know. Invite +someone that is sitting alone to sit with you. Invite people to go do +something fun via social media. The more people you connect, the more +those people will connect you with others. The best part is that if +those people were nice, it’s likely that the people they introduce you +to will *also* be nice. + + +Satistically, any time you are at an event with a lot of people, +some minor subset of those people are not nice people. If someone +engages you and you do not feel safe or they make you feel inferior +or ashamed, quickly break that engagement and find someone else to +talk to. If you see someone else having a bad interaction, [offer +them a safe escape][11] and invite them to join you. + + +#### 3. Play the yes game. + +You've just a couple of days to soak up as many opportunities and +experiences as possible. If you're lucky enough to be offered an +invitation and you don't have an incredibly compelling reason to turn +it down (unmovable conflict, you feel unsafe or you do not trust the +person inviting you), [say yes][12]. It helps to think of this as a game. It +will encourage you to try something you might otherwise bow out +of. I'm a little scared of hack sessions. I play the yes game and I +make myself go at conferences. It has always ended up being super +fun. Bring a buddy if you are feeling timid! It's a perfect +opportunity to be an includer. These invitations are almost always +where the greatest opportunities are hiding. + +### Part 3 - Reinforcing Positive Interactions + +#### 1. Reward bravery. + +Remind yourself that everyone you meet at a conference is being +brave. If someone is brave enough to come talk to you, engage them! If +you are lucky enough to get to talk to one of the speakers or +organizers, thank them for the time and effort they poured into +sharing something they care about with you. Publicly praising the +brave people you've talked to on social media is a great way to +reinforce the confidence needed to take risks and be brave. + +#### 2. Follow up. + +Conferences are a whirlwind tour. Everyone you meet has met hundreds +of people in a matter of days. If you uncovered an opportunity, don't +wait to follow up. Before you go to sleep each night, queue up emails +or social media touches to follow up with the people you met. Say +thank you to the people that helped make your experience great. If +someone offered you advice or encouraged you to try something new, +reach back out to them to let them know you followed their advice and +what the outcome was. + +## Community is about participation. + +Conferences are about community, and community is all about +participation. Participation happens on a spectrum. If you are attending your +first conference or you're just working up to making the jump from +spectator to participant, assess your limits. Don't go gangbusters on +your first try. Pick a few strategies that sound like they might be in +your wheelhouse and make that your first step. If those work well for +you, try something you're a little more uncomfortable with. If all you +can manage is to be kind to the people that talk to you, you've done +your part to help build a positive community. If you can help one or +two or twenty people have a wonderful conference experience, you'll +walk away with some fantastic new contacts, some inspiring new ideas, +enhanced confidence and perhaps a solid professional opportunity or +two. + +I’m Lydia Guarino ([@lydiaguarino][2] on twitter), and I build UI for a living at [The Frontside][3]. If you enjoyed this, I’d love to hear from you. + +If you have a project you'd like to work with us on, +[get in touch](mailto:cowboyd@frontside.com). We're currently taking on +new projects. + +[1]: http://posttestserver.com/ +[2]: https://twitter.com/lydiaguarino +[3]: https://frontside.com +[4]: http://www.harpersbazaar.com/culture/features/a10441/why-i-wear-the-same-thing-to-work-everday/ +[5]: http://www.inc.com/eric-v-holtzclaw/five-ways-to-use-social-media-at-conferences.html +[6]: http://www.salestrainingadvice.com/2005/11/the-art-of-asking-good-questions-by-tim-hagen.html +[7]: http://emberconf.com/ +[8]: http://www.inc.com/janine-popick/4-reasons-your-employees-should-attend-conferences.html +[9]: https://www.youtube.com/watch?v=xsG0gDkvDPw +[10]: http://oss-watch.ac.uk/resources/toptipscommunities +[11]: http://www.ashedryden.com/blog/increasing-diversity-at-your-conference +[12]: http://www.huffingtonpost.com/panache-desai/the-power-of-one-magnific_b_6802330.html diff --git a/blog/2016-05-17-global-accessibility-awareness-day/gaad-logo.png b/blog/2016-05-17-global-accessibility-awareness-day/gaad-logo.png new file mode 100644 index 00000000..ccbef0bb Binary files /dev/null and b/blog/2016-05-17-global-accessibility-awareness-day/gaad-logo.png differ diff --git a/blog/2016-05-17-global-accessibility-awareness-day/index.md b/blog/2016-05-17-global-accessibility-awareness-day/index.md new file mode 100644 index 00000000..87163f66 --- /dev/null +++ b/blog/2016-05-17-global-accessibility-awareness-day/index.md @@ -0,0 +1,103 @@ +--- +title: Global Accessibility Awareness Day +author: Robert DeLuca +tags: + - accessibility + - ember.js + - community +image: gaad-logo.png +directory_index: false +--- + +Thursday May 19th marks the fifth +[Global Accessibility Awareness Day](http://www.globalaccessibilityawarenessday.org/). The +goal for Global Accessibility Awareness Day (or GAAD for short) is to +get people who work in technology to start talking, thinking, or +learning about accessibility. + +If you're not familiar, accessibility is about building solutions for +those who have disabilities. For the web, it means considering those +who are deaf, blind, colorblind, or have motor skill disabilities in +your design and development. + +A group of people that deeply care about accessibility and +Ember.js have banded together. That group is called +[ember-a11y](https://github.com/ember-a11y). Our goal is to make ember +accessible by default as much as we can, and the first step in that +direction is our +[ember-a11y](https://github.com/ember-a11y/ember-a11y) addon that +fills the gaps of the Ember router. Before this addon, those who rely +on screen readers would never know the page updated when switching +routes. [Here is a quick video demonstrating this.](https://www.youtube.com/watch?v=1BGmTj4j3ms) + +That's only the start of what we want to accomplish as a +team. We're putting together a website that isolates and compiles some +of the top ember addons on [Ember Observer](http://ember-observer.com) +so you or assistive technology providers can test them. You can +[find the website here](https://ember-a11y.github.io/a11y-demo-app/). + +For GAAD 2016 we would love your help adding as many of the +[top component addons from ember observer](https://emberobserver.com/categories/components) +as possible. To stop work duplication, +[add an issue](https://github.com/ember-a11y/a11y-demo-app/issues/new) +**BEFORE** you start implementing the addon. That way, if someone else has +already started work on the addon you wanted to implement, you would know. + +If that doesn't strike your fancy and would like to contribute to +addons, +[check out this spreadsheet.](https://docs.google.com/spreadsheets/d/1q4DkaNwH8mh7xZJa1TmrHNcFuFuWdQ80iG88c7N4QII/edit#gid=0) +Here we've started a list of addons that need accessiblity work. The +list can range anything from adding a `role` attribute to a +div acting as a button, to pushing accessibility through for the +wildly popular date picker component, Pikaday. To start working on +something from the spreadsheet, place your name in the "assignee" +column and highlight the row according to the legend at the bottom of +the document. You can also add things to the spreadsheet you find from +testing other addons! + +You could also catch this Google IO event about Accessibility titled +["Accessibility is My Favorite Part of the Platform".](https://events.google.com/io2016/schedule?sid=38631cfd-0bef-e511-a517-00155d5066d7#day2/38631cfd-0bef-e511-aI517-00155d5066d7) +It is a great talk that we can learn a lot from. + +If none of that looks interesting to you, at the very least spend 10-15 +mins with a screen reader on any of your favorite sites. Try closing +your eyes or dimming the screen and navigating only using the screen +reader. This will help you understand how anyone who relies on a +screen reader uses a website. **This might be one of the most valuable +things you do on GAAD.** + +If you're an OS X user, +[WebAIM has a excellent tutorial on how to use VoiceOver](http://webaim.org/articles/voiceover/). Use +CMD + F5 to toggle VoiceOver on or off. You can also toggle VoiceOver +on your iPhone if ask Siri to turn it on or off. + +If you're on Windows, you can use +[JAWS on a free trial](http://www.freedomscientific.com/downloads/jaws) +or [NVDA](http://www.nvaccess.org/download/) which is free. JAWS is +the most popular screen reader +currently. [WebAIM also has a great JAWS tutorial](http://webaim.org/articles/jaws/) +and [NVDA tutorial.](http://webaim.org/articles/nvda/) + +For me, this Global Accessibility Awareness Day will be sponsored by +The Frontside. I'm going to try to give back to the community as much +as possible. My day is going to be split into 3 parts: + +- Push accessibility through [for Pikaday](https://github.com/dbushell/Pikaday/pull/458) +- Pairing hours [1-3pm] +- [Add template linting for common accessibility mistakes](https://github.com/rwjblue/ember-template-lint/issues/41) + +I would like to pair with as many people as possible in 3 hours. Each +pairing session will be 25 minutes long and we can work on anything +accessibility-related you would like. This could be dropping the +ember-a11y addon into one of your apps, solving an issue from the +spreadsheet, or using a screen reader on websites. Sound interesting? +[Sign up here!](http://pair.ember-a11y.com) We're going to be using Screenhero or Skype for +screen sharing. + +I'll be around all day to help. Feel free to reach out on twitter +([@robdel12](http://twitter.com/robdel12)) or on Slack (robdel12)! For Slack you +can snag me in the [web-a11y Slack](https://web-a11y.herokuapp.com/) +or the +[Ember community slack](https://ember-community-slackin.herokuapp.com/). What +are you waiting for? Let's get out there and make the web a little more +accessible! :) diff --git a/blog/2016-05-17-so-you-just-finished-a-bootcamp-now-what/codedog.jpg b/blog/2016-05-17-so-you-just-finished-a-bootcamp-now-what/codedog.jpg new file mode 100644 index 00000000..fe014e50 Binary files /dev/null and b/blog/2016-05-17-so-you-just-finished-a-bootcamp-now-what/codedog.jpg differ diff --git a/blog/2016-05-17-so-you-just-finished-a-bootcamp-now-what/geekdom.jpg b/blog/2016-05-17-so-you-just-finished-a-bootcamp-now-what/geekdom.jpg new file mode 100644 index 00000000..9218c01e Binary files /dev/null and b/blog/2016-05-17-so-you-just-finished-a-bootcamp-now-what/geekdom.jpg differ diff --git a/blog/2016-05-17-so-you-just-finished-a-bootcamp-now-what/index.md b/blog/2016-05-17-so-you-just-finished-a-bootcamp-now-what/index.md new file mode 100644 index 00000000..b41824dd --- /dev/null +++ b/blog/2016-05-17-so-you-just-finished-a-bootcamp-now-what/index.md @@ -0,0 +1,163 @@ +--- +title: So You Just Finished a Bootcamp, Now What? +author: Stephanie Riera +tags: + - code + - bootcamp + - frontside + - job + - hiring + - interview + - oss + - community +img: techstars.jpg +--- + +You took the plunge and here you are, a newly minted code bootcamper. You've endured the brain inundation of information and the inherent stress of not knowing what you're doing. +Congratulations, you're here now and you're still asking yourself, "What do I do next?", I have no idea what I'm doing. You're here though, thanks Google. + +
    + +
    + +As a developer, our job 90% of the time is to figure things out and create solutions using the resources we have available. You are a developer, you are just unemployed and finding a job is kind of the same process. It boils down to utilizing the available resources and creating your own solutions. + +I've been in your shoes. I quit my secure job in a laboratory and decided to switch from Molecular Biology to Programming. I heard about bootcamps on NPR and felt like it was the best option given my student loan debt wasn't going to pay for my next degree. Drinking magma from the center of the Earth sounded better than another 4 years in college for a CS degree. + +After finishing the bootcamp I wasn't sure where to go next. Most of the developer positions in my city had been occupied by the previous bootcamp cohorts and finding a job was proving to be more difficult than originally advertised. I have compiled all the lessons I've learned along the way and hope it helps you on your journey. + +## Expectations vs Reality + +##### Bootcampers' expectation: + +You graduated and by the end of the week you'll have your dream job in the bag. Better yet, your bootcamp will magically find the job for you. _Cue turntable stopping abruptly_. +Some bootcamps may guarantee _job placement_ but that doesn't translate to working in a desireable company or project. Alternatively, other bootcamps will refund you some portion of what you paid if you don't get a job after a period of time. + +Technically, you are not guaranteed a job nor are you entitled to one. Some bootcamps help with the employment process by relaying available positions but you risk starting your career somewhere you shouldn't start at. You may very well find yourself unemployed 5 months after having graduated. I'll discuss this further in the next section. + +##### Bootcampers' reality: + +Finding a job is akin to speed dating- it takes effort. You put on your best outfit and go to the interview with the hope of impressing them while getting to feel them out. Don't forget, they're doing the same thing too. The relationship should always be mutually beneficial. + +Every job will have have its own set responsibilities, company size, and culture. Before you start applying, sit down and think of what your ideal first job would look like. Do they participate in code reviews? Will they ask you to learn .NET and evolve into a customer support guru? Are they a TDD (Test Driven Development) shop or do they ship code lightening fast? Do you prefer working for a giant company or a startup? + +What is important to you? Not sure yet? That's fine too because even if you land a not so great job, it's experience nonetheless and you'll discover what you like or don't like. It may take kissing a couple frogs. + +##### Employer's reality: + +Not everyone is a fan of bootcamp grads. Every bootcamp is different in how they present the curriculum and the scope varies greatly. You are not vetted and you lack the industry experience. Not all bootcamp graduates are good programmers and not all bootcamps prepare their students to be hireable. Combine those two and you have companies that have been burned from hiring bootcamp grads. If they consider hiring you then know that they are taking a risk on you. You are an investment and you are promising a return. + +Some companies say they ain't got time for that and prefer hiring a mid or senior developer. Others offer internships or contract-to-hire gigs as a form of test trial before actually committing which can be advantageous for both parties. Then there are progressive companies that understand the value of building a diverse team of all backgrounds and levels of experience. These companies have mentorship and personal growth entwined in their company culture and they take it to heart despite the challenges. + +## Unemployment may happen, and yes it sucks. + +If this happens, just power through. Even if money gets tight and you need a temporary job to pay the bills, remind yourself why this is temporary. Use your lunch breaks to catch up on podcasts or that JS book. Make sure any kind of free time is sacred and reserved to either studying or sleep. + +If you have the luxury of staying at home for a couple months while you find a job, don't waste it. Seeing your reflection on the black screen between Netflix episodes sitting on the couch covered in cheeto crumbs at 2 pm is no bueno. + +To avoid this, keep yourself accountable and productive by building your own 'work' schedule. +This is what mine looked like until I was busy working 3 different side gigs: + + - Wake up between 8:30 - 9 am + - Exercise + - Breakfast & shower + - Finding jobs, applying, & phone interviews + - Lunch + - Optional: Go for a walk or switch activities as a mental break + - Work on projects, portfolio, OSS + - Dinner + - Go to meetups or keep coding if there are no meetups that evening + - 10:30 pm bedtime (avoid 3 am interweb voids) + +### Whatever you do, don't stop working + +##### Cast a wide net + +There's more than just searching on [Indeed][4]. Freelancing builds your portfolio, you can find small projects on [Upwork][5], [Fiverr][6], and [Craigslist][7]. Leverage your connections and let them know you're in the market, they might know someone. Recruiters are a great asset because you don't need to pay for their services, they get a cut from the Company that hires you. If you do this, make sure you are specific on what you're searching for. Give the recruiter your website or [GitHub][8] profile so that they can get to know you. Do some research on nearby shops and check out their careers page, not all of them post on job sites. Don't forget to check the city government jobs and language specific job boards. + +##### Nail your interview + +Do your research on the company interviewing you, it shows you're interested in their product, values, and history. Get the basic interview questions down, practice makes perfect. I went to interviews for jobs I knew I'd turn down if I received an offer because I knew it was good practice. + +Now for the sound of death: pair programming interview. This can be nerve wracking especially for a junior developer yet it can be a blessing in disguise. How? Well, this gives your prospective employer a litmus test of where you are as a developer. You can avoid unrealistic expectations and it's okay to admit when you don't understand a concept. Remember, _your attitude under stressful situations_ says more than the code you're writing. + +--- + +Last note, if you're a bootcamp grad, you probably became an expert in another industry. Don't neglect the fact that you had the courage to change career paths and the grit to learn a completely new discipline. You may not have all the experience right now but you have tenacity and other qualities that are equally valuable to an employer. **You bring an unique experience and contribution to the table.** + +--- + +##### OSS + +Open Source Software is valuable for everyone. When software is reviewed by hundreds of developers, it's hard for bugs to go unnoticed and the quality of the code is better than that of a 8 person dev team. Companies are jumping onboard the OSS train and nonprofits can use all the help they can get. As a developer you benefit from seeing how others create solutions and it's nice to contribute to a greater something. You can find the trending OSS projects on GitHub [here][9] and start pushing code. + +## Have an online presence + +People are going to be doing their homework and looking you up. Let's face it, you're working in the tech world now, you should probably step your game up. Please do the following: + +- Use one (professional) email address for consistency. +- Invest in a good headshot & use it across all your social media accounts. A white or brick background, good lighting and iPhone can do the trick. +- Tidy up your resume and tailor it to the job you're applying for. Save yourself the trouble and have multiple resumes. (Ex- Retail resume, Developer resume, Marketing resume, etc) +- Don't skip the cover letter. This is your opportunity to show off your writing skills and explain why you're a great hire they can't pass up. It also sets you apart from those candidates that aren't writing them. +- You should have a [LinkedIn][10] and it should not be empty. Later it will come in handy to stay connected with the professionals you meet and you can search for jobs here too. +- Push all your projects to GitHub. +- Don't be lazy, write those ReadMe's. Without proper documentation, a user won't know how to use your app and has no where to go find answers. If you leave developers with just a directory structure, it's not only useless but a fool proof way of having them leave your repo. +- Have your own domain where you link to your resume, GitHub, LinkedIn and other mediums like Twitter. This is a great place to exhibit your skills when asked about your experience. +- Clean up your online profiles. Google yourself, remove embarrassing tweets and delete those questionable Myspace photos. + +## Be active in the community + +Getting stuck in code at home is horrifying because it takes hours to get unstuck. Being around other developers can make a huge difference. Check [Meetup.com][11] for tech groups in your city and don't be shy. Meetups offer a safe environment to learn new topics through their lightning talks, the ability to present your projects, receive constructive feedback, and lose the speaking jitters. Meetups organize fun hackathons and it's a great place to get your networking on. Let me tell you when it comes to jobs, it's all about _who you know_. + +
    + +
    + +Finding a coworking space in your area is paramount. You'll be informed of what is going on in your city's tech ecosystem, meet ambitious people, and even find out about positions with the local startups. Everyone here is busy hustling and simply being in that environment is better than trying to work from home or the loud Starbucks. These people like to work hard and play hard. The opportunity of making amazing friends along the way is real. Surround yourself with people that inspire you. + +Never leave your home without your business cards and a positive attitude. If you don't have any, you can order some on [Vistaprint][12] or [Moo][13]. Your card should include your contact information, the title of the job you desire (ie- Backend Developer), and your GitHub account. Design your card to be an extension of yourself, people will remember you. There should be no room for missed connections, always do your due diligence and follow up with an email. A friendly smile can go a long way. Be the person that introduces themselves or starts a conversation with the person sitting by themselves. + +### Give first + +Something I learned while working for Techstars was the value of giving first without the expectation of gaining anything in return. I can't stress how important and rewarding this is. You can be of service to others in a variety of ways: + +- Share advice in the form of tweets, a blog or a podcast. Sit down with someone who is interested in programming but is unsure. Sharing your experience even if you're not where you want to be yet can actually be inspiring to others. + +- Show what you learned at a meetup or at a brownbag lunch. Some schools host hackathons for children or have chapters of [Girls Who Code][14] and they're always in need of helping hands. You don't need to be a seasoned developer to explain how the DOM works or how CSS can add style to a page. When you support your local YMCA or code group for veterans, you are spreading knowledge while solidifying these concepts. _The one who does the talking, does the learning._ + +- Can't find any interesting meetups nearby? Create your own! I won't lie, it will take some planning and preparation on your end during the beginning but after that it gets easier especially after other organizers join. My bestfriend and I organized the first RailsGirls (a Ruby on Rails introductory hackathon for women of all ages) in San Antonio. We had a successful 30+ attendee turnout and I coached my own group without knowing a line of Ruby or the Rails framework. + +- Join a non-profit organization like [Code for America][15] where you can tackle civic problems for the greater good. You can always be of assistance remotely. + +Offer your time free of charge. When you help out the local girls soccer team by designing their logo, you are freeing their budget to allocate funds elsewhere. You've become a contributing member of the community and when someone asks if anyone knows of a good designer, you have people that will vouch for you organically. Your name comes up everytime someone compliments their jersey. + +
    + +
    + +Organic leads aside, let me tell you the most satisfying part. _Knowing you've made a difference in someone's life._ After the [Rails Girls][16] event, an older woman came and grabbed my hands to thank me for organizing the event. She said she never would've imagined that she'd be able to create an application from scratch. The majority of women who attended had never touched a line of code previously but left exhilarated and ready to continue learning because they knew it was achievable. I strongly recommend combining your passion with helping others because it's more fulfilling than you'd think. + +## Be kind, be patient + +This journey can be a long one with unexpected twists and bumps on the road. I've given you all the pointers on how to hustle hard but I must also touch on personal wellbeing. I've asked you to be kind and of service to others but that should also include yourself. You might have moments of self doubt, frustration, anxiety, and I need you to know, I had those moments. If you know within yourself that this is what you want but you don't have the answers to everything else, that's okay. These difficult moments taught me how to _be comfortable with being uncomfortable._ These moments shape you. Have patience and trust in your struggle- even if those around you don't. + +Circumvent outside noise. If you feel like your fire is being distinguished, listen to inspirational podcasts, read and watch autobiographies that amaze you. If you're feeling exhausted, listen to your body. Get a massage to relieve some tension. Go for a walk, watch a movie or go to bed early. Feed your body, mind, and soul. This trek is challenging but if you apply yourself, you'd be surprised at the things you can accomplish in a year. If there is a will, there is a way. + +I’m Stephanie Riera ([@stefriera][2] on twitter), and I build web applications for a living at [The Frontside][3]. If you enjoyed this, I’d love to hear from you. + +[1]: http://posttestserver.com/ +[2]: https://twitter.com/stefriera +[3]: https://frontside.com +[4]: https://indeed.com +[5]: https://upwork.com +[6]: https://fiverr.com +[7]: https://craigslist.com +[8]: https://github.com +[9]: https://github.com/trending +[10]: https://linkedin.com +[11]: https://meetup.com +[12]: https://vistaprint.com +[13]: https:/moo.com +[14]: https://girlswhocode.com +[15]: https://codeforamerica.org +[16]: https://railsgirls.com diff --git a/blog/2016-05-17-so-you-just-finished-a-bootcamp-now-what/railsgirlsSA.jpg b/blog/2016-05-17-so-you-just-finished-a-bootcamp-now-what/railsgirlsSA.jpg new file mode 100644 index 00000000..8af52791 Binary files /dev/null and b/blog/2016-05-17-so-you-just-finished-a-bootcamp-now-what/railsgirlsSA.jpg differ diff --git a/blog/2016-05-17-so-you-just-finished-a-bootcamp-now-what/techstars.jpg b/blog/2016-05-17-so-you-just-finished-a-bootcamp-now-what/techstars.jpg new file mode 100644 index 00000000..0614da06 Binary files /dev/null and b/blog/2016-05-17-so-you-just-finished-a-bootcamp-now-what/techstars.jpg differ diff --git a/blog/2016-05-19-accessibility-why-it-matters/accessibility-chart.png b/blog/2016-05-19-accessibility-why-it-matters/accessibility-chart.png new file mode 100644 index 00000000..5d676f47 Binary files /dev/null and b/blog/2016-05-19-accessibility-why-it-matters/accessibility-chart.png differ diff --git a/blog/2016-05-19-accessibility-why-it-matters/gaad-logo.png b/blog/2016-05-19-accessibility-why-it-matters/gaad-logo.png new file mode 100644 index 00000000..8fb5fb8c Binary files /dev/null and b/blog/2016-05-19-accessibility-why-it-matters/gaad-logo.png differ diff --git a/blog/2016-05-19-accessibility-why-it-matters/index.md b/blog/2016-05-19-accessibility-why-it-matters/index.md new file mode 100644 index 00000000..07e663a3 --- /dev/null +++ b/blog/2016-05-19-accessibility-why-it-matters/index.md @@ -0,0 +1,101 @@ +--- +title: Accessibility & Why it Matters +author: Stephanie Riera +tags: + - code + - frontside + - accessibility + - web accessibility + - disability + - gaad + - ember-a11y +image: gaad-logo.png +--- + +In honor of [GAAD][1] (Global Accessibility Awareness Day), I interviewed a developer who is active in the accessibility community to gain some insights on the importance of web accessibility. + +Applying for jobs, registering your vehicle, selecting classes for the next school semester, and other tasks can become close to impossible if you have no access to the internet. Imagine for a second that you had no access to Google, Netflix, and dank memes. This sucks. Now let's imagine you couldn't use your computer's mouse or trackpad. How would you navigate the page or click on an image? Life would suck, royally. This is the reality for a population of internet users. + +### What is accessibility? +[Accessibility][2] refers to the design of products, devices, services, or environments for people who experience disabilities. Usability affects from the blind and color blind to the deaf and seizure prone. People in developing countries use the internet but may not necessarily know English. A site with poor usability makes it challenging for the elderly and those with motor skills disabilities to use. + +
    + +
    +([Image][3]: Blind students enjoying computer games at Techshare India 2012) + +Federal websites must adhere to Section [508][4] which is a law that states Government sites must be accessible for people with disabilities. Some sites follow the [ADA][5] (Americans with Disabilities Act) guidelines loosely but the majority of websites could make a concerted effort to improve. + +#### The dialogue is just not there. +And it's unfortunate. You'd think that with the current tech boom more developers and startups would pay attention to accessibility. The fact of the matter is, unless you have a disability or are directly affected by a family member with one, chances are you've never given it much thought. Even I didn't learn about web accessibility while I was studying web development. + +
    + +
    +([Image][6]: Google Trends graph of web accessibility and guidelines over time, captured May 16, 2016) + +Despite the lack of awareness, this leaves room for opportunity. Accessibility applications can be expensive and have a steep learning curve. There is an untapped market with both native and web accessibility applications that have capacity for great improvement. The value you contribute through innovation and open source software is limitless. + + +### Why does making my site accessible matter? +The UN Convention on the Rights of Persons with Disabilities recognizes access to information and communications technologies, including the Web, as a **basic [human right][7].** + +The internet has become more than humanity's encyclopedia. Cultural and social behaviors bubble out of the world wide web and become elements of today's society. The internet provides a medium for people with disabilities to interact. Everyone benefits when we _support social inclusion for all._ Tech is integrated in our culture and everyday life. Let’s try an experiment. For the next 30 seconds, navigate any webpage you want (this article can wait), and try navigating without a mouse. Hit tab and enter. See what it’s like to click links, fill in a form, whatever you like. Or better yet, if you have 3 or 4 minutes, turn on [VoiceOver][19], and navigate with just the audio. Use a screen reader with voice recognition if you're feeling lucky. + +By just taking those few seconds or couple of minutes, you can empathize with an entire world of users whose experience on the web is completely different from our own. And in the end, isn’t creating great experiences what software development is all about? This insight not only makes you a better developer but paves the way for building better UX. Beautifully designed sites avoid unnecessary noise and are navigable. Elegant applications incorporate architecture that flows and actions that are clear. Together we can make web accessible by default, the _standard_. + + +#### Alright, how do I start making my site accessible? +It's easier if you start putting thought into the site structure and picking colors from day one. Consider the flow of the site, would you be able to navigate through it if you were drunk? Test your colors with a contrast checker and start adding links to skip repetitive content. Imagine a screen reader describing the Facebook navigation every time you load the page. After the fifth time, you'd know the messages icon goes right after the friend requests. + +Make sure you’re being semantic with your HTML tags. Use an actual ```