<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN"
"http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head><meta http-equiv="content-type" content="text/html; charset=utf-8" />
<title>[163429] trunk/PerformanceTests</title>
</head>
<body>
<style type="text/css"><!--
#msg dl.meta { border: 1px #006 solid; background: #369; padding: 6px; color: #fff; }
#msg dl.meta dt { float: left; width: 6em; font-weight: bold; }
#msg dt:after { content:':';}
#msg dl, #msg dt, #msg ul, #msg li, #header, #footer, #logmsg { font-family: verdana,arial,helvetica,sans-serif; font-size: 10pt; }
#msg dl a { font-weight: bold}
#msg dl a:link { color:#fc3; }
#msg dl a:active { color:#ff0; }
#msg dl a:visited { color:#cc6; }
h3 { font-family: verdana,arial,helvetica,sans-serif; font-size: 10pt; font-weight: bold; }
#msg pre { overflow: auto; background: #ffc; border: 1px #fa0 solid; padding: 6px; }
#logmsg { background: #ffc; border: 1px #fa0 solid; padding: 1em 1em 0 1em; }
#logmsg p, #logmsg pre, #logmsg blockquote { margin: 0 0 1em 0; }
#logmsg p, #logmsg li, #logmsg dt, #logmsg dd { line-height: 14pt; }
#logmsg h1, #logmsg h2, #logmsg h3, #logmsg h4, #logmsg h5, #logmsg h6 { margin: .5em 0; }
#logmsg h1:first-child, #logmsg h2:first-child, #logmsg h3:first-child, #logmsg h4:first-child, #logmsg h5:first-child, #logmsg h6:first-child { margin-top: 0; }
#logmsg ul, #logmsg ol { padding: 0; list-style-position: inside; margin: 0 0 0 1em; }
#logmsg ul { text-indent: -1em; padding-left: 1em; }#logmsg ol { text-indent: -1.5em; padding-left: 1.5em; }
#logmsg > ul, #logmsg > ol { margin: 0 0 1em 0; }
#logmsg pre { background: #eee; padding: 1em; }
#logmsg blockquote { border: 1px solid #fa0; border-left-width: 10px; padding: 1em 1em 0 1em; background: white;}
#logmsg dl { margin: 0; }
#logmsg dt { font-weight: bold; }
#logmsg dd { margin: 0; padding: 0 0 0.5em 0; }
#logmsg dd:before { content:'\00bb';}
#logmsg table { border-spacing: 0px; border-collapse: collapse; border-top: 4px solid #fa0; border-bottom: 1px solid #fa0; background: #fff; }
#logmsg table th { text-align: left; font-weight: normal; padding: 0.2em 0.5em; border-top: 1px dotted #fa0; }
#logmsg table td { text-align: right; border-top: 1px dotted #fa0; padding: 0.2em 0.5em; }
#logmsg table thead th { text-align: center; border-bottom: 1px solid #fa0; }
#logmsg table th.Corner { text-align: left; }
#logmsg hr { border: none 0; border-top: 2px dashed #fa0; height: 1px; }
#header, #footer { color: #fff; background: #636; border: 1px #300 solid; padding: 6px; }
#patch { width: 100%; }
#patch h4 {font-family: verdana,arial,helvetica,sans-serif;font-size:10pt;padding:8px;background:#369;color:#fff;margin:0;}
#patch .propset h4, #patch .binary h4 {margin:0;}
#patch pre {padding:0;line-height:1.2em;margin:0;}
#patch .diff {width:100%;background:#eee;padding: 0 0 10px 0;overflow:auto;}
#patch .propset .diff, #patch .binary .diff {padding:10px 0;}
#patch span {display:block;padding:0 10px;}
#patch .modfile, #patch .addfile, #patch .delfile, #patch .propset, #patch .binary, #patch .copfile {border:1px solid #ccc;margin:10px 0;}
#patch ins {background:#dfd;text-decoration:none;display:block;padding:0 10px;}
#patch del {background:#fdd;text-decoration:none;display:block;padding:0 10px;}
#patch .lines, .info {color:#888;background:#fff;}
--></style>
<div id="msg">
<dl class="meta">
<dt>Revision</dt> <dd><a href="http://trac.webkit.org/projects/webkit/changeset/163429">163429</a></dd>
<dt>Author</dt> <dd>rniwa@webkit.org</dd>
<dt>Date</dt> <dd>2014-02-04 21:36:04 -0800 (Tue, 04 Feb 2014)</dd>
</dl>
<h3>Log Message</h3>
<pre>DoYouEvenBench: Update Ember.js test case
https://bugs.webkit.org/show_bug.cgi?id=128227
Reviewed by Benjamin Poulain.
Updated the Ember.js TodoMVC implementation.
* DoYouEvenBench/resources/tests.js:
* DoYouEvenBench/resources/todomvc/architecture-examples/emberjs/bower.json:
* DoYouEvenBench/resources/todomvc/architecture-examples/emberjs/bower_components/ember-data/ember-data.js: Added.
* DoYouEvenBench/resources/todomvc/architecture-examples/emberjs/bower_components/ember-localstorage-adapter/localstorage_adapter.js:
* DoYouEvenBench/resources/todomvc/architecture-examples/emberjs/bower_components/ember/ember.js:
* DoYouEvenBench/resources/todomvc/architecture-examples/emberjs/bower_components/handlebars/handlebars.js:
* DoYouEvenBench/resources/todomvc/architecture-examples/emberjs/bower_components/jquery/jquery.js:
* DoYouEvenBench/resources/todomvc/architecture-examples/emberjs/bower_components/todomvc-common/base.css:
* DoYouEvenBench/resources/todomvc/architecture-examples/emberjs/bower_components/todomvc-common/base.js:
* DoYouEvenBench/resources/todomvc/architecture-examples/emberjs/index.html:
* DoYouEvenBench/resources/todomvc/architecture-examples/emberjs/js/app.js:
* DoYouEvenBench/resources/todomvc/architecture-examples/emberjs/js/controllers/todo_controller.js:
* DoYouEvenBench/resources/todomvc/architecture-examples/emberjs/js/controllers/todos_controller.js:
* DoYouEvenBench/resources/todomvc/architecture-examples/emberjs/js/helpers/pluralize.js: Added.
* DoYouEvenBench/resources/todomvc/architecture-examples/emberjs/js/libs/ember-data.js: Removed.
* DoYouEvenBench/resources/todomvc/architecture-examples/emberjs/js/models/store.js: Removed.
* DoYouEvenBench/resources/todomvc/architecture-examples/emberjs/js/models/todo.js:
* DoYouEvenBench/resources/todomvc/architecture-examples/emberjs/js/router.js:
* DoYouEvenBench/resources/todomvc/architecture-examples/emberjs/js/views/edit_todo_view.js:
* DoYouEvenBench/resources/todomvc/architecture-examples/emberjs/js/views/todos_view.js: Added.
* DoYouEvenBench/resources/todomvc/architecture-examples/emberjs/readme.md:
* DoYouEvenBench/resources/todomvc/architecture-examples/emberjs/test.html:</pre>
<h3>Modified Paths</h3>
<ul>
<li><a href="#trunkPerformanceTestsChangeLog">trunk/PerformanceTests/ChangeLog</a></li>
<li><a href="#trunkPerformanceTestsDoYouEvenBenchresourcestestsjs">trunk/PerformanceTests/DoYouEvenBench/resources/tests.js</a></li>
<li><a href="#trunkPerformanceTestsDoYouEvenBenchresourcestodomvcarchitectureexamplesemberjsbowerjson">trunk/PerformanceTests/DoYouEvenBench/resources/todomvc/architecture-examples/emberjs/bower.json</a></li>
<li><a href="#trunkPerformanceTestsDoYouEvenBenchresourcestodomvcarchitectureexamplesemberjsbower_componentsemberemberjs">trunk/PerformanceTests/DoYouEvenBench/resources/todomvc/architecture-examples/emberjs/bower_components/ember/ember.js</a></li>
<li><a href="#trunkPerformanceTestsDoYouEvenBenchresourcestodomvcarchitectureexamplesemberjsbower_componentsemberlocalstorageadapterlocalstorage_adapterjs">trunk/PerformanceTests/DoYouEvenBench/resources/todomvc/architecture-examples/emberjs/bower_components/ember-localstorage-adapter/localstorage_adapter.js</a></li>
<li><a href="#trunkPerformanceTestsDoYouEvenBenchresourcestodomvcarchitectureexamplesemberjsbower_componentshandlebarshandlebarsjs">trunk/PerformanceTests/DoYouEvenBench/resources/todomvc/architecture-examples/emberjs/bower_components/handlebars/handlebars.js</a></li>
<li><a href="#trunkPerformanceTestsDoYouEvenBenchresourcestodomvcarchitectureexamplesemberjsbower_componentsjqueryjqueryjs">trunk/PerformanceTests/DoYouEvenBench/resources/todomvc/architecture-examples/emberjs/bower_components/jquery/jquery.js</a></li>
<li><a href="#trunkPerformanceTestsDoYouEvenBenchresourcestodomvcarchitectureexamplesemberjsbower_componentstodomvccommonbasecss">trunk/PerformanceTests/DoYouEvenBench/resources/todomvc/architecture-examples/emberjs/bower_components/todomvc-common/base.css</a></li>
<li><a href="#trunkPerformanceTestsDoYouEvenBenchresourcestodomvcarchitectureexamplesemberjsbower_componentstodomvccommonbasejs">trunk/PerformanceTests/DoYouEvenBench/resources/todomvc/architecture-examples/emberjs/bower_components/todomvc-common/base.js</a></li>
<li><a href="#trunkPerformanceTestsDoYouEvenBenchresourcestodomvcarchitectureexamplesemberjsindexhtml">trunk/PerformanceTests/DoYouEvenBench/resources/todomvc/architecture-examples/emberjs/index.html</a></li>
<li><a href="#trunkPerformanceTestsDoYouEvenBenchresourcestodomvcarchitectureexamplesemberjsjsappjs">trunk/PerformanceTests/DoYouEvenBench/resources/todomvc/architecture-examples/emberjs/js/app.js</a></li>
<li><a href="#trunkPerformanceTestsDoYouEvenBenchresourcestodomvcarchitectureexamplesemberjsjscontrollerstodo_controllerjs">trunk/PerformanceTests/DoYouEvenBench/resources/todomvc/architecture-examples/emberjs/js/controllers/todo_controller.js</a></li>
<li><a href="#trunkPerformanceTestsDoYouEvenBenchresourcestodomvcarchitectureexamplesemberjsjscontrollerstodos_controllerjs">trunk/PerformanceTests/DoYouEvenBench/resources/todomvc/architecture-examples/emberjs/js/controllers/todos_controller.js</a></li>
<li><a href="#trunkPerformanceTestsDoYouEvenBenchresourcestodomvcarchitectureexamplesemberjsjsmodelstodojs">trunk/PerformanceTests/DoYouEvenBench/resources/todomvc/architecture-examples/emberjs/js/models/todo.js</a></li>
<li><a href="#trunkPerformanceTestsDoYouEvenBenchresourcestodomvcarchitectureexamplesemberjsjsrouterjs">trunk/PerformanceTests/DoYouEvenBench/resources/todomvc/architecture-examples/emberjs/js/router.js</a></li>
<li><a href="#trunkPerformanceTestsDoYouEvenBenchresourcestodomvcarchitectureexamplesemberjsjsviewsedit_todo_viewjs">trunk/PerformanceTests/DoYouEvenBench/resources/todomvc/architecture-examples/emberjs/js/views/edit_todo_view.js</a></li>
<li><a href="#trunkPerformanceTestsDoYouEvenBenchresourcestodomvcarchitectureexamplesemberjsreadmemd">trunk/PerformanceTests/DoYouEvenBench/resources/todomvc/architecture-examples/emberjs/readme.md</a></li>
<li><a href="#trunkPerformanceTestsDoYouEvenBenchresourcestodomvcarchitectureexamplesemberjstesthtml">trunk/PerformanceTests/DoYouEvenBench/resources/todomvc/architecture-examples/emberjs/test.html</a></li>
</ul>
<h3>Added Paths</h3>
<ul>
<li>trunk/PerformanceTests/DoYouEvenBench/resources/todomvc/architecture-examples/emberjs/bower_components/ember-data/</li>
<li><a href="#trunkPerformanceTestsDoYouEvenBenchresourcestodomvcarchitectureexamplesemberjsbower_componentsemberdataemberdatajs">trunk/PerformanceTests/DoYouEvenBench/resources/todomvc/architecture-examples/emberjs/bower_components/ember-data/ember-data.js</a></li>
<li>trunk/PerformanceTests/DoYouEvenBench/resources/todomvc/architecture-examples/emberjs/js/helpers/</li>
<li><a href="#trunkPerformanceTestsDoYouEvenBenchresourcestodomvcarchitectureexamplesemberjsjshelperspluralizejs">trunk/PerformanceTests/DoYouEvenBench/resources/todomvc/architecture-examples/emberjs/js/helpers/pluralize.js</a></li>
<li><a href="#trunkPerformanceTestsDoYouEvenBenchresourcestodomvcarchitectureexamplesemberjsjsviewstodos_viewjs">trunk/PerformanceTests/DoYouEvenBench/resources/todomvc/architecture-examples/emberjs/js/views/todos_view.js</a></li>
</ul>
<h3>Removed Paths</h3>
<ul>
<li>trunk/PerformanceTests/DoYouEvenBench/resources/todomvc/architecture-examples/emberjs/js/libs/</li>
<li><a href="#trunkPerformanceTestsDoYouEvenBenchresourcestodomvcarchitectureexamplesemberjsjsmodelsstorejs">trunk/PerformanceTests/DoYouEvenBench/resources/todomvc/architecture-examples/emberjs/js/models/store.js</a></li>
</ul>
</div>
<div id="patch">
<h3>Diff</h3>
<a id="trunkPerformanceTestsChangeLog"></a>
<div class="modfile"><h4>Modified: trunk/PerformanceTests/ChangeLog (163428 => 163429)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/PerformanceTests/ChangeLog        2014-02-05 05:32:21 UTC (rev 163428)
+++ trunk/PerformanceTests/ChangeLog        2014-02-05 05:36:04 UTC (rev 163429)
</span><span class="lines">@@ -1,3 +1,35 @@
</span><ins>+2014-02-04 Ryosuke Niwa <rniwa@webkit.org>
+
+ DoYouEvenBench: Update Ember.js test case
+ https://bugs.webkit.org/show_bug.cgi?id=128227
+
+ Reviewed by Benjamin Poulain.
+
+ Updated the Ember.js TodoMVC implementation.
+
+ * DoYouEvenBench/resources/tests.js:
+ * DoYouEvenBench/resources/todomvc/architecture-examples/emberjs/bower.json:
+ * DoYouEvenBench/resources/todomvc/architecture-examples/emberjs/bower_components/ember-data/ember-data.js: Added.
+ * DoYouEvenBench/resources/todomvc/architecture-examples/emberjs/bower_components/ember-localstorage-adapter/localstorage_adapter.js:
+ * DoYouEvenBench/resources/todomvc/architecture-examples/emberjs/bower_components/ember/ember.js:
+ * DoYouEvenBench/resources/todomvc/architecture-examples/emberjs/bower_components/handlebars/handlebars.js:
+ * DoYouEvenBench/resources/todomvc/architecture-examples/emberjs/bower_components/jquery/jquery.js:
+ * DoYouEvenBench/resources/todomvc/architecture-examples/emberjs/bower_components/todomvc-common/base.css:
+ * DoYouEvenBench/resources/todomvc/architecture-examples/emberjs/bower_components/todomvc-common/base.js:
+ * DoYouEvenBench/resources/todomvc/architecture-examples/emberjs/index.html:
+ * DoYouEvenBench/resources/todomvc/architecture-examples/emberjs/js/app.js:
+ * DoYouEvenBench/resources/todomvc/architecture-examples/emberjs/js/controllers/todo_controller.js:
+ * DoYouEvenBench/resources/todomvc/architecture-examples/emberjs/js/controllers/todos_controller.js:
+ * DoYouEvenBench/resources/todomvc/architecture-examples/emberjs/js/helpers/pluralize.js: Added.
+ * DoYouEvenBench/resources/todomvc/architecture-examples/emberjs/js/libs/ember-data.js: Removed.
+ * DoYouEvenBench/resources/todomvc/architecture-examples/emberjs/js/models/store.js: Removed.
+ * DoYouEvenBench/resources/todomvc/architecture-examples/emberjs/js/models/todo.js:
+ * DoYouEvenBench/resources/todomvc/architecture-examples/emberjs/js/router.js:
+ * DoYouEvenBench/resources/todomvc/architecture-examples/emberjs/js/views/edit_todo_view.js:
+ * DoYouEvenBench/resources/todomvc/architecture-examples/emberjs/js/views/todos_view.js: Added.
+ * DoYouEvenBench/resources/todomvc/architecture-examples/emberjs/readme.md:
+ * DoYouEvenBench/resources/todomvc/architecture-examples/emberjs/test.html:
+
</ins><span class="cx"> 2014-02-04 Zoltan Horvath <zoltan@webkit.org>
</span><span class="cx">
</span><span class="cx"> [CSS Shapes] Add initial performance test for shape-outside: content-box
</span></span></pre></div>
<a id="trunkPerformanceTestsDoYouEvenBenchresourcestestsjs"></a>
<div class="modfile"><h4>Modified: trunk/PerformanceTests/DoYouEvenBench/resources/tests.js (163428 => 163429)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/PerformanceTests/DoYouEvenBench/resources/tests.js        2014-02-05 05:32:21 UTC (rev 163428)
+++ trunk/PerformanceTests/DoYouEvenBench/resources/tests.js        2014-02-05 05:36:04 UTC (rev 163429)
</span><span class="lines">@@ -35,12 +35,6 @@
</span><span class="cx"> name: 'EmberJS-TodoMVC',
</span><span class="cx"> url: 'todomvc/architecture-examples/emberjs/index.html',
</span><span class="cx"> prepare: function (runner, contentWindow, contentDocument) {
</span><del>- contentWindow.Todos.Store = contentWindow.DS.Store.extend({
- revision: 12,
- adapter: 'Todos.LSAdapter',
- commit: function () { }
- });
-
</del><span class="cx"> return runner.waitForElement('#new-todo').then(function (element) {
</span><span class="cx"> element.focus();
</span><span class="cx"> return {
</span></span></pre></div>
<a id="trunkPerformanceTestsDoYouEvenBenchresourcestodomvcarchitectureexamplesemberjsbowerjson"></a>
<div class="modfile"><h4>Modified: trunk/PerformanceTests/DoYouEvenBench/resources/todomvc/architecture-examples/emberjs/bower.json (163428 => 163429)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/PerformanceTests/DoYouEvenBench/resources/todomvc/architecture-examples/emberjs/bower.json        2014-02-05 05:32:21 UTC (rev 163428)
+++ trunk/PerformanceTests/DoYouEvenBench/resources/todomvc/architecture-examples/emberjs/bower.json        2014-02-05 05:36:04 UTC (rev 163429)
</span><span class="lines">@@ -3,9 +3,10 @@
</span><span class="cx"> "version": "0.0.0",
</span><span class="cx"> "dependencies": {
</span><span class="cx"> "todomvc-common": "~0.1.4",
</span><del>- "jquery": "~1.9.1",
- "handlebars": "~1.0.0-rc.3",
- "ember": "~1.0.0-rc.1",
</del><ins>+ "jquery": "~2.1.0",
+ "handlebars": "~1.3.0",
+ "ember": "~1.3.1",
+ "ember-data": "1.0.0-beta.6",
</ins><span class="cx"> "ember-localstorage-adapter": "latest"
</span><span class="cx"> }
</span><span class="cx"> }
</span></span></pre></div>
<a id="trunkPerformanceTestsDoYouEvenBenchresourcestodomvcarchitectureexamplesemberjsbower_componentsemberemberjs"></a>
<div class="modfile"><h4>Modified: trunk/PerformanceTests/DoYouEvenBench/resources/todomvc/architecture-examples/emberjs/bower_components/ember/ember.js (163428 => 163429)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/PerformanceTests/DoYouEvenBench/resources/todomvc/architecture-examples/emberjs/bower_components/ember/ember.js        2014-02-05 05:32:21 UTC (rev 163428)
+++ trunk/PerformanceTests/DoYouEvenBench/resources/todomvc/architecture-examples/emberjs/bower_components/ember/ember.js        2014-02-05 05:36:04 UTC (rev 163429)
</span><span class="lines">@@ -1,5 +1,12 @@
</span><del>-// Version: v1.0.0-rc.1
-// Last commit: 8b061b4 (2013-02-15 12:10:22 -0800)
</del><ins>+/*!
+ * @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.3.1
+ */
</ins><span class="cx">
</span><span class="cx">
</span><span class="cx"> (function() {
</span><span class="lines">@@ -24,7 +31,20 @@
</span><span class="cx"> }
</span><span class="cx"> }
</span><span class="cx">
</span><del>-Ember.ENV = 'undefined' === typeof ENV ? {} : ENV;
</del><ins>+// This needs to be kept in sync with the logic in
+// `packages/ember-metal/lib/core.js`.
+//
+// This is duplicated here to ensure that `Ember.ENV`
+// is setup even if `Ember` is not loaded yet.
+if (Ember.ENV) {
+ // do nothing if Ember.ENV is already setup
+} else if ('undefined' !== typeof EmberENV) {
+ Ember.ENV = EmberENV;
+} else if('undefined' !== typeof ENV) {
+ Ember.ENV = ENV;
+} else {
+ Ember.ENV = {};
+}
</ins><span class="cx">
</span><span class="cx"> if (!('MANDATORY_SETTER' in Ember.ENV)) {
</span><span class="cx"> Ember.ENV.MANDATORY_SETTER = true; // default to true for debug dist
</span><span class="lines">@@ -49,7 +69,14 @@
</span><span class="cx"> falsy, an exception will be thrown.
</span><span class="cx"> */
</span><span class="cx"> Ember.assert = function(desc, test) {
</span><del>- if (!test) throw new Error("assertion failed: "+desc);
</del><ins>+ if (!test) {
+ Ember.Logger.assert(test, desc);
+ }
+
+ if (Ember.testing && !test) {
+ // when testing, ensure test failures when assertions fail
+ throw new Ember.Error("Assertion Failed: " + desc);
+ }
</ins><span class="cx"> };
</span><span class="cx">
</span><span class="cx">
</span><span class="lines">@@ -95,12 +122,12 @@
</span><span class="cx"> will be displayed.
</span><span class="cx"> */
</span><span class="cx"> Ember.deprecate = function(message, test) {
</span><del>- if (Ember && Ember.TESTING_DEPRECATION) { return; }
</del><ins>+ if (Ember.TESTING_DEPRECATION) { return; }
</ins><span class="cx">
</span><span class="cx"> if (arguments.length === 1) { test = false; }
</span><span class="cx"> if (test) { return; }
</span><span class="cx">
</span><del>- if (Ember && Ember.ENV.RAISE_ON_DEPRECATION) { throw new Error(message); }
</del><ins>+ if (Ember.ENV.RAISE_ON_DEPRECATION) { throw new Ember.Error(message); }
</ins><span class="cx">
</span><span class="cx"> var error;
</span><span class="cx">
</span><span class="lines">@@ -131,15 +158,22 @@
</span><span class="cx">
</span><span class="cx">
</span><span class="cx"> /**
</span><ins>+ Alias an old, deprecated method with its new counterpart.
+
</ins><span class="cx"> Display a deprecation warning with the provided message and a stack trace
</span><del>- (Chrome and Firefox only) when the wrapped method is called.
</del><ins>+ (Chrome and Firefox only) when the assigned method is called.
</ins><span class="cx">
</span><span class="cx"> Ember build tools will not remove calls to `Ember.deprecateFunc()`, though
</span><span class="cx"> no warnings will be shown in production.
</span><span class="cx">
</span><ins>+ ```javascript
+ Ember.oldMethod = Ember.deprecateFunc("Please use the new, updated method", Ember.newMethod);
+ ```
+
</ins><span class="cx"> @method deprecateFunc
</span><span class="cx"> @param {String} message A description of the deprecation.
</span><del>- @param {Function} func The function to be deprecated.
</del><ins>+ @param {Function} func The new function called to replace its deprecated counterpart.
+ @return {Function} a new function that wrapped the original function with a deprecation warning
</ins><span class="cx"> */
</span><span class="cx"> Ember.deprecateFunc = function(message, func) {
</span><span class="cx"> return function() {
</span><span class="lines">@@ -148,14 +182,33 @@
</span><span class="cx"> };
</span><span class="cx"> };
</span><span class="cx">
</span><ins>+
+// Inform the developer about the Ember Inspector if not installed.
+if (!Ember.testing) {
+ if (typeof window !== 'undefined' && window.chrome && window.addEventListener) {
+ window.addEventListener("load", function() {
+ if (document.body && document.body.dataset && !document.body.dataset.emberExtension) {
+ Ember.debug('For more advanced debugging, install the Ember Inspector from https://chrome.google.com/webstore/detail/ember-inspector/bmdblncegkenkacieihfhpjfppoconhi');
+ }
+ }, false);
+ }
+}
+
</ins><span class="cx"> })();
</span><span class="cx">
</span><del>-// Version: v1.0.0-rc.1
-// Last commit: 8b061b4 (2013-02-15 12:10:22 -0800)
</del><ins>+/*!
+ * @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.3.1
+ */
</ins><span class="cx">
</span><span class="cx">
</span><span class="cx"> (function() {
</span><del>-var define, requireModule;
</del><ins>+var define, requireModule, require, requirejs;
</ins><span class="cx">
</span><span class="cx"> (function() {
</span><span class="cx"> var registry = {}, seen = {};
</span><span class="lines">@@ -164,10 +217,16 @@
</span><span class="cx"> registry[name] = { deps: deps, callback: callback };
</span><span class="cx"> };
</span><span class="cx">
</span><del>- requireModule = function(name) {
</del><ins>+ requirejs = require = requireModule = function(name) {
+ requirejs._eak_seen = registry;
+
</ins><span class="cx"> if (seen[name]) { return seen[name]; }
</span><span class="cx"> seen[name] = {};
</span><span class="cx">
</span><ins>+ if (!registry[name]) {
+ throw new Error("Could not find module " + name);
+ }
+
</ins><span class="cx"> var mod = registry[name],
</span><span class="cx"> deps = mod.deps,
</span><span class="cx"> callback = mod.callback,
</span><span class="lines">@@ -178,16 +237,32 @@
</span><span class="cx"> if (deps[i] === 'exports') {
</span><span class="cx"> reified.push(exports = {});
</span><span class="cx"> } else {
</span><del>- reified.push(requireModule(deps[i]));
</del><ins>+ reified.push(requireModule(resolve(deps[i])));
</ins><span class="cx"> }
</span><span class="cx"> }
</span><span class="cx">
</span><span class="cx"> var value = callback.apply(this, reified);
</span><span class="cx"> return seen[name] = exports || value;
</span><ins>+
+ function resolve(child) {
+ if (child.charAt(0) !== '.') { return child; }
+ var parts = child.split("/");
+ var parentBase = name.split("/").slice(0, -1);
+
+ for (var i=0, l=parts.length; i<l; i++) {
+ var part = parts[i];
+
+ if (part === '..') { parentBase.pop(); }
+ else if (part === '.') { continue; }
+ else { parentBase.push(part); }
+ }
+
+ return parentBase.join("/");
+ }
</ins><span class="cx"> };
</span><span class="cx"> })();
</span><span class="cx"> (function() {
</span><del>-/*globals Em:true ENV */
</del><ins>+/*globals Em:true ENV EmberENV MetamorphENV:true */
</ins><span class="cx">
</span><span class="cx"> /**
</span><span class="cx"> @module ember
</span><span class="lines">@@ -211,7 +286,7 @@
</span><span class="cx">
</span><span class="cx"> @class Ember
</span><span class="cx"> @static
</span><del>- @version 1.0.0-rc.1
</del><ins>+ @version 1.3.1
</ins><span class="cx"> */
</span><span class="cx">
</span><span class="cx"> if ('undefined' === typeof Ember) {
</span><span class="lines">@@ -238,23 +313,86 @@
</span><span class="cx"> /**
</span><span class="cx"> @property VERSION
</span><span class="cx"> @type String
</span><del>- @default '1.0.0-rc.1'
- @final
</del><ins>+ @default '1.3.1'
+ @static
</ins><span class="cx"> */
</span><del>-Ember.VERSION = '1.0.0-rc.1';
</del><ins>+Ember.VERSION = '1.3.1';
</ins><span class="cx">
</span><span class="cx"> /**
</span><del>- Standard environmental variables. You can define these in a global `ENV`
- variable before loading Ember to control various configuration
- settings.
</del><ins>+ Standard environmental variables. You can define these in a global `EmberENV`
+ variable before loading Ember to control various configuration settings.
</ins><span class="cx">
</span><ins>+ For backwards compatibility with earlier versions of Ember the global `ENV`
+ variable will be used if `EmberENV` is not defined.
+
</ins><span class="cx"> @property ENV
</span><span class="cx"> @type Hash
</span><span class="cx"> */
</span><del>-Ember.ENV = Ember.ENV || ('undefined' === typeof ENV ? {} : ENV);
</del><span class="cx">
</span><ins>+// This needs to be kept in sync with the logic in
+// `packages/ember-debug/lib/main.js`.
+if (Ember.ENV) {
+ // do nothing if Ember.ENV is already setup
+} else if ('undefined' !== typeof EmberENV) {
+ Ember.ENV = EmberENV;
+} else if('undefined' !== typeof ENV) {
+ Ember.ENV = ENV;
+} else {
+ Ember.ENV = {};
+}
+
</ins><span class="cx"> Ember.config = Ember.config || {};
</span><span class="cx">
</span><ins>+// We disable the RANGE API by default for performance reasons
+if ('undefined' === typeof Ember.ENV.DISABLE_RANGE_API) {
+ Ember.ENV.DISABLE_RANGE_API = true;
+}
+
+if ("undefined" === typeof MetamorphENV) {
+ exports.MetamorphENV = {};
+}
+
+MetamorphENV.DISABLE_RANGE_API = Ember.ENV.DISABLE_RANGE_API;
+
+/**
+ Hash of enabled Canary features. Add to before creating your application.
+
+ You can also define `ENV.FEATURES` if you need to enable features flagged at runtime.
+
+ @property FEATURES
+ @type Hash
+*/
+
+Ember.FEATURES = Ember.ENV.FEATURES || {};
+
+/**
+ Test that a feature is enabled. Parsed by Ember's build tools to leave
+ experimental features out of beta/stable builds.
+
+ You can define the following configuration options:
+
+ * `ENV.ENABLE_ALL_FEATURES` - force all features to be enabled.
+ * `ENV.ENABLE_OPTIONAL_FEATURES` - enable any features that have not been explicitly
+ enabled/disabled.
+
+ @method isEnabled
+ @param {string} feature
+*/
+
+Ember.FEATURES.isEnabled = function(feature) {
+ var featureValue = Ember.FEATURES[feature];
+
+ if (Ember.ENV.ENABLE_ALL_FEATURES) {
+ return true;
+ } else if (featureValue === true || featureValue === false || featureValue === undefined) {
+ return featureValue;
+ } else if (Ember.ENV.ENABLE_OPTIONAL_FEATURES) {
+ return true;
+ } else {
+ return false;
+ }
+};
+
</ins><span class="cx"> // ..........................................................
</span><span class="cx"> // BOOTSTRAP
</span><span class="cx"> //
</span><span class="lines">@@ -298,8 +436,17 @@
</span><span class="cx"> Ember.SHIM_ES5 = (Ember.ENV.SHIM_ES5 === false) ? false : Ember.EXTEND_PROTOTYPES;
</span><span class="cx">
</span><span class="cx"> /**
</span><del>- Empty function. Useful for some operations.
</del><ins>+ Determines whether Ember logs info about version of used libraries
</ins><span class="cx">
</span><ins>+ @property LOG_VERSION
+ @type Boolean
+ @default true
+*/
+Ember.LOG_VERSION = (Ember.ENV.LOG_VERSION === false) ? false : true;
+
+/**
+ Empty function. Useful for some operations. Always returns `this`.
+
</ins><span class="cx"> @method K
</span><span class="cx"> @private
</span><span class="cx"> @return {Object}
</span><span class="lines">@@ -327,87 +474,80 @@
</span><span class="cx"> */
</span><span class="cx"> Ember.uuid = 0;
</span><span class="cx">
</span><del>-// ..........................................................
-// LOGGER
-//
</del><ins>+/**
+ Merge the contents of two objects together into the first object.
</ins><span class="cx">
</span><del>-function consoleMethod(name) {
- if (imports.console && imports.console[name]) {
- // Older IE doesn't support apply, but Chrome needs it
- if (imports.console[name].apply) {
- return function() {
- imports.console[name].apply(imports.console, arguments);
- };
- } else {
- return function() {
- var message = Array.prototype.join.call(arguments, ', ');
- imports.console[name](message);
- };
- }
- }
-}
</del><ins>+ ```javascript
+ Ember.merge({first: 'Tom'}, {last: 'Dale'}); // {first: 'Tom', last: 'Dale'}
+ var a = {first: 'Yehuda'}, b = {last: 'Katz'};
+ Ember.merge(a, b); // a == {first: 'Yehuda', last: 'Katz'}, b == {last: 'Katz'}
+ ```
</ins><span class="cx">
</span><del>-/**
- Inside Ember-Metal, simply uses the methods from `imports.console`.
- Override this to provide more robust logging functionality.
-
- @class Logger
- @namespace Ember
</del><ins>+ @method merge
+ @for Ember
+ @param {Object} original The object to merge into
+ @param {Object} updates The object to copy properties from
+ @return {Object}
</ins><span class="cx"> */
</span><del>-Ember.Logger = {
- log: consoleMethod('log') || Ember.K,
- warn: consoleMethod('warn') || Ember.K,
- error: consoleMethod('error') || Ember.K,
- info: consoleMethod('info') || Ember.K,
- debug: consoleMethod('debug') || consoleMethod('info') || Ember.K
</del><ins>+Ember.merge = function(original, updates) {
+ for (var prop in updates) {
+ if (!updates.hasOwnProperty(prop)) { continue; }
+ original[prop] = updates[prop];
+ }
+ return original;
</ins><span class="cx"> };
</span><span class="cx">
</span><ins>+/**
+ Returns true if the passed value is null or undefined. This avoids errors
+ from JSLint complaining about use of ==, which can be technically
+ confusing.
</ins><span class="cx">
</span><del>-// ..........................................................
-// ERROR HANDLING
-//
</del><ins>+ ```javascript
+ Ember.isNone(); // true
+ Ember.isNone(null); // true
+ Ember.isNone(undefined); // true
+ Ember.isNone(''); // false
+ Ember.isNone([]); // false
+ Ember.isNone(function() {}); // false
+ ```
</ins><span class="cx">
</span><del>-/**
- A function may be assigned to `Ember.onerror` to be called when Ember
- internals encounter an error. This is useful for specialized error handling
- and reporting code.
-
- @event onerror
</del><ins>+ @method isNone
</ins><span class="cx"> @for Ember
</span><del>- @param {Exception} error the error object
</del><ins>+ @param {Object} obj Value to test
+ @return {Boolean}
</ins><span class="cx"> */
</span><del>-Ember.onerror = null;
</del><ins>+Ember.isNone = function(obj) {
+ return obj === null || obj === undefined;
+};
+Ember.none = Ember.deprecateFunc("Ember.none is deprecated. Please use Ember.isNone instead.", Ember.isNone);
</ins><span class="cx">
</span><span class="cx"> /**
</span><del>- @private
</del><ins>+ Verifies that a value is `null` or an empty string, empty array,
+ or empty function.
</ins><span class="cx">
</span><del>- Wrap code block in a try/catch if {{#crossLink "Ember/onerror"}}{{/crossLink}} is set.
</del><ins>+ Constrains the rules on `Ember.isNone` by returning false for empty
+ string and empty arrays.
</ins><span class="cx">
</span><del>- @method handleErrors
</del><ins>+ ```javascript
+ Ember.isEmpty(); // true
+ Ember.isEmpty(null); // true
+ Ember.isEmpty(undefined); // true
+ Ember.isEmpty(''); // true
+ Ember.isEmpty([]); // true
+ Ember.isEmpty('Adam Hawkins'); // false
+ Ember.isEmpty([0,1,2]); // false
+ ```
+
+ @method isEmpty
</ins><span class="cx"> @for Ember
</span><del>- @param {Function} func
- @param [context]
</del><ins>+ @param {Object} obj Value to test
+ @return {Boolean}
</ins><span class="cx"> */
</span><del>-Ember.handleErrors = function(func, context) {
- // Unfortunately in some browsers we lose the backtrace if we rethrow the existing error,
- // so in the event that we don't have an `onerror` handler we don't wrap in a try/catch
- if ('function' === typeof Ember.onerror) {
- try {
- return func.apply(context || this);
- } catch (error) {
- Ember.onerror(error);
- }
- } else {
- return func.apply(context || this);
- }
</del><ins>+Ember.isEmpty = function(obj) {
+ return Ember.isNone(obj) || (obj.length === 0 && typeof obj !== 'function') || (typeof obj === 'object' && Ember.get(obj, 'length') === 0);
</ins><span class="cx"> };
</span><ins>+Ember.empty = Ember.deprecateFunc("Ember.empty is deprecated. Please use Ember.isEmpty instead.", Ember.isEmpty) ;
</ins><span class="cx">
</span><del>-Ember.merge = function(original, updates) {
- for (var prop in updates) {
- if (!updates.hasOwnProperty(prop)) { continue; }
- original[prop] = updates[prop];
- }
-};
</del><span class="cx">
</span><span class="cx"> })();
</span><span class="cx">
</span><span class="lines">@@ -437,6 +577,13 @@
</span><span class="cx"> */
</span><span class="cx"> Ember.create = Object.create;
</span><span class="cx">
</span><ins>+// IE8 has Object.create but it couldn't treat property descriptors.
+if (Ember.create) {
+ if (Ember.create({a: 1}, {a: {value: 2}}).a !== 2) {
+ Ember.create = null;
+ }
+}
+
</ins><span class="cx"> // STUB_OBJECT_CREATE allows us to override other libraries that stub
</span><span class="cx"> // Object.create different than we would prefer
</span><span class="cx"> if (!Ember.create || Ember.ENV.STUB_OBJECT_CREATE) {
</span><span class="lines">@@ -466,7 +613,7 @@
</span><span class="cx"> // Catch IE8 where Object.defineProperty exists but only works on DOM elements
</span><span class="cx"> if (defineProperty) {
</span><span class="cx"> try {
</span><del>- defineProperty({}, 'a',{get:function(){}});
</del><ins>+ defineProperty({}, 'a',{get:function() {}});
</ins><span class="cx"> } catch (e) {
</span><span class="cx"> defineProperty = null;
</span><span class="cx"> }
</span><span class="lines">@@ -497,7 +644,7 @@
</span><span class="cx">
</span><span class="cx"> // This is for Safari 5.0, which supports Object.defineProperty, but not
</span><span class="cx"> // on DOM nodes.
</span><del>- canDefinePropertyOnDOM = (function(){
</del><ins>+ canDefinePropertyOnDOM = (function() {
</ins><span class="cx"> try {
</span><span class="cx"> defineProperty(document.createElement('div'), 'definePropertyOnDOM', {});
</span><span class="cx"> return true;
</span><span class="lines">@@ -509,7 +656,7 @@
</span><span class="cx"> if (!canRedefineProperties) {
</span><span class="cx"> defineProperty = null;
</span><span class="cx"> } else if (!canDefinePropertyOnDOM) {
</span><del>- defineProperty = function(obj, keyName, desc){
</del><ins>+ defineProperty = function(obj, keyName, desc) {
</ins><span class="cx"> var isNode;
</span><span class="cx">
</span><span class="cx"> if (typeof Node === "object") {
</span><span class="lines">@@ -572,11 +719,203 @@
</span><span class="cx">
</span><span class="cx">
</span><span class="cx"> (function() {
</span><ins>+/*jshint newcap:false*/
</ins><span class="cx"> /**
</span><span class="cx"> @module ember-metal
</span><span class="cx"> */
</span><span class="cx">
</span><ins>+// NOTE: There is a bug in jshint that doesn't recognize `Object()` without `new`
+// as being ok unless both `newcap:false` and not `use strict`.
+// https://github.com/jshint/jshint/issues/392
</ins><span class="cx">
</span><ins>+// Testing this is not ideal, but we want to use native functions
+// if available, but not to use versions created by libraries like Prototype
+var isNativeFunc = function(func) {
+ // This should probably work in all browsers likely to have ES5 array methods
+ return func && Function.prototype.toString.call(func).indexOf('[native code]') > -1;
+};
+
+// From: https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/array/map
+var arrayMap = isNativeFunc(Array.prototype.map) ? Array.prototype.map : function(fun /*, thisp */) {
+ //"use strict";
+
+ if (this === void 0 || this === null) {
+ throw new TypeError();
+ }
+
+ var t = Object(this);
+ var len = t.length >>> 0;
+ if (typeof fun !== "function") {
+ throw new TypeError();
+ }
+
+ var res = new Array(len);
+ var thisp = arguments[1];
+ for (var i = 0; i < len; i++) {
+ if (i in t) {
+ res[i] = fun.call(thisp, t[i], i, t);
+ }
+ }
+
+ return res;
+};
+
+// From: https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/array/foreach
+var arrayForEach = isNativeFunc(Array.prototype.forEach) ? Array.prototype.forEach : function(fun /*, thisp */) {
+ //"use strict";
+
+ if (this === void 0 || this === null) {
+ throw new TypeError();
+ }
+
+ var t = Object(this);
+ var len = t.length >>> 0;
+ if (typeof fun !== "function") {
+ throw new TypeError();
+ }
+
+ var thisp = arguments[1];
+ for (var i = 0; i < len; i++) {
+ if (i in t) {
+ fun.call(thisp, t[i], i, t);
+ }
+ }
+};
+
+var arrayIndexOf = isNativeFunc(Array.prototype.indexOf) ? Array.prototype.indexOf : function (obj, fromIndex) {
+ if (fromIndex === null || fromIndex === undefined) { fromIndex = 0; }
+ else if (fromIndex < 0) { fromIndex = Math.max(0, this.length + fromIndex); }
+ for (var i = fromIndex, j = this.length; i < j; i++) {
+ if (this[i] === obj) { return i; }
+ }
+ return -1;
+};
+
+/**
+ Array polyfills to support ES5 features in older browsers.
+
+ @namespace Ember
+ @property ArrayPolyfills
+*/
+Ember.ArrayPolyfills = {
+ map: arrayMap,
+ forEach: arrayForEach,
+ indexOf: arrayIndexOf
+};
+
+if (Ember.SHIM_ES5) {
+ if (!Array.prototype.map) {
+ Array.prototype.map = arrayMap;
+ }
+
+ if (!Array.prototype.forEach) {
+ Array.prototype.forEach = arrayForEach;
+ }
+
+ if (!Array.prototype.indexOf) {
+ Array.prototype.indexOf = arrayIndexOf;
+ }
+}
+
+})();
+
+
+
+(function() {
+var errorProps = ['description', 'fileName', 'lineNumber', 'message', 'name', 'number', 'stack'];
+
+/**
+ A subclass of the JavaScript Error object for use in Ember.
+
+ @class Error
+ @namespace Ember
+ @extends Error
+ @constructor
+*/
+Ember.Error = function() {
+ var tmp = Error.apply(this, arguments);
+
+ // Adds a `stack` property to the given error object that will yield the
+ // stack trace at the time captureStackTrace was called.
+ // When collecting the stack trace all frames above the topmost call
+ // to this function, including that call, will be left out of the
+ // stack trace.
+ // This is useful because we can hide Ember implementation details
+ // that are not very helpful for the user.
+ if (Error.captureStackTrace) {
+ Error.captureStackTrace(this, Ember.Error);
+ }
+ // Unfortunately errors are not enumerable in Chrome (at least), so `for prop in tmp` doesn't work.
+ for (var idx = 0; idx < errorProps.length; idx++) {
+ this[errorProps[idx]] = tmp[errorProps[idx]];
+ }
+};
+
+Ember.Error.prototype = Ember.create(Error.prototype);
+
+// ..........................................................
+// ERROR HANDLING
+//
+
+/**
+ A function may be assigned to `Ember.onerror` to be called when Ember
+ internals encounter an error. This is useful for specialized error handling
+ and reporting code.
+
+ ```javascript
+ Ember.onerror = function(error) {
+ Em.$.ajax('/report-error', 'POST', {
+ stack: error.stack,
+ otherInformation: 'whatever app state you want to provide'
+ });
+ };
+ ```
+
+ @event onerror
+ @for Ember
+ @param {Exception} error the error object
+*/
+Ember.onerror = null;
+
+/**
+ Wrap code block in a try/catch if `Ember.onerror` is set.
+
+ @private
+ @method handleErrors
+ @for Ember
+ @param {Function} func
+ @param [context]
+*/
+Ember.handleErrors = function(func, context) {
+ // Unfortunately in some browsers we lose the backtrace if we rethrow the existing error,
+ // so in the event that we don't have an `onerror` handler we don't wrap in a try/catch
+ if ('function' === typeof Ember.onerror) {
+ try {
+ return func.call(context || this);
+ } catch (error) {
+ Ember.onerror(error);
+ }
+ } else {
+ return func.call(context || this);
+ }
+};
+
+})();
+
+
+
+(function() {
+/**
+@module ember-metal
+*/
+
+/**
+ Prefix used for guids through out Ember.
+ @private
+*/
+Ember.GUID_PREFIX = 'ember';
+
+
</ins><span class="cx"> var o_defineProperty = Ember.platform.defineProperty,
</span><span class="cx"> o_create = Ember.create,
</span><span class="cx"> // Used for guid generation...
</span><span class="lines">@@ -588,8 +927,6 @@
</span><span class="cx"> var MANDATORY_SETTER = Ember.ENV.MANDATORY_SETTER;
</span><span class="cx">
</span><span class="cx"> /**
</span><del>- @private
-
</del><span class="cx"> A unique key used to assign guids and other private metadata to objects.
</span><span class="cx"> If you inspect an object in your browser debugger you will often see these.
</span><span class="cx"> They can be safely ignored.
</span><span class="lines">@@ -597,6 +934,7 @@
</span><span class="cx"> On browsers that support it, these properties are added with enumeration
</span><span class="cx"> disabled so they won't show up when you iterate over your properties.
</span><span class="cx">
</span><ins>+ @private
</ins><span class="cx"> @property GUID_KEY
</span><span class="cx"> @for Ember
</span><span class="cx"> @type String
</span><span class="lines">@@ -612,12 +950,11 @@
</span><span class="cx"> };
</span><span class="cx">
</span><span class="cx"> /**
</span><del>- @private
-
</del><span class="cx"> Generates a new guid, optionally saving the guid to the object that you
</span><span class="cx"> pass in. You will rarely need to use this method. Instead you should
</span><span class="cx"> call `Ember.guidFor(obj)`, which return an existing guid if available.
</span><span class="cx">
</span><ins>+ @private
</ins><span class="cx"> @method generateGuid
</span><span class="cx"> @for Ember
</span><span class="cx"> @param {Object} [obj] Object the guid will be used for. If passed in, the guid will
</span><span class="lines">@@ -630,18 +967,16 @@
</span><span class="cx"> @return {String} the guid
</span><span class="cx"> */
</span><span class="cx"> Ember.generateGuid = function generateGuid(obj, prefix) {
</span><del>- if (!prefix) prefix = 'ember';
</del><ins>+ if (!prefix) prefix = Ember.GUID_PREFIX;
</ins><span class="cx"> var ret = (prefix + (uuid++));
</span><span class="cx"> if (obj) {
</span><span class="cx"> GUID_DESC.value = ret;
</span><span class="cx"> o_defineProperty(obj, GUID_KEY, GUID_DESC);
</span><span class="cx"> }
</span><del>- return ret ;
</del><ins>+ return ret;
</ins><span class="cx"> };
</span><span class="cx">
</span><span class="cx"> /**
</span><del>- @private
-
</del><span class="cx"> Returns a unique id for the object. If the object does not yet have a guid,
</span><span class="cx"> one will be assigned to it. You can call this on any object,
</span><span class="cx"> `Ember.Object`-based or not, but be aware that it will add a `_guid`
</span><span class="lines">@@ -649,9 +984,10 @@
</span><span class="cx">
</span><span class="cx"> You can also use this method on DOM Element objects.
</span><span class="cx">
</span><ins>+ @private
</ins><span class="cx"> @method guidFor
</span><span class="cx"> @for Ember
</span><del>- @param obj {Object} any object, string, number, Element, or primitive
</del><ins>+ @param {Object} obj any object, string, number, Element, or primitive
</ins><span class="cx"> @return {String} the unique guid for this instance.
</span><span class="cx"> */
</span><span class="cx"> Ember.guidFor = function guidFor(obj) {
</span><span class="lines">@@ -660,7 +996,7 @@
</span><span class="cx"> if (obj === undefined) return "(undefined)";
</span><span class="cx"> if (obj === null) return "(null)";
</span><span class="cx">
</span><del>- var cache, ret;
</del><ins>+ var ret;
</ins><span class="cx"> var type = typeof obj;
</span><span class="cx">
</span><span class="cx"> // Don't allow prototype changes to String etc. to change the guidFor
</span><span class="lines">@@ -713,18 +1049,6 @@
</span><span class="cx"> */
</span><span class="cx"> Ember.META_KEY = META_KEY;
</span><span class="cx">
</span><del>-// Placeholder for non-writable metas.
-var EMPTY_META = {
- descs: {},
- watching: {}
-};
-
-if (MANDATORY_SETTER) { EMPTY_META.values = {}; }
-
-Ember.EMPTY_META = EMPTY_META;
-
-if (Object.freeze) Object.freeze(EMPTY_META);
-
</del><span class="cx"> var isDefinePropertySimulated = Ember.platform.defineProperty.isSimulated;
</span><span class="cx">
</span><span class="cx"> function Meta(obj) {
</span><span class="lines">@@ -734,6 +1058,20 @@
</span><span class="cx"> this.source = obj;
</span><span class="cx"> }
</span><span class="cx">
</span><ins>+Meta.prototype = {
+ descs: null,
+ deps: null,
+ watching: null,
+ listeners: null,
+ cache: null,
+ source: null,
+ mixins: null,
+ bindings: null,
+ chains: null,
+ chainWatchers: null,
+ values: null
+};
+
</ins><span class="cx"> if (isDefinePropertySimulated) {
</span><span class="cx"> // on platforms that don't support enumerable false
</span><span class="cx"> // make meta fail jQuery.isPlainObject() to hide from
</span><span class="lines">@@ -746,6 +1084,13 @@
</span><span class="cx"> Meta.prototype.toJSON = function () { };
</span><span class="cx"> }
</span><span class="cx">
</span><ins>+// Placeholder for non-writable metas.
+var EMPTY_META = new Meta(null);
+
+if (MANDATORY_SETTER) { EMPTY_META.values = {}; }
+
+Ember.EMPTY_META = EMPTY_META;
+
</ins><span class="cx"> /**
</span><span class="cx"> Retrieves the meta hash for an object. If `writable` is true ensures the
</span><span class="cx"> hash is writable for this object as well.
</span><span class="lines">@@ -762,7 +1107,7 @@
</span><span class="cx"> @param {Object} obj The object to retrieve meta for
</span><span class="cx"> @param {Boolean} [writable=true] Pass `false` if you do not intend to modify
</span><span class="cx"> the meta hash, allowing the method to avoid making an unnecessary copy.
</span><del>- @return {Hash}
</del><ins>+ @return {Object} the meta hash for an object
</ins><span class="cx"> */
</span><span class="cx"> Ember.meta = function meta(obj, writable) {
</span><span class="cx">
</span><span class="lines">@@ -809,6 +1154,7 @@
</span><span class="cx"> };
</span><span class="cx">
</span><span class="cx"> /**
</span><ins>+ @deprecated
</ins><span class="cx"> @private
</span><span class="cx">
</span><span class="cx"> In order to store defaults for a class, a prototype may need to create
</span><span class="lines">@@ -841,6 +1187,7 @@
</span><span class="cx"> shared with its constructor
</span><span class="cx"> */
</span><span class="cx"> Ember.metaPath = function metaPath(obj, path, writable) {
</span><ins>+ Ember.deprecate("Ember.metaPath is deprecated and will be removed from future releases.");
</ins><span class="cx"> var meta = Ember.meta(obj, writable), keyName, value;
</span><span class="cx">
</span><span class="cx"> for (var i=0, l=path.length; i<l; i++) {
</span><span class="lines">@@ -863,12 +1210,11 @@
</span><span class="cx"> };
</span><span class="cx">
</span><span class="cx"> /**
</span><del>- @private
-
</del><span class="cx"> Wraps the passed function so that `this._super` will point to the superFunc
</span><span class="cx"> when the function is invoked. This is the primitive we use to implement
</span><span class="cx"> calls to super.
</span><span class="cx">
</span><ins>+ @private
</ins><span class="cx"> @method wrap
</span><span class="cx"> @for Ember
</span><span class="cx"> @param {Function} func The function to call
</span><span class="lines">@@ -889,6 +1235,7 @@
</span><span class="cx"> superWrapper.wrappedFunction = func;
</span><span class="cx"> superWrapper.__ember_observes__ = func.__ember_observes__;
</span><span class="cx"> superWrapper.__ember_observesBefore__ = func.__ember_observesBefore__;
</span><ins>+ superWrapper.__ember_listens__ = func.__ember_listens__;
</ins><span class="cx">
</span><span class="cx"> return superWrapper;
</span><span class="cx"> };
</span><span class="lines">@@ -914,7 +1261,7 @@
</span><span class="cx"> @method isArray
</span><span class="cx"> @for Ember
</span><span class="cx"> @param {Object} obj The object to test
</span><del>- @return {Boolean}
</del><ins>+ @return {Boolean} true if the passed object is an array or Array-like
</ins><span class="cx"> */
</span><span class="cx"> Ember.isArray = function(obj) {
</span><span class="cx"> if (!obj || obj.setInterval) { return false; }
</span><span class="lines">@@ -957,10 +1304,18 @@
</span><span class="cx"> /**
</span><span class="cx"> Checks to see if the `methodName` exists on the `obj`.
</span><span class="cx">
</span><ins>+ ```javascript
+ var foo = {bar: Ember.K, baz: null};
+ Ember.canInvoke(foo, 'bar'); // true
+ Ember.canInvoke(foo, 'baz'); // false
+ Ember.canInvoke(foo, 'bat'); // false
+ ```
+
</ins><span class="cx"> @method canInvoke
</span><span class="cx"> @for Ember
</span><span class="cx"> @param {Object} obj The object to check for the method
</span><span class="cx"> @param {String} methodName The method name to check for
</span><ins>+ @return {Boolean}
</ins><span class="cx"> */
</span><span class="cx"> Ember.canInvoke = canInvoke;
</span><span class="cx">
</span><span class="lines">@@ -968,12 +1323,19 @@
</span><span class="cx"> Checks to see if the `methodName` exists on the `obj`,
</span><span class="cx"> and if it does, invokes it with the arguments passed.
</span><span class="cx">
</span><ins>+ ```javascript
+ var d = new Date('03/15/2013');
+ Ember.tryInvoke(d, 'getTime'); // 1363320000000
+ Ember.tryInvoke(d, 'setFullYear', [2014]); // 1394856000000
+ Ember.tryInvoke(d, 'noSuchMethod', [2014]); // undefined
+ ```
+
</ins><span class="cx"> @method tryInvoke
</span><span class="cx"> @for Ember
</span><span class="cx"> @param {Object} obj The object to check for the method
</span><span class="cx"> @param {String} methodName The method name to check for
</span><span class="cx"> @param {Array} [args] The arguments to pass to the method
</span><del>- @return {anything} the return value of the invoked method or undefined if it cannot be invoked
</del><ins>+ @return {*} the return value of the invoked method or undefined if it cannot be invoked
</ins><span class="cx"> */
</span><span class="cx"> Ember.tryInvoke = function(obj, methodName, args) {
</span><span class="cx"> if (canInvoke(obj, methodName)) {
</span><span class="lines">@@ -999,13 +1361,24 @@
</span><span class="cx"> Provides try { } finally { } functionality, while working
</span><span class="cx"> around Safari's double finally bug.
</span><span class="cx">
</span><ins>+ ```javascript
+ var tryable = function() {
+ someResource.lock();
+ runCallback(); // May throw error.
+ };
+ var finalizer = function() {
+ someResource.unlock();
+ };
+ Ember.tryFinally(tryable, finalizer);
+ ```
+
</ins><span class="cx"> @method tryFinally
</span><span class="cx"> @for Ember
</span><del>- @param {Function} function The function to run the try callback
- @param {Function} function The function to run the finally callback
- @param [binding]
- @return {anything} The return value is the that of the finalizer,
- unless that valueis undefined, in which case it is the return value
</del><ins>+ @param {Function} tryable The function to run the try callback
+ @param {Function} finalizer The function to run the finally callback
+ @param {Object} [binding] The optional calling object. Defaults to 'this'
+ @return {*} The return value is the that of the finalizer,
+ unless that value is undefined, in which case it is the return value
</ins><span class="cx"> of the tryable
</span><span class="cx"> */
</span><span class="cx">
</span><span class="lines">@@ -1020,7 +1393,7 @@
</span><span class="cx"> } finally {
</span><span class="cx"> try {
</span><span class="cx"> finalResult = finalizer.call(binding);
</span><del>- } catch (e){
</del><ins>+ } catch (e) {
</ins><span class="cx"> finalError = e;
</span><span class="cx"> }
</span><span class="cx"> }
</span><span class="lines">@@ -1049,19 +1422,43 @@
</span><span class="cx"> Provides try { } catch finally { } functionality, while working
</span><span class="cx"> around Safari's double finally bug.
</span><span class="cx">
</span><ins>+ ```javascript
+ var tryable = function() {
+ for (i=0, l=listeners.length; i<l; i++) {
+ listener = listeners[i];
+ beforeValues[i] = listener.before(name, time(), payload);
+ }
+
+ return callback.call(binding);
+ };
+
+ var catchable = function(e) {
+ payload = payload || {};
+ payload.exception = e;
+ };
+
+ var finalizer = function() {
+ for (i=0, l=listeners.length; i<l; i++) {
+ listener = listeners[i];
+ listener.after(name, time(), payload, beforeValues[i]);
+ }
+ };
+ Ember.tryCatchFinally(tryable, catchable, finalizer);
+ ```
+
</ins><span class="cx"> @method tryCatchFinally
</span><span class="cx"> @for Ember
</span><del>- @param {Function} function The function to run the try callback
- @param {Function} function The function to run the catchable callback
- @param {Function} function The function to run the finally callback
- @param [binding]
- @return {anything} The return value is the that of the finalizer,
</del><ins>+ @param {Function} tryable The function to run the try callback
+ @param {Function} catchable The function to run the catchable callback
+ @param {Function} finalizer The function to run the finally callback
+ @param {Object} [binding] The optional calling object. Defaults to 'this'
+ @return {*} The return value is the that of the finalizer,
</ins><span class="cx"> unless that value is undefined, in which case it is the return value
</span><span class="cx"> of the tryable.
</span><span class="cx"> */
</span><span class="cx"> if (needsFinallyFix) {
</span><span class="cx"> Ember.tryCatchFinally = function(tryable, catchable, finalizer, binding) {
</span><del>- var result, finalResult, finalError, finalReturn;
</del><ins>+ var result, finalResult, finalError;
</ins><span class="cx">
</span><span class="cx"> binding = binding || this;
</span><span class="cx">
</span><span class="lines">@@ -1072,7 +1469,7 @@
</span><span class="cx"> } finally {
</span><span class="cx"> try {
</span><span class="cx"> finalResult = finalizer.call(binding);
</span><del>- } catch (e){
</del><ins>+ } catch (e) {
</ins><span class="cx"> finalError = e;
</span><span class="cx"> }
</span><span class="cx"> }
</span><span class="lines">@@ -1099,6 +1496,86 @@
</span><span class="cx"> };
</span><span class="cx"> }
</span><span class="cx">
</span><ins>+// ........................................
+// TYPING & ARRAY MESSAGING
+//
+
+var TYPE_MAP = {};
+var t = "Boolean Number String Function Array Date RegExp Object".split(" ");
+Ember.ArrayPolyfills.forEach.call(t, function(name) {
+ TYPE_MAP[ "[object " + name + "]" ] = name.toLowerCase();
+});
+
+var toString = Object.prototype.toString;
+
+/**
+ Returns a consistent type for the passed item.
+
+ Use this instead of the built-in `typeof` to get the type of an item.
+ It will return the same result across all browsers and includes a bit
+ more detail. Here is what will be returned:
+
+ | Return Value | Meaning |
+ |---------------|------------------------------------------------------|
+ | 'string' | String primitive or String object. |
+ | 'number' | Number primitive or Number object. |
+ | 'boolean' | Boolean primitive or Boolean object. |
+ | 'null' | Null value |
+ | 'undefined' | Undefined value |
+ | 'function' | A function |
+ | 'array' | An instance of Array |
+ | 'regexp' | An instance of RegExp |
+ | 'date' | An instance of Date |
+ | 'class' | An Ember class (created using Ember.Object.extend()) |
+ | 'instance' | An Ember object instance |
+ | 'error' | An instance of the Error object |
+ | 'object' | A JavaScript object not inheriting from Ember.Object |
+
+ Examples:
+
+ ```javascript
+ Ember.typeOf(); // 'undefined'
+ Ember.typeOf(null); // 'null'
+ Ember.typeOf(undefined); // 'undefined'
+ Ember.typeOf('michael'); // 'string'
+ Ember.typeOf(new String('michael')); // 'string'
+ Ember.typeOf(101); // 'number'
+ Ember.typeOf(new Number(101)); // 'number'
+ Ember.typeOf(true); // 'boolean'
+ Ember.typeOf(new Boolean(true)); // 'boolean'
+ Ember.typeOf(Ember.makeArray); // 'function'
+ Ember.typeOf([1,2,90]); // 'array'
+ Ember.typeOf(/abc/); // 'regexp'
+ Ember.typeOf(new Date()); // 'date'
+ Ember.typeOf(Ember.Object.extend()); // 'class'
+ Ember.typeOf(Ember.Object.create()); // 'instance'
+ Ember.typeOf(new Error('teamocil')); // 'error'
+
+ // "normal" JavaScript object
+ Ember.typeOf({a: 'b'}); // 'object'
+ ```
+
+ @method typeOf
+ @for Ember
+ @param {Object} item the item to check
+ @return {String} the type
+*/
+Ember.typeOf = function(item) {
+ var ret;
+
+ ret = (item === null || item === undefined) ? String(item) : TYPE_MAP[toString.call(item)] || 'object';
+
+ if (ret === 'function') {
+ if (Ember.Object && Ember.Object.detect(item)) ret = 'class';
+ } else if (ret === 'object') {
+ if (item instanceof Error) ret = 'error';
+ else if (Ember.Object && item instanceof Ember.Object) ret = 'instance';
+ else if (item instanceof Date) ret = 'date';
+ }
+
+ return ret;
+};
+
</ins><span class="cx"> })();
</span><span class="cx">
</span><span class="cx">
</span><span class="lines">@@ -1171,13 +1648,23 @@
</span><span class="cx"> };
</span><span class="cx">
</span><span class="cx"> var time = (function() {
</span><del>- var perf = 'undefined' !== typeof window ? window.performance || {} : {};
- var fn = perf.now || perf.mozNow || perf.webkitNow || perf.msNow || perf.oNow;
- // fn.bind will be available in all the browsers that support the advanced window.performance... ;-)
- return fn ? fn.bind(perf) : function() { return +new Date(); };
</del><ins>+ var perf = 'undefined' !== typeof window ? window.performance || {} : {};
+ var fn = perf.now || perf.mozNow || perf.webkitNow || perf.msNow || perf.oNow;
+ // fn.bind will be available in all the browsers that support the advanced window.performance... ;-)
+ return fn ? fn.bind(perf) : function() { return +new Date(); };
</ins><span class="cx"> })();
</span><span class="cx">
</span><ins>+/**
+ Notifies event's subscribers, calls `before` and `after` hooks.
</ins><span class="cx">
</span><ins>+ @method instrument
+ @namespace Ember.Instrumentation
+
+ @param {String} [name] Namespaced event name.
+ @param {Object} payload
+ @param {Function} callback Function that you're instrumenting.
+ @param {Object} binding Context that instrument function is called with.
+*/
</ins><span class="cx"> Ember.Instrumentation.instrument = function(name, payload, callback, binding) {
</span><span class="cx"> var listeners = cache[name], timeName, ret;
</span><span class="cx">
</span><span class="lines">@@ -1198,7 +1685,7 @@
</span><span class="cx">
</span><span class="cx"> var beforeValues = [], listener, i, l;
</span><span class="cx">
</span><del>- function tryable(){
</del><ins>+ function tryable() {
</ins><span class="cx"> for (i=0, l=listeners.length; i<l; i++) {
</span><span class="cx"> listener = listeners[i];
</span><span class="cx"> beforeValues[i] = listener.before(name, time(), payload);
</span><span class="lines">@@ -1207,7 +1694,7 @@
</span><span class="cx"> return callback.call(binding);
</span><span class="cx"> }
</span><span class="cx">
</span><del>- function catchable(e){
</del><ins>+ function catchable(e) {
</ins><span class="cx"> payload = payload || {};
</span><span class="cx"> payload.exception = e;
</span><span class="cx"> }
</span><span class="lines">@@ -1226,6 +1713,17 @@
</span><span class="cx"> return Ember.tryCatchFinally(tryable, catchable, finalizer);
</span><span class="cx"> };
</span><span class="cx">
</span><ins>+/**
+ Subscribes to a particular event or instrumented block of code.
+
+ @method subscribe
+ @namespace Ember.Instrumentation
+
+ @param {String} [pattern] Namespaced event name.
+ @param {Object} [object] Before and After hooks.
+
+ @return {Subscriber}
+*/
</ins><span class="cx"> Ember.Instrumentation.subscribe = function(pattern, object) {
</span><span class="cx"> var paths = pattern.split("."), path, regex = [];
</span><span class="cx">
</span><span class="lines">@@ -1253,6 +1751,14 @@
</span><span class="cx"> return subscriber;
</span><span class="cx"> };
</span><span class="cx">
</span><ins>+/**
+ Unsubscribes from a particular event or instrumented block of code.
+
+ @method unsubscribe
+ @namespace Ember.Instrumentation
+
+ @param {Object} [subscriber]
+*/
</ins><span class="cx"> Ember.Instrumentation.unsubscribe = function(subscriber) {
</span><span class="cx"> var index;
</span><span class="cx">
</span><span class="lines">@@ -1266,6 +1772,12 @@
</span><span class="cx"> cache = {};
</span><span class="cx"> };
</span><span class="cx">
</span><ins>+/**
+ Resets `Ember.Instrumentation` by flushing list of subscribers.
+
+ @method reset
+ @namespace Ember.Instrumentation
+*/
</ins><span class="cx"> Ember.Instrumentation.reset = function() {
</span><span class="cx"> subscribers = [];
</span><span class="cx"> cache = {};
</span><span class="lines">@@ -1273,23 +1785,28 @@
</span><span class="cx">
</span><span class="cx"> Ember.instrument = Ember.Instrumentation.instrument;
</span><span class="cx"> Ember.subscribe = Ember.Instrumentation.subscribe;
</span><del>-
</del><span class="cx"> })();
</span><span class="cx">
</span><span class="cx">
</span><span class="cx">
</span><span class="cx"> (function() {
</span><ins>+var map, forEach, indexOf, splice;
+map = Array.prototype.map || Ember.ArrayPolyfills.map;
+forEach = Array.prototype.forEach || Ember.ArrayPolyfills.forEach;
+indexOf = Array.prototype.indexOf || Ember.ArrayPolyfills.indexOf;
+splice = Array.prototype.splice;
+
</ins><span class="cx"> var utils = Ember.EnumerableUtils = {
</span><span class="cx"> map: function(obj, callback, thisArg) {
</span><del>- return obj.map ? obj.map.call(obj, callback, thisArg) : Array.prototype.map.call(obj, callback, thisArg);
</del><ins>+ return obj.map ? obj.map.call(obj, callback, thisArg) : map.call(obj, callback, thisArg);
</ins><span class="cx"> },
</span><span class="cx">
</span><span class="cx"> forEach: function(obj, callback, thisArg) {
</span><del>- return obj.forEach ? obj.forEach.call(obj, callback, thisArg) : Array.prototype.forEach.call(obj, callback, thisArg);
</del><ins>+ return obj.forEach ? obj.forEach.call(obj, callback, thisArg) : forEach.call(obj, callback, thisArg);
</ins><span class="cx"> },
</span><span class="cx">
</span><span class="cx"> indexOf: function(obj, element, index) {
</span><del>- return obj.indexOf ? obj.indexOf.call(obj, element, index) : Array.prototype.indexOf.call(obj, element, index);
</del><ins>+ return obj.indexOf ? obj.indexOf.call(obj, element, index) : indexOf.call(obj, element, index);
</ins><span class="cx"> },
</span><span class="cx">
</span><span class="cx"> indexesOf: function(obj, elements) {
</span><span class="lines">@@ -1308,20 +1825,39 @@
</span><span class="cx"> if (index !== -1) { array.splice(index, 1); }
</span><span class="cx"> },
</span><span class="cx">
</span><ins>+ _replace: function(array, idx, amt, objects) {
+ var args = [].concat(objects), chunk, ret = [],
+ // https://code.google.com/p/chromium/issues/detail?id=56588
+ size = 60000, start = idx, ends = amt, count;
+
+ while (args.length) {
+ count = ends > size ? size : ends;
+ if (count <= 0) { count = 0; }
+
+ chunk = args.splice(0, size);
+ chunk = [start, count].concat(chunk);
+
+ start += size;
+ ends -= count;
+
+ ret = ret.concat(splice.apply(array, chunk));
+ }
+ return ret;
+ },
+
</ins><span class="cx"> replace: function(array, idx, amt, objects) {
</span><span class="cx"> if (array.replace) {
</span><span class="cx"> return array.replace(idx, amt, objects);
</span><span class="cx"> } else {
</span><del>- var args = Array.prototype.concat.apply([idx, amt], objects);
- return array.splice.apply(array, args);
</del><ins>+ return utils._replace(array, idx, amt, objects);
</ins><span class="cx"> }
</span><span class="cx"> },
</span><span class="cx">
</span><span class="cx"> intersection: function(array1, array2) {
</span><span class="cx"> var intersection = [];
</span><span class="cx">
</span><del>- array1.forEach(function(element) {
- if (array2.indexOf(element) >= 0) {
</del><ins>+ utils.forEach(array1, function(element) {
+ if (utils.indexOf(array2, element) >= 0) {
</ins><span class="cx"> intersection.push(element);
</span><span class="cx"> }
</span><span class="cx"> });
</span><span class="lines">@@ -1335,98 +1871,1019 @@
</span><span class="cx">
</span><span class="cx">
</span><span class="cx"> (function() {
</span><del>-/*jshint newcap:false*/
</del><span class="cx"> /**
</span><span class="cx"> @module ember-metal
</span><span class="cx"> */
</span><span class="cx">
</span><del>-// NOTE: There is a bug in jshint that doesn't recognize `Object()` without `new`
-// as being ok unless both `newcap:false` and not `use strict`.
-// https://github.com/jshint/jshint/issues/392
</del><ins>+var META_KEY = Ember.META_KEY, get;
</ins><span class="cx">
</span><del>-// Testing this is not ideal, but we want to use native functions
-// if available, but not to use versions created by libraries like Prototype
-var isNativeFunc = function(func) {
- // This should probably work in all browsers likely to have ES5 array methods
- return func && Function.prototype.toString.call(func).indexOf('[native code]') > -1;
</del><ins>+var MANDATORY_SETTER = Ember.ENV.MANDATORY_SETTER;
+
+var IS_GLOBAL_PATH = /^([A-Z$]|([0-9][A-Z$])).*[\.\*]/;
+var HAS_THIS = /^this[\.\*]/;
+var FIRST_KEY = /^([^\.\*]+)/;
+
+// ..........................................................
+// GET AND SET
+//
+// If we are on a platform that supports accessors we can use those.
+// Otherwise simulate accessors by looking up the property directly on the
+// object.
+
+/**
+ Gets the value of a property on an object. If the property is computed,
+ the function will be invoked. If the property is not defined but the
+ object implements the `unknownProperty` method then that will be invoked.
+
+ If you plan to run on IE8 and older browsers then you should use this
+ method anytime you want to retrieve a property on an object that you don't
+ know for sure is private. (Properties beginning with an underscore '_'
+ are considered private.)
+
+ On all newer browsers, you only need to use this method to retrieve
+ properties if the property might not be defined on the object and you want
+ to respect the `unknownProperty` handler. Otherwise you can ignore this
+ method.
+
+ Note that if the object itself is `undefined`, this method will throw
+ an error.
+
+ @method get
+ @for Ember
+ @param {Object} obj The object to retrieve from.
+ @param {String} keyName The property key to retrieve
+ @return {Object} the property value or `null`.
+*/
+get = function get(obj, keyName) {
+ // Helpers that operate with 'this' within an #each
+ if (keyName === '') {
+ return obj;
+ }
+
+ if (!keyName && 'string'===typeof obj) {
+ keyName = obj;
+ obj = null;
+ }
+
+ Ember.assert("Cannot call get with "+ keyName +" key.", !!keyName);
+ Ember.assert("Cannot call get with '"+ keyName +"' on an undefined object.", obj !== undefined);
+
+ if (obj === null || keyName.indexOf('.') !== -1) {
+ return getPath(obj, keyName);
+ }
+
+ var meta = obj[META_KEY], desc = meta && meta.descs[keyName], ret;
+ if (desc) {
+ return desc.get(obj, keyName);
+ } else {
+ if (MANDATORY_SETTER && meta && meta.watching[keyName] > 0) {
+ ret = meta.values[keyName];
+ } else {
+ ret = obj[keyName];
+ }
+
+ if (ret === undefined &&
+ 'object' === typeof obj && !(keyName in obj) && 'function' === typeof obj.unknownProperty) {
+ return obj.unknownProperty(keyName);
+ }
+
+ return ret;
+ }
</ins><span class="cx"> };
</span><span class="cx">
</span><del>-// From: https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/array/map
-var arrayMap = isNativeFunc(Array.prototype.map) ? Array.prototype.map : function(fun /*, thisp */) {
- //"use strict";
</del><ins>+// Currently used only by Ember Data tests
+if (Ember.config.overrideAccessors) {
+ Ember.get = get;
+ Ember.config.overrideAccessors();
+ get = Ember.get;
+}
</ins><span class="cx">
</span><del>- if (this === void 0 || this === null) {
- throw new TypeError();
</del><ins>+/**
+ Normalizes a target/path pair to reflect that actual target/path that should
+ be observed, etc. This takes into account passing in global property
+ paths (i.e. a path beginning with a captial letter not defined on the
+ target) and * separators.
+
+ @private
+ @method normalizeTuple
+ @for Ember
+ @param {Object} target The current target. May be `null`.
+ @param {String} path A path on the target or a global property path.
+ @return {Array} a temporary array with the normalized target/path pair.
+*/
+var normalizeTuple = Ember.normalizeTuple = function(target, path) {
+ var hasThis = HAS_THIS.test(path),
+ isGlobal = !hasThis && IS_GLOBAL_PATH.test(path),
+ key;
+
+ if (!target || isGlobal) target = Ember.lookup;
+ if (hasThis) path = path.slice(5);
+
+ if (target === Ember.lookup) {
+ key = path.match(FIRST_KEY)[0];
+ target = get(target, key);
+ path = path.slice(key.length+1);
</ins><span class="cx"> }
</span><span class="cx">
</span><del>- var t = Object(this);
- var len = t.length >>> 0;
- if (typeof fun !== "function") {
- throw new TypeError();
</del><ins>+ // must return some kind of path to be valid else other things will break.
+ if (!path || path.length===0) throw new Ember.Error('Invalid Path');
+
+ return [ target, path ];
+};
+
+var getPath = Ember._getPath = function(root, path) {
+ var hasThis, parts, tuple, idx, len;
+
+ // If there is no root and path is a key name, return that
+ // property from the global object.
+ // E.g. get('Ember') -> Ember
+ if (root === null && path.indexOf('.') === -1) { return get(Ember.lookup, path); }
+
+ // detect complicated paths and normalize them
+ hasThis = HAS_THIS.test(path);
+
+ if (!root || hasThis) {
+ tuple = normalizeTuple(root, path);
+ root = tuple[0];
+ path = tuple[1];
+ tuple.length = 0;
</ins><span class="cx"> }
</span><span class="cx">
</span><del>- var res = new Array(len);
- var thisp = arguments[1];
- for (var i = 0; i < len; i++) {
- if (i in t) {
- res[i] = fun.call(thisp, t[i], i, t);
</del><ins>+ parts = path.split(".");
+ len = parts.length;
+ for (idx = 0; root != null && idx < len; idx++) {
+ root = get(root, parts[idx], true);
+ if (root && root.isDestroyed) { return undefined; }
+ }
+ return root;
+};
+
+Ember.getWithDefault = function(root, key, defaultValue) {
+ var value = get(root, key);
+
+ if (value === undefined) { return defaultValue; }
+ return value;
+};
+
+
+Ember.get = get;
+
+})();
+
+
+
+(function() {
+/**
+@module ember-metal
+*/
+
+var o_create = Ember.create,
+ metaFor = Ember.meta,
+ META_KEY = Ember.META_KEY,
+ a_slice = [].slice,
+ /* listener flags */
+ ONCE = 1, SUSPENDED = 2;
+
+/*
+ The event system uses a series of nested hashes to store listeners on an
+ object. When a listener is registered, or when an event arrives, these
+ hashes are consulted to determine which target and action pair to invoke.
+
+ The hashes are stored in the object's meta hash, and look like this:
+
+ // Object's meta hash
+ {
+ listeners: { // variable name: `listenerSet`
+ "foo:changed": [ // variable name: `actions`
+ target, method, flags
+ ]
+ }
+ }
+
+*/
+
+function indexOf(array, target, method) {
+ var index = -1;
+ for (var i = 0, l = array.length; i < l; i += 3) {
+ if (target === array[i] && method === array[i+1]) { index = i; break; }
+ }
+ return index;
+}
+
+function actionsFor(obj, eventName) {
+ var meta = metaFor(obj, true),
+ actions;
+
+ if (!meta.listeners) { meta.listeners = {}; }
+
+ if (!meta.hasOwnProperty('listeners')) {
+ // setup inherited copy of the listeners object
+ meta.listeners = o_create(meta.listeners);
+ }
+
+ actions = meta.listeners[eventName];
+
+ // if there are actions, but the eventName doesn't exist in our listeners, then copy them from the prototype
+ if (actions && !meta.listeners.hasOwnProperty(eventName)) {
+ actions = meta.listeners[eventName] = meta.listeners[eventName].slice();
+ } else if (!actions) {
+ actions = meta.listeners[eventName] = [];
+ }
+
+ return actions;
+}
+
+function actionsUnion(obj, eventName, otherActions) {
+ var meta = obj[META_KEY],
+ actions = meta && meta.listeners && meta.listeners[eventName];
+
+ if (!actions) { return; }
+ for (var i = actions.length - 3; i >= 0; i -= 3) {
+ var target = actions[i],
+ method = actions[i+1],
+ flags = actions[i+2],
+ actionIndex = indexOf(otherActions, target, method);
+
+ if (actionIndex === -1) {
+ otherActions.push(target, method, flags);
</ins><span class="cx"> }
</span><span class="cx"> }
</span><ins>+}
</ins><span class="cx">
</span><del>- return res;
</del><ins>+function actionsDiff(obj, eventName, otherActions) {
+ var meta = obj[META_KEY],
+ actions = meta && meta.listeners && meta.listeners[eventName],
+ diffActions = [];
+
+ if (!actions) { return; }
+ for (var i = actions.length - 3; i >= 0; i -= 3) {
+ var target = actions[i],
+ method = actions[i+1],
+ flags = actions[i+2],
+ actionIndex = indexOf(otherActions, target, method);
+
+ if (actionIndex !== -1) { continue; }
+
+ otherActions.push(target, method, flags);
+ diffActions.push(target, method, flags);
+ }
+
+ return diffActions;
+}
+
+/**
+ Add an event listener
+
+ @method addListener
+ @for Ember
+ @param obj
+ @param {String} eventName
+ @param {Object|Function} targetOrMethod A target object or a function
+ @param {Function|String} method A function or the name of a function to be called on `target`
+ @param {Boolean} once A flag whether a function should only be called once
+*/
+function addListener(obj, eventName, target, method, once) {
+ Ember.assert("You must pass at least an object and event name to Ember.addListener", !!obj && !!eventName);
+
+ if (!method && 'function' === typeof target) {
+ method = target;
+ target = null;
+ }
+
+ var actions = actionsFor(obj, eventName),
+ actionIndex = indexOf(actions, target, method),
+ flags = 0;
+
+ if (once) flags |= ONCE;
+
+ if (actionIndex !== -1) { return; }
+
+ actions.push(target, method, flags);
+
+ if ('function' === typeof obj.didAddListener) {
+ obj.didAddListener(eventName, target, method);
+ }
+}
+
+/**
+ Remove an event listener
+
+ Arguments should match those passed to `Ember.addListener`.
+
+ @method removeListener
+ @for Ember
+ @param obj
+ @param {String} eventName
+ @param {Object|Function} targetOrMethod A target object or a function
+ @param {Function|String} method A function or the name of a function to be called on `target`
+*/
+function removeListener(obj, eventName, target, method) {
+ Ember.assert("You must pass at least an object and event name to Ember.removeListener", !!obj && !!eventName);
+
+ if (!method && 'function' === typeof target) {
+ method = target;
+ target = null;
+ }
+
+ function _removeListener(target, method) {
+ var actions = actionsFor(obj, eventName),
+ actionIndex = indexOf(actions, target, method);
+
+ // action doesn't exist, give up silently
+ if (actionIndex === -1) { return; }
+
+ actions.splice(actionIndex, 3);
+
+ if ('function' === typeof obj.didRemoveListener) {
+ obj.didRemoveListener(eventName, target, method);
+ }
+ }
+
+ if (method) {
+ _removeListener(target, method);
+ } else {
+ var meta = obj[META_KEY],
+ actions = meta && meta.listeners && meta.listeners[eventName];
+
+ if (!actions) { return; }
+ for (var i = actions.length - 3; i >= 0; i -= 3) {
+ _removeListener(actions[i], actions[i+1]);
+ }
+ }
+}
+
+/**
+ Suspend listener during callback.
+
+ This should only be used by the target of the event listener
+ when it is taking an action that would cause the event, e.g.
+ an object might suspend its property change listener while it is
+ setting that property.
+
+ @private
+ @method suspendListener
+ @for Ember
+ @param obj
+ @param {String} eventName
+ @param {Object|Function} targetOrMethod A target object or a function
+ @param {Function|String} method A function or the name of a function to be called on `target`
+ @param {Function} callback
+*/
+function suspendListener(obj, eventName, target, method, callback) {
+ if (!method && 'function' === typeof target) {
+ method = target;
+ target = null;
+ }
+
+ var actions = actionsFor(obj, eventName),
+ actionIndex = indexOf(actions, target, method);
+
+ if (actionIndex !== -1) {
+ actions[actionIndex+2] |= SUSPENDED; // mark the action as suspended
+ }
+
+ function tryable() { return callback.call(target); }
+ function finalizer() { if (actionIndex !== -1) { actions[actionIndex+2] &= ~SUSPENDED; } }
+
+ return Ember.tryFinally(tryable, finalizer);
+}
+
+/**
+ Suspends multiple listeners during a callback.
+
+ @private
+ @method suspendListeners
+ @for Ember
+ @param obj
+ @param {Array} eventName Array of event names
+ @param {Object|Function} targetOrMethod A target object or a function
+ @param {Function|String} method A function or the name of a function to be called on `target`
+ @param {Function} callback
+*/
+function suspendListeners(obj, eventNames, target, method, callback) {
+ if (!method && 'function' === typeof target) {
+ method = target;
+ target = null;
+ }
+
+ var suspendedActions = [],
+ actionsList = [],
+ eventName, actions, i, l;
+
+ for (i=0, l=eventNames.length; i<l; i++) {
+ eventName = eventNames[i];
+ actions = actionsFor(obj, eventName);
+ var actionIndex = indexOf(actions, target, method);
+
+ if (actionIndex !== -1) {
+ actions[actionIndex+2] |= SUSPENDED;
+ suspendedActions.push(actionIndex);
+ actionsList.push(actions);
+ }
+ }
+
+ function tryable() { return callback.call(target); }
+
+ function finalizer() {
+ for (var i = 0, l = suspendedActions.length; i < l; i++) {
+ var actionIndex = suspendedActions[i];
+ actionsList[i][actionIndex+2] &= ~SUSPENDED;
+ }
+ }
+
+ return Ember.tryFinally(tryable, finalizer);
+}
+
+/**
+ Return a list of currently watched events
+
+ @private
+ @method watchedEvents
+ @for Ember
+ @param obj
+*/
+function watchedEvents(obj) {
+ var listeners = obj[META_KEY].listeners, ret = [];
+
+ if (listeners) {
+ for(var eventName in listeners) {
+ if (listeners[eventName]) { ret.push(eventName); }
+ }
+ }
+ return ret;
+}
+
+/**
+ Send an event. The execution of suspended listeners
+ is skipped, and once listeners are removed. A listener without
+ a target is executed on the passed object. If an array of actions
+ is not passed, the actions stored on the passed object are invoked.
+
+ @method sendEvent
+ @for Ember
+ @param obj
+ @param {String} eventName
+ @param {Array} params Optional parameters for each listener.
+ @param {Array} actions Optional array of actions (listeners).
+ @return true
+*/
+function sendEvent(obj, eventName, params, actions) {
+ // first give object a chance to handle it
+ if (obj !== Ember && 'function' === typeof obj.sendEvent) {
+ obj.sendEvent(eventName, params);
+ }
+
+ if (!actions) {
+ var meta = obj[META_KEY];
+ actions = meta && meta.listeners && meta.listeners[eventName];
+ }
+
+ if (!actions) { return; }
+
+ for (var i = actions.length - 3; i >= 0; i -= 3) { // looping in reverse for once listeners
+ var target = actions[i], method = actions[i+1], flags = actions[i+2];
+ if (!method) { continue; }
+ if (flags & SUSPENDED) { continue; }
+ if (flags & ONCE) { removeListener(obj, eventName, target, method); }
+ if (!target) { target = obj; }
+ if ('string' === typeof method) { method = target[method]; }
+ if (params) {
+ method.apply(target, params);
+ } else {
+ method.call(target);
+ }
+ }
+ return true;
+}
+
+/**
+ @private
+ @method hasListeners
+ @for Ember
+ @param obj
+ @param {String} eventName
+*/
+function hasListeners(obj, eventName) {
+ var meta = obj[META_KEY],
+ actions = meta && meta.listeners && meta.listeners[eventName];
+
+ return !!(actions && actions.length);
+}
+
+/**
+ @private
+ @method listenersFor
+ @for Ember
+ @param obj
+ @param {String} eventName
+*/
+function listenersFor(obj, eventName) {
+ var ret = [];
+ var meta = obj[META_KEY],
+ actions = meta && meta.listeners && meta.listeners[eventName];
+
+ if (!actions) { return ret; }
+
+ for (var i = 0, l = actions.length; i < l; i += 3) {
+ var target = actions[i],
+ method = actions[i+1];
+ ret.push([target, method]);
+ }
+
+ return ret;
+}
+
+/**
+ Define a property as a function that should be executed when
+ a specified event or events are triggered.
+
+
+ ``` javascript
+ var Job = Ember.Object.extend({
+ logCompleted: Ember.on('completed', function(){
+ console.log('Job completed!');
+ })
+ });
+ var job = Job.create();
+ Ember.sendEvent(job, 'completed'); // Logs "Job completed!"
+ ```
+
+ @method on
+ @for Ember
+ @param {String} eventNames*
+ @param {Function} func
+ @return func
+*/
+Ember.on = function(){
+ var func = a_slice.call(arguments, -1)[0],
+ events = a_slice.call(arguments, 0, -1);
+ func.__ember_listens__ = events;
+ return func;
</ins><span class="cx"> };
</span><span class="cx">
</span><del>-// From: https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/array/foreach
-var arrayForEach = isNativeFunc(Array.prototype.forEach) ? Array.prototype.forEach : function(fun /*, thisp */) {
- //"use strict";
</del><ins>+Ember.addListener = addListener;
+Ember.removeListener = removeListener;
+Ember._suspendListener = suspendListener;
+Ember._suspendListeners = suspendListeners;
+Ember.sendEvent = sendEvent;
+Ember.hasListeners = hasListeners;
+Ember.watchedEvents = watchedEvents;
+Ember.listenersFor = listenersFor;
+Ember.listenersDiff = actionsDiff;
+Ember.listenersUnion = actionsUnion;
</ins><span class="cx">
</span><del>- if (this === void 0 || this === null) {
- throw new TypeError();
</del><ins>+})();
+
+
+
+(function() {
+var guidFor = Ember.guidFor,
+ sendEvent = Ember.sendEvent;
+
+/*
+ this.observerSet = {
+ [senderGuid]: { // variable name: `keySet`
+ [keyName]: listIndex
+ }
+ },
+ this.observers = [
+ {
+ sender: obj,
+ keyName: keyName,
+ eventName: eventName,
+ listeners: [
+ [target, method, flags]
+ ]
+ },
+ ...
+ ]
+*/
+var ObserverSet = Ember._ObserverSet = function() {
+ this.clear();
+};
+
+ObserverSet.prototype.add = function(sender, keyName, eventName) {
+ var observerSet = this.observerSet,
+ observers = this.observers,
+ senderGuid = guidFor(sender),
+ keySet = observerSet[senderGuid],
+ index;
+
+ if (!keySet) {
+ observerSet[senderGuid] = keySet = {};
</ins><span class="cx"> }
</span><ins>+ index = keySet[keyName];
+ if (index === undefined) {
+ index = observers.push({
+ sender: sender,
+ keyName: keyName,
+ eventName: eventName,
+ listeners: []
+ }) - 1;
+ keySet[keyName] = index;
+ }
+ return observers[index].listeners;
+};
</ins><span class="cx">
</span><del>- var t = Object(this);
- var len = t.length >>> 0;
- if (typeof fun !== "function") {
- throw new TypeError();
</del><ins>+ObserverSet.prototype.flush = function() {
+ var observers = this.observers, i, len, observer, sender;
+ this.clear();
+ for (i=0, len=observers.length; i < len; ++i) {
+ observer = observers[i];
+ sender = observer.sender;
+ if (sender.isDestroying || sender.isDestroyed) { continue; }
+ sendEvent(sender, observer.eventName, [sender, observer.keyName], observer.listeners);
</ins><span class="cx"> }
</span><ins>+};
</ins><span class="cx">
</span><del>- var thisp = arguments[1];
- for (var i = 0; i < len; i++) {
- if (i in t) {
- fun.call(thisp, t[i], i, t);
</del><ins>+ObserverSet.prototype.clear = function() {
+ this.observerSet = {};
+ this.observers = [];
+};
+})();
+
+
+
+(function() {
+var metaFor = Ember.meta,
+ guidFor = Ember.guidFor,
+ tryFinally = Ember.tryFinally,
+ sendEvent = Ember.sendEvent,
+ listenersUnion = Ember.listenersUnion,
+ listenersDiff = Ember.listenersDiff,
+ ObserverSet = Ember._ObserverSet,
+ beforeObserverSet = new ObserverSet(),
+ observerSet = new ObserverSet(),
+ deferred = 0;
+
+// ..........................................................
+// PROPERTY CHANGES
+//
+
+/**
+ This function is called just before an object property is about to change.
+ It will notify any before observers and prepare caches among other things.
+
+ Normally you will not need to call this method directly but if for some
+ reason you can't directly watch a property you can invoke this method
+ manually along with `Ember.propertyDidChange()` which you should call just
+ after the property value changes.
+
+ @method propertyWillChange
+ @for Ember
+ @param {Object} obj The object with the property that will change
+ @param {String} keyName The property key (or path) that will change.
+ @return {void}
+*/
+function propertyWillChange(obj, keyName) {
+ var m = metaFor(obj, false),
+ watching = m.watching[keyName] > 0 || keyName === 'length',
+ proto = m.proto,
+ desc = m.descs[keyName];
+
+ if (!watching) { return; }
+ if (proto === obj) { return; }
+ if (desc && desc.willChange) { desc.willChange(obj, keyName); }
+ dependentKeysWillChange(obj, keyName, m);
+ chainsWillChange(obj, keyName, m);
+ notifyBeforeObservers(obj, keyName);
+}
+Ember.propertyWillChange = propertyWillChange;
+
+/**
+ This function is called just after an object property has changed.
+ It will notify any observers and clear caches among other things.
+
+ Normally you will not need to call this method directly but if for some
+ reason you can't directly watch a property you can invoke this method
+ manually along with `Ember.propertyWillChange()` which you should call just
+ before the property value changes.
+
+ @method propertyDidChange
+ @for Ember
+ @param {Object} obj The object with the property that will change
+ @param {String} keyName The property key (or path) that will change.
+ @return {void}
+*/
+function propertyDidChange(obj, keyName) {
+ var m = metaFor(obj, false),
+ watching = m.watching[keyName] > 0 || keyName === 'length',
+ proto = m.proto,
+ desc = m.descs[keyName];
+
+ if (proto === obj) { return; }
+
+ // shouldn't this mean that we're watching this key?
+ if (desc && desc.didChange) { desc.didChange(obj, keyName); }
+ if (!watching && keyName !== 'length') { return; }
+
+ dependentKeysDidChange(obj, keyName, m);
+ chainsDidChange(obj, keyName, m, false);
+ notifyObservers(obj, keyName);
+}
+Ember.propertyDidChange = propertyDidChange;
+
+var WILL_SEEN, DID_SEEN;
+
+// called whenever a property is about to change to clear the cache of any dependent keys (and notify those properties of changes, etc...)
+function dependentKeysWillChange(obj, depKey, meta) {
+ if (obj.isDestroying) { return; }
+
+ var seen = WILL_SEEN, top = !seen;
+ if (top) { seen = WILL_SEEN = {}; }
+ iterDeps(propertyWillChange, obj, depKey, seen, meta);
+ if (top) { WILL_SEEN = null; }
+}
+
+// called whenever a property has just changed to update dependent keys
+function dependentKeysDidChange(obj, depKey, meta) {
+ if (obj.isDestroying) { return; }
+
+ var seen = DID_SEEN, top = !seen;
+ if (top) { seen = DID_SEEN = {}; }
+ iterDeps(propertyDidChange, obj, depKey, seen, meta);
+ if (top) { DID_SEEN = null; }
+}
+
+function iterDeps(method, obj, depKey, seen, meta) {
+ var guid = guidFor(obj);
+ if (!seen[guid]) seen[guid] = {};
+ if (seen[guid][depKey]) return;
+ seen[guid][depKey] = true;
+
+ var deps = meta.deps;
+ deps = deps && deps[depKey];
+ if (deps) {
+ for(var key in deps) {
+ var desc = meta.descs[key];
+ if (desc && desc._suspended === obj) continue;
+ method(obj, key);
</ins><span class="cx"> }
</span><span class="cx"> }
</span><ins>+}
+
+function chainsWillChange(obj, keyName, m) {
+ if (!(m.hasOwnProperty('chainWatchers') &&
+ m.chainWatchers[keyName])) {
+ return;
+ }
+
+ var nodes = m.chainWatchers[keyName],
+ events = [],
+ i, l;
+
+ for(i = 0, l = nodes.length; i < l; i++) {
+ nodes[i].willChange(events);
+ }
+
+ for (i = 0, l = events.length; i < l; i += 2) {
+ propertyWillChange(events[i], events[i+1]);
+ }
+}
+
+function chainsDidChange(obj, keyName, m, suppressEvents) {
+ if (!(m.hasOwnProperty('chainWatchers') &&
+ m.chainWatchers[keyName])) {
+ return;
+ }
+
+ var nodes = m.chainWatchers[keyName],
+ events = suppressEvents ? null : [],
+ i, l;
+
+ for(i = 0, l = nodes.length; i < l; i++) {
+ nodes[i].didChange(events);
+ }
+
+ if (suppressEvents) {
+ return;
+ }
+
+ for (i = 0, l = events.length; i < l; i += 2) {
+ propertyDidChange(events[i], events[i+1]);
+ }
+}
+
+Ember.overrideChains = function(obj, keyName, m) {
+ chainsDidChange(obj, keyName, m, true);
</ins><span class="cx"> };
</span><span class="cx">
</span><del>-var arrayIndexOf = isNativeFunc(Array.prototype.indexOf) ? Array.prototype.indexOf : function (obj, fromIndex) {
- if (fromIndex === null || fromIndex === undefined) { fromIndex = 0; }
- else if (fromIndex < 0) { fromIndex = Math.max(0, this.length + fromIndex); }
- for (var i = fromIndex, j = this.length; i < j; i++) {
- if (this[i] === obj) { return i; }
</del><ins>+/**
+ @method beginPropertyChanges
+ @chainable
+ @private
+*/
+function beginPropertyChanges() {
+ deferred++;
+}
+
+Ember.beginPropertyChanges = beginPropertyChanges;
+
+/**
+ @method endPropertyChanges
+ @private
+*/
+function endPropertyChanges() {
+ deferred--;
+ if (deferred<=0) {
+ beforeObserverSet.clear();
+ observerSet.flush();
</ins><span class="cx"> }
</span><del>- return -1;
</del><ins>+}
+
+Ember.endPropertyChanges = endPropertyChanges;
+
+/**
+ Make a series of property changes together in an
+ exception-safe way.
+
+ ```javascript
+ Ember.changeProperties(function() {
+ obj1.set('foo', mayBlowUpWhenSet);
+ obj2.set('bar', baz);
+ });
+ ```
+
+ @method changeProperties
+ @param {Function} callback
+ @param [binding]
+*/
+Ember.changeProperties = function(cb, binding) {
+ beginPropertyChanges();
+ tryFinally(cb, endPropertyChanges, binding);
</ins><span class="cx"> };
</span><span class="cx">
</span><del>-Ember.ArrayPolyfills = {
- map: arrayMap,
- forEach: arrayForEach,
- indexOf: arrayIndexOf
</del><ins>+function notifyBeforeObservers(obj, keyName) {
+ if (obj.isDestroying) { return; }
+
+ var eventName = keyName + ':before', listeners, diff;
+ if (deferred) {
+ listeners = beforeObserverSet.add(obj, keyName, eventName);
+ diff = listenersDiff(obj, eventName, listeners);
+ sendEvent(obj, eventName, [obj, keyName], diff);
+ } else {
+ sendEvent(obj, eventName, [obj, keyName]);
+ }
+}
+
+function notifyObservers(obj, keyName) {
+ if (obj.isDestroying) { return; }
+
+ var eventName = keyName + ':change', listeners;
+ if (deferred) {
+ listeners = observerSet.add(obj, keyName, eventName);
+ listenersUnion(obj, eventName, listeners);
+ } else {
+ sendEvent(obj, eventName, [obj, keyName]);
+ }
+}
+
+})();
+
+
+
+(function() {
+// META_KEY
+// _getPath
+// propertyWillChange, propertyDidChange
+
+var META_KEY = Ember.META_KEY,
+ MANDATORY_SETTER = Ember.ENV.MANDATORY_SETTER,
+ IS_GLOBAL = /^([A-Z$]|([0-9][A-Z$]))/,
+ getPath = Ember._getPath;
+
+/**
+ Sets the value of a property on an object, respecting computed properties
+ and notifying observers and other listeners of the change. If the
+ property is not defined but the object implements the `setUnknownProperty`
+ method then that will be invoked as well.
+
+ If you plan to run on IE8 and older browsers then you should use this
+ method anytime you want to set a property on an object that you don't
+ know for sure is private. (Properties beginning with an underscore '_'
+ are considered private.)
+
+ On all newer browsers, you only need to use this method to set
+ properties if the property might not be defined on the object and you want
+ to respect the `setUnknownProperty` handler. Otherwise you can ignore this
+ method.
+
+ @method set
+ @for Ember
+ @param {Object} obj The object to modify.
+ @param {String} keyName The property key to set
+ @param {Object} value The value to set
+ @return {Object} the passed value.
+*/
+var set = function set(obj, keyName, value, tolerant) {
+ if (typeof obj === 'string') {
+ Ember.assert("Path '" + obj + "' must be global if no obj is given.", IS_GLOBAL.test(obj));
+ value = keyName;
+ keyName = obj;
+ obj = null;
+ }
+
+ Ember.assert("Cannot call set with "+ keyName +" key.", !!keyName);
+
+ if (!obj || keyName.indexOf('.') !== -1) {
+ return setPath(obj, keyName, value, tolerant);
+ }
+
+ Ember.assert("You need to provide an object and key to `set`.", !!obj && keyName !== undefined);
+ Ember.assert('calling set on destroyed object', !obj.isDestroyed);
+
+ var meta = obj[META_KEY], desc = meta && meta.descs[keyName],
+ isUnknown, currentValue;
+ if (desc) {
+ desc.set(obj, keyName, value);
+ } else {
+ isUnknown = 'object' === typeof obj && !(keyName in obj);
+
+ // setUnknownProperty is called if `obj` is an object,
+ // the property does not already exist, and the
+ // `setUnknownProperty` method exists on the object
+ if (isUnknown && 'function' === typeof obj.setUnknownProperty) {
+ obj.setUnknownProperty(keyName, value);
+ } else if (meta && meta.watching[keyName] > 0) {
+ if (MANDATORY_SETTER) {
+ currentValue = meta.values[keyName];
+ } else {
+ currentValue = obj[keyName];
+ }
+ // only trigger a change if the value has changed
+ if (value !== currentValue) {
+ Ember.propertyWillChange(obj, keyName);
+ if (MANDATORY_SETTER) {
+ if ((currentValue === undefined && !(keyName in obj)) || !obj.propertyIsEnumerable(keyName)) {
+ Ember.defineProperty(obj, keyName, null, value); // setup mandatory setter
+ } else {
+ meta.values[keyName] = value;
+ }
+ } else {
+ obj[keyName] = value;
+ }
+ Ember.propertyDidChange(obj, keyName);
+ }
+ } else {
+ obj[keyName] = value;
+ }
+ }
+ return value;
</ins><span class="cx"> };
</span><span class="cx">
</span><del>-if (Ember.SHIM_ES5) {
- if (!Array.prototype.map) {
- Array.prototype.map = arrayMap;
</del><ins>+// Currently used only by Ember Data tests
+if (Ember.config.overrideAccessors) {
+ Ember.set = set;
+ Ember.config.overrideAccessors();
+ set = Ember.set;
+}
+
+function setPath(root, path, value, tolerant) {
+ var keyName;
+
+ // get the last part of the path
+ keyName = path.slice(path.lastIndexOf('.') + 1);
+
+ // get the first part of the part
+ path = (path === keyName) ? keyName : path.slice(0, path.length-(keyName.length+1));
+
+ // unless the path is this, look up the first part to
+ // get the root
+ if (path !== 'this') {
+ root = getPath(root, path);
</ins><span class="cx"> }
</span><span class="cx">
</span><del>- if (!Array.prototype.forEach) {
- Array.prototype.forEach = arrayForEach;
</del><ins>+ if (!keyName || keyName.length === 0) {
+ throw new Ember.Error('Property set failed: You passed an empty path');
</ins><span class="cx"> }
</span><span class="cx">
</span><del>- if (!Array.prototype.indexOf) {
- Array.prototype.indexOf = arrayIndexOf;
</del><ins>+ if (!root) {
+ if (tolerant) { return; }
+ else { throw new Ember.Error('Property set failed: object in path "'+path+'" could not be found or was destroyed.'); }
</ins><span class="cx"> }
</span><ins>+
+ return set(root, keyName, value);
</ins><span class="cx"> }
</span><span class="cx">
</span><ins>+Ember.set = set;
+
+/**
+ Error-tolerant form of `Ember.set`. Will not blow up if any part of the
+ chain is `undefined`, `null`, or destroyed.
+
+ This is primarily used when syncing bindings, which may try to update after
+ an object has been destroyed.
+
+ @method trySet
+ @for Ember
+ @param {Object} obj The object to modify.
+ @param {String} path The property path to set
+ @param {Object} value The value to set
+*/
+Ember.trySet = function(root, path, value) {
+ return set(root, path, value, true);
+};
+
</ins><span class="cx"> })();
</span><span class="cx">
</span><span class="cx">
</span><span class="lines">@@ -1455,7 +2912,8 @@
</span><span class="cx"> Map is mocked out to look like an Ember object, so you can do
</span><span class="cx"> `Ember.Map.create()` for symmetry with other Ember classes.
</span><span class="cx"> */
</span><del>-var guidFor = Ember.guidFor,
</del><ins>+var set = Ember.set,
+ guidFor = Ember.guidFor,
</ins><span class="cx"> indexOf = Ember.ArrayPolyfills.indexOf;
</span><span class="cx">
</span><span class="cx"> var copy = function(obj) {
</span><span class="lines">@@ -1474,6 +2932,7 @@
</span><span class="cx">
</span><span class="cx"> newObject.keys = keys;
</span><span class="cx"> newObject.values = values;
</span><ins>+ newObject.length = original.length;
</ins><span class="cx">
</span><span class="cx"> return newObject;
</span><span class="cx"> };
</span><span class="lines">@@ -1565,12 +3024,12 @@
</span><span class="cx">
</span><span class="cx"> /**
</span><span class="cx"> @method forEach
</span><del>- @param {Function} function
- @param target
</del><ins>+ @param {Function} fn
+ @param self
</ins><span class="cx"> */
</span><span class="cx"> forEach: function(fn, self) {
</span><span class="cx"> // allow mutation during iteration
</span><del>- var list = this.list.slice();
</del><ins>+ var list = this.toArray();
</ins><span class="cx">
</span><span class="cx"> for (var i = 0, j = list.length; i < j; i++) {
</span><span class="cx"> fn.call(self, list[i]);
</span><span class="lines">@@ -1593,7 +3052,7 @@
</span><span class="cx"> var set = new OrderedSet();
</span><span class="cx">
</span><span class="cx"> set.presenceSet = copy(this.presenceSet);
</span><del>- set.list = this.list.slice();
</del><ins>+ set.list = this.toArray();
</ins><span class="cx">
</span><span class="cx"> return set;
</span><span class="cx"> }
</span><span class="lines">@@ -1634,11 +3093,21 @@
</span><span class="cx">
</span><span class="cx"> Map.prototype = {
</span><span class="cx"> /**
</span><ins>+ This property will change as the number of objects in the map changes.
+
+ @property length
+ @type number
+ @default 0
+ */
+ length: 0,
+
+
+ /**
</ins><span class="cx"> Retrieve the value associated with a given key.
</span><span class="cx">
</span><span class="cx"> @method get
</span><del>- @param {anything} key
- @return {anything} the value associated with the key, or `undefined`
</del><ins>+ @param {*} key
+ @return {*} the value associated with the key, or `undefined`
</ins><span class="cx"> */
</span><span class="cx"> get: function(key) {
</span><span class="cx"> var values = this.values,
</span><span class="lines">@@ -1652,8 +3121,8 @@
</span><span class="cx"> provided, the new value will replace the old value.
</span><span class="cx">
</span><span class="cx"> @method set
</span><del>- @param {anything} key
- @param {anything} value
</del><ins>+ @param {*} key
+ @param {*} value
</ins><span class="cx"> */
</span><span class="cx"> set: function(key, value) {
</span><span class="cx"> var keys = this.keys,
</span><span class="lines">@@ -1662,13 +3131,14 @@
</span><span class="cx">
</span><span class="cx"> keys.add(key);
</span><span class="cx"> values[guid] = value;
</span><ins>+ set(this, 'length', keys.list.length);
</ins><span class="cx"> },
</span><span class="cx">
</span><span class="cx"> /**
</span><span class="cx"> Removes a value from the map for an associated key.
</span><span class="cx">
</span><span class="cx"> @method remove
</span><del>- @param {anything} key
</del><ins>+ @param {*} key
</ins><span class="cx"> @return {Boolean} true if an item was removed, false otherwise
</span><span class="cx"> */
</span><span class="cx"> remove: function(key) {
</span><span class="lines">@@ -1676,13 +3146,12 @@
</span><span class="cx"> // to use in browsers that are not ES6 friendly;
</span><span class="cx"> var keys = this.keys,
</span><span class="cx"> values = this.values,
</span><del>- guid = guidFor(key),
- value;
</del><ins>+ guid = guidFor(key);
</ins><span class="cx">
</span><span class="cx"> if (values.hasOwnProperty(guid)) {
</span><span class="cx"> keys.remove(key);
</span><del>- value = values[guid];
</del><span class="cx"> delete values[guid];
</span><ins>+ set(this, 'length', keys.list.length);
</ins><span class="cx"> return true;
</span><span class="cx"> } else {
</span><span class="cx"> return false;
</span><span class="lines">@@ -1693,7 +3162,7 @@
</span><span class="cx"> Check whether a key is present.
</span><span class="cx">
</span><span class="cx"> @method has
</span><del>- @param {anything} key
</del><ins>+ @param {*} key
</ins><span class="cx"> @return {Boolean} true if the item was present, false otherwise
</span><span class="cx"> */
</span><span class="cx"> has: function(key) {
</span><span class="lines">@@ -1711,7 +3180,7 @@
</span><span class="cx">
</span><span class="cx"> @method forEach
</span><span class="cx"> @param {Function} callback
</span><del>- @param {anything} self if passed, the `this` value inside the
</del><ins>+ @param {*} self if passed, the `this` value inside the
</ins><span class="cx"> callback. By default, `this` is the map.
</span><span class="cx"> */
</span><span class="cx"> forEach: function(callback, self) {
</span><span class="lines">@@ -1740,7 +3209,7 @@
</span><span class="cx"> @private
</span><span class="cx"> @constructor
</span><span class="cx"> @param [options]
</span><del>- @param {anything} [options.defaultValue]
</del><ins>+ @param {*} [options.defaultValue]
</ins><span class="cx"> */
</span><span class="cx"> var MapWithDefault = Ember.MapWithDefault = function(options) {
</span><span class="cx"> Map.call(this);
</span><span class="lines">@@ -1751,8 +3220,8 @@
</span><span class="cx"> @method create
</span><span class="cx"> @static
</span><span class="cx"> @param [options]
</span><del>- @param {anything} [options.defaultValue]
- @return {Ember.MapWithDefault|Ember.Map} If options are passed, returns
</del><ins>+ @param {*} [options.defaultValue]
+ @return {Ember.MapWithDefault|Ember.Map} If options are passed, returns
</ins><span class="cx"> `Ember.MapWithDefault` otherwise returns `Ember.Map`
</span><span class="cx"> */
</span><span class="cx"> MapWithDefault.create = function(options) {
</span><span class="lines">@@ -1769,8 +3238,8 @@
</span><span class="cx"> Retrieve the value associated with a given key.
</span><span class="cx">
</span><span class="cx"> @method get
</span><del>- @param {anything} key
- @return {anything} the value associated with the key, or the default value
</del><ins>+ @param {*} key
+ @return {*} the value associated with the key, or the default value
</ins><span class="cx"> */
</span><span class="cx"> MapWithDefault.prototype.get = function(key) {
</span><span class="cx"> var hasValue = this.has(key);
</span><span class="lines">@@ -1799,317 +3268,143 @@
</span><span class="cx">
</span><span class="cx">
</span><span class="cx"> (function() {
</span><del>-/**
-@module ember-metal
-*/
-
-var META_KEY = Ember.META_KEY, get, set;
-
-var MANDATORY_SETTER = Ember.ENV.MANDATORY_SETTER;
-
-var IS_GLOBAL = /^([A-Z$]|([0-9][A-Z$]))/;
-var IS_GLOBAL_PATH = /^([A-Z$]|([0-9][A-Z$])).*[\.\*]/;
-var HAS_THIS = /^this[\.\*]/;
-var FIRST_KEY = /^([^\.\*]+)/;
-
-// ..........................................................
-// GET AND SET
-//
-// If we are on a platform that supports accessors we can get use those.
-// Otherwise simulate accessors by looking up the property directly on the
-// object.
-
-/**
- Gets the value of a property on an object. If the property is computed,
- the function will be invoked. If the property is not defined but the
- object implements the `unknownProperty` method then that will be invoked.
-
- If you plan to run on IE8 and older browsers then you should use this
- method anytime you want to retrieve a property on an object that you don't
- know for sure is private. (Properties beginning with an underscore '_'
- are considered private.)
-
- On all newer browsers, you only need to use this method to retrieve
- properties if the property might not be defined on the object and you want
- to respect the `unknownProperty` handler. Otherwise you can ignore this
- method.
-
- Note that if the object itself is `undefined`, this method will throw
- an error.
-
- @method get
- @for Ember
- @param {Object} obj The object to retrieve from.
- @param {String} keyName The property key to retrieve
- @return {Object} the property value or `null`.
-*/
-get = function get(obj, keyName) {
- // Helpers that operate with 'this' within an #each
- if (keyName === '') {
- return obj;
</del><ins>+function consoleMethod(name) {
+ var consoleObj, logToConsole;
+ if (Ember.imports.console) {
+ consoleObj = Ember.imports.console;
+ } else if (typeof console !== 'undefined') {
+ consoleObj = console;
</ins><span class="cx"> }
</span><span class="cx">
</span><del>- if (!keyName && 'string'===typeof obj) {
- keyName = obj;
- obj = null;
- }
</del><ins>+ var method = typeof consoleObj === 'object' ? consoleObj[name] : null;
</ins><span class="cx">
</span><del>- if (!obj || keyName.indexOf('.') !== -1) {
- Ember.assert("Cannot call get with '"+ keyName +"' on an undefined object.", obj !== undefined);
- return getPath(obj, keyName);
- }
-
- Ember.assert("You need to provide an object and key to `get`.", !!obj && keyName);
-
- var meta = obj[META_KEY], desc = meta && meta.descs[keyName], ret;
- if (desc) {
- return desc.get(obj, keyName);
- } else {
- if (MANDATORY_SETTER && meta && meta.watching[keyName] > 0) {
- ret = meta.values[keyName];
</del><ins>+ if (method) {
+ // Older IE doesn't support apply, but Chrome needs it
+ if (method.apply) {
+ logToConsole = function() {
+ method.apply(consoleObj, arguments);
+ };
+ logToConsole.displayName = 'console.' + name;
+ return logToConsole;
</ins><span class="cx"> } else {
</span><del>- ret = obj[keyName];
</del><ins>+ return function() {
+ var message = Array.prototype.join.call(arguments, ', ');
+ method(message);
+ };
</ins><span class="cx"> }
</span><ins>+ }
+}
</ins><span class="cx">
</span><del>- if (ret === undefined &&
- 'object' === typeof obj && !(keyName in obj) && 'function' === typeof obj.unknownProperty) {
- return obj.unknownProperty(keyName);
</del><ins>+function assertPolyfill(test, message) {
+ if (!test) {
+ try {
+ // attempt to preserve the stack
+ throw new Ember.Error("assertion failed: " + message);
+ } catch(error) {
+ setTimeout(function() {
+ throw error;
+ }, 0);
</ins><span class="cx"> }
</span><del>-
- return ret;
</del><span class="cx"> }
</span><del>-};
</del><ins>+}
</ins><span class="cx">
</span><span class="cx"> /**
</span><del>- Sets the value of a property on an object, respecting computed properties
- and notifying observers and other listeners of the change. If the
- property is not defined but the object implements the `unknownProperty`
- method then that will be invoked as well.
</del><ins>+ Inside Ember-Metal, simply uses the methods from `imports.console`.
+ Override this to provide more robust logging functionality.
</ins><span class="cx">
</span><del>- If you plan to run on IE8 and older browsers then you should use this
- method anytime you want to set a property on an object that you don't
- know for sure is private. (Properties beginning with an underscore '_'
- are considered private.)
-
- On all newer browsers, you only need to use this method to set
- properties if the property might not be defined on the object and you want
- to respect the `unknownProperty` handler. Otherwise you can ignore this
- method.
-
- @method set
- @for Ember
- @param {Object} obj The object to modify.
- @param {String} keyName The property key to set
- @param {Object} value The value to set
- @return {Object} the passed value.
</del><ins>+ @class Logger
+ @namespace Ember
</ins><span class="cx"> */
</span><del>-set = function set(obj, keyName, value, tolerant) {
- if (typeof obj === 'string') {
- Ember.assert("Path '" + obj + "' must be global if no obj is given.", IS_GLOBAL.test(obj));
- value = keyName;
- keyName = obj;
- obj = null;
- }
</del><ins>+Ember.Logger = {
+ /**
+ Logs the arguments to the console.
+ You can pass as many arguments as you want and they will be joined together with a space.
</ins><span class="cx">
</span><del>- if (!obj || keyName.indexOf('.') !== -1) {
- return setPath(obj, keyName, value, tolerant);
- }
</del><ins>+ ```javascript
+ var foo = 1;
+ Ember.Logger.log('log value of foo:', foo); // "log value of foo: 1" will be printed to the console
+ ```
</ins><span class="cx">
</span><del>- Ember.assert("You need to provide an object and key to `set`.", !!obj && keyName !== undefined);
- Ember.assert('calling set on destroyed object', !obj.isDestroyed);
</del><ins>+ @method log
+ @for Ember.Logger
+ @param {*} arguments
+ */
+ log: consoleMethod('log') || Ember.K,
</ins><span class="cx">
</span><del>- var meta = obj[META_KEY], desc = meta && meta.descs[keyName],
- isUnknown, currentValue;
- if (desc) {
- desc.set(obj, keyName, value);
- } else {
- isUnknown = 'object' === typeof obj && !(keyName in obj);
</del><ins>+ /**
+ Prints the arguments to the console with a warning icon.
+ You can pass as many arguments as you want and they will be joined together with a space.
</ins><span class="cx">
</span><del>- // setUnknownProperty is called if `obj` is an object,
- // the property does not already exist, and the
- // `setUnknownProperty` method exists on the object
- if (isUnknown && 'function' === typeof obj.setUnknownProperty) {
- obj.setUnknownProperty(keyName, value);
- } else if (meta && meta.watching[keyName] > 0) {
- if (MANDATORY_SETTER) {
- currentValue = meta.values[keyName];
- } else {
- currentValue = obj[keyName];
- }
- // only trigger a change if the value has changed
- if (value !== currentValue) {
- Ember.propertyWillChange(obj, keyName);
- if (MANDATORY_SETTER) {
- if (currentValue === undefined && !(keyName in obj)) {
- Ember.defineProperty(obj, keyName, null, value); // setup mandatory setter
- } else {
- meta.values[keyName] = value;
- }
- } else {
- obj[keyName] = value;
- }
- Ember.propertyDidChange(obj, keyName);
- }
- } else {
- obj[keyName] = value;
- }
- }
- return value;
-};
</del><ins>+ ```javascript
+ Ember.Logger.warn('Something happened!'); // "Something happened!" will be printed to the console with a warning icon.
+ ```
</ins><span class="cx">
</span><del>-// Currently used only by Ember Data tests
-if (Ember.config.overrideAccessors) {
- Ember.get = get;
- Ember.set = set;
- Ember.config.overrideAccessors();
- get = Ember.get;
- set = Ember.set;
-}
</del><ins>+ @method warn
+ @for Ember.Logger
+ @param {*} arguments
+ */
+ warn: consoleMethod('warn') || Ember.K,
</ins><span class="cx">
</span><del>-function firstKey(path) {
- return path.match(FIRST_KEY)[0];
-}
</del><ins>+ /**
+ Prints the arguments to the console with an error icon, red text and a stack trace.
+ You can pass as many arguments as you want and they will be joined together with a space.
</ins><span class="cx">
</span><del>-// assumes path is already normalized
-function normalizeTuple(target, path) {
- var hasThis = HAS_THIS.test(path),
- isGlobal = !hasThis && IS_GLOBAL_PATH.test(path),
- key;
</del><ins>+ ```javascript
+ Ember.Logger.error('Danger! Danger!'); // "Danger! Danger!" will be printed to the console in red text.
+ ```
</ins><span class="cx">
</span><del>- if (!target || isGlobal) target = Ember.lookup;
- if (hasThis) path = path.slice(5);
</del><ins>+ @method error
+ @for Ember.Logger
+ @param {*} arguments
+ */
+ error: consoleMethod('error') || Ember.K,
</ins><span class="cx">
</span><del>- if (target === Ember.lookup) {
- key = firstKey(path);
- target = get(target, key);
- path = path.slice(key.length+1);
- }
</del><ins>+ /**
+ Logs the arguments to the console.
+ You can pass as many arguments as you want and they will be joined together with a space.
</ins><span class="cx">
</span><del>- // must return some kind of path to be valid else other things will break.
- if (!path || path.length===0) throw new Error('Invalid Path');
</del><ins>+ ```javascript
+ var foo = 1;
+ Ember.Logger.info('log value of foo:', foo); // "log value of foo: 1" will be printed to the console
+ ```
</ins><span class="cx">
</span><del>- return [ target, path ];
-}
</del><ins>+ @method info
+ @for Ember.Logger
+ @param {*} arguments
+ */
+ info: consoleMethod('info') || Ember.K,
</ins><span class="cx">
</span><del>-function getPath(root, path) {
- var hasThis, parts, tuple, idx, len;
</del><ins>+ /**
+ Logs the arguments to the console in blue text.
+ You can pass as many arguments as you want and they will be joined together with a space.
</ins><span class="cx">
</span><del>- // If there is no root and path is a key name, return that
- // property from the global object.
- // E.g. get('Ember') -> Ember
- if (root === null && path.indexOf('.') === -1) { return get(Ember.lookup, path); }
</del><ins>+ ```javascript
+ var foo = 1;
+ Ember.Logger.debug('log value of foo:', foo); // "log value of foo: 1" will be printed to the console
+ ```
</ins><span class="cx">
</span><del>- // detect complicated paths and normalize them
- hasThis = HAS_THIS.test(path);
</del><ins>+ @method debug
+ @for Ember.Logger
+ @param {*} arguments
+ */
+ debug: consoleMethod('debug') || consoleMethod('info') || Ember.K,
</ins><span class="cx">
</span><del>- if (!root || hasThis) {
- tuple = normalizeTuple(root, path);
- root = tuple[0];
- path = tuple[1];
- tuple.length = 0;
- }
</del><ins>+ /**
+ If the value passed into `Ember.Logger.assert` is not truthy it will throw an error with a stack trace.
</ins><span class="cx">
</span><del>- parts = path.split(".");
- len = parts.length;
- for (idx=0; root && idx<len; idx++) {
- root = get(root, parts[idx], true);
- if (root && root.isDestroyed) { return undefined; }
- }
- return root;
-}
</del><ins>+ ```javascript
+ Ember.Logger.assert(true); // undefined
+ Ember.Logger.assert(true === false); // Throws an Assertion failed error.
+ ```
</ins><span class="cx">
</span><del>-function setPath(root, path, value, tolerant) {
- var keyName;
-
- // get the last part of the path
- keyName = path.slice(path.lastIndexOf('.') + 1);
-
- // get the first part of the part
- path = path.slice(0, path.length-(keyName.length+1));
-
- // unless the path is this, look up the first part to
- // get the root
- if (path !== 'this') {
- root = getPath(root, path);
- }
-
- if (!keyName || keyName.length === 0) {
- throw new Error('You passed an empty path');
- }
-
- if (!root) {
- if (tolerant) { return; }
- else { throw new Error('Object in path '+path+' could not be found or was destroyed.'); }
- }
-
- return set(root, keyName, value);
-}
-
-/**
- @private
-
- Normalizes a target/path pair to reflect that actual target/path that should
- be observed, etc. This takes into account passing in global property
- paths (i.e. a path beginning with a captial letter not defined on the
- target) and * separators.
-
- @method normalizeTuple
- @for Ember
- @param {Object} target The current target. May be `null`.
- @param {String} path A path on the target or a global property path.
- @return {Array} a temporary array with the normalized target/path pair.
-*/
-Ember.normalizeTuple = function(target, path) {
- return normalizeTuple(target, path);
</del><ins>+ @method assert
+ @for Ember.Logger
+ @param {Boolean} bool Value to test
+ */
+ assert: consoleMethod('assert') || assertPolyfill
</ins><span class="cx"> };
</span><span class="cx">
</span><del>-Ember.getWithDefault = function(root, key, defaultValue) {
- var value = get(root, key);
</del><span class="cx">
</span><del>- if (value === undefined) { return defaultValue; }
- return value;
-};
-
-
-Ember.get = get;
-Ember.getPath = Ember.deprecateFunc('getPath is deprecated since get now supports paths', Ember.get);
-
-Ember.set = set;
-Ember.setPath = Ember.deprecateFunc('setPath is deprecated since set now supports paths', Ember.set);
-
-/**
- Error-tolerant form of `Ember.set`. Will not blow up if any part of the
- chain is `undefined`, `null`, or destroyed.
-
- This is primarily used when syncing bindings, which may try to update after
- an object has been destroyed.
-
- @method trySet
- @for Ember
- @param {Object} obj The object to modify.
- @param {String} keyName The property key to set
- @param {Object} value The value to set
-*/
-Ember.trySet = function(root, path, value) {
- return set(root, path, value, true);
-};
-Ember.trySetPath = Ember.deprecateFunc('trySetPath has been renamed to trySet', Ember.trySet);
-
-/**
- Returns true if the provided path is global (e.g., `MyApp.fooController.bar`)
- instead of local (`foo.bar.baz`).
-
- @method isGlobalPath
- @for Ember
- @private
- @param {String} path
- @return Boolean
-*/
-Ember.isGlobalPath = function(path) {
- return IS_GLOBAL.test(path);
-};
-
-
</del><span class="cx"> })();
</span><span class="cx">
</span><span class="cx">
</span><span class="lines">@@ -2119,11 +3414,8 @@
</span><span class="cx"> @module ember-metal
</span><span class="cx"> */
</span><span class="cx">
</span><del>-var GUID_KEY = Ember.GUID_KEY,
- META_KEY = Ember.META_KEY,
- EMPTY_META = Ember.EMPTY_META,
</del><ins>+var META_KEY = Ember.META_KEY,
</ins><span class="cx"> metaFor = Ember.meta,
</span><del>- o_create = Ember.create,
</del><span class="cx"> objectDefineProperty = Ember.platform.defineProperty;
</span><span class="cx">
</span><span class="cx"> var MANDATORY_SETTER = Ember.ENV.MANDATORY_SETTER;
</span><span class="lines">@@ -2133,7 +3425,7 @@
</span><span class="cx"> //
</span><span class="cx">
</span><span class="cx"> /**
</span><del>- Objects of this type can implement an interface to responds requests to
</del><ins>+ Objects of this type can implement an interface to respond to requests to
</ins><span class="cx"> get and set. The default implementation handles simple properties.
</span><span class="cx">
</span><span class="cx"> You generally won't need to create or subclass this directly.
</span><span class="lines">@@ -2143,7 +3435,7 @@
</span><span class="cx"> @private
</span><span class="cx"> @constructor
</span><span class="cx"> */
</span><del>-var Descriptor = Ember.Descriptor = function() {};
</del><ins>+Ember.Descriptor = function() {};
</ins><span class="cx">
</span><span class="cx"> // ..........................................................
</span><span class="cx"> // DEFINING PROPERTIES API
</span><span class="lines">@@ -2161,8 +3453,6 @@
</span><span class="cx"> };
</span><span class="cx">
</span><span class="cx"> /**
</span><del>- @private
-
</del><span class="cx"> NOTE: This is a low-level method used by other parts of the API. You almost
</span><span class="cx"> never want to call this method directly. Instead you should use
</span><span class="cx"> `Ember.mixin()` to define new properties.
</span><span class="lines">@@ -2196,6 +3486,7 @@
</span><span class="cx"> }).property('firstName', 'lastName'));
</span><span class="cx"> ```
</span><span class="cx">
</span><ins>+ @private
</ins><span class="cx"> @method defineProperty
</span><span class="cx"> @for Ember
</span><span class="cx"> @param {Object} obj the object to define this property on. This may be a prototype.
</span><span class="lines">@@ -2203,7 +3494,7 @@
</span><span class="cx"> @param {Ember.Descriptor} [desc] an instance of `Ember.Descriptor` (typically a
</span><span class="cx"> computed property) or an ES5 descriptor.
</span><span class="cx"> You must provide this or `data` but not both.
</span><del>- @param {anything} [data] something other than a descriptor, that will
</del><ins>+ @param {*} [data] something other than a descriptor, that will
</ins><span class="cx"> become the explicit value of this property.
</span><span class="cx"> */
</span><span class="cx"> Ember.defineProperty = function(obj, keyName, desc, data, meta) {
</span><span class="lines">@@ -2232,7 +3523,6 @@
</span><span class="cx"> } else {
</span><span class="cx"> obj[keyName] = undefined; // make enumerable
</span><span class="cx"> }
</span><del>- desc.setup(obj, keyName);
</del><span class="cx"> } else {
</span><span class="cx"> descs[keyName] = undefined; // shadow descriptor in proto
</span><span class="cx"> if (desc == null) {
</span><span class="lines">@@ -2274,341 +3564,183 @@
</span><span class="cx">
</span><span class="cx">
</span><span class="cx"> (function() {
</span><del>-// Ember.tryFinally
</del><ins>+var get = Ember.get;
+
</ins><span class="cx"> /**
</span><del>-@module ember-metal
-*/
</del><ins>+ To get multiple properties at once, call `Ember.getProperties`
+ with an object followed by a list of strings or an array:
</ins><span class="cx">
</span><del>-var AFTER_OBSERVERS = ':change';
-var BEFORE_OBSERVERS = ':before';
</del><ins>+ ```javascript
+ Ember.getProperties(record, 'firstName', 'lastName', 'zipCode'); // { firstName: 'John', lastName: 'Doe', zipCode: '10011' }
+ ```
</ins><span class="cx">
</span><del>-var guidFor = Ember.guidFor;
</del><ins>+ is equivalent to:
</ins><span class="cx">
</span><del>-var deferred = 0;
</del><ins>+ ```javascript
+ Ember.getProperties(record, ['firstName', 'lastName', 'zipCode']); // { firstName: 'John', lastName: 'Doe', zipCode: '10011' }
+ ```
</ins><span class="cx">
</span><del>-/*
- this.observerSet = {
- [senderGuid]: { // variable name: `keySet`
- [keyName]: listIndex
- }
- },
- this.observers = [
- {
- sender: obj,
- keyName: keyName,
- eventName: eventName,
- listeners: [
- [target, method, onceFlag, suspendedFlag]
- ]
- },
- ...
- ]
</del><ins>+ @method getProperties
+ @param obj
+ @param {String...|Array} list of keys to get
+ @return {Hash}
</ins><span class="cx"> */
</span><del>-function ObserverSet() {
- this.clear();
-}
</del><ins>+Ember.getProperties = function(obj) {
+ var ret = {},
+ propertyNames = arguments,
+ i = 1;
</ins><span class="cx">
</span><del>-ObserverSet.prototype.add = function(sender, keyName, eventName) {
- var observerSet = this.observerSet,
- observers = this.observers,
- senderGuid = Ember.guidFor(sender),
- keySet = observerSet[senderGuid],
- index;
-
- if (!keySet) {
- observerSet[senderGuid] = keySet = {};
</del><ins>+ if (arguments.length === 2 && Ember.typeOf(arguments[1]) === 'array') {
+ i = 0;
+ propertyNames = arguments[1];
</ins><span class="cx"> }
</span><del>- index = keySet[keyName];
- if (index === undefined) {
- index = observers.push({
- sender: sender,
- keyName: keyName,
- eventName: eventName,
- listeners: []
- }) - 1;
- keySet[keyName] = index;
</del><ins>+ for(var len = propertyNames.length; i < len; i++) {
+ ret[propertyNames[i]] = get(obj, propertyNames[i]);
</ins><span class="cx"> }
</span><del>- return observers[index].listeners;
</del><ins>+ return ret;
</ins><span class="cx"> };
</span><span class="cx">
</span><del>-ObserverSet.prototype.flush = function() {
- var observers = this.observers, i, len, observer, sender;
- this.clear();
- for (i=0, len=observers.length; i < len; ++i) {
- observer = observers[i];
- sender = observer.sender;
- if (sender.isDestroying || sender.isDestroyed) { continue; }
- Ember.sendEvent(sender, observer.eventName, [sender, observer.keyName], observer.listeners);
- }
-};
</del><ins>+})();
</ins><span class="cx">
</span><del>-ObserverSet.prototype.clear = function() {
- this.observerSet = {};
- this.observers = [];
-};
</del><span class="cx">
</span><del>-var beforeObserverSet = new ObserverSet(), observerSet = new ObserverSet();
</del><span class="cx">
</span><del>-/**
- @method beginPropertyChanges
- @chainable
-*/
-Ember.beginPropertyChanges = function() {
- deferred++;
-};
</del><ins>+(function() {
+var changeProperties = Ember.changeProperties,
+ set = Ember.set;
</ins><span class="cx">
</span><span class="cx"> /**
</span><del>- @method endPropertyChanges
-*/
-Ember.endPropertyChanges = function() {
- deferred--;
- if (deferred<=0) {
- beforeObserverSet.clear();
- observerSet.flush();
- }
-};
</del><ins>+ Set a list of properties on an object. These properties are set inside
+ a single `beginPropertyChanges` and `endPropertyChanges` batch, so
+ observers will be buffered.
</ins><span class="cx">
</span><del>-/**
- Make a series of property changes together in an
- exception-safe way.
-
</del><span class="cx"> ```javascript
</span><del>- Ember.changeProperties(function() {
- obj1.set('foo', mayBlowUpWhenSet);
- obj2.set('bar', baz);
- });
</del><ins>+ anObject.setProperties({
+ firstName: "Stanley",
+ lastName: "Stuart",
+ age: "21"
+ })
</ins><span class="cx"> ```
</span><span class="cx">
</span><del>- @method changeProperties
- @param {Function} callback
- @param [binding]
-*/
-Ember.changeProperties = function(cb, binding){
- Ember.beginPropertyChanges();
- Ember.tryFinally(cb, Ember.endPropertyChanges, binding);
-};
-
-/**
- Set a list of properties on an object. These properties are set inside
- a single `beginPropertyChanges` and `endPropertyChanges` batch, so
- observers will be buffered.
-
</del><span class="cx"> @method setProperties
</span><del>- @param target
- @param {Hash} properties
- @return target
</del><ins>+ @param self
+ @param {Object} hash
+ @return self
</ins><span class="cx"> */
</span><span class="cx"> Ember.setProperties = function(self, hash) {
</span><del>- Ember.changeProperties(function(){
</del><ins>+ changeProperties(function() {
</ins><span class="cx"> for(var prop in hash) {
</span><del>- if (hash.hasOwnProperty(prop)) Ember.set(self, prop, hash[prop]);
</del><ins>+ if (hash.hasOwnProperty(prop)) { set(self, prop, hash[prop]); }
</ins><span class="cx"> }
</span><span class="cx"> });
</span><span class="cx"> return self;
</span><span class="cx"> };
</span><span class="cx">
</span><ins>+})();
</ins><span class="cx">
</span><del>-function changeEvent(keyName) {
- return keyName+AFTER_OBSERVERS;
-}
</del><span class="cx">
</span><del>-function beforeEvent(keyName) {
- return keyName+BEFORE_OBSERVERS;
-}
</del><span class="cx">
</span><del>-/**
- @method addObserver
- @param obj
- @param {String} path
- @param {Object|Function} targetOrMethod
- @param {Function|String} [method]
-*/
-Ember.addObserver = function(obj, path, target, method) {
- Ember.addListener(obj, changeEvent(path), target, method);
- Ember.watch(obj, path);
- return this;
-};
</del><ins>+(function() {
+var metaFor = Ember.meta, // utils.js
+ typeOf = Ember.typeOf, // utils.js
+ MANDATORY_SETTER = Ember.ENV.MANDATORY_SETTER,
+ o_defineProperty = Ember.platform.defineProperty;
</ins><span class="cx">
</span><del>-Ember.observersFor = function(obj, path) {
- return Ember.listenersFor(obj, changeEvent(path));
-};
</del><ins>+Ember.watchKey = function(obj, keyName) {
+ // can't watch length on Array - it is special...
+ if (keyName === 'length' && typeOf(obj) === 'array') { return; }
</ins><span class="cx">
</span><del>-/**
- @method removeObserver
- @param obj
- @param {String} path
- @param {Object|Function} targetOrMethod
- @param {Function|String} [method]
-*/
-Ember.removeObserver = function(obj, path, target, method) {
- Ember.unwatch(obj, path);
- Ember.removeListener(obj, changeEvent(path), target, method);
- return this;
-};
</del><ins>+ var m = metaFor(obj), watching = m.watching;
</ins><span class="cx">
</span><del>-/**
- @method addBeforeObserver
- @param obj
- @param {String} path
- @param {Object|Function} targetOrMethod
- @param {Function|String} [method]
-*/
-Ember.addBeforeObserver = function(obj, path, target, method) {
- Ember.addListener(obj, beforeEvent(path), target, method);
- Ember.watch(obj, path);
- return this;
-};
</del><ins>+ // activate watching first time
+ if (!watching[keyName]) {
+ watching[keyName] = 1;
</ins><span class="cx">
</span><del>-// Suspend observer during callback.
-//
-// This should only be used by the target of the observer
-// while it is setting the observed path.
-Ember._suspendBeforeObserver = function(obj, path, target, method, callback) {
- return Ember._suspendListener(obj, beforeEvent(path), target, method, callback);
-};
</del><ins>+ if ('function' === typeof obj.willWatchProperty) {
+ obj.willWatchProperty(keyName);
+ }
</ins><span class="cx">
</span><del>-Ember._suspendObserver = function(obj, path, target, method, callback) {
- return Ember._suspendListener(obj, changeEvent(path), target, method, callback);
</del><ins>+ if (MANDATORY_SETTER && keyName in obj) {
+ m.values[keyName] = obj[keyName];
+ o_defineProperty(obj, keyName, {
+ configurable: true,
+ enumerable: obj.propertyIsEnumerable(keyName),
+ set: Ember.MANDATORY_SETTER_FUNCTION,
+ get: Ember.DEFAULT_GETTER_FUNCTION(keyName)
+ });
+ }
+ } else {
+ watching[keyName] = (watching[keyName] || 0) + 1;
+ }
</ins><span class="cx"> };
</span><span class="cx">
</span><del>-var map = Ember.ArrayPolyfills.map;
</del><span class="cx">
</span><del>-Ember._suspendBeforeObservers = function(obj, paths, target, method, callback) {
- var events = map.call(paths, beforeEvent);
- return Ember._suspendListeners(obj, events, target, method, callback);
-};
</del><ins>+Ember.unwatchKey = function(obj, keyName) {
+ var m = metaFor(obj), watching = m.watching;
</ins><span class="cx">
</span><del>-Ember._suspendObservers = function(obj, paths, target, method, callback) {
- var events = map.call(paths, changeEvent);
- return Ember._suspendListeners(obj, events, target, method, callback);
-};
</del><ins>+ if (watching[keyName] === 1) {
+ watching[keyName] = 0;
</ins><span class="cx">
</span><del>-Ember.beforeObserversFor = function(obj, path) {
- return Ember.listenersFor(obj, beforeEvent(path));
-};
</del><ins>+ if ('function' === typeof obj.didUnwatchProperty) {
+ obj.didUnwatchProperty(keyName);
+ }
</ins><span class="cx">
</span><del>-/**
- @method removeBeforeObserver
- @param obj
- @param {String} path
- @param {Object|Function} targetOrMethod
- @param {Function|String} [method]
-*/
-Ember.removeBeforeObserver = function(obj, path, target, method) {
- Ember.unwatch(obj, path);
- Ember.removeListener(obj, beforeEvent(path), target, method);
- return this;
-};
-
-Ember.notifyBeforeObservers = function(obj, keyName) {
- if (obj.isDestroying) { return; }
-
- var eventName = beforeEvent(keyName), listeners, listenersDiff;
- if (deferred) {
- listeners = beforeObserverSet.add(obj, keyName, eventName);
- listenersDiff = Ember.listenersDiff(obj, eventName, listeners);
- Ember.sendEvent(obj, eventName, [obj, keyName], listenersDiff);
- } else {
- Ember.sendEvent(obj, eventName, [obj, keyName]);
</del><ins>+ if (MANDATORY_SETTER && keyName in obj) {
+ o_defineProperty(obj, keyName, {
+ configurable: true,
+ enumerable: obj.propertyIsEnumerable(keyName),
+ set: function(val) {
+ // redefine to set as enumerable
+ o_defineProperty(obj, keyName, {
+ configurable: true,
+ writable: true,
+ enumerable: true,
+ value: val
+ });
+ delete m.values[keyName];
+ },
+ get: Ember.DEFAULT_GETTER_FUNCTION(keyName)
+ });
+ }
+ } else if (watching[keyName] > 1) {
+ watching[keyName]--;
</ins><span class="cx"> }
</span><span class="cx"> };
</span><span class="cx">
</span><del>-Ember.notifyObservers = function(obj, keyName) {
- if (obj.isDestroying) { return; }
-
- var eventName = changeEvent(keyName), listeners;
- if (deferred) {
- listeners = observerSet.add(obj, keyName, eventName);
- Ember.listenersUnion(obj, eventName, listeners);
- } else {
- Ember.sendEvent(obj, eventName, [obj, keyName]);
- }
-};
-
</del><span class="cx"> })();
</span><span class="cx">
</span><span class="cx">
</span><span class="cx">
</span><span class="cx"> (function() {
</span><del>-/**
-@module ember-metal
-*/
-
-var guidFor = Ember.guidFor, // utils.js
- metaFor = Ember.meta, // utils.js
- get = Ember.get, // accessors.js
- set = Ember.set, // accessors.js
- normalizeTuple = Ember.normalizeTuple, // accessors.js
- GUID_KEY = Ember.GUID_KEY, // utils.js
- META_KEY = Ember.META_KEY, // utils.js
- // circular reference observer depends on Ember.watch
- // we should move change events to this file or its own property_events.js
- notifyObservers = Ember.notifyObservers, // observer.js
</del><ins>+var metaFor = Ember.meta, // utils.js
+ get = Ember.get, // property_get.js
+ normalizeTuple = Ember.normalizeTuple, // property_get.js
</ins><span class="cx"> forEach = Ember.ArrayPolyfills.forEach, // array.js
</span><del>- FIRST_KEY = /^([^\.\*]+)/,
- IS_PATH = /[\.\*]/;
</del><ins>+ warn = Ember.warn,
+ watchKey = Ember.watchKey,
+ unwatchKey = Ember.unwatchKey,
+ FIRST_KEY = /^([^\.\*]+)/;
</ins><span class="cx">
</span><del>-var MANDATORY_SETTER = Ember.ENV.MANDATORY_SETTER,
-o_defineProperty = Ember.platform.defineProperty;
-
</del><span class="cx"> function firstKey(path) {
</span><span class="cx"> return path.match(FIRST_KEY)[0];
</span><span class="cx"> }
</span><span class="cx">
</span><del>-// returns true if the passed path is just a keyName
-function isKeyName(path) {
- return path==='*' || !IS_PATH.test(path);
-}
</del><ins>+var pendingQueue = [];
</ins><span class="cx">
</span><del>-// ..........................................................
-// DEPENDENT KEYS
-//
</del><ins>+// attempts to add the pendingQueue chains again. If some of them end up
+// back in the queue and reschedule is true, schedules a timeout to try
+// again.
+Ember.flushPendingChains = function() {
+ if (pendingQueue.length === 0) { return; } // nothing to do
</ins><span class="cx">
</span><del>-function iterDeps(method, obj, depKey, seen, meta) {
</del><ins>+ var queue = pendingQueue;
+ pendingQueue = [];
</ins><span class="cx">
</span><del>- var guid = guidFor(obj);
- if (!seen[guid]) seen[guid] = {};
- if (seen[guid][depKey]) return;
- seen[guid][depKey] = true;
</del><ins>+ forEach.call(queue, function(q) { q[0].add(q[1]); });
</ins><span class="cx">
</span><del>- var deps = meta.deps;
- deps = deps && deps[depKey];
- if (deps) {
- for(var key in deps) {
- var desc = meta.descs[key];
- if (desc && desc._suspended === obj) continue;
- method(obj, key);
- }
- }
-}
</del><ins>+ warn('Watching an undefined global, Ember expects watched globals to be setup by the time the run loop is flushed, check for typos', pendingQueue.length === 0);
+};
</ins><span class="cx">
</span><span class="cx">
</span><del>-var WILL_SEEN, DID_SEEN;
-
-// called whenever a property is about to change to clear the cache of any dependent keys (and notify those properties of changes, etc...)
-function dependentKeysWillChange(obj, depKey, meta) {
- if (obj.isDestroying) { return; }
-
- var seen = WILL_SEEN, top = !seen;
- if (top) { seen = WILL_SEEN = {}; }
- iterDeps(propertyWillChange, obj, depKey, seen, meta);
- if (top) { WILL_SEEN = null; }
-}
-
-// called whenever a property has just changed to update dependent keys
-function dependentKeysDidChange(obj, depKey, meta) {
- if (obj.isDestroying) { return; }
-
- var seen = DID_SEEN, top = !seen;
- if (top) { seen = DID_SEEN = {}; }
- iterDeps(propertyDidChange, obj, depKey, seen, meta);
- if (top) { DID_SEEN = null; }
-}
-
-// ..........................................................
-// CHAIN
-//
-
</del><span class="cx"> function addChainWatcher(obj, keyName, node) {
</span><span class="cx"> if (!obj || ('object' !== typeof obj)) { return; } // nothing to do
</span><span class="cx">
</span><span class="lines">@@ -2620,10 +3752,10 @@
</span><span class="cx">
</span><span class="cx"> if (!nodes[keyName]) { nodes[keyName] = []; }
</span><span class="cx"> nodes[keyName].push(node);
</span><del>- Ember.watch(obj, keyName);
</del><ins>+ watchKey(obj, keyName);
</ins><span class="cx"> }
</span><span class="cx">
</span><del>-function removeChainWatcher(obj, keyName, node) {
</del><ins>+var removeChainWatcher = Ember.removeChainWatcher = function(obj, keyName, node) {
</ins><span class="cx"> if (!obj || 'object' !== typeof obj) { return; } // nothing to do
</span><span class="cx">
</span><span class="cx"> var m = metaFor(obj, false);
</span><span class="lines">@@ -2637,34 +3769,13 @@
</span><span class="cx"> if (nodes[i] === node) { nodes.splice(i, 1); }
</span><span class="cx"> }
</span><span class="cx"> }
</span><del>- Ember.unwatch(obj, keyName);
-}
</del><ins>+ unwatchKey(obj, keyName);
+};
</ins><span class="cx">
</span><del>-var pendingQueue = [];
-
-// attempts to add the pendingQueue chains again. If some of them end up
-// back in the queue and reschedule is true, schedules a timeout to try
-// again.
-function flushPendingChains() {
- if (pendingQueue.length === 0) { return; } // nothing to do
-
- var queue = pendingQueue;
- pendingQueue = [];
-
- forEach.call(queue, function(q) { q[0].add(q[1]); });
-
- Ember.warn('Watching an undefined global, Ember expects watched globals to be setup by the time the run loop is flushed, check for typos', pendingQueue.length === 0);
-}
-
-function isProto(pvalue) {
- return metaFor(pvalue, false).proto === pvalue;
-}
-
</del><span class="cx"> // A ChainNode watches a single key on an object. If you provide a starting
</span><span class="cx"> // value for the key then the node won't actually watch it. For a root node
</span><span class="cx"> // pass null for parent and key and object for value.
</span><del>-var ChainNode = function(parent, key, value) {
- var obj;
</del><ins>+var ChainNode = Ember._ChainNode = function(parent, key, value) {
</ins><span class="cx"> this._parent = parent;
</span><span class="cx"> this._key = key;
</span><span class="cx">
</span><span class="lines">@@ -2695,10 +3806,32 @@
</span><span class="cx">
</span><span class="cx"> var ChainNodePrototype = ChainNode.prototype;
</span><span class="cx">
</span><ins>+function lazyGet(obj, key) {
+ if (!obj) return undefined;
+
+ var meta = metaFor(obj, false);
+ // check if object meant only to be a prototype
+ if (meta.proto === obj) return undefined;
+
+ if (key === "@each") return get(obj, key);
+
+ // if a CP only return cached value
+ var desc = meta.descs[key];
+ if (desc && desc._cacheable) {
+ if (key in meta.cache) {
+ return meta.cache[key];
+ } else {
+ return undefined;
+ }
+ }
+
+ return get(obj, key);
+}
+
</ins><span class="cx"> ChainNodePrototype.value = function() {
</span><span class="cx"> if (this._value === undefined && this._watching) {
</span><span class="cx"> var obj = this._parent.value();
</span><del>- this._value = (obj && !isProto(obj)) ? get(obj, this._key) : undefined;
</del><ins>+ this._value = lazyGet(obj, this._key);
</ins><span class="cx"> }
</span><span class="cx"> return this._value;
</span><span class="cx"> };
</span><span class="lines">@@ -2818,42 +3951,50 @@
</span><span class="cx">
</span><span class="cx"> };
</span><span class="cx">
</span><del>-ChainNodePrototype.willChange = function() {
</del><ins>+ChainNodePrototype.willChange = function(events) {
</ins><span class="cx"> var chains = this._chains;
</span><span class="cx"> if (chains) {
</span><span class="cx"> for(var key in chains) {
</span><span class="cx"> if (!chains.hasOwnProperty(key)) { continue; }
</span><del>- chains[key].willChange();
</del><ins>+ chains[key].willChange(events);
</ins><span class="cx"> }
</span><span class="cx"> }
</span><span class="cx">
</span><del>- if (this._parent) { this._parent.chainWillChange(this, this._key, 1); }
</del><ins>+ if (this._parent) { this._parent.chainWillChange(this, this._key, 1, events); }
</ins><span class="cx"> };
</span><span class="cx">
</span><del>-ChainNodePrototype.chainWillChange = function(chain, path, depth) {
</del><ins>+ChainNodePrototype.chainWillChange = function(chain, path, depth, events) {
</ins><span class="cx"> if (this._key) { path = this._key + '.' + path; }
</span><span class="cx">
</span><span class="cx"> if (this._parent) {
</span><del>- this._parent.chainWillChange(this, path, depth+1);
</del><ins>+ this._parent.chainWillChange(this, path, depth+1, events);
</ins><span class="cx"> } else {
</span><del>- if (depth > 1) { Ember.propertyWillChange(this.value(), path); }
</del><ins>+ if (depth > 1) {
+ events.push(this.value(), path);
+ }
</ins><span class="cx"> path = 'this.' + path;
</span><del>- if (this._paths[path] > 0) { Ember.propertyWillChange(this.value(), path); }
</del><ins>+ if (this._paths[path] > 0) {
+ events.push(this.value(), path);
+ }
</ins><span class="cx"> }
</span><span class="cx"> };
</span><span class="cx">
</span><del>-ChainNodePrototype.chainDidChange = function(chain, path, depth) {
</del><ins>+ChainNodePrototype.chainDidChange = function(chain, path, depth, events) {
</ins><span class="cx"> if (this._key) { path = this._key + '.' + path; }
</span><span class="cx"> if (this._parent) {
</span><del>- this._parent.chainDidChange(this, path, depth+1);
</del><ins>+ this._parent.chainDidChange(this, path, depth+1, events);
</ins><span class="cx"> } else {
</span><del>- if (depth > 1) { Ember.propertyDidChange(this.value(), path); }
</del><ins>+ if (depth > 1) {
+ events.push(this.value(), path);
+ }
</ins><span class="cx"> path = 'this.' + path;
</span><del>- if (this._paths[path] > 0) { Ember.propertyDidChange(this.value(), path); }
</del><ins>+ if (this._paths[path] > 0) {
+ events.push(this.value(), path);
+ }
</ins><span class="cx"> }
</span><span class="cx"> };
</span><span class="cx">
</span><del>-ChainNodePrototype.didChange = function(suppressEvent) {
</del><ins>+ChainNodePrototype.didChange = function(events) {
</ins><span class="cx"> // invalidate my own value first.
</span><span class="cx"> if (this._watching) {
</span><span class="cx"> var obj = this._parent.value();
</span><span class="lines">@@ -2875,16 +4016,42 @@
</span><span class="cx"> if (chains) {
</span><span class="cx"> for(var key in chains) {
</span><span class="cx"> if (!chains.hasOwnProperty(key)) { continue; }
</span><del>- chains[key].didChange(suppressEvent);
</del><ins>+ chains[key].didChange(events);
</ins><span class="cx"> }
</span><span class="cx"> }
</span><span class="cx">
</span><del>- if (suppressEvent) { return; }
</del><ins>+ // if no events are passed in then we only care about the above wiring update
+ if (events === null) { return; }
</ins><span class="cx">
</span><span class="cx"> // and finally tell parent about my path changing...
</span><del>- if (this._parent) { this._parent.chainDidChange(this, this._key, 1); }
</del><ins>+ if (this._parent) { this._parent.chainDidChange(this, this._key, 1, events); }
</ins><span class="cx"> };
</span><span class="cx">
</span><ins>+Ember.finishChains = function(obj) {
+ var m = metaFor(obj, false), chains = m.chains;
+ if (chains) {
+ if (chains.value() !== obj) {
+ m.chains = chains = chains.copy(obj);
+ }
+ chains.didChange(null);
+ }
+};
+
+})();
+
+
+
+(function() {
+
+})();
+
+
+
+(function() {
+var metaFor = Ember.meta, // utils.js
+ typeOf = Ember.typeOf, // utils.js
+ ChainNode = Ember._ChainNode; // chains.js
+
</ins><span class="cx"> // get the chains for the current object. If the current object has
</span><span class="cx"> // chains inherited from the proto they will be cloned and reconfigured for
</span><span class="cx"> // the current object.
</span><span class="lines">@@ -2898,89 +4065,78 @@
</span><span class="cx"> return ret;
</span><span class="cx"> }
</span><span class="cx">
</span><del>-Ember.overrideChains = function(obj, keyName, m) {
- chainsDidChange(obj, keyName, m, true);
-};
</del><ins>+Ember.watchPath = function(obj, keyPath) {
+ // can't watch length on Array - it is special...
+ if (keyPath === 'length' && typeOf(obj) === 'array') { return; }
</ins><span class="cx">
</span><del>-function chainsWillChange(obj, keyName, m, arg) {
- if (!m.hasOwnProperty('chainWatchers')) { return; } // nothing to do
</del><ins>+ var m = metaFor(obj), watching = m.watching;
</ins><span class="cx">
</span><del>- var nodes = m.chainWatchers;
</del><ins>+ if (!watching[keyPath]) { // activate watching first time
+ watching[keyPath] = 1;
+ chainsFor(obj).add(keyPath);
+ } else {
+ watching[keyPath] = (watching[keyPath] || 0) + 1;
+ }
+};
</ins><span class="cx">
</span><del>- nodes = nodes[keyName];
- if (!nodes) { return; }
</del><ins>+Ember.unwatchPath = function(obj, keyPath) {
+ var m = metaFor(obj), watching = m.watching;
</ins><span class="cx">
</span><del>- for(var i = 0, l = nodes.length; i < l; i++) {
- nodes[i].willChange(arg);
</del><ins>+ if (watching[keyPath] === 1) {
+ watching[keyPath] = 0;
+ chainsFor(obj).remove(keyPath);
+ } else if (watching[keyPath] > 1) {
+ watching[keyPath]--;
</ins><span class="cx"> }
</span><del>-}
</del><ins>+};
+})();
</ins><span class="cx">
</span><del>-function chainsDidChange(obj, keyName, m, arg) {
- if (!m.hasOwnProperty('chainWatchers')) { return; } // nothing to do
</del><span class="cx">
</span><del>- var nodes = m.chainWatchers;
</del><span class="cx">
</span><del>- nodes = nodes[keyName];
- if (!nodes) { return; }
</del><ins>+(function() {
+/**
+@module ember-metal
+*/
</ins><span class="cx">
</span><del>- // looping in reverse because the chainWatchers array can be modified inside didChange
- for (var i = nodes.length - 1; i >= 0; i--) {
- nodes[i].didChange(arg);
- }
</del><ins>+var metaFor = Ember.meta, // utils.js
+ GUID_KEY = Ember.GUID_KEY, // utils.js
+ META_KEY = Ember.META_KEY, // utils.js
+ removeChainWatcher = Ember.removeChainWatcher,
+ watchKey = Ember.watchKey, // watch_key.js
+ unwatchKey = Ember.unwatchKey,
+ watchPath = Ember.watchPath, // watch_path.js
+ unwatchPath = Ember.unwatchPath,
+ typeOf = Ember.typeOf, // utils.js
+ generateGuid = Ember.generateGuid,
+ IS_PATH = /[\.\*]/;
+
+// returns true if the passed path is just a keyName
+function isKeyName(path) {
+ return path==='*' || !IS_PATH.test(path);
</ins><span class="cx"> }
</span><span class="cx">
</span><del>-// ..........................................................
-// WATCH
-//
-
</del><span class="cx"> /**
</span><del>- @private
-
</del><span class="cx"> Starts watching a property on an object. Whenever the property changes,
</span><span class="cx"> invokes `Ember.propertyWillChange` and `Ember.propertyDidChange`. This is the
</span><span class="cx"> primitive used by observers and dependent keys; usually you will never call
</span><span class="cx"> this method directly but instead use higher level methods like
</span><span class="cx"> `Ember.addObserver()`
</span><span class="cx">
</span><ins>+ @private
</ins><span class="cx"> @method watch
</span><span class="cx"> @for Ember
</span><span class="cx"> @param obj
</span><span class="cx"> @param {String} keyName
</span><span class="cx"> */
</span><del>-Ember.watch = function(obj, keyName) {
</del><ins>+Ember.watch = function(obj, _keyPath) {
</ins><span class="cx"> // can't watch length on Array - it is special...
</span><del>- if (keyName === 'length' && Ember.typeOf(obj) === 'array') { return this; }
</del><ins>+ if (_keyPath === 'length' && typeOf(obj) === 'array') { return; }
</ins><span class="cx">
</span><del>- var m = metaFor(obj), watching = m.watching, desc;
-
- // activate watching first time
- if (!watching[keyName]) {
- watching[keyName] = 1;
- if (isKeyName(keyName)) {
- desc = m.descs[keyName];
- if (desc && desc.willWatch) { desc.willWatch(obj, keyName); }
-
- if ('function' === typeof obj.willWatchProperty) {
- obj.willWatchProperty(keyName);
- }
-
- if (MANDATORY_SETTER && keyName in obj) {
- m.values[keyName] = obj[keyName];
- o_defineProperty(obj, keyName, {
- configurable: true,
- enumerable: true,
- set: Ember.MANDATORY_SETTER_FUNCTION,
- get: Ember.DEFAULT_GETTER_FUNCTION(keyName)
- });
- }
- } else {
- chainsFor(obj).add(keyName);
- }
-
- } else {
- watching[keyName] = (watching[keyName] || 0) + 1;
</del><ins>+ if (isKeyName(_keyPath)) {
+ watchKey(obj, _keyPath);
+ } else {
+ watchPath(obj, _keyPath);
</ins><span class="cx"> }
</span><del>- return this;
</del><span class="cx"> };
</span><span class="cx">
</span><span class="cx"> Ember.isWatching = function isWatching(obj, key) {
</span><span class="lines">@@ -2988,52 +4144,25 @@
</span><span class="cx"> return (meta && meta.watching[key]) > 0;
</span><span class="cx"> };
</span><span class="cx">
</span><del>-Ember.watch.flushPending = flushPendingChains;
</del><ins>+Ember.watch.flushPending = Ember.flushPendingChains;
</ins><span class="cx">
</span><del>-Ember.unwatch = function(obj, keyName) {
</del><ins>+Ember.unwatch = function(obj, _keyPath) {
</ins><span class="cx"> // can't watch length on Array - it is special...
</span><del>- if (keyName === 'length' && Ember.typeOf(obj) === 'array') { return this; }
</del><ins>+ if (_keyPath === 'length' && typeOf(obj) === 'array') { return; }
</ins><span class="cx">
</span><del>- var m = metaFor(obj), watching = m.watching, desc;
-
- if (watching[keyName] === 1) {
- watching[keyName] = 0;
-
- if (isKeyName(keyName)) {
- desc = m.descs[keyName];
- if (desc && desc.didUnwatch) { desc.didUnwatch(obj, keyName); }
-
- if ('function' === typeof obj.didUnwatchProperty) {
- obj.didUnwatchProperty(keyName);
- }
-
- if (MANDATORY_SETTER && keyName in obj) {
- o_defineProperty(obj, keyName, {
- configurable: true,
- enumerable: true,
- writable: true,
- value: m.values[keyName]
- });
- delete m.values[keyName];
- }
- } else {
- chainsFor(obj).remove(keyName);
- }
-
- } else if (watching[keyName]>1) {
- watching[keyName]--;
</del><ins>+ if (isKeyName(_keyPath)) {
+ unwatchKey(obj, _keyPath);
+ } else {
+ unwatchPath(obj, _keyPath);
</ins><span class="cx"> }
</span><del>-
- return this;
</del><span class="cx"> };
</span><span class="cx">
</span><span class="cx"> /**
</span><del>- @private
-
</del><span class="cx"> Call on an object when you first beget it from another object. This will
</span><span class="cx"> setup any chained watchers on the object instance as needed. This method is
</span><span class="cx"> safe to call multiple times.
</span><span class="cx">
</span><ins>+ @private
</ins><span class="cx"> @method rewatch
</span><span class="cx"> @for Ember
</span><span class="cx"> @param obj
</span><span class="lines">@@ -3043,96 +4172,15 @@
</span><span class="cx">
</span><span class="cx"> // make sure the object has its own guid.
</span><span class="cx"> if (GUID_KEY in obj && !obj.hasOwnProperty(GUID_KEY)) {
</span><del>- Ember.generateGuid(obj, 'ember');
</del><ins>+ generateGuid(obj);
</ins><span class="cx"> }
</span><span class="cx">
</span><span class="cx"> // make sure any chained watchers update.
</span><span class="cx"> if (chains && chains.value() !== obj) {
</span><span class="cx"> m.chains = chains.copy(obj);
</span><span class="cx"> }
</span><del>-
- return this;
</del><span class="cx"> };
</span><span class="cx">
</span><del>-Ember.finishChains = function(obj) {
- var m = metaFor(obj, false), chains = m.chains;
- if (chains) {
- if (chains.value() !== obj) {
- m.chains = chains = chains.copy(obj);
- }
- chains.didChange(true);
- }
-};
-
-// ..........................................................
-// PROPERTY CHANGES
-//
-
-/**
- This function is called just before an object property is about to change.
- It will notify any before observers and prepare caches among other things.
-
- Normally you will not need to call this method directly but if for some
- reason you can't directly watch a property you can invoke this method
- manually along with `Ember.propertyDidChange()` which you should call just
- after the property value changes.
-
- @method propertyWillChange
- @for Ember
- @param {Object} obj The object with the property that will change
- @param {String} keyName The property key (or path) that will change.
- @return {void}
-*/
-function propertyWillChange(obj, keyName, value) {
- var m = metaFor(obj, false),
- watching = m.watching[keyName] > 0 || keyName === 'length',
- proto = m.proto,
- desc = m.descs[keyName];
-
- if (!watching) { return; }
- if (proto === obj) { return; }
- if (desc && desc.willChange) { desc.willChange(obj, keyName); }
- dependentKeysWillChange(obj, keyName, m);
- chainsWillChange(obj, keyName, m);
- Ember.notifyBeforeObservers(obj, keyName);
-}
-
-Ember.propertyWillChange = propertyWillChange;
-
-/**
- This function is called just after an object property has changed.
- It will notify any observers and clear caches among other things.
-
- Normally you will not need to call this method directly but if for some
- reason you can't directly watch a property you can invoke this method
- manually along with `Ember.propertyWilLChange()` which you should call just
- before the property value changes.
-
- @method propertyDidChange
- @for Ember
- @param {Object} obj The object with the property that will change
- @param {String} keyName The property key (or path) that will change.
- @return {void}
-*/
-function propertyDidChange(obj, keyName) {
- var m = metaFor(obj, false),
- watching = m.watching[keyName] > 0 || keyName === 'length',
- proto = m.proto,
- desc = m.descs[keyName];
-
- if (proto === obj) { return; }
-
- // shouldn't this mean that we're watching this key?
- if (desc && desc.didChange) { desc.didChange(obj, keyName); }
- if (!watching && keyName !== 'length') { return; }
-
- dependentKeysDidChange(obj, keyName, m);
- chainsDidChange(obj, keyName, m);
- Ember.notifyObservers(obj, keyName);
-}
-
-Ember.propertyDidChange = propertyDidChange;
-
</del><span class="cx"> var NODE_STACK = [];
</span><span class="cx">
</span><span class="cx"> /**
</span><span class="lines">@@ -3191,13 +4239,13 @@
</span><span class="cx"> var get = Ember.get,
</span><span class="cx"> set = Ember.set,
</span><span class="cx"> metaFor = Ember.meta,
</span><del>- guidFor = Ember.guidFor,
</del><span class="cx"> a_slice = [].slice,
</span><span class="cx"> o_create = Ember.create,
</span><span class="cx"> META_KEY = Ember.META_KEY,
</span><span class="cx"> watch = Ember.watch,
</span><span class="cx"> unwatch = Ember.unwatch;
</span><span class="cx">
</span><ins>+
</ins><span class="cx"> // ..........................................................
</span><span class="cx"> // DEPENDENT KEYS
</span><span class="cx"> //
</span><span class="lines">@@ -3213,7 +4261,7 @@
</span><span class="cx"> This function returns a map of unique dependencies for a
</span><span class="cx"> given object and key.
</span><span class="cx"> */
</span><del>-function keysForDep(obj, depsMeta, depKey) {
</del><ins>+function keysForDep(depsMeta, depKey) {
</ins><span class="cx"> var keys = depsMeta[depKey];
</span><span class="cx"> if (!keys) {
</span><span class="cx"> // if there are no dependencies yet for a the given key
</span><span class="lines">@@ -3227,20 +4275,8 @@
</span><span class="cx"> return keys;
</span><span class="cx"> }
</span><span class="cx">
</span><del>-/* return obj[META_KEY].deps */
-function metaForDeps(obj, meta) {
- var deps = meta.deps;
- // If the current object has no dependencies...
- if (!deps) {
- // initialize the dependencies with a pointer back to
- // the current object
- deps = meta.deps = {};
- } else if (!meta.hasOwnProperty('deps')) {
- // otherwise if the dependencies are inherited from the
- // object's superclass, clone the deps
- deps = meta.deps = o_create(deps);
- }
- return deps;
</del><ins>+function metaForDeps(meta) {
+ return keysForDep(meta, 'deps');
</ins><span class="cx"> }
</span><span class="cx">
</span><span class="cx"> function addDependentKeys(desc, obj, keyName, meta) {
</span><span class="lines">@@ -3249,12 +4285,12 @@
</span><span class="cx"> var depKeys = desc._dependentKeys, depsMeta, idx, len, depKey, keys;
</span><span class="cx"> if (!depKeys) return;
</span><span class="cx">
</span><del>- depsMeta = metaForDeps(obj, meta);
</del><ins>+ depsMeta = metaForDeps(meta);
</ins><span class="cx">
</span><span class="cx"> for(idx = 0, len = depKeys.length; idx < len; idx++) {
</span><span class="cx"> depKey = depKeys[idx];
</span><span class="cx"> // Lookup keys meta for depKey
</span><del>- keys = keysForDep(obj, depsMeta, depKey);
</del><ins>+ keys = keysForDep(depsMeta, depKey);
</ins><span class="cx"> // Increment the number of times depKey depends on keyName.
</span><span class="cx"> keys[keyName] = (keys[keyName] || 0) + 1;
</span><span class="cx"> // Watch the depKey
</span><span class="lines">@@ -3268,12 +4304,12 @@
</span><span class="cx"> var depKeys = desc._dependentKeys, depsMeta, idx, len, depKey, keys;
</span><span class="cx"> if (!depKeys) return;
</span><span class="cx">
</span><del>- depsMeta = metaForDeps(obj, meta);
</del><ins>+ depsMeta = metaForDeps(meta);
</ins><span class="cx">
</span><span class="cx"> for(idx = 0, len = depKeys.length; idx < len; idx++) {
</span><span class="cx"> depKey = depKeys[idx];
</span><span class="cx"> // Lookup keys meta for depKey
</span><del>- keys = keysForDep(obj, depsMeta, depKey);
</del><ins>+ keys = keysForDep(depsMeta, depKey);
</ins><span class="cx"> // Increment the number of times depKey depends on keyName.
</span><span class="cx"> keys[keyName] = (keys[keyName] || 0) - 1;
</span><span class="cx"> // Watch the depKey
</span><span class="lines">@@ -3286,6 +4322,81 @@
</span><span class="cx"> //
</span><span class="cx">
</span><span class="cx"> /**
</span><ins>+ A computed property transforms an objects function into a property.
+
+ By default the function backing the computed property will only be called
+ once and the result will be cached. You can specify various properties
+ that your computed property is dependent on. This will force the cached
+ result to be recomputed if the dependencies are modified.
+
+ In the following example we declare a computed property (by calling
+ `.property()` on the fullName function) and setup the properties
+ dependencies (depending on firstName and lastName). The fullName function
+ will be called once (regardless of how many times it is accessed) as long
+ as it's dependencies have not been changed. Once firstName or lastName are updated
+ any future calls (or anything bound) to fullName will incorporate the new
+ values.
+
+ ```javascript
+ Person = Ember.Object.extend({
+ // these will be supplied by `create`
+ firstName: null,
+ lastName: null,
+
+ fullName: function() {
+ var firstName = this.get('firstName');
+ var lastName = this.get('lastName');
+
+ return firstName + ' ' + lastName;
+ }.property('firstName', 'lastName')
+ });
+
+ var tom = Person.create({
+ firstName: "Tom",
+ lastName: "Dale"
+ });
+
+ tom.get('fullName') // "Tom Dale"
+ ```
+
+ You can also define what Ember should do when setting a computed property.
+ If you try to set a computed property, it will be invoked with the key and
+ value you want to set it to. You can also accept the previous value as the
+ third parameter.
+
+ ```javascript
+
+ Person = Ember.Object.extend({
+ // these will be supplied by `create`
+ firstName: null,
+ lastName: null,
+
+ fullName: function(key, value, oldValue) {
+ // getter
+ if (arguments.length === 1) {
+ var firstName = this.get('firstName');
+ var lastName = this.get('lastName');
+
+ return firstName + ' ' + lastName;
+
+ // setter
+ } else {
+ var name = value.split(" ");
+
+ this.set('firstName', name[0]);
+ this.set('lastName', name[1]);
+
+ return value;
+ }
+ }.property('firstName', 'lastName')
+ });
+
+ var person = Person.create();
+ person.set('fullName', "Peter Wagenet");
+ person.get('firstName') // Peter
+ person.get('lastName') // Wagenet
+ ```
+
</ins><span class="cx"> @class ComputedProperty
</span><span class="cx"> @namespace Ember
</span><span class="cx"> @extends Ember.Descriptor
</span><span class="lines">@@ -3293,8 +4404,10 @@
</span><span class="cx"> */
</span><span class="cx"> function ComputedProperty(func, opts) {
</span><span class="cx"> this.func = func;
</span><ins>+
</ins><span class="cx"> this._cacheable = (opts && opts.cacheable !== undefined) ? opts.cacheable : true;
</span><span class="cx"> this._dependentKeys = opts && opts.dependentKeys;
</span><ins>+ this._readOnly = opts && (opts.readOnly !== undefined || !!opts.readOnly);
</ins><span class="cx"> }
</span><span class="cx">
</span><span class="cx"> Ember.ComputedProperty = ComputedProperty;
</span><span class="lines">@@ -3303,26 +4416,18 @@
</span><span class="cx"> var ComputedPropertyPrototype = ComputedProperty.prototype;
</span><span class="cx">
</span><span class="cx"> /**
</span><del>- Call on a computed property to set it into cacheable mode. When in this
- mode the computed property will automatically cache the return value of
- your function until one of the dependent keys changes.
</del><ins>+ Properties are cacheable by default. Computed property will automatically
+ cache the return value of your function until one of the dependent keys changes.
</ins><span class="cx">
</span><del>- ```javascript
- MyApp.president = Ember.Object.create({
- fullName: function() {
- return this.get('firstName') + ' ' + this.get('lastName');
</del><ins>+ Call `volatile()` to set it into non-cached mode. When in this mode
+ the computed property will not automatically cache the return value.
</ins><span class="cx">
</span><del>- // After calculating the value of this function, Ember will
- // return that value without re-executing this function until
- // one of the dependent properties change.
- }.property('firstName', 'lastName')
- });
- ```
</del><ins>+ However, if a property is properly observable, there is no reason to disable
+ caching.
</ins><span class="cx">
</span><del>- Properties are cacheable by default.
-
</del><span class="cx"> @method cacheable
</span><span class="cx"> @param {Boolean} aFlag optional set to `false` to disable caching
</span><ins>+ @return {Ember.ComputedProperty} this
</ins><span class="cx"> @chainable
</span><span class="cx"> */
</span><span class="cx"> ComputedPropertyPrototype.cacheable = function(aFlag) {
</span><span class="lines">@@ -3335,14 +4440,15 @@
</span><span class="cx"> mode the computed property will not automatically cache the return value.
</span><span class="cx">
</span><span class="cx"> ```javascript
</span><del>- MyApp.outsideService = Ember.Object.create({
</del><ins>+ MyApp.outsideService = Ember.Object.extend({
</ins><span class="cx"> value: function() {
</span><span class="cx"> return OutsideService.getValue();
</span><span class="cx"> }.property().volatile()
</span><del>- });
</del><ins>+ }).create();
</ins><span class="cx"> ```
</span><span class="cx">
</span><span class="cx"> @method volatile
</span><ins>+ @return {Ember.ComputedProperty} this
</ins><span class="cx"> @chainable
</span><span class="cx"> */
</span><span class="cx"> ComputedPropertyPrototype.volatile = function() {
</span><span class="lines">@@ -3350,11 +4456,36 @@
</span><span class="cx"> };
</span><span class="cx">
</span><span class="cx"> /**
</span><ins>+ Call on a computed property to set it into read-only mode. When in this
+ mode the computed property will throw an error when set.
+
+ ```javascript
+ MyApp.Person = Ember.Object.extend({
+ guid: function() {
+ return 'guid-guid-guid';
+ }.property().readOnly()
+ });
+
+ MyApp.person = MyApp.Person.create();
+
+ MyApp.person.set('guid', 'new-guid'); // will throw an exception
+ ```
+
+ @method readOnly
+ @return {Ember.ComputedProperty} this
+ @chainable
+*/
+ComputedPropertyPrototype.readOnly = function(readOnly) {
+ this._readOnly = readOnly === undefined || !!readOnly;
+ return this;
+};
+
+/**
</ins><span class="cx"> Sets the dependent keys on this computed property. Pass any number of
</span><span class="cx"> arguments containing key paths that this computed property depends on.
</span><span class="cx">
</span><span class="cx"> ```javascript
</span><del>- MyApp.president = Ember.Object.create({
</del><ins>+ MyApp.President = Ember.Object.extend({
</ins><span class="cx"> fullName: Ember.computed(function() {
</span><span class="cx"> return this.get('firstName') + ' ' + this.get('lastName');
</span><span class="cx">
</span><span class="lines">@@ -3362,17 +4493,26 @@
</span><span class="cx"> // and lastName
</span><span class="cx"> }).property('firstName', 'lastName')
</span><span class="cx"> });
</span><ins>+
+ MyApp.president = MyApp.President.create({
+ firstName: 'Barack',
+ lastName: 'Obama',
+ });
+ MyApp.president.get('fullName'); // Barack Obama
</ins><span class="cx"> ```
</span><span class="cx">
</span><span class="cx"> @method property
</span><span class="cx"> @param {String} path* zero or more property paths
</span><ins>+ @return {Ember.ComputedProperty} this
</ins><span class="cx"> @chainable
</span><span class="cx"> */
</span><span class="cx"> ComputedPropertyPrototype.property = function() {
</span><del>- var args = [];
- for (var i = 0, l = arguments.length; i < l; i++) {
- args.push(arguments[i]);
- }
</del><ins>+ var args;
+
+
+ args = a_slice.call(arguments);
+
+
</ins><span class="cx"> this._dependentKeys = args;
</span><span class="cx"> return this;
</span><span class="cx"> };
</span><span class="lines">@@ -3412,25 +4552,6 @@
</span><span class="cx"> };
</span><span class="cx">
</span><span class="cx"> /* impl descriptor API */
</span><del>-ComputedPropertyPrototype.willWatch = function(obj, keyName) {
- // watch already creates meta for this instance
- var meta = obj[META_KEY];
- Ember.assert('watch should have setup meta to be writable', meta.source === obj);
- if (!(keyName in meta.cache)) {
- addDependentKeys(this, obj, keyName, meta);
- }
-};
-
-ComputedPropertyPrototype.didUnwatch = function(obj, keyName) {
- var meta = obj[META_KEY];
- Ember.assert('unwatch should have setup meta to be writable', meta.source === obj);
- if (!(keyName in meta.cache)) {
- // unwatch already creates meta for this instance
- removeDependentKeys(this, obj, keyName, meta);
- }
-};
-
-/* impl descriptor API */
</del><span class="cx"> ComputedPropertyPrototype.didChange = function(obj, keyName) {
</span><span class="cx"> // _suspended is set via a CP.set to ensure we don't clear
</span><span class="cx"> // the cached value set by the setter
</span><span class="lines">@@ -3438,31 +4559,76 @@
</span><span class="cx"> var meta = metaFor(obj);
</span><span class="cx"> if (keyName in meta.cache) {
</span><span class="cx"> delete meta.cache[keyName];
</span><del>- if (!meta.watching[keyName]) {
- removeDependentKeys(this, obj, keyName, meta);
- }
</del><ins>+ removeDependentKeys(this, obj, keyName, meta);
</ins><span class="cx"> }
</span><span class="cx"> }
</span><span class="cx"> };
</span><span class="cx">
</span><del>-/* impl descriptor API */
</del><ins>+function finishChains(chainNodes)
+{
+ for (var i=0, l=chainNodes.length; i<l; i++) {
+ chainNodes[i].didChange(null);
+ }
+}
+
+/**
+ Access the value of the function backing the computed property.
+ If this property has already been cached, return the cached result.
+ Otherwise, call the function passing the property name as an argument.
+
+ ```javascript
+ Person = Ember.Object.extend({
+ fullName: function(keyName) {
+ // the keyName parameter is 'fullName' in this case.
+
+ return this.get('firstName') + ' ' + this.get('lastName');
+ }.property('firstName', 'lastName')
+ });
+
+
+ var tom = Person.create({
+ firstName: "Tom",
+ lastName: "Dale"
+ });
+
+ tom.get('fullName') // "Tom Dale"
+ ```
+
+ @method get
+ @param {String} keyName The key being accessed.
+ @return {Object} The return value of the function backing the CP.
+*/
</ins><span class="cx"> ComputedPropertyPrototype.get = function(obj, keyName) {
</span><del>- var ret, cache, meta;
</del><ins>+ var ret, cache, meta, chainNodes;
</ins><span class="cx"> if (this._cacheable) {
</span><span class="cx"> meta = metaFor(obj);
</span><span class="cx"> cache = meta.cache;
</span><span class="cx"> if (keyName in cache) { return cache[keyName]; }
</span><span class="cx"> ret = cache[keyName] = this.func.call(obj, keyName);
</span><del>- if (!meta.watching[keyName]) {
- addDependentKeys(this, obj, keyName, meta);
- }
</del><ins>+ chainNodes = meta.chainWatchers && meta.chainWatchers[keyName];
+ if (chainNodes) { finishChains(chainNodes); }
+ addDependentKeys(this, obj, keyName, meta);
</ins><span class="cx"> } else {
</span><span class="cx"> ret = this.func.call(obj, keyName);
</span><span class="cx"> }
</span><span class="cx"> return ret;
</span><span class="cx"> };
</span><span class="cx">
</span><del>-/* impl descriptor API */
</del><ins>+/**
+ Set the value of a computed property. If the function that backs your
+ computed property does not accept arguments then the default action for
+ setting would be to define the property on the current object, and set
+ the value of the property to the value being set.
+
+ Generally speaking if you intend for your computed property to be set
+ your backing function should accept either two or three arguments.
+
+ @method set
+ @param {String} keyName The key being accessed.
+ @param {Object} newValue The new value being assigned.
+ @param {String} oldValue The old value being replaced.
+ @return {Object} The return value of the function backing the CP.
+*/
</ins><span class="cx"> ComputedPropertyPrototype.set = function(obj, keyName, value) {
</span><span class="cx"> var cacheable = this._cacheable,
</span><span class="cx"> func = this.func,
</span><span class="lines">@@ -3471,27 +4637,33 @@
</span><span class="cx"> oldSuspended = this._suspended,
</span><span class="cx"> hadCachedValue = false,
</span><span class="cx"> cache = meta.cache,
</span><del>- cachedValue, ret;
</del><ins>+ funcArgLength, cachedValue, ret;
</ins><span class="cx">
</span><ins>+ if (this._readOnly) {
+ throw new Ember.Error('Cannot Set: ' + keyName + ' on: ' + obj.toString() );
+ }
+
</ins><span class="cx"> this._suspended = obj;
</span><span class="cx">
</span><span class="cx"> try {
</span><ins>+
</ins><span class="cx"> if (cacheable && cache.hasOwnProperty(keyName)) {
</span><span class="cx"> cachedValue = cache[keyName];
</span><span class="cx"> hadCachedValue = true;
</span><span class="cx"> }
</span><span class="cx">
</span><del>- // Check if the CP has been wrapped
- if (func.wrappedFunction) { func = func.wrappedFunction; }
</del><ins>+ // Check if the CP has been wrapped. If if has, use the
+ // length from the wrapped function.
+ funcArgLength = (func.wrappedFunction ? func.wrappedFunction.length : func.length);
</ins><span class="cx">
</span><span class="cx"> // For backwards-compatibility with computed properties
</span><span class="cx"> // that check for arguments.length === 2 to determine if
</span><span class="cx"> // they are being get or set, only pass the old cached
</span><span class="cx"> // value if the computed property opts into a third
</span><span class="cx"> // argument.
</span><del>- if (func.length === 3) {
</del><ins>+ if (funcArgLength === 3) {
</ins><span class="cx"> ret = func.call(obj, keyName, value, cachedValue);
</span><del>- } else if (func.length === 2) {
</del><ins>+ } else if (funcArgLength === 2) {
</ins><span class="cx"> ret = func.call(obj, keyName, value);
</span><span class="cx"> } else {
</span><span class="cx"> Ember.defineProperty(obj, keyName, null, cachedValue);
</span><span class="lines">@@ -3508,7 +4680,7 @@
</span><span class="cx"> }
</span><span class="cx">
</span><span class="cx"> if (cacheable) {
</span><del>- if (!watched && !hadCachedValue) {
</del><ins>+ if (!hadCachedValue) {
</ins><span class="cx"> addDependentKeys(this, obj, keyName, meta);
</span><span class="cx"> }
</span><span class="cx"> cache[keyName] = ret;
</span><span class="lines">@@ -3521,19 +4693,11 @@
</span><span class="cx"> return ret;
</span><span class="cx"> };
</span><span class="cx">
</span><del>-/* called when property is defined */
-ComputedPropertyPrototype.setup = function(obj, keyName) {
- var meta = obj[META_KEY];
- if (meta && meta.watching[keyName]) {
- addDependentKeys(this, obj, keyName, metaFor(obj));
- }
-};
-
</del><span class="cx"> /* called before property is overridden */
</span><span class="cx"> ComputedPropertyPrototype.teardown = function(obj, keyName) {
</span><span class="cx"> var meta = metaFor(obj);
</span><span class="cx">
</span><del>- if (meta.watching[keyName] || keyName in meta.cache) {
</del><ins>+ if (keyName in meta.cache) {
</ins><span class="cx"> removeDependentKeys(this, obj, keyName, meta);
</span><span class="cx"> }
</span><span class="cx">
</span><span class="lines">@@ -3552,7 +4716,6 @@
</span><span class="cx"> The function should accept two parameters, key and value. If value is not
</span><span class="cx"> undefined you should set the value first. In either case return the
</span><span class="cx"> current value of the property.
</span><del>-
</del><span class="cx"> @method computed
</span><span class="cx"> @for Ember
</span><span class="cx"> @param {Function} func The computed property function.
</span><span class="lines">@@ -3566,6 +4729,10 @@
</span><span class="cx"> func = a_slice.call(arguments, -1)[0];
</span><span class="cx"> }
</span><span class="cx">
</span><ins>+ if (typeof func !== "function") {
+ throw new Ember.Error("Computed Property declared without a property function");
+ }
+
</ins><span class="cx"> var cp = new ComputedProperty(func);
</span><span class="cx">
</span><span class="cx"> if (args) {
</span><span class="lines">@@ -3586,6 +4753,7 @@
</span><span class="cx"> @param {Object} obj the object whose property you want to check
</span><span class="cx"> @param {String} key the name of the property whose cached value you want
</span><span class="cx"> to return
</span><ins>+ @return {Object} the cached value
</ins><span class="cx"> */
</span><span class="cx"> Ember.cacheFor = function cacheFor(obj, key) {
</span><span class="cx"> var cache = metaFor(obj, false).cache;
</span><span class="lines">@@ -3595,622 +4763,1380 @@
</span><span class="cx"> }
</span><span class="cx"> };
</span><span class="cx">
</span><ins>+function getProperties(self, propertyNames) {
+ var ret = {};
+ for(var i = 0; i < propertyNames.length; i++) {
+ ret[propertyNames[i]] = get(self, propertyNames[i]);
+ }
+ return ret;
+}
+
+function registerComputed(name, macro) {
+ Ember.computed[name] = function(dependentKey) {
+ var args = a_slice.call(arguments);
+ return Ember.computed(dependentKey, function() {
+ return macro.apply(this, args);
+ });
+ };
+}
+
+function registerComputedWithProperties(name, macro) {
+ Ember.computed[name] = function() {
+ var properties = a_slice.call(arguments);
+
+ var computed = Ember.computed(function() {
+ return macro.apply(this, [getProperties(this, properties)]);
+ });
+
+ return computed.property.apply(computed, properties);
+ };
+}
+
</ins><span class="cx"> /**
</span><del>- @method computed.not
</del><ins>+ A computed property that returns true if the value of the dependent
+ property is null, an empty string, empty array, or empty function.
+
+ Note: When using `Ember.computed.empty` to watch an array make sure to
+ use the `array.[]` syntax so the computed can subscribe to transitions
+ from empty to non-empty states.
+
+ Example
+
+ ```javascript
+ var ToDoList = Ember.Object.extend({
+ done: Ember.computed.empty('todos.[]') // detect array changes
+ });
+ var todoList = ToDoList.create({todos: ['Unit Test', 'Documentation', 'Release']});
+ todoList.get('done'); // false
+ todoList.get('todos').clear(); // []
+ todoList.get('done'); // true
+ ```
+
+ @method computed.empty
</ins><span class="cx"> @for Ember
</span><span class="cx"> @param {String} dependentKey
</span><ins>+ @return {Ember.ComputedProperty} computed property which negate
+ the original value for property
</ins><span class="cx"> */
</span><del>-Ember.computed.not = function(dependentKey) {
- return Ember.computed(dependentKey, function(key) {
- return !get(this, dependentKey);
</del><ins>+registerComputed('empty', function(dependentKey) {
+ return Ember.isEmpty(get(this, dependentKey));
+});
+
+/**
+ A computed property that returns true if the value of the dependent
+ property is NOT null, an empty string, empty array, or empty function.
+
+ Note: When using `Ember.computed.notEmpty` to watch an array make sure to
+ use the `array.[]` syntax so the computed can subscribe to transitions
+ from empty to non-empty states.
+
+ Example
+
+ ```javascript
+ var Hamster = Ember.Object.extend({
+ hasStuff: Ember.computed.notEmpty('backpack.[]')
</ins><span class="cx"> });
</span><del>-};
</del><ins>+ var hamster = Hamster.create({backpack: ['Food', 'Sleeping Bag', 'Tent']});
+ hamster.get('hasStuff'); // true
+ hamster.get('backpack').clear(); // []
+ hamster.get('hasStuff'); // false
+ ```
</ins><span class="cx">
</span><del>-/**
- @method computed.empty
</del><ins>+ @method computed.notEmpty
</ins><span class="cx"> @for Ember
</span><span class="cx"> @param {String} dependentKey
</span><ins>+ @return {Ember.ComputedProperty} computed property which returns true if
+ original value for property is not empty.
</ins><span class="cx"> */
</span><del>-Ember.computed.empty = function(dependentKey) {
- return Ember.computed(dependentKey, function(key) {
- var val = get(this, dependentKey);
- return val === undefined || val === null || val === '' || (Ember.isArray(val) && get(val, 'length') === 0);
</del><ins>+registerComputed('notEmpty', function(dependentKey) {
+ return !Ember.isEmpty(get(this, dependentKey));
+});
+
+/**
+ A computed property that returns true if the value of the dependent
+ property is null or undefined. This avoids errors from JSLint complaining
+ about use of ==, which can be technically confusing.
+
+ Example
+
+ ```javascript
+ var Hamster = Ember.Object.extend({
+ isHungry: Ember.computed.none('food')
</ins><span class="cx"> });
</span><del>-};
</del><ins>+ var hamster = Hamster.create();
+ hamster.get('isHungry'); // true
+ hamster.set('food', 'Banana');
+ hamster.get('isHungry'); // false
+ hamster.set('food', null);
+ hamster.get('isHungry'); // true
+ ```
</ins><span class="cx">
</span><del>-/**
- @method computed.bool
</del><ins>+ @method computed.none
</ins><span class="cx"> @for Ember
</span><span class="cx"> @param {String} dependentKey
</span><ins>+ @return {Ember.ComputedProperty} computed property which
+ returns true if original value for property is null or undefined.
</ins><span class="cx"> */
</span><del>-Ember.computed.bool = function(dependentKey) {
- return Ember.computed(dependentKey, function(key) {
- return !!get(this, dependentKey);
</del><ins>+registerComputed('none', function(dependentKey) {
+ return Ember.isNone(get(this, dependentKey));
+});
+
+/**
+ A computed property that returns the inverse boolean value
+ of the original value for the dependent property.
+
+ Example
+
+ ```javascript
+ var User = Ember.Object.extend({
+ isAnonymous: Ember.computed.not('loggedIn')
</ins><span class="cx"> });
</span><del>-};
</del><ins>+ var user = User.create({loggedIn: false});
+ user.get('isAnonymous'); // true
+ user.set('loggedIn', true);
+ user.get('isAnonymous'); // false
+ ```
</ins><span class="cx">
</span><del>-/**
- @method computed.alias
</del><ins>+ @method computed.not
</ins><span class="cx"> @for Ember
</span><span class="cx"> @param {String} dependentKey
</span><ins>+ @return {Ember.ComputedProperty} computed property which returns
+ inverse of the original value for property
</ins><span class="cx"> */
</span><del>-Ember.computed.alias = function(dependentKey) {
- return Ember.computed(dependentKey, function(key, value){
- if (arguments.length === 1) {
- return get(this, dependentKey);
- } else {
- set(this, dependentKey, value);
- return value;
- }
</del><ins>+registerComputed('not', function(dependentKey) {
+ return !get(this, dependentKey);
+});
+
+/**
+ A computed property that converts the provided dependent property
+ into a boolean value.
+
+ ```javascript
+ var Hamster = Ember.Object.extend({
+ hasBananas: Ember.computed.bool('numBananas')
</ins><span class="cx"> });
</span><del>-};
</del><ins>+ var hamster = Hamster.create();
+ hamster.get('hasBananas'); // false
+ hamster.set('numBananas', 0);
+ hamster.get('hasBananas'); // false
+ hamster.set('numBananas', 1);
+ hamster.get('hasBananas'); // true
+ hamster.set('numBananas', null);
+ hamster.get('hasBananas'); // false
+ ```
</ins><span class="cx">
</span><del>-})();
</del><ins>+ @method computed.bool
+ @for Ember
+ @param {String} dependentKey
+ @return {Ember.ComputedProperty} computed property which converts
+ to boolean the original value for property
+*/
+registerComputed('bool', function(dependentKey) {
+ return !!get(this, dependentKey);
+});
</ins><span class="cx">
</span><ins>+/**
+ A computed property which matches the original value for the
+ dependent property against a given RegExp, returning `true`
+ if they values matches the RegExp and `false` if it does not.
</ins><span class="cx">
</span><ins>+ Example
</ins><span class="cx">
</span><del>-(function() {
-/**
-@module ember-metal
</del><ins>+ ```javascript
+ var User = Ember.Object.extend({
+ hasValidEmail: Ember.computed.match('email', /^.+@.+\..+$/)
+ });
+ var user = User.create({loggedIn: false});
+ user.get('hasValidEmail'); // false
+ user.set('email', '');
+ user.get('hasValidEmail'); // false
+ user.set('email', 'ember_hamster@example.com');
+ user.get('hasValidEmail'); // true
+ ```
+
+ @method computed.match
+ @for Ember
+ @param {String} dependentKey
+ @param {RegExp} regexp
+ @return {Ember.ComputedProperty} computed property which match
+ the original value for property against a given RegExp
</ins><span class="cx"> */
</span><ins>+registerComputed('match', function(dependentKey, regexp) {
+ var value = get(this, dependentKey);
+ return typeof value === 'string' ? regexp.test(value) : false;
+});
</ins><span class="cx">
</span><del>-var o_create = Ember.create,
- metaFor = Ember.meta,
- metaPath = Ember.metaPath,
- META_KEY = Ember.META_KEY;
</del><ins>+/**
+ A computed property that returns true if the provided dependent property
+ is equal to the given value.
</ins><span class="cx">
</span><del>-/*
- The event system uses a series of nested hashes to store listeners on an
- object. When a listener is registered, or when an event arrives, these
- hashes are consulted to determine which target and action pair to invoke.
</del><ins>+ Example
</ins><span class="cx">
</span><del>- The hashes are stored in the object's meta hash, and look like this:
</del><ins>+ ```javascript
+ var Hamster = Ember.Object.extend({
+ napTime: Ember.computed.equal('state', 'sleepy')
+ });
+ var hamster = Hamster.create();
+ hamster.get('napTime'); // false
+ hamster.set('state', 'sleepy');
+ hamster.get('napTime'); // true
+ hamster.set('state', 'hungry');
+ hamster.get('napTime'); // false
+ ```
</ins><span class="cx">
</span><del>- // Object's meta hash
- {
- listeners: { // variable name: `listenerSet`
- "foo:changed": [ // variable name: `actions`
- [target, method, onceFlag, suspendedFlag]
- ]
- }
- }
-
</del><ins>+ @method computed.equal
+ @for Ember
+ @param {String} dependentKey
+ @param {String|Number|Object} value
+ @return {Ember.ComputedProperty} computed property which returns true if
+ the original value for property is equal to the given value.
</ins><span class="cx"> */
</span><ins>+registerComputed('equal', function(dependentKey, value) {
+ return get(this, dependentKey) === value;
+});
</ins><span class="cx">
</span><del>-function indexOf(array, target, method) {
- var index = -1;
- for (var i = 0, l = array.length; i < l; i++) {
- if (target === array[i][0] && method === array[i][1]) { index = i; break; }
- }
- return index;
-}
</del><ins>+/**
+ A computed property that returns true if the provied dependent property
+ is greater than the provided value.
</ins><span class="cx">
</span><del>-function actionsFor(obj, eventName) {
- var meta = metaFor(obj, true),
- actions;
</del><ins>+ Example
</ins><span class="cx">
</span><del>- if (!meta.listeners) { meta.listeners = {}; }
</del><ins>+ ```javascript
+ var Hamster = Ember.Object.extend({
+ hasTooManyBananas: Ember.computed.gt('numBananas', 10)
+ });
+ var hamster = Hamster.create();
+ hamster.get('hasTooManyBananas'); // false
+ hamster.set('numBananas', 3);
+ hamster.get('hasTooManyBananas'); // false
+ hamster.set('numBananas', 11);
+ hamster.get('hasTooManyBananas'); // true
+ ```
</ins><span class="cx">
</span><del>- if (!meta.hasOwnProperty('listeners')) {
- // setup inherited copy of the listeners object
- meta.listeners = o_create(meta.listeners);
- }
</del><ins>+ @method computed.gt
+ @for Ember
+ @param {String} dependentKey
+ @param {Number} value
+ @return {Ember.ComputedProperty} computed property which returns true if
+ the original value for property is greater then given value.
+*/
+registerComputed('gt', function(dependentKey, value) {
+ return get(this, dependentKey) > value;
+});
</ins><span class="cx">
</span><del>- actions = meta.listeners[eventName];
</del><ins>+/**
+ A computed property that returns true if the provided dependent property
+ is greater than or equal to the provided value.
</ins><span class="cx">
</span><del>- // if there are actions, but the eventName doesn't exist in our listeners, then copy them from the prototype
- if (actions && !meta.listeners.hasOwnProperty(eventName)) {
- actions = meta.listeners[eventName] = meta.listeners[eventName].slice();
- } else if (!actions) {
- actions = meta.listeners[eventName] = [];
- }
</del><ins>+ Example
</ins><span class="cx">
</span><del>- return actions;
-}
</del><ins>+ ```javascript
+ var Hamster = Ember.Object.extend({
+ hasTooManyBananas: Ember.computed.gte('numBananas', 10)
+ });
+ var hamster = Hamster.create();
+ hamster.get('hasTooManyBananas'); // false
+ hamster.set('numBananas', 3);
+ hamster.get('hasTooManyBananas'); // false
+ hamster.set('numBananas', 10);
+ hamster.get('hasTooManyBananas'); // true
+ ```
</ins><span class="cx">
</span><del>-function actionsUnion(obj, eventName, otherActions) {
- var meta = obj[META_KEY],
- actions = meta && meta.listeners && meta.listeners[eventName];
</del><ins>+ @method computed.gte
+ @for Ember
+ @param {String} dependentKey
+ @param {Number} value
+ @return {Ember.ComputedProperty} computed property which returns true if
+ the original value for property is greater or equal then given value.
+*/
+registerComputed('gte', function(dependentKey, value) {
+ return get(this, dependentKey) >= value;
+});
</ins><span class="cx">
</span><del>- if (!actions) { return; }
- for (var i = actions.length - 1; i >= 0; i--) {
- var target = actions[i][0],
- method = actions[i][1],
- once = actions[i][2],
- suspended = actions[i][3],
- actionIndex = indexOf(otherActions, target, method);
</del><ins>+/**
+ A computed property that returns true if the provided dependent property
+ is less than the provided value.
</ins><span class="cx">
</span><del>- if (actionIndex === -1) {
- otherActions.push([target, method, once, suspended]);
- }
- }
-}
</del><ins>+ Example
</ins><span class="cx">
</span><del>-function actionsDiff(obj, eventName, otherActions) {
- var meta = obj[META_KEY],
- actions = meta && meta.listeners && meta.listeners[eventName],
- diffActions = [];
</del><ins>+ ```javascript
+ var Hamster = Ember.Object.extend({
+ needsMoreBananas: Ember.computed.lt('numBananas', 3)
+ });
+ var hamster = Hamster.create();
+ hamster.get('needsMoreBananas'); // true
+ hamster.set('numBananas', 3);
+ hamster.get('needsMoreBananas'); // false
+ hamster.set('numBananas', 2);
+ hamster.get('needsMoreBananas'); // true
+ ```
</ins><span class="cx">
</span><del>- if (!actions) { return; }
- for (var i = actions.length - 1; i >= 0; i--) {
- var target = actions[i][0],
- method = actions[i][1],
- once = actions[i][2],
- suspended = actions[i][3],
- actionIndex = indexOf(otherActions, target, method);
</del><ins>+ @method computed.lt
+ @for Ember
+ @param {String} dependentKey
+ @param {Number} value
+ @return {Ember.ComputedProperty} computed property which returns true if
+ the original value for property is less then given value.
+*/
+registerComputed('lt', function(dependentKey, value) {
+ return get(this, dependentKey) < value;
+});
</ins><span class="cx">
</span><del>- if (actionIndex !== -1) { continue; }
</del><ins>+/**
+ A computed property that returns true if the provided dependent property
+ is less than or equal to the provided value.
</ins><span class="cx">
</span><del>- otherActions.push([target, method, once, suspended]);
- diffActions.push([target, method, once, suspended]);
- }
</del><ins>+ Example
</ins><span class="cx">
</span><del>- return diffActions;
-}
</del><ins>+ ```javascript
+ var Hamster = Ember.Object.extend({
+ needsMoreBananas: Ember.computed.lte('numBananas', 3)
+ });
+ var hamster = Hamster.create();
+ hamster.get('needsMoreBananas'); // true
+ hamster.set('numBananas', 5);
+ hamster.get('needsMoreBananas'); // false
+ hamster.set('numBananas', 3);
+ hamster.get('needsMoreBananas'); // true
+ ```
</ins><span class="cx">
</span><del>-/**
- Add an event listener
-
- @method addListener
</del><ins>+ @method computed.lte
</ins><span class="cx"> @for Ember
</span><del>- @param obj
- @param {String} eventName
- @param {Object|Function} targetOrMethod A target object or a function
- @param {Function|String} method A function or the name of a function to be called on `target`
</del><ins>+ @param {String} dependentKey
+ @param {Number} value
+ @return {Ember.ComputedProperty} computed property which returns true if
+ the original value for property is less or equal then given value.
</ins><span class="cx"> */
</span><del>-function addListener(obj, eventName, target, method, once) {
- Ember.assert("You must pass at least an object and event name to Ember.addListener", !!obj && !!eventName);
</del><ins>+registerComputed('lte', function(dependentKey, value) {
+ return get(this, dependentKey) <= value;
+});
</ins><span class="cx">
</span><del>- if (!method && 'function' === typeof target) {
- method = target;
- target = null;
- }
</del><ins>+/**
+ A computed property that performs a logical `and` on the
+ original values for the provided dependent properties.
</ins><span class="cx">
</span><del>- var actions = actionsFor(obj, eventName),
- actionIndex = indexOf(actions, target, method);
</del><ins>+ Example
</ins><span class="cx">
</span><del>- if (actionIndex !== -1) { return; }
</del><ins>+ ```javascript
+ var Hamster = Ember.Object.extend({
+ readyForCamp: Ember.computed.and('hasTent', 'hasBackpack')
+ });
+ var hamster = Hamster.create();
+ hamster.get('readyForCamp'); // false
+ hamster.set('hasTent', true);
+ hamster.get('readyForCamp'); // false
+ hamster.set('hasBackpack', true);
+ hamster.get('readyForCamp'); // true
+ ```
</ins><span class="cx">
</span><del>- actions.push([target, method, once, undefined]);
-
- if ('function' === typeof obj.didAddListener) {
- obj.didAddListener(eventName, target, method);
</del><ins>+ @method computed.and
+ @for Ember
+ @param {String} dependentKey*
+ @return {Ember.ComputedProperty} computed property which performs
+ a logical `and` on the values of all the original values for properties.
+*/
+registerComputedWithProperties('and', function(properties) {
+ for (var key in properties) {
+ if (properties.hasOwnProperty(key) && !properties[key]) {
+ return false;
+ }
</ins><span class="cx"> }
</span><del>-}
</del><ins>+ return true;
+});
</ins><span class="cx">
</span><span class="cx"> /**
</span><del>- Remove an event listener
</del><ins>+ A computed property which performs a logical `or` on the
+ original values for the provided dependent properties.
</ins><span class="cx">
</span><del>- Arguments should match those passed to {{#crossLink "Ember/addListener"}}{{/crossLink}}
</del><ins>+ Example
</ins><span class="cx">
</span><del>- @method removeListener
</del><ins>+ ```javascript
+ var Hamster = Ember.Object.extend({
+ readyForRain: Ember.computed.or('hasJacket', 'hasUmbrella')
+ });
+ var hamster = Hamster.create();
+ hamster.get('readyForRain'); // false
+ hamster.set('hasJacket', true);
+ hamster.get('readyForRain'); // true
+ ```
+
+ @method computed.or
</ins><span class="cx"> @for Ember
</span><del>- @param obj
- @param {String} eventName
- @param {Object|Function} targetOrMethod A target object or a function
- @param {Function|String} method A function or the name of a function to be called on `target`
</del><ins>+ @param {String} dependentKey*
+ @return {Ember.ComputedProperty} computed property which performs
+ a logical `or` on the values of all the original values for properties.
</ins><span class="cx"> */
</span><del>-function removeListener(obj, eventName, target, method) {
- Ember.assert("You must pass at least an object and event name to Ember.removeListener", !!obj && !!eventName);
-
- if (!method && 'function' === typeof target) {
- method = target;
- target = null;
</del><ins>+registerComputedWithProperties('or', function(properties) {
+ for (var key in properties) {
+ if (properties.hasOwnProperty(key) && properties[key]) {
+ return true;
+ }
</ins><span class="cx"> }
</span><ins>+ return false;
+});
</ins><span class="cx">
</span><del>- function _removeListener(target, method, once) {
- var actions = actionsFor(obj, eventName),
- actionIndex = indexOf(actions, target, method);
</del><ins>+/**
+ A computed property that returns the first truthy value
+ from a list of dependent properties.
</ins><span class="cx">
</span><del>- // action doesn't exist, give up silently
- if (actionIndex === -1) { return; }
</del><ins>+ Example
</ins><span class="cx">
</span><del>- actions.splice(actionIndex, 1);
</del><ins>+ ```javascript
+ var Hamster = Ember.Object.extend({
+ hasClothes: Ember.computed.any('hat', 'shirt')
+ });
+ var hamster = Hamster.create();
+ hamster.get('hasClothes'); // null
+ hamster.set('shirt', 'Hawaiian Shirt');
+ hamster.get('hasClothes'); // 'Hawaiian Shirt'
+ ```
</ins><span class="cx">
</span><del>- if ('function' === typeof obj.didRemoveListener) {
- obj.didRemoveListener(eventName, target, method);
</del><ins>+ @method computed.any
+ @for Ember
+ @param {String} dependentKey*
+ @return {Ember.ComputedProperty} computed property which returns
+ the first truthy value of given list of properties.
+*/
+registerComputedWithProperties('any', function(properties) {
+ for (var key in properties) {
+ if (properties.hasOwnProperty(key) && properties[key]) {
+ return properties[key];
</ins><span class="cx"> }
</span><span class="cx"> }
</span><ins>+ return null;
+});
</ins><span class="cx">
</span><del>- if (method) {
- _removeListener(target, method);
- } else {
- var meta = obj[META_KEY],
- actions = meta && meta.listeners && meta.listeners[eventName];
</del><ins>+/**
+ A computed property that returns the array of values
+ for the provided dependent properties.
</ins><span class="cx">
</span><del>- if (!actions) { return; }
- for (var i = actions.length - 1; i >= 0; i--) {
- _removeListener(actions[i][0], actions[i][1]);
</del><ins>+ Example
+
+ ```javascript
+ var Hamster = Ember.Object.extend({
+ clothes: Ember.computed.collect('hat', 'shirt')
+ });
+ var hamster = Hamster.create();
+ hamster.get('clothes'); // [null, null]
+ hamster.set('hat', 'Camp Hat');
+ hamster.set('shirt', 'Camp Shirt');
+ hamster.get('clothes'); // ['Camp Hat', 'Camp Shirt']
+ ```
+
+ @method computed.collect
+ @for Ember
+ @param {String} dependentKey*
+ @return {Ember.ComputedProperty} computed property which maps
+ values of all passed properties in to an array.
+*/
+registerComputedWithProperties('collect', function(properties) {
+ var res = [];
+ for (var key in properties) {
+ if (properties.hasOwnProperty(key)) {
+ if (Ember.isNone(properties[key])) {
+ res.push(null);
+ } else {
+ res.push(properties[key]);
+ }
</ins><span class="cx"> }
</span><span class="cx"> }
</span><del>-}
</del><ins>+ return res;
+});
</ins><span class="cx">
</span><span class="cx"> /**
</span><del>- @private
</del><ins>+ Creates a new property that is an alias for another property
+ on an object. Calls to `get` or `set` this property behave as
+ though they were called on the original property.
</ins><span class="cx">
</span><del>- Suspend listener during callback.
</del><ins>+ ```javascript
+ Person = Ember.Object.extend({
+ name: 'Alex Matchneer',
+ nomen: Ember.computed.alias('name')
+ });
</ins><span class="cx">
</span><del>- This should only be used by the target of the event listener
- when it is taking an action that would cause the event, e.g.
- an object might suspend its property change listener while it is
- setting that property.
</del><ins>+ alex = Person.create();
+ alex.get('nomen'); // 'Alex Matchneer'
+ alex.get('name'); // 'Alex Matchneer'
</ins><span class="cx">
</span><del>- @method suspendListener
</del><ins>+ alex.set('nomen', '@machty');
+ alex.get('name'); // '@machty'
+ ```
+ @method computed.alias
</ins><span class="cx"> @for Ember
</span><del>- @param obj
- @param {String} eventName
- @param {Object|Function} targetOrMethod A target object or a function
- @param {Function|String} method A function or the name of a function to be called on `target`
- @param {Function} callback
</del><ins>+ @param {String} dependentKey
+ @return {Ember.ComputedProperty} computed property which creates an
+ alias to the original value for property.
</ins><span class="cx"> */
</span><del>-function suspendListener(obj, eventName, target, method, callback) {
- if (!method && 'function' === typeof target) {
- method = target;
- target = null;
- }
</del><ins>+Ember.computed.alias = function(dependentKey) {
+ return Ember.computed(dependentKey, function(key, value) {
+ if (arguments.length > 1) {
+ set(this, dependentKey, value);
+ return value;
+ } else {
+ return get(this, dependentKey);
+ }
+ });
+};
</ins><span class="cx">
</span><del>- var actions = actionsFor(obj, eventName),
- actionIndex = indexOf(actions, target, method),
- action;
</del><ins>+/**
+ Where `computed.alias` aliases `get` and `set`, and allows for bidirectional
+ data flow, `computed.oneWay` only provides an aliased `get`. The `set` will
+ not mutate the upstream property, rather causes the current property to
+ become the value set. This causes the downstream property to permentantly
+ diverge from the upstream property.
</ins><span class="cx">
</span><del>- if (actionIndex !== -1) {
- action = actions[actionIndex].slice(); // copy it, otherwise we're modifying a shared object
- action[3] = true; // mark the action as suspended
- actions[actionIndex] = action; // replace the shared object with our copy
- }
</del><ins>+ Example
</ins><span class="cx">
</span><del>- function tryable() { return callback.call(target); }
- function finalizer() { if (action) { action[3] = undefined; } }
</del><ins>+ ```javascript
+ User = Ember.Object.extend({
+ firstName: null,
+ lastName: null,
+ nickName: Ember.computed.oneWay('firstName')
+ });
</ins><span class="cx">
</span><del>- return Ember.tryFinally(tryable, finalizer);
-}
</del><ins>+ user = User.create({
+ firstName: 'Teddy',
+ lastName: 'Zeenny'
+ });
</ins><span class="cx">
</span><del>-/**
- @private
</del><ins>+ user.get('nickName');
+ # 'Teddy'
</ins><span class="cx">
</span><del>- Suspend listener during callback.
</del><ins>+ user.set('nickName', 'TeddyBear');
+ # 'TeddyBear'
</ins><span class="cx">
</span><del>- This should only be used by the target of the event listener
- when it is taking an action that would cause the event, e.g.
- an object might suspend its property change listener while it is
- setting that property.
</del><ins>+ user.get('firstName');
+ # 'Teddy'
+ ```
</ins><span class="cx">
</span><del>- @method suspendListener
</del><ins>+ @method computed.oneWay
</ins><span class="cx"> @for Ember
</span><del>- @param obj
- @param {Array} eventName Array of event names
- @param {Object|Function} targetOrMethod A target object or a function
- @param {Function|String} method A function or the name of a function to be called on `target`
- @param {Function} callback
</del><ins>+ @param {String} dependentKey
+ @return {Ember.ComputedProperty} computed property which creates a
+ one way computed property to the original value for property.
</ins><span class="cx"> */
</span><del>-function suspendListeners(obj, eventNames, target, method, callback) {
- if (!method && 'function' === typeof target) {
- method = target;
- target = null;
- }
</del><ins>+Ember.computed.oneWay = function(dependentKey) {
+ return Ember.computed(dependentKey, function() {
+ return get(this, dependentKey);
+ });
+};
</ins><span class="cx">
</span><del>- var suspendedActions = [],
- eventName, actions, action, i, l;
</del><span class="cx">
</span><del>- for (i=0, l=eventNames.length; i<l; i++) {
- eventName = eventNames[i];
- actions = actionsFor(obj, eventName);
- var actionIndex = indexOf(actions, target, method);
</del><ins>+/**
+ A computed property that acts like a standard getter and setter,
+ but returns the value at the provided `defaultPath` if the
+ property itself has not been set to a value
</ins><span class="cx">
</span><del>- if (actionIndex !== -1) {
- action = actions[actionIndex].slice();
- action[3] = true;
- actions[actionIndex] = action;
- suspendedActions.push(action);
- }
- }
</del><ins>+ Example
</ins><span class="cx">
</span><del>- function tryable() { return callback.call(target); }
</del><ins>+ ```javascript
+ var Hamster = Ember.Object.extend({
+ wishList: Ember.computed.defaultTo('favoriteFood')
+ });
+ var hamster = Hamster.create({favoriteFood: 'Banana'});
+ hamster.get('wishList'); // 'Banana'
+ hamster.set('wishList', 'More Unit Tests');
+ hamster.get('wishList'); // 'More Unit Tests'
+ hamster.get('favoriteFood'); // 'Banana'
+ ```
</ins><span class="cx">
</span><del>- function finalizer() {
- for (i = 0, l = suspendedActions.length; i < l; i++) {
- suspendedActions[i][3] = undefined;
</del><ins>+ @method computed.defaultTo
+ @for Ember
+ @param {String} defaultPath
+ @return {Ember.ComputedProperty} computed property which acts like
+ a standard getter and setter, but defaults to the value from `defaultPath`.
+*/
+Ember.computed.defaultTo = function(defaultPath) {
+ return Ember.computed(function(key, newValue, cachedValue) {
+ if (arguments.length === 1) {
+ return cachedValue != null ? cachedValue : get(this, defaultPath);
</ins><span class="cx"> }
</span><del>- }
</del><ins>+ return newValue != null ? newValue : get(this, defaultPath);
+ });
+};
</ins><span class="cx">
</span><del>- return Ember.tryFinally(tryable, finalizer);
-}
</del><span class="cx">
</span><del>-/**
- @private
</del><ins>+})();
</ins><span class="cx">
</span><del>- Return a list of currently watched events
</del><span class="cx">
</span><del>- @method watchedEvents
- @for Ember
- @param obj
</del><ins>+
+(function() {
+// Ember.tryFinally
+/**
+@module ember-metal
</ins><span class="cx"> */
</span><del>-function watchedEvents(obj) {
- var listeners = obj[META_KEY].listeners, ret = [];
</del><span class="cx">
</span><del>- if (listeners) {
- for(var eventName in listeners) {
- if (listeners[eventName]) { ret.push(eventName); }
- }
- }
- return ret;
</del><ins>+var AFTER_OBSERVERS = ':change',
+ BEFORE_OBSERVERS = ':before';
+
+function changeEvent(keyName) {
+ return keyName+AFTER_OBSERVERS;
</ins><span class="cx"> }
</span><span class="cx">
</span><ins>+function beforeEvent(keyName) {
+ return keyName+BEFORE_OBSERVERS;
+}
+
</ins><span class="cx"> /**
</span><del>- @method sendEvent
- @for Ember
</del><ins>+ @method addObserver
</ins><span class="cx"> @param obj
</span><del>- @param {String} eventName
- @param {Array} params
- @return true
</del><ins>+ @param {String} path
+ @param {Object|Function} targetOrMethod
+ @param {Function|String} [method]
</ins><span class="cx"> */
</span><del>-function sendEvent(obj, eventName, params, actions) {
- // first give object a chance to handle it
- if (obj !== Ember && 'function' === typeof obj.sendEvent) {
- obj.sendEvent(eventName, params);
- }
</del><ins>+Ember.addObserver = function(obj, _path, target, method) {
+ Ember.addListener(obj, changeEvent(_path), target, method);
+ Ember.watch(obj, _path);
</ins><span class="cx">
</span><del>- if (!actions) {
- var meta = obj[META_KEY];
- actions = meta && meta.listeners && meta.listeners[eventName];
- }
</del><ins>+ return this;
+};
</ins><span class="cx">
</span><del>- if (!actions) { return; }
</del><ins>+Ember.observersFor = function(obj, path) {
+ return Ember.listenersFor(obj, changeEvent(path));
+};
</ins><span class="cx">
</span><del>- for (var i = actions.length - 1; i >= 0; i--) { // looping in reverse for once listeners
- if (!actions[i] || actions[i][3] === true) { continue; }
-
- var target = actions[i][0],
- method = actions[i][1],
- once = actions[i][2];
-
- if (once) { removeListener(obj, eventName, target, method); }
- if (!target) { target = obj; }
- if ('string' === typeof method) { method = target[method]; }
- if (params) {
- method.apply(target, params);
- } else {
- method.apply(target);
- }
- }
- return true;
-}
-
</del><span class="cx"> /**
</span><del>- @private
- @method hasListeners
- @for Ember
</del><ins>+ @method removeObserver
</ins><span class="cx"> @param obj
</span><del>- @param {String} eventName
</del><ins>+ @param {String} path
+ @param {Object|Function} targetOrMethod
+ @param {Function|String} [method]
</ins><span class="cx"> */
</span><del>-function hasListeners(obj, eventName) {
- var meta = obj[META_KEY],
- actions = meta && meta.listeners && meta.listeners[eventName];
</del><ins>+Ember.removeObserver = function(obj, _path, target, method) {
+ Ember.unwatch(obj, _path);
+ Ember.removeListener(obj, changeEvent(_path), target, method);
</ins><span class="cx">
</span><del>- return !!(actions && actions.length);
-}
</del><ins>+ return this;
+};
</ins><span class="cx">
</span><span class="cx"> /**
</span><del>- @private
- @method listenersFor
- @for Ember
</del><ins>+ @method addBeforeObserver
</ins><span class="cx"> @param obj
</span><del>- @param {String} eventName
</del><ins>+ @param {String} path
+ @param {Object|Function} targetOrMethod
+ @param {Function|String} [method]
</ins><span class="cx"> */
</span><del>-function listenersFor(obj, eventName) {
- var ret = [];
- var meta = obj[META_KEY],
- actions = meta && meta.listeners && meta.listeners[eventName];
</del><ins>+Ember.addBeforeObserver = function(obj, _path, target, method) {
+ Ember.addListener(obj, beforeEvent(_path), target, method);
+ Ember.watch(obj, _path);
</ins><span class="cx">
</span><del>- if (!actions) { return ret; }
</del><ins>+ return this;
+};
</ins><span class="cx">
</span><del>- for (var i = 0, l = actions.length; i < l; i++) {
- var target = actions[i][0],
- method = actions[i][1];
- ret.push([target, method]);
- }
</del><ins>+// Suspend observer during callback.
+//
+// This should only be used by the target of the observer
+// while it is setting the observed path.
+Ember._suspendBeforeObserver = function(obj, path, target, method, callback) {
+ return Ember._suspendListener(obj, beforeEvent(path), target, method, callback);
+};
</ins><span class="cx">
</span><del>- return ret;
-}
</del><ins>+Ember._suspendObserver = function(obj, path, target, method, callback) {
+ return Ember._suspendListener(obj, changeEvent(path), target, method, callback);
+};
</ins><span class="cx">
</span><del>-Ember.addListener = addListener;
-Ember.removeListener = removeListener;
-Ember._suspendListener = suspendListener;
-Ember._suspendListeners = suspendListeners;
-Ember.sendEvent = sendEvent;
-Ember.hasListeners = hasListeners;
-Ember.watchedEvents = watchedEvents;
-Ember.listenersFor = listenersFor;
-Ember.listenersDiff = actionsDiff;
-Ember.listenersUnion = actionsUnion;
</del><ins>+var map = Ember.ArrayPolyfills.map;
</ins><span class="cx">
</span><ins>+Ember._suspendBeforeObservers = function(obj, paths, target, method, callback) {
+ var events = map.call(paths, beforeEvent);
+ return Ember._suspendListeners(obj, events, target, method, callback);
+};
+
+Ember._suspendObservers = function(obj, paths, target, method, callback) {
+ var events = map.call(paths, changeEvent);
+ return Ember._suspendListeners(obj, events, target, method, callback);
+};
+
+Ember.beforeObserversFor = function(obj, path) {
+ return Ember.listenersFor(obj, beforeEvent(path));
+};
+
+/**
+ @method removeBeforeObserver
+ @param obj
+ @param {String} path
+ @param {Object|Function} targetOrMethod
+ @param {Function|String} [method]
+*/
+Ember.removeBeforeObserver = function(obj, _path, target, method) {
+ Ember.unwatch(obj, _path);
+ Ember.removeListener(obj, beforeEvent(_path), target, method);
+
+ return this;
+};
+
</ins><span class="cx"> })();
</span><span class="cx">
</span><span class="cx">
</span><span class="cx">
</span><span class="cx"> (function() {
</span><del>-// Ember.Logger
-// Ember.watch.flushPending
-// Ember.beginPropertyChanges, Ember.endPropertyChanges
-// Ember.guidFor, Ember.tryFinally
</del><ins>+define("backburner/queue",
+ ["exports"],
+ function(__exports__) {
+ "use strict";
+ function Queue(daq, name, options) {
+ this.daq = daq;
+ this.name = name;
+ this.options = options;
+ this._queue = [];
+ }
</ins><span class="cx">
</span><del>-/**
-@module ember-metal
-*/
</del><ins>+ Queue.prototype = {
+ daq: null,
+ name: null,
+ options: null,
+ _queue: null,
</ins><span class="cx">
</span><del>-// ..........................................................
-// HELPERS
-//
</del><ins>+ push: function(target, method, args, stack) {
+ var queue = this._queue;
+ queue.push(target, method, args, stack);
+ return {queue: this, target: target, method: method};
+ },
</ins><span class="cx">
</span><del>-var slice = [].slice,
- forEach = Ember.ArrayPolyfills.forEach;
</del><ins>+ pushUnique: function(target, method, args, stack) {
+ var queue = this._queue, currentTarget, currentMethod, i, l;
</ins><span class="cx">
</span><del>-// invokes passed params - normalizing so you can pass target/func,
-// target/string or just func
-function invoke(target, method, args, ignore) {
</del><ins>+ for (i = 0, l = queue.length; i < l; i += 4) {
+ currentTarget = queue[i];
+ currentMethod = queue[i+1];
</ins><span class="cx">
</span><del>- if (method === undefined) {
- method = target;
- target = undefined;
- }
</del><ins>+ if (currentTarget === target && currentMethod === method) {
+ queue[i+2] = args; // replace args
+ queue[i+3] = stack; // replace stack
+ return {queue: this, target: target, method: method}; // TODO: test this code path
+ }
+ }
</ins><span class="cx">
</span><del>- if ('string' === typeof method) { method = target[method]; }
- if (args && ignore > 0) {
- args = args.length > ignore ? slice.call(args, ignore) : null;
- }
</del><ins>+ this._queue.push(target, method, args, stack);
+ return {queue: this, target: target, method: method};
+ },
</ins><span class="cx">
</span><del>- return Ember.handleErrors(function() {
- // IE8's Function.prototype.apply doesn't accept undefined/null arguments.
- return method.apply(target || this, args || []);
- }, this);
-}
</del><ins>+ // TODO: remove me, only being used for Ember.run.sync
+ flush: function() {
+ var queue = this._queue,
+ options = this.options,
+ before = options && options.before,
+ after = options && options.after,
+ target, method, args, stack, i, l = queue.length;
</ins><span class="cx">
</span><ins>+ if (l && before) { before(); }
+ for (i = 0; i < l; i += 4) {
+ target = queue[i];
+ method = queue[i+1];
+ args = queue[i+2];
+ stack = queue[i+3]; // Debugging assistance
</ins><span class="cx">
</span><del>-// ..........................................................
-// RUNLOOP
-//
</del><ins>+ // TODO: error handling
+ if (args && args.length > 0) {
+ method.apply(target, args);
+ } else {
+ method.call(target);
+ }
+ }
+ if (l && after) { after(); }
</ins><span class="cx">
</span><del>-var timerMark; // used by timers...
</del><ins>+ // check if new items have been added
+ if (queue.length > l) {
+ this._queue = queue.slice(l);
+ this.flush();
+ } else {
+ this._queue.length = 0;
+ }
+ },
</ins><span class="cx">
</span><del>-/**
-Ember RunLoop (Private)
</del><ins>+ cancel: function(actionToCancel) {
+ var queue = this._queue, currentTarget, currentMethod, i, l;
</ins><span class="cx">
</span><del>-@class RunLoop
-@namespace Ember
-@private
-@constructor
-*/
-var RunLoop = function(prev) {
- this._prev = prev || null;
- this.onceTimers = {};
-};
</del><ins>+ for (i = 0, l = queue.length; i < l; i += 4) {
+ currentTarget = queue[i];
+ currentMethod = queue[i+1];
</ins><span class="cx">
</span><del>-RunLoop.prototype = {
- /**
- @method end
- */
- end: function() {
- this.flush();
- },
</del><ins>+ if (currentTarget === actionToCancel.target && currentMethod === actionToCancel.method) {
+ queue.splice(i, 4);
+ return true;
+ }
+ }
</ins><span class="cx">
</span><del>- /**
- @method prev
- */
- prev: function() {
- return this._prev;
- },
</del><ins>+ // if not found in current queue
+ // could be in the queue that is being flushed
+ queue = this._queueBeingFlushed;
+ if (!queue) {
+ return;
+ }
+ for (i = 0, l = queue.length; i < l; i += 4) {
+ currentTarget = queue[i];
+ currentMethod = queue[i+1];
</ins><span class="cx">
</span><del>- // ..........................................................
- // Delayed Actions
- //
</del><ins>+ if (currentTarget === actionToCancel.target && currentMethod === actionToCancel.method) {
+ // don't mess with array during flush
+ // just nullify the method
+ queue[i+1] = null;
+ return true;
+ }
+ }
+ }
+ };
</ins><span class="cx">
</span><del>- /**
- @method schedule
- @param {String} queueName
- @param target
- @param method
- */
- schedule: function(queueName, target, method) {
- var queues = this._queues, queue;
- if (!queues) { queues = this._queues = {}; }
- queue = queues[queueName];
- if (!queue) { queue = queues[queueName] = []; }
</del><span class="cx">
</span><del>- var args = arguments.length > 3 ? slice.call(arguments, 3) : null;
- queue.push({ target: target, method: method, args: args });
- return this;
- },
</del><ins>+ __exports__.Queue = Queue;
+ });
</ins><span class="cx">
</span><del>- /**
- @method flush
- @param {String} queueName
- */
- flush: function(queueName) {
- var queueNames, idx, len, queue, log;
</del><ins>+define("backburner/deferred_action_queues",
+ ["backburner/queue","exports"],
+ function(__dependency1__, __exports__) {
+ "use strict";
+ var Queue = __dependency1__.Queue;
</ins><span class="cx">
</span><del>- if (!this._queues) { return this; } // nothing to do
</del><ins>+ function DeferredActionQueues(queueNames, options) {
+ var queues = this.queues = {};
+ this.queueNames = queueNames = queueNames || [];
</ins><span class="cx">
</span><del>- function iter(item) {
- invoke(item.target, item.method, item.args);
</del><ins>+ var queueName;
+ for (var i = 0, l = queueNames.length; i < l; i++) {
+ queueName = queueNames[i];
+ queues[queueName] = new Queue(this, queueName, options[queueName]);
+ }
</ins><span class="cx"> }
</span><span class="cx">
</span><del>- function tryable() {
- forEach.call(queue, iter);
- }
</del><ins>+ DeferredActionQueues.prototype = {
+ queueNames: null,
+ queues: null,
</ins><span class="cx">
</span><del>- Ember.watch.flushPending(); // make sure all chained watchers are setup
</del><ins>+ schedule: function(queueName, target, method, args, onceFlag, stack) {
+ var queues = this.queues,
+ queue = queues[queueName];
</ins><span class="cx">
</span><del>- if (queueName) {
- while (this._queues && (queue = this._queues[queueName])) {
- this._queues[queueName] = null;
</del><ins>+ if (!queue) { throw new Error("You attempted to schedule an action in a queue (" + queueName + ") that doesn't exist"); }
</ins><span class="cx">
</span><del>- // the sync phase is to allow property changes to propagate. don't
- // invoke observers until that is finished.
- if (queueName === 'sync') {
- log = Ember.LOG_BINDINGS;
- if (log) { Ember.Logger.log('Begin: Flush Sync Queue'); }
</del><ins>+ if (onceFlag) {
+ return queue.pushUnique(target, method, args, stack);
+ } else {
+ return queue.push(target, method, args, stack);
+ }
+ },
</ins><span class="cx">
</span><del>- Ember.beginPropertyChanges();
</del><ins>+ flush: function() {
+ var queues = this.queues,
+ queueNames = this.queueNames,
+ queueName, queue, queueItems, priorQueueNameIndex,
+ queueNameIndex = 0, numberOfQueues = queueNames.length;
</ins><span class="cx">
</span><del>- Ember.tryFinally(tryable, Ember.endPropertyChanges);
</del><ins>+ outerloop:
+ while (queueNameIndex < numberOfQueues) {
+ queueName = queueNames[queueNameIndex];
+ queue = queues[queueName];
+ queueItems = queue._queueBeingFlushed = queue._queue.slice();
+ queue._queue = [];
</ins><span class="cx">
</span><del>- if (log) { Ember.Logger.log('End: Flush Sync Queue'); }
</del><ins>+ var options = queue.options,
+ before = options && options.before,
+ after = options && options.after,
+ target, method, args, stack,
+ queueIndex = 0, numberOfQueueItems = queueItems.length;
</ins><span class="cx">
</span><del>- } else {
- forEach.call(queue, iter);
</del><ins>+ if (numberOfQueueItems && before) { before(); }
+ while (queueIndex < numberOfQueueItems) {
+ target = queueItems[queueIndex];
+ method = queueItems[queueIndex+1];
+ args = queueItems[queueIndex+2];
+ stack = queueItems[queueIndex+3]; // Debugging assistance
+
+ if (typeof method === 'string') { method = target[method]; }
+
+ // method could have been nullified / canceled during flush
+ if (method) {
+ // TODO: error handling
+ if (args && args.length > 0) {
+ method.apply(target, args);
+ } else {
+ method.call(target);
+ }
+ }
+
+ queueIndex += 4;
+ }
+ queue._queueBeingFlushed = null;
+ if (numberOfQueueItems && after) { after(); }
+
+ if ((priorQueueNameIndex = indexOfPriorQueueWithActions(this, queueNameIndex)) !== -1) {
+ queueNameIndex = priorQueueNameIndex;
+ continue outerloop;
+ }
+
+ queueNameIndex++;
</ins><span class="cx"> }
</span><span class="cx"> }
</span><ins>+ };
</ins><span class="cx">
</span><del>- } else {
- queueNames = Ember.run.queues;
- len = queueNames.length;
- idx = 0;
</del><ins>+ function indexOfPriorQueueWithActions(daq, currentQueueIndex) {
+ var queueName, queue;
</ins><span class="cx">
</span><del>- outerloop:
- while (idx < len) {
- queueName = queueNames[idx];
- queue = this._queues && this._queues[queueName];
- delete this._queues[queueName];
</del><ins>+ for (var i = 0, l = currentQueueIndex; i <= l; i++) {
+ queueName = daq.queueNames[i];
+ queue = daq.queues[queueName];
+ if (queue._queue.length) { return i; }
+ }
</ins><span class="cx">
</span><del>- if (queue) {
- // the sync phase is to allow property changes to propagate. don't
- // invoke observers until that is finished.
- if (queueName === 'sync') {
- log = Ember.LOG_BINDINGS;
- if (log) { Ember.Logger.log('Begin: Flush Sync Queue'); }
</del><ins>+ return -1;
+ }
</ins><span class="cx">
</span><del>- Ember.beginPropertyChanges();
</del><span class="cx">
</span><del>- Ember.tryFinally(tryable, Ember.endPropertyChanges);
</del><ins>+ __exports__.DeferredActionQueues = DeferredActionQueues;
+ });
</ins><span class="cx">
</span><del>- if (log) { Ember.Logger.log('End: Flush Sync Queue'); }
</del><ins>+define("backburner",
+ ["backburner/deferred_action_queues","exports"],
+ function(__dependency1__, __exports__) {
+ "use strict";
+ var DeferredActionQueues = __dependency1__.DeferredActionQueues;
+
+ var slice = [].slice,
+ pop = [].pop,
+ throttlers = [],
+ debouncees = [],
+ timers = [],
+ autorun, laterTimer, laterTimerExpiresAt,
+ global = this,
+ NUMBER = /\d+/;
+
+ function isCoercableNumber(number) {
+ return typeof number === 'number' || NUMBER.test(number);
+ }
+
+ function Backburner(queueNames, options) {
+ this.queueNames = queueNames;
+ this.options = options || {};
+ if (!this.options.defaultQueue) {
+ this.options.defaultQueue = queueNames[0];
+ }
+ this.instanceStack = [];
+ }
+
+ Backburner.prototype = {
+ queueNames: null,
+ options: null,
+ currentInstance: null,
+ instanceStack: null,
+
+ begin: function() {
+ var onBegin = this.options && this.options.onBegin,
+ previousInstance = this.currentInstance;
+
+ if (previousInstance) {
+ this.instanceStack.push(previousInstance);
+ }
+
+ this.currentInstance = new DeferredActionQueues(this.queueNames, this.options);
+ if (onBegin) {
+ onBegin(this.currentInstance, previousInstance);
+ }
+ },
+
+ end: function() {
+ var onEnd = this.options && this.options.onEnd,
+ currentInstance = this.currentInstance,
+ nextInstance = null;
+
+ try {
+ currentInstance.flush();
+ } finally {
+ this.currentInstance = null;
+
+ if (this.instanceStack.length) {
+ nextInstance = this.instanceStack.pop();
+ this.currentInstance = nextInstance;
+ }
+
+ if (onEnd) {
+ onEnd(currentInstance, nextInstance);
+ }
+ }
+ },
+
+ run: function(target, method /*, args */) {
+ var ret;
+ this.begin();
+
+ if (!method) {
+ method = target;
+ target = null;
+ }
+
+ if (typeof method === 'string') {
+ method = target[method];
+ }
+
+ // Prevent Safari double-finally.
+ var finallyAlreadyCalled = false;
+ try {
+ if (arguments.length > 2) {
+ ret = method.apply(target, slice.call(arguments, 2));
</ins><span class="cx"> } else {
</span><del>- forEach.call(queue, iter);
</del><ins>+ ret = method.call(target);
</ins><span class="cx"> }
</span><ins>+ } finally {
+ if (!finallyAlreadyCalled) {
+ finallyAlreadyCalled = true;
+ this.end();
+ }
</ins><span class="cx"> }
</span><ins>+ return ret;
+ },
</ins><span class="cx">
</span><del>- // Loop through prior queues
- for (var i = 0; i <= idx; i++) {
- if (this._queues && this._queues[queueNames[i]]) {
- // Start over at the first queue with contents
- idx = i;
- continue outerloop;
</del><ins>+ defer: function(queueName, target, method /* , args */) {
+ if (!method) {
+ method = target;
+ target = null;
+ }
+
+ if (typeof method === 'string') {
+ method = target[method];
+ }
+
+ var stack = this.DEBUG ? new Error() : undefined,
+ args = arguments.length > 3 ? slice.call(arguments, 3) : undefined;
+ if (!this.currentInstance) { createAutorun(this); }
+ return this.currentInstance.schedule(queueName, target, method, args, false, stack);
+ },
+
+ deferOnce: function(queueName, target, method /* , args */) {
+ if (!method) {
+ method = target;
+ target = null;
+ }
+
+ if (typeof method === 'string') {
+ method = target[method];
+ }
+
+ var stack = this.DEBUG ? new Error() : undefined,
+ args = arguments.length > 3 ? slice.call(arguments, 3) : undefined;
+ if (!this.currentInstance) { createAutorun(this); }
+ return this.currentInstance.schedule(queueName, target, method, args, true, stack);
+ },
+
+ setTimeout: function() {
+ var args = slice.call(arguments);
+ var length = args.length;
+ var method, wait, target;
+ var self = this;
+ var methodOrTarget, methodOrWait, methodOrArgs;
+
+ if (length === 0) {
+ return;
+ } else if (length === 1) {
+ method = args.shift();
+ wait = 0;
+ } else if (length === 2) {
+ methodOrTarget = args[0];
+ methodOrWait = args[1];
+
+ if (typeof methodOrWait === 'function' || typeof methodOrTarget[methodOrWait] === 'function') {
+ target = args.shift();
+ method = args.shift();
+ wait = 0;
+ } else if (isCoercableNumber(methodOrWait)) {
+ method = args.shift();
+ wait = args.shift();
+ } else {
+ method = args.shift();
+ wait = 0;
</ins><span class="cx"> }
</span><ins>+ } else {
+ var last = args[args.length - 1];
+
+ if (isCoercableNumber(last)) {
+ wait = args.pop();
+ }
+
+ methodOrTarget = args[0];
+ methodOrArgs = args[1];
+
+ if (typeof methodOrArgs === 'function' || (typeof methodOrArgs === 'string' &&
+ methodOrTarget !== null &&
+ methodOrArgs in methodOrTarget)) {
+ target = args.shift();
+ method = args.shift();
+ } else {
+ method = args.shift();
+ }
</ins><span class="cx"> }
</span><span class="cx">
</span><del>- idx++;
</del><ins>+ var executeAt = (+new Date()) + parseInt(wait, 10);
+
+ if (typeof method === 'string') {
+ method = target[method];
+ }
+
+ function fn() {
+ method.apply(target, args);
+ }
+
+ // find position to insert - TODO: binary search
+ var i, l;
+ for (i = 0, l = timers.length; i < l; i += 2) {
+ if (executeAt < timers[i]) { break; }
+ }
+
+ timers.splice(i, 0, executeAt, fn);
+
+ updateLaterTimer(self, executeAt, wait);
+
+ return fn;
+ },
+
+ throttle: function(target, method /* , args, wait */) {
+ var self = this,
+ args = arguments,
+ wait = parseInt(pop.call(args), 10),
+ throttler,
+ index,
+ timer;
+
+ index = findThrottler(target, method);
+ if (index > -1) { return throttlers[index]; } // throttled
+
+ timer = global.setTimeout(function() {
+ self.run.apply(self, args);
+
+ var index = findThrottler(target, method);
+ if (index > -1) { throttlers.splice(index, 1); }
+ }, wait);
+
+ throttler = [target, method, timer];
+
+ throttlers.push(throttler);
+
+ return throttler;
+ },
+
+ debounce: function(target, method /* , args, wait, [immediate] */) {
+ var self = this,
+ args = arguments,
+ immediate = pop.call(args),
+ wait,
+ index,
+ debouncee,
+ timer;
+
+ if (typeof immediate === "number" || typeof immediate === "string") {
+ wait = immediate;
+ immediate = false;
+ } else {
+ wait = pop.call(args);
+ }
+
+ wait = parseInt(wait, 10);
+ // Remove debouncee
+ index = findDebouncee(target, method);
+
+ if (index > -1) {
+ debouncee = debouncees[index];
+ debouncees.splice(index, 1);
+ clearTimeout(debouncee[2]);
+ }
+
+ timer = global.setTimeout(function() {
+ if (!immediate) {
+ self.run.apply(self, args);
+ }
+ var index = findDebouncee(target, method);
+ if (index > -1) {
+ debouncees.splice(index, 1);
+ }
+ }, wait);
+
+ if (immediate && index === -1) {
+ self.run.apply(self, args);
+ }
+
+ debouncee = [target, method, timer];
+
+ debouncees.push(debouncee);
+
+ return debouncee;
+ },
+
+ cancelTimers: function() {
+ var i, len;
+
+ for (i = 0, len = throttlers.length; i < len; i++) {
+ clearTimeout(throttlers[i][2]);
+ }
+ throttlers = [];
+
+ for (i = 0, len = debouncees.length; i < len; i++) {
+ clearTimeout(debouncees[i][2]);
+ }
+ debouncees = [];
+
+ if (laterTimer) {
+ clearTimeout(laterTimer);
+ laterTimer = null;
+ }
+ timers = [];
+
+ if (autorun) {
+ clearTimeout(autorun);
+ autorun = null;
+ }
+ },
+
+ hasTimers: function() {
+ return !!timers.length || autorun;
+ },
+
+ cancel: function(timer) {
+ var timerType = typeof timer;
+
+ if (timer && timerType === 'object' && timer.queue && timer.method) { // we're cancelling a deferOnce
+ return timer.queue.cancel(timer);
+ } else if (timerType === 'function') { // we're cancelling a setTimeout
+ for (var i = 0, l = timers.length; i < l; i += 2) {
+ if (timers[i + 1] === timer) {
+ timers.splice(i, 2); // remove the two elements
+ return true;
+ }
+ }
+ } else if (window.toString.call(timer) === "[object Array]"){ // we're cancelling a throttle or debounce
+ return this._cancelItem(findThrottler, throttlers, timer) ||
+ this._cancelItem(findDebouncee, debouncees, timer);
+ } else {
+ return; // timer was null or not a timer
+ }
+ },
+
+ _cancelItem: function(findMethod, array, timer){
+ var item,
+ index;
+
+ if (timer.length < 3) { return false; }
+
+ index = findMethod(timer[0], timer[1]);
+
+ if(index > -1) {
+
+ item = array[index];
+
+ if(item[2] === timer[2]){
+ array.splice(index, 1);
+ clearTimeout(timer[2]);
+ return true;
+ }
+ }
+
+ return false;
</ins><span class="cx"> }
</span><ins>+
+ };
+
+ Backburner.prototype.schedule = Backburner.prototype.defer;
+ Backburner.prototype.scheduleOnce = Backburner.prototype.deferOnce;
+ Backburner.prototype.later = Backburner.prototype.setTimeout;
+
+ function createAutorun(backburner) {
+ backburner.begin();
+ autorun = global.setTimeout(function() {
+ autorun = null;
+ backburner.end();
+ });
</ins><span class="cx"> }
</span><span class="cx">
</span><del>- timerMark = null;
</del><ins>+ function updateLaterTimer(self, executeAt, wait) {
+ if (!laterTimer || executeAt < laterTimerExpiresAt) {
+ if (laterTimer) {
+ clearTimeout(laterTimer);
+ }
+ laterTimer = global.setTimeout(function() {
+ laterTimer = null;
+ laterTimerExpiresAt = null;
+ executeTimers(self);
+ }, wait);
+ laterTimerExpiresAt = executeAt;
+ }
+ }
</ins><span class="cx">
</span><del>- return this;
- }
</del><ins>+ function executeTimers(self) {
+ var now = +new Date(),
+ time, fns, i, l;
</ins><span class="cx">
</span><ins>+ self.run(function() {
+ // TODO: binary search
+ for (i = 0, l = timers.length; i < l; i += 2) {
+ time = timers[i];
+ if (time > now) { break; }
+ }
+
+ fns = timers.splice(0, i);
+
+ for (i = 1, l = fns.length; i < l; i += 2) {
+ self.schedule(self.options.defaultQueue, null, fns[i]);
+ }
+ });
+
+ if (timers.length) {
+ updateLaterTimer(self, timers[0], timers[0] - now);
+ }
+ }
+
+ function findDebouncee(target, method) {
+ var debouncee,
+ index = -1;
+
+ for (var i = 0, l = debouncees.length; i < l; i++) {
+ debouncee = debouncees[i];
+ if (debouncee[0] === target && debouncee[1] === method) {
+ index = i;
+ break;
+ }
+ }
+
+ return index;
+ }
+
+ function findThrottler(target, method) {
+ var throttler,
+ index = -1;
+
+ for (var i = 0, l = throttlers.length; i < l; i++) {
+ throttler = throttlers[i];
+ if (throttler[0] === target && throttler[1] === method) {
+ index = i;
+ break;
+ }
+ }
+
+ return index;
+ }
+
+
+ __exports__.Backburner = Backburner;
+ });
+})();
+
+
+
+(function() {
+var onBegin = function(current) {
+ Ember.run.currentRunLoop = current;
</ins><span class="cx"> };
</span><span class="cx">
</span><del>-Ember.RunLoop = RunLoop;
</del><ins>+var onEnd = function(current, next) {
+ Ember.run.currentRunLoop = next;
+};
</ins><span class="cx">
</span><ins>+var Backburner = requireModule('backburner').Backburner,
+ backburner = new Backburner(['sync', 'actions', 'destroy'], {
+ sync: {
+ before: Ember.beginPropertyChanges,
+ after: Ember.endPropertyChanges
+ },
+ defaultQueue: 'actions',
+ onBegin: onBegin,
+ onEnd: onEnd
+ }),
+ slice = [].slice;
+
</ins><span class="cx"> // ..........................................................
</span><span class="cx"> // Ember.run - this is ideally the only public API the dev sees
</span><span class="cx"> //
</span><span class="lines">@@ -4226,8 +6152,8 @@
</span><span class="cx"> call.
</span><span class="cx">
</span><span class="cx"> ```javascript
</span><del>- Ember.run(function(){
- // code to be execute within a RunLoop
</del><ins>+ Ember.run(function() {
+ // code to be execute within a RunLoop
</ins><span class="cx"> });
</span><span class="cx"> ```
</span><span class="cx">
</span><span class="lines">@@ -4243,30 +6169,84 @@
</span><span class="cx"> @return {Object} return value from invoking the passed function.
</span><span class="cx"> */
</span><span class="cx"> Ember.run = function(target, method) {
</span><del>- var loop,
- args = arguments;
- run.begin();
</del><ins>+ var ret;
</ins><span class="cx">
</span><del>- function tryable() {
- if (target || method) {
- return invoke(target, method, args, 2);
</del><ins>+ if (Ember.onerror) {
+ try {
+ ret = backburner.run.apply(backburner, arguments);
+ } catch (e) {
+ Ember.onerror(e);
</ins><span class="cx"> }
</span><ins>+ } else {
+ ret = backburner.run.apply(backburner, arguments);
</ins><span class="cx"> }
</span><span class="cx">
</span><del>- return Ember.tryFinally(tryable, run.end);
</del><ins>+ return ret;
</ins><span class="cx"> };
</span><span class="cx">
</span><ins>+/**
+ If no run-loop is present, it creates a new one. If a run loop is
+ present it will queue itself to run on the existing run-loops action
+ queue.
+
+ Please note: This is not for normal usage, and should be used sparingly.
+
+ If invoked when not within a run loop:
+
+ ```javascript
+ Ember.run.join(function() {
+ // creates a new run-loop
+ });
+ ```
+
+ Alternatively, if called within an existing run loop:
+
+ ```javascript
+ Ember.run(function() {
+ // creates a new run-loop
+ Ember.run.join(function() {
+ // joins with the existing run-loop, and queues for invocation on
+ // the existing run-loops action queue.
+ });
+ });
+ ```
+
+ @method join
+ @namespace Ember
+ @param {Object} [target] target of method to call
+ @param {Function|String} method Method to invoke.
+ May be a function or a string. If you pass a string
+ then it will be looked up on the passed target.
+ @param {Object} [args*] Any additional arguments you wish to pass to the method.
+ @return {Object} Return value from invoking the passed function. Please note,
+ when called within an existing loop, no return value is possible.
+*/
+Ember.run.join = function(target, method) {
+ if (!Ember.run.currentRunLoop) {
+ return Ember.run.apply(Ember.run, arguments);
+ }
+
+ var args = slice.call(arguments);
+ args.unshift('actions');
+ Ember.run.schedule.apply(Ember.run, args);
+};
+
+Ember.run.backburner = backburner;
+
</ins><span class="cx"> var run = Ember.run;
</span><span class="cx">
</span><ins>+Ember.run.currentRunLoop = null;
</ins><span class="cx">
</span><ins>+Ember.run.queues = backburner.queueNames;
+
</ins><span class="cx"> /**
</span><span class="cx"> Begins a new RunLoop. Any deferred actions invoked after the begin will
</span><span class="cx"> be buffered until you invoke a matching call to `Ember.run.end()`. This is
</span><del>- an lower-level way to use a RunLoop instead of using `Ember.run()`.
</del><ins>+ a lower-level way to use a RunLoop instead of using `Ember.run()`.
</ins><span class="cx">
</span><span class="cx"> ```javascript
</span><span class="cx"> Ember.run.begin();
</span><del>- // code to be execute within a RunLoop
</del><ins>+ // code to be execute within a RunLoop
</ins><span class="cx"> Ember.run.end();
</span><span class="cx"> ```
</span><span class="cx">
</span><span class="lines">@@ -4274,7 +6254,7 @@
</span><span class="cx"> @return {void}
</span><span class="cx"> */
</span><span class="cx"> Ember.run.begin = function() {
</span><del>- run.currentRunLoop = new RunLoop(run.currentRunLoop);
</del><ins>+ backburner.begin();
</ins><span class="cx"> };
</span><span class="cx">
</span><span class="cx"> /**
</span><span class="lines">@@ -4284,7 +6264,7 @@
</span><span class="cx">
</span><span class="cx"> ```javascript
</span><span class="cx"> Ember.run.begin();
</span><del>- // code to be execute within a RunLoop
</del><ins>+ // code to be execute within a RunLoop
</ins><span class="cx"> Ember.run.end();
</span><span class="cx"> ```
</span><span class="cx">
</span><span class="lines">@@ -4292,12 +6272,7 @@
</span><span class="cx"> @return {void}
</span><span class="cx"> */
</span><span class="cx"> Ember.run.end = function() {
</span><del>- Ember.assert('must have a current run loop', run.currentRunLoop);
-
- function tryable() { run.currentRunLoop.end(); }
- function finalizer() { run.currentRunLoop = run.currentRunLoop.prev(); }
-
- Ember.tryFinally(tryable, finalizer);
</del><ins>+ backburner.end();
</ins><span class="cx"> };
</span><span class="cx">
</span><span class="cx"> /**
</span><span class="lines">@@ -4308,9 +6283,8 @@
</span><span class="cx">
</span><span class="cx"> @property queues
</span><span class="cx"> @type Array
</span><del>- @default ['sync', 'actions', 'destroy', 'timers']
</del><ins>+ @default ['sync', 'actions', 'destroy']
</ins><span class="cx"> */
</span><del>-Ember.run.queues = ['sync', 'actions', 'destroy', 'timers'];
</del><span class="cx">
</span><span class="cx"> /**
</span><span class="cx"> Adds the passed target/method and any optional arguments to the named
</span><span class="lines">@@ -4320,22 +6294,23 @@
</span><span class="cx">
</span><span class="cx"> At the end of a RunLoop, any methods scheduled in this way will be invoked.
</span><span class="cx"> Methods will be invoked in an order matching the named queues defined in
</span><del>- the `run.queues` property.
</del><ins>+ the `Ember.run.queues` property.
</ins><span class="cx">
</span><span class="cx"> ```javascript
</span><del>- Ember.run.schedule('timers', this, function(){
- // this will be executed at the end of the RunLoop, when timers are run
- console.log("scheduled on timers queue");
</del><ins>+ Ember.run.schedule('sync', this, function() {
+ // this will be executed in the first RunLoop queue, when bindings are synced
+ console.log("scheduled on sync queue");
</ins><span class="cx"> });
</span><span class="cx">
</span><del>- Ember.run.schedule('sync', this, function(){
- // this will be executed at the end of the RunLoop, when bindings are synced
- console.log("scheduled on sync queue");
</del><ins>+ Ember.run.schedule('actions', this, function() {
+ // this will be executed in the 'actions' queue, after bindings have synced.
+ console.log("scheduled on actions queue");
</ins><span class="cx"> });
</span><span class="cx">
</span><del>- // Note the functions will be run in order based on the run queues order. Output would be:
</del><ins>+ // Note the functions will be run in order based on the run queues order.
+ // Output would be:
</ins><span class="cx"> // scheduled on sync queue
</span><del>- // scheduled on timers queue
</del><ins>+ // scheduled on actions queue
</ins><span class="cx"> ```
</span><span class="cx">
</span><span class="cx"> @method schedule
</span><span class="lines">@@ -4349,70 +6324,28 @@
</span><span class="cx"> @return {void}
</span><span class="cx"> */
</span><span class="cx"> Ember.run.schedule = function(queue, target, method) {
</span><del>- var loop = run.autorun();
- loop.schedule.apply(loop, arguments);
</del><ins>+ checkAutoRun();
+ backburner.schedule.apply(backburner, arguments);
</ins><span class="cx"> };
</span><span class="cx">
</span><del>-var scheduledAutorun;
-function autorun() {
- scheduledAutorun = null;
- if (run.currentRunLoop) { run.end(); }
-}
-
</del><span class="cx"> // Used by global test teardown
</span><span class="cx"> Ember.run.hasScheduledTimers = function() {
</span><del>- return !!(scheduledAutorun || scheduledLater || scheduledNext);
</del><ins>+ return backburner.hasTimers();
</ins><span class="cx"> };
</span><span class="cx">
</span><span class="cx"> // Used by global test teardown
</span><span class="cx"> Ember.run.cancelTimers = function () {
</span><del>- if (scheduledAutorun) {
- clearTimeout(scheduledAutorun);
- scheduledAutorun = null;
- }
- if (scheduledLater) {
- clearTimeout(scheduledLater);
- scheduledLater = null;
- }
- if (scheduledNext) {
- clearTimeout(scheduledNext);
- scheduledNext = null;
- }
- timers = {};
</del><ins>+ backburner.cancelTimers();
</ins><span class="cx"> };
</span><span class="cx">
</span><span class="cx"> /**
</span><del>- Begins a new RunLoop if necessary and schedules a timer to flush the
- RunLoop at a later time. This method is used by parts of Ember to
- ensure the RunLoop always finishes. You normally do not need to call this
- method directly. Instead use `Ember.run()`
-
- @method autorun
- @example
- Ember.run.autorun();
- @return {Ember.RunLoop} the new current RunLoop
-*/
-Ember.run.autorun = function() {
- if (!run.currentRunLoop) {
- Ember.assert("You have turned on testing mode, which disabled the run-loop's autorun. You will need to wrap any code with asynchronous side-effects in an Ember.run", !Ember.testing);
-
- run.begin();
-
- if (!scheduledAutorun) {
- scheduledAutorun = setTimeout(autorun, 1);
- }
- }
-
- return run.currentRunLoop;
-};
-
-/**
</del><span class="cx"> Immediately flushes any events scheduled in the 'sync' queue. Bindings
</span><span class="cx"> use this queue so this method is a useful way to immediately force all
</span><span class="cx"> bindings in the application to sync.
</span><span class="cx">
</span><span class="cx"> You should call this method anytime you need any changed state to propagate
</span><del>- throughout the app immediately without repainting the UI.
</del><ins>+ throughout the app immediately without repainting the UI (which happens
+ in the later 'render' queue added by the `ember-views` package).
</ins><span class="cx">
</span><span class="cx"> ```javascript
</span><span class="cx"> Ember.run.sync();
</span><span class="lines">@@ -4422,37 +6355,11 @@
</span><span class="cx"> @return {void}
</span><span class="cx"> */
</span><span class="cx"> Ember.run.sync = function() {
</span><del>- run.autorun();
- run.currentRunLoop.flush('sync');
</del><ins>+ if (backburner.currentInstance) {
+ backburner.currentInstance.queues.sync.flush();
+ }
</ins><span class="cx"> };
</span><span class="cx">
</span><del>-// ..........................................................
-// TIMERS
-//
-
-var timers = {}; // active timers...
-
-var scheduledLater;
-function invokeLaterTimers() {
- scheduledLater = null;
- var now = (+ new Date()), earliest = -1;
- for (var key in timers) {
- if (!timers.hasOwnProperty(key)) { continue; }
- var timer = timers[key];
- if (timer && timer.expires) {
- if (now >= timer.expires) {
- delete timers[key];
- invoke(timer.target, timer.method, timer.args, 2);
- } else {
- if (earliest<0 || (timer.expires < earliest)) earliest=timer.expires;
- }
- }
- }
-
- // schedule next timeout to fire...
- if (earliest > 0) { scheduledLater = setTimeout(invokeLaterTimers, earliest-(+ new Date())); }
-}
-
</del><span class="cx"> /**
</span><span class="cx"> Invokes the passed target/method and optional arguments after a specified
</span><span class="cx"> period if time. The last parameter of this method must always be a number
</span><span class="lines">@@ -4464,7 +6371,7 @@
</span><span class="cx"> together, which is often more efficient than using a real setTimeout.
</span><span class="cx">
</span><span class="cx"> ```javascript
</span><del>- Ember.run.later(myContext, function(){
</del><ins>+ Ember.run.later(myContext, function() {
</ins><span class="cx"> // code here will execute within a RunLoop in about 500ms with this == myContext
</span><span class="cx"> }, 500);
</span><span class="cx"> ```
</span><span class="lines">@@ -4475,124 +6382,132 @@
</span><span class="cx"> If you pass a string it will be resolved on the
</span><span class="cx"> target at the time the method is invoked.
</span><span class="cx"> @param {Object} [args*] Optional arguments to pass to the timeout.
</span><del>- @param {Number} wait
- Number of milliseconds to wait.
</del><ins>+ @param {Number} wait Number of milliseconds to wait.
</ins><span class="cx"> @return {String} a string you can use to cancel the timer in
</span><del>- {{#crossLink "Ember/run.cancel"}}{{/crossLink}} later.
</del><ins>+ `Ember.run.cancel` later.
</ins><span class="cx"> */
</span><span class="cx"> Ember.run.later = function(target, method) {
</span><del>- var args, expires, timer, guid, wait;
</del><ins>+ return backburner.later.apply(backburner, arguments);
+};
</ins><span class="cx">
</span><del>- // setTimeout compatibility...
- if (arguments.length===2 && 'function' === typeof target) {
- wait = method;
- method = target;
- target = undefined;
- args = [target, method];
- } else {
- args = slice.call(arguments);
- wait = args.pop();
- }
</del><ins>+/**
+ Schedule a function to run one time during the current RunLoop. This is equivalent
+ to calling `scheduleOnce` with the "actions" queue.
</ins><span class="cx">
</span><del>- expires = (+ new Date()) + wait;
- timer = { target: target, method: method, expires: expires, args: args };
- guid = Ember.guidFor(timer);
- timers[guid] = timer;
- run.once(timers, invokeLaterTimers);
- return guid;
</del><ins>+ @method once
+ @param {Object} [target] The target of the method to invoke.
+ @param {Function|String} method The method to invoke.
+ If you pass a string it will be resolved on the
+ target at the time the method is invoked.
+ @param {Object} [args*] Optional arguments to pass to the timeout.
+ @return {Object} timer
+*/
+Ember.run.once = function(target, method) {
+ checkAutoRun();
+ var args = slice.call(arguments);
+ args.unshift('actions');
+ return backburner.scheduleOnce.apply(backburner, args);
</ins><span class="cx"> };
</span><span class="cx">
</span><del>-function invokeOnceTimer(guid, onceTimers) {
- if (onceTimers[this.tguid]) { delete onceTimers[this.tguid][this.mguid]; }
- if (timers[guid]) { invoke(this.target, this.method, this.args); }
- delete timers[guid];
-}
-
-function scheduleOnce(queue, target, method, args) {
- var tguid = Ember.guidFor(target),
- mguid = Ember.guidFor(method),
- onceTimers = run.autorun().onceTimers,
- guid = onceTimers[tguid] && onceTimers[tguid][mguid],
- timer;
-
- if (guid && timers[guid]) {
- timers[guid].args = args; // replace args
- } else {
- timer = {
- target: target,
- method: method,
- args: args,
- tguid: tguid,
- mguid: mguid
- };
-
- guid = Ember.guidFor(timer);
- timers[guid] = timer;
- if (!onceTimers[tguid]) { onceTimers[tguid] = {}; }
- onceTimers[tguid][mguid] = guid; // so it isn't scheduled more than once
-
- run.schedule(queue, timer, invokeOnceTimer, guid, onceTimers);
- }
-
- return guid;
-}
-
</del><span class="cx"> /**
</span><del>- Schedules an item to run one time during the current RunLoop. Calling
- this method with the same target/method combination will have no effect.
</del><ins>+ Schedules a function to run one time in a given queue of the current RunLoop.
+ Calling this method with the same queue/target/method combination will have
+ no effect (past the initial call).
</ins><span class="cx">
</span><span class="cx"> Note that although you can pass optional arguments these will not be
</span><span class="cx"> considered when looking for duplicates. New arguments will replace previous
</span><span class="cx"> calls.
</span><span class="cx">
</span><span class="cx"> ```javascript
</span><del>- Ember.run(function(){
- var doFoo = function() { foo(); }
- Ember.run.once(myContext, doFoo);
- Ember.run.once(myContext, doFoo);
- // doFoo will only be executed once at the end of the RunLoop
</del><ins>+ Ember.run(function() {
+ var sayHi = function() { console.log('hi'); }
+ Ember.run.scheduleOnce('afterRender', myContext, sayHi);
+ Ember.run.scheduleOnce('afterRender', myContext, sayHi);
+ // sayHi will only be executed once, in the afterRender queue of the RunLoop
</ins><span class="cx"> });
</span><span class="cx"> ```
</span><span class="cx">
</span><del>- @method once
- @param {Object} [target] target of method to invoke
</del><ins>+ Also note that passing an anonymous function to `Ember.run.scheduleOnce` will
+ not prevent additional calls with an identical anonymous function from
+ scheduling the items multiple times, e.g.:
+
+ ```javascript
+ function scheduleIt() {
+ Ember.run.scheduleOnce('actions', myContext, function() { console.log("Closure"); });
+ }
+ scheduleIt();
+ scheduleIt();
+ // "Closure" will print twice, even though we're using `Ember.run.scheduleOnce`,
+ // because the function we pass to it is anonymous and won't match the
+ // previously scheduled operation.
+ ```
+
+ Available queues, and their order, can be found at `Ember.run.queues`
+
+ @method scheduleOnce
+ @param {String} [queue] The name of the queue to schedule against. Default queues are 'sync' and 'actions'.
+ @param {Object} [target] The target of the method to invoke.
</ins><span class="cx"> @param {Function|String} method The method to invoke.
</span><span class="cx"> If you pass a string it will be resolved on the
</span><span class="cx"> target at the time the method is invoked.
</span><span class="cx"> @param {Object} [args*] Optional arguments to pass to the timeout.
</span><span class="cx"> @return {Object} timer
</span><span class="cx"> */
</span><del>-Ember.run.once = function(target, method) {
- return scheduleOnce('actions', target, method, slice.call(arguments, 2));
</del><ins>+Ember.run.scheduleOnce = function(queue, target, method) {
+ checkAutoRun();
+ return backburner.scheduleOnce.apply(backburner, arguments);
</ins><span class="cx"> };
</span><span class="cx">
</span><del>-Ember.run.scheduleOnce = function(queue, target, method, args) {
- return scheduleOnce(queue, target, method, slice.call(arguments, 3));
-};
</del><ins>+/**
+ Schedules an item to run from within a separate run loop, after
+ control has been returned to the system. This is equivalent to calling
+ `Ember.run.later` with a wait time of 1ms.
</ins><span class="cx">
</span><del>-var scheduledNext;
-function invokeNextTimers() {
- scheduledNext = null;
- for(var key in timers) {
- if (!timers.hasOwnProperty(key)) { continue; }
- var timer = timers[key];
- if (timer.next) {
- delete timers[key];
- invoke(timer.target, timer.method, timer.args, 2);
- }
- }
-}
</del><ins>+ ```javascript
+ Ember.run.next(myContext, function() {
+ // code to be executed in the next run loop,
+ // which will be scheduled after the current one
+ });
+ ```
</ins><span class="cx">
</span><del>-/**
- Schedules an item to run after control has been returned to the system.
- This is often equivalent to calling `setTimeout(function() {}, 1)`.
</del><ins>+ Multiple operations scheduled with `Ember.run.next` will coalesce
+ into the same later run loop, along with any other operations
+ scheduled by `Ember.run.later` that expire right around the same
+ time that `Ember.run.next` operations will fire.
</ins><span class="cx">
</span><ins>+ Note that there are often alternatives to using `Ember.run.next`.
+ For instance, if you'd like to schedule an operation to happen
+ after all DOM element operations have completed within the current
+ run loop, you can make use of the `afterRender` run loop queue (added
+ by the `ember-views` package, along with the preceding `render` queue
+ where all the DOM element operations happen). Example:
+
</ins><span class="cx"> ```javascript
</span><del>- Ember.run.next(myContext, function(){
- // code to be executed in the next RunLoop, which will be scheduled after the current one
</del><ins>+ App.MyCollectionView = Ember.CollectionView.extend({
+ didInsertElement: function() {
+ Ember.run.scheduleOnce('afterRender', this, 'processChildElements');
+ },
+ processChildElements: function() {
+ // ... do something with collectionView's child view
+ // elements after they've finished rendering, which
+ // can't be done within the CollectionView's
+ // `didInsertElement` hook because that gets run
+ // before the child elements have been added to the DOM.
+ }
</ins><span class="cx"> });
</span><span class="cx"> ```
</span><span class="cx">
</span><ins>+ One benefit of the above approach compared to using `Ember.run.next` is
+ that you will be able to perform DOM/CSS operations before unprocessed
+ elements are rendered to the screen, which may prevent flickering or
+ other artifacts caused by delaying processing until after rendering.
+
+ The other major benefit to the above approach is that `Ember.run.next`
+ introduces an element of non-determinism, which can make things much
+ harder to test, due to its reliance on `setTimeout`; it's much harder
+ to guarantee the order of scheduled operations when they are scheduled
+ outside of the current run loop, i.e. with `Ember.run.next`.
+
</ins><span class="cx"> @method next
</span><span class="cx"> @param {Object} [target] target of method to invoke
</span><span class="cx"> @param {Function|String} method The method to invoke.
</span><span class="lines">@@ -4601,20 +6516,10 @@
</span><span class="cx"> @param {Object} [args*] Optional arguments to pass to the timeout.
</span><span class="cx"> @return {Object} timer
</span><span class="cx"> */
</span><del>-Ember.run.next = function(target, method) {
- var guid,
- timer = {
- target: target,
- method: method,
- args: slice.call(arguments),
- next: true
- };
-
- guid = Ember.guidFor(timer);
- timers[guid] = timer;
-
- if (!scheduledNext) { scheduledNext = setTimeout(invokeNextTimers, 1); }
- return guid;
</del><ins>+Ember.run.next = function() {
+ var args = slice.call(arguments);
+ args.push(1);
+ return backburner.later.apply(backburner, args);
</ins><span class="cx"> };
</span><span class="cx">
</span><span class="cx"> /**
</span><span class="lines">@@ -4622,17 +6527,17 @@
</span><span class="cx"> `Ember.run.once()`, or `Ember.run.next()`.
</span><span class="cx">
</span><span class="cx"> ```javascript
</span><del>- var runNext = Ember.run.next(myContext, function(){
</del><ins>+ var runNext = Ember.run.next(myContext, function() {
</ins><span class="cx"> // will not be executed
</span><span class="cx"> });
</span><span class="cx"> Ember.run.cancel(runNext);
</span><span class="cx">
</span><del>- var runLater = Ember.run.later(myContext, function(){
</del><ins>+ var runLater = Ember.run.later(myContext, function() {
</ins><span class="cx"> // will not be executed
</span><span class="cx"> }, 500);
</span><span class="cx"> Ember.run.cancel(runLater);
</span><span class="cx">
</span><del>- var runOnce = Ember.run.once(myContext, function(){
</del><ins>+ var runOnce = Ember.run.once(myContext, function() {
</ins><span class="cx"> // will not be executed
</span><span class="cx"> });
</span><span class="cx"> Ember.run.cancel(runOnce);
</span><span class="lines">@@ -4643,17 +6548,102 @@
</span><span class="cx"> @return {void}
</span><span class="cx"> */
</span><span class="cx"> Ember.run.cancel = function(timer) {
</span><del>- delete timers[timer];
</del><ins>+ return backburner.cancel(timer);
</ins><span class="cx"> };
</span><span class="cx">
</span><ins>+/**
+ Delay calling the target method until the debounce period has elapsed
+ with no additional debounce calls. If `debounce` is called again before
+ the specified time has elapsed, the timer is reset and the entire period
+ must pass again before the target method is called.
+
+ This method should be used when an event may be called multiple times
+ but the action should only be called once when the event is done firing.
+ A common example is for scroll events where you only want updates to
+ happen once scrolling has ceased.
+
+ ```javascript
+ var myFunc = function() { console.log(this.name + ' ran.'); };
+ var myContext = {name: 'debounce'};
+
+ Ember.run.debounce(myContext, myFunc, 150);
+
+ // less than 150ms passes
+
+ Ember.run.debounce(myContext, myFunc, 150);
+
+ // 150ms passes
+ // myFunc is invoked with context myContext
+ // console logs 'debounce ran.' one time.
+ ```
+
+ @method debounce
+ @param {Object} [target] target of method to invoke
+ @param {Function|String} method The method to invoke.
+ May be a function or a string. If you pass a string
+ then it will be looked up on the passed target.
+ @param {Object} [args*] Optional arguments to pass to the timeout.
+ @param {Number} wait Number of milliseconds to wait.
+ @param {Boolean} immediate Trigger the function on the leading instead of the trailing edge of the wait interval.
+ @return {void}
+*/
+Ember.run.debounce = function() {
+ return backburner.debounce.apply(backburner, arguments);
+};
+
+/**
+ Ensure that the target method is never called more frequently than
+ the specified spacing period.
+
+ ```javascript
+ var myFunc = function() { console.log(this.name + ' ran.'); };
+ var myContext = {name: 'throttle'};
+
+ Ember.run.throttle(myContext, myFunc, 150);
+
+ // 50ms passes
+ Ember.run.throttle(myContext, myFunc, 150);
+
+ // 50ms passes
+ Ember.run.throttle(myContext, myFunc, 150);
+
+ // 50ms passes
+ Ember.run.throttle(myContext, myFunc, 150);
+
+ // 150ms passes
+ // myFunc is invoked with context myContext
+ // console logs 'throttle ran.' twice, 150ms apart.
+ ```
+
+ @method throttle
+ @param {Object} [target] target of method to invoke
+ @param {Function|String} method The method to invoke.
+ May be a function or a string. If you pass a string
+ then it will be looked up on the passed target.
+ @param {Object} [args*] Optional arguments to pass to the timeout.
+ @param {Number} spacing Number of milliseconds to space out requests.
+ @return {void}
+*/
+Ember.run.throttle = function() {
+ return backburner.throttle.apply(backburner, arguments);
+};
+
+// Make sure it's not an autorun during testing
+function checkAutoRun() {
+ if (!Ember.run.currentRunLoop) {
+ Ember.assert("You have turned on testing mode, which disabled the run-loop's autorun. You will need to wrap any code with asynchronous side-effects in an Ember.run", !Ember.testing);
+ }
+}
+
</ins><span class="cx"> })();
</span><span class="cx">
</span><span class="cx">
</span><span class="cx">
</span><span class="cx"> (function() {
</span><span class="cx"> // Ember.Logger
</span><del>-// get, set, trySet
-// guidFor, isArray, meta
</del><ins>+// get
+// set
+// guidFor, meta
</ins><span class="cx"> // addObserver, removeObserver
</span><span class="cx"> // Ember.run.schedule
</span><span class="cx"> /**
</span><span class="lines">@@ -4679,9 +6669,22 @@
</span><span class="cx"> var get = Ember.get,
</span><span class="cx"> set = Ember.set,
</span><span class="cx"> guidFor = Ember.guidFor,
</span><del>- isGlobalPath = Ember.isGlobalPath;
</del><ins>+ IS_GLOBAL = /^([A-Z$]|([0-9][A-Z$]))/;
</ins><span class="cx">
</span><ins>+/**
+ Returns true if the provided path is global (e.g., `MyApp.fooController.bar`)
+ instead of local (`foo.bar.baz`).
</ins><span class="cx">
</span><ins>+ @method isGlobalPath
+ @for Ember
+ @private
+ @param {String} path
+ @return Boolean
+*/
+var isGlobalPath = Ember.isGlobalPath = function(path) {
+ return IS_GLOBAL.test(path);
+};
+
</ins><span class="cx"> function getWithGlobals(obj, path) {
</span><span class="cx"> return get(isGlobalPath(path) ? Ember.lookup : obj, path);
</span><span class="cx"> }
</span><span class="lines">@@ -4707,7 +6710,7 @@
</span><span class="cx"> This copies the Binding so it can be connected to another object.
</span><span class="cx">
</span><span class="cx"> @method copy
</span><del>- @return {Ember.Binding}
</del><ins>+ @return {Ember.Binding} `this`
</ins><span class="cx"> */
</span><span class="cx"> copy: function () {
</span><span class="cx"> var copy = new Binding(this._to, this._from);
</span><span class="lines">@@ -4729,7 +6732,7 @@
</span><span class="cx"> `get()` - see that method for more information.
</span><span class="cx">
</span><span class="cx"> @method from
</span><del>- @param {String} propertyPath the property path to connect to
</del><ins>+ @param {String} path the property path to connect to
</ins><span class="cx"> @return {Ember.Binding} `this`
</span><span class="cx"> */
</span><span class="cx"> from: function(path) {
</span><span class="lines">@@ -4747,7 +6750,7 @@
</span><span class="cx"> `get()` - see that method for more information.
</span><span class="cx">
</span><span class="cx"> @method to
</span><del>- @param {String|Tuple} propertyPath A property path or tuple
</del><ins>+ @param {String|Tuple} path A property path or tuple
</ins><span class="cx"> @return {Ember.Binding} `this`
</span><span class="cx"> */
</span><span class="cx"> to: function(path) {
</span><span class="lines">@@ -4769,6 +6772,10 @@
</span><span class="cx"> return this;
</span><span class="cx"> },
</span><span class="cx">
</span><ins>+ /**
+ @method toString
+ @return {String} string representation of binding
+ */
</ins><span class="cx"> toString: function() {
</span><span class="cx"> var oneWay = this._oneWay ? '[oneWay]' : '';
</span><span class="cx"> return "Ember.Binding<" + guidFor(this) + ">(" + this._from + " -> " + this._to + ")" + oneWay;
</span><span class="lines">@@ -4911,8 +6918,8 @@
</span><span class="cx">
</span><span class="cx"> mixinProperties(Binding, {
</span><span class="cx">
</span><del>- /**
- See {{#crossLink "Ember.Binding/from"}}{{/crossLink}}
</del><ins>+ /*
+ See `Ember.Binding.from`.
</ins><span class="cx">
</span><span class="cx"> @method from
</span><span class="cx"> @static
</span><span class="lines">@@ -4922,8 +6929,8 @@
</span><span class="cx"> return binding.from.apply(binding, arguments);
</span><span class="cx"> },
</span><span class="cx">
</span><del>- /**
- See {{#crossLink "Ember.Binding/to"}}{{/crossLink}}
</del><ins>+ /*
+ See `Ember.Binding.to`.
</ins><span class="cx">
</span><span class="cx"> @method to
</span><span class="cx"> @static
</span><span class="lines">@@ -4940,13 +6947,14 @@
</span><span class="cx"> This means that if you change the "to" side directly, the "from" side may have
</span><span class="cx"> a different value.
</span><span class="cx">
</span><del>- See {{#crossLink "Binding/oneWay"}}{{/crossLink}}
</del><ins>+ See `Binding.oneWay`.
</ins><span class="cx">
</span><span class="cx"> @method oneWay
</span><span class="cx"> @param {String} from from path.
</span><span class="cx"> @param {Boolean} [flag] (Optional) passing nothing here will make the
</span><span class="cx"> binding `oneWay`. You can instead pass `false` to disable `oneWay`, making the
</span><span class="cx"> binding two way again.
</span><ins>+ @return {Ember.Binding} `this`
</ins><span class="cx"> */
</span><span class="cx"> oneWay: function(from, flag) {
</span><span class="cx"> var C = this, binding = new C(null, from);
</span><span class="lines">@@ -4968,7 +6976,7 @@
</span><span class="cx"> Properties ending in a `Binding` suffix will be converted to `Ember.Binding`
</span><span class="cx"> instances. The value of this property should be a string representing a path
</span><span class="cx"> to another object or a custom binding instanced created using Binding helpers
</span><del>- (see "Customizing Your Bindings"):
</del><ins>+ (see "One Way Bindings"):
</ins><span class="cx">
</span><span class="cx"> ```
</span><span class="cx"> valueBinding: "MyApp.someController.title"
</span><span class="lines">@@ -5001,7 +7009,7 @@
</span><span class="cx">
</span><span class="cx"> You should consider using one way bindings anytime you have an object that
</span><span class="cx"> may be created frequently and you do not intend to change a property; only
</span><del>- to monitor it for changes. (such as in the example above).
</del><ins>+ to monitor it for changes (such as in the example above).
</ins><span class="cx">
</span><span class="cx"> ## Adding Bindings Manually
</span><span class="cx">
</span><span class="lines">@@ -5111,7 +7119,8 @@
</span><span class="cx">
</span><span class="cx"> (function() {
</span><span class="cx"> /**
</span><del>-@module ember-metal
</del><ins>+@module ember
+@submodule ember-metal
</ins><span class="cx"> */
</span><span class="cx">
</span><span class="cx"> var Mixin, REQUIRED, Alias,
</span><span class="lines">@@ -5119,11 +7128,11 @@
</span><span class="cx"> a_indexOf = Ember.ArrayPolyfills.indexOf,
</span><span class="cx"> a_forEach = Ember.ArrayPolyfills.forEach,
</span><span class="cx"> a_slice = [].slice,
</span><del>- EMPTY_META = {}, // dummy for non-writable meta
</del><span class="cx"> o_create = Ember.create,
</span><span class="cx"> defineProperty = Ember.defineProperty,
</span><span class="cx"> guidFor = Ember.guidFor;
</span><span class="cx">
</span><ins>+
</ins><span class="cx"> function mixinsMeta(obj) {
</span><span class="cx"> var m = Ember.meta(obj, true), ret = m.mixins;
</span><span class="cx"> if (!ret) {
</span><span class="lines">@@ -5171,13 +7180,13 @@
</span><span class="cx"> }
</span><span class="cx"> }
</span><span class="cx">
</span><del>-function concatenatedProperties(props, values, base) {
</del><ins>+function concatenatedMixinProperties(concatProp, props, values, base) {
</ins><span class="cx"> var concats;
</span><span class="cx">
</span><span class="cx"> // reset before adding each new mixin to pickup concats from previous
</span><del>- concats = values.concatenatedProperties || base.concatenatedProperties;
- if (props.concatenatedProperties) {
- concats = concats ? concats.concat(props.concatenatedProperties) : props.concatenatedProperties;
</del><ins>+ concats = values[concatProp] || base[concatProp];
+ if (props[concatProp]) {
+ concats = concats ? concats.concat(props[concatProp]) : props[concatProp];
</ins><span class="cx"> }
</span><span class="cx">
</span><span class="cx"> return concats;
</span><span class="lines">@@ -5244,7 +7253,28 @@
</span><span class="cx"> }
</span><span class="cx"> }
</span><span class="cx">
</span><del>-function addNormalizedProperty(base, key, value, meta, descs, values, concats) {
</del><ins>+function applyMergedProperties(obj, key, value, values) {
+ var baseValue = values[key] || obj[key];
+
+ if (!baseValue) { return value; }
+
+ var newBase = Ember.merge({}, baseValue);
+ for (var prop in value) {
+ if (!value.hasOwnProperty(prop)) { continue; }
+
+ var propValue = value[prop];
+ if (isMethod(propValue)) {
+ // TODO: support for Computed Properties, etc?
+ newBase[prop] = giveMethodSuper(obj, prop, propValue, baseValue, {});
+ } else {
+ newBase[prop] = propValue;
+ }
+ }
+
+ return newBase;
+}
+
+function addNormalizedProperty(base, key, value, meta, descs, values, concats, mergings) {
</ins><span class="cx"> if (value instanceof Ember.Descriptor) {
</span><span class="cx"> if (value === REQUIRED && descs[key]) { return CONTINUE; }
</span><span class="cx">
</span><span class="lines">@@ -5257,11 +7287,14 @@
</span><span class="cx"> descs[key] = value;
</span><span class="cx"> values[key] = undefined;
</span><span class="cx"> } else {
</span><del>- // impl super if needed...
- if (isMethod(value)) {
</del><ins>+ if ((concats && a_indexOf.call(concats, key) >= 0) ||
+ key === 'concatenatedProperties' ||
+ key === 'mergedProperties') {
+ value = applyConcatenatedProperties(base, key, value, values);
+ } else if ((mergings && a_indexOf.call(mergings, key) >= 0)) {
+ value = applyMergedProperties(base, key, value, values);
+ } else if (isMethod(value)) {
</ins><span class="cx"> value = giveMethodSuper(base, key, value, values, descs);
</span><del>- } else if ((concats && a_indexOf.call(concats, key) >= 0) || key === 'concatenatedProperties') {
- value = applyConcatenatedProperties(base, key, value, values);
</del><span class="cx"> }
</span><span class="cx">
</span><span class="cx"> descs[key] = undefined;
</span><span class="lines">@@ -5269,8 +7302,8 @@
</span><span class="cx"> }
</span><span class="cx"> }
</span><span class="cx">
</span><del>-function mergeMixins(mixins, m, descs, values, base) {
- var mixin, props, key, concats, meta;
</del><ins>+function mergeMixins(mixins, m, descs, values, base, keys) {
+ var mixin, props, key, concats, mergings, meta;
</ins><span class="cx">
</span><span class="cx"> function removeKeys(keyName) {
</span><span class="cx"> delete descs[keyName];
</span><span class="lines">@@ -5279,37 +7312,33 @@
</span><span class="cx">
</span><span class="cx"> for(var i=0, l=mixins.length; i<l; i++) {
</span><span class="cx"> mixin = mixins[i];
</span><del>- Ember.assert('Expected hash or Mixin instance, got ' + Object.prototype.toString.call(mixin), typeof mixin === 'object' && mixin !== null && Object.prototype.toString.call(mixin) !== '[object Array]');
</del><ins>+ Ember.assert('Expected hash or Mixin instance, got ' + Object.prototype.toString.call(mixin),
+ typeof mixin === 'object' && mixin !== null && Object.prototype.toString.call(mixin) !== '[object Array]');
</ins><span class="cx">
</span><span class="cx"> props = mixinProperties(m, mixin);
</span><span class="cx"> if (props === CONTINUE) { continue; }
</span><span class="cx">
</span><span class="cx"> if (props) {
</span><span class="cx"> meta = Ember.meta(base);
</span><del>- concats = concatenatedProperties(props, values, base);
</del><ins>+ if (base.willMergeMixin) { base.willMergeMixin(props); }
+ concats = concatenatedMixinProperties('concatenatedProperties', props, values, base);
+ mergings = concatenatedMixinProperties('mergedProperties', props, values, base);
</ins><span class="cx">
</span><span class="cx"> for (key in props) {
</span><span class="cx"> if (!props.hasOwnProperty(key)) { continue; }
</span><del>- addNormalizedProperty(base, key, props[key], meta, descs, values, concats);
</del><ins>+ keys.push(key);
+ addNormalizedProperty(base, key, props[key], meta, descs, values, concats, mergings);
</ins><span class="cx"> }
</span><span class="cx">
</span><span class="cx"> // manually copy toString() because some JS engines do not enumerate it
</span><span class="cx"> if (props.hasOwnProperty('toString')) { base.toString = props.toString; }
</span><span class="cx"> } else if (mixin.mixins) {
</span><del>- mergeMixins(mixin.mixins, m, descs, values, base);
</del><ins>+ mergeMixins(mixin.mixins, m, descs, values, base, keys);
</ins><span class="cx"> if (mixin._without) { a_forEach.call(mixin._without, removeKeys); }
</span><span class="cx"> }
</span><span class="cx"> }
</span><span class="cx"> }
</span><span class="cx">
</span><del>-function writableReq(obj) {
- var m = Ember.meta(obj), req = m.required;
- if (!req || !m.hasOwnProperty('required')) {
- req = m.required = req ? o_create(req) : {};
- }
- return req;
-}
-
</del><span class="cx"> var IS_BINDING = Ember.IS_BINDING = /^.+Binding$/;
</span><span class="cx">
</span><span class="cx"> function detectBinding(obj, key, value, m) {
</span><span class="lines">@@ -5368,42 +7397,48 @@
</span><span class="cx"> return { desc: desc, value: value };
</span><span class="cx"> }
</span><span class="cx">
</span><del>-function updateObservers(obj, key, observer, observerKey, method) {
- if ('function' !== typeof observer) { return; }
</del><ins>+function updateObserversAndListeners(obj, key, observerOrListener, pathsKey, updateMethod) {
+ var paths = observerOrListener[pathsKey];
</ins><span class="cx">
</span><del>- var paths = observer[observerKey];
-
</del><span class="cx"> if (paths) {
</span><span class="cx"> for (var i=0, l=paths.length; i<l; i++) {
</span><del>- Ember[method](obj, paths[i], null, key);
</del><ins>+ Ember[updateMethod](obj, paths[i], null, key);
</ins><span class="cx"> }
</span><span class="cx"> }
</span><span class="cx"> }
</span><span class="cx">
</span><del>-function replaceObservers(obj, key, observer) {
- var prevObserver = obj[key];
</del><ins>+function replaceObserversAndListeners(obj, key, observerOrListener) {
+ var prev = obj[key];
</ins><span class="cx">
</span><del>- updateObservers(obj, key, prevObserver, '__ember_observesBefore__', 'removeBeforeObserver');
- updateObservers(obj, key, prevObserver, '__ember_observes__', 'removeObserver');
</del><ins>+ if ('function' === typeof prev) {
+ updateObserversAndListeners(obj, key, prev, '__ember_observesBefore__', 'removeBeforeObserver');
+ updateObserversAndListeners(obj, key, prev, '__ember_observes__', 'removeObserver');
+ updateObserversAndListeners(obj, key, prev, '__ember_listens__', 'removeListener');
+ }
</ins><span class="cx">
</span><del>- updateObservers(obj, key, observer, '__ember_observesBefore__', 'addBeforeObserver');
- updateObservers(obj, key, observer, '__ember_observes__', 'addObserver');
</del><ins>+ if ('function' === typeof observerOrListener) {
+ updateObserversAndListeners(obj, key, observerOrListener, '__ember_observesBefore__', 'addBeforeObserver');
+ updateObserversAndListeners(obj, key, observerOrListener, '__ember_observes__', 'addObserver');
+ updateObserversAndListeners(obj, key, observerOrListener, '__ember_listens__', 'addListener');
+ }
</ins><span class="cx"> }
</span><span class="cx">
</span><span class="cx"> function applyMixin(obj, mixins, partial) {
</span><span class="cx"> var descs = {}, values = {}, m = Ember.meta(obj),
</span><del>- key, value, desc;
</del><ins>+ key, value, desc, keys = [];
</ins><span class="cx">
</span><span class="cx"> // Go through all mixins and hashes passed in, and:
</span><span class="cx"> //
</span><span class="cx"> // * Handle concatenated properties
</span><ins>+ // * Handle merged properties
</ins><span class="cx"> // * Set up _super wrapping if necessary
</span><span class="cx"> // * Set up computed property descriptors
</span><span class="cx"> // * Copying `toString` in broken browsers
</span><del>- mergeMixins(mixins, mixinsMeta(obj), descs, values, obj);
</del><ins>+ mergeMixins(mixins, mixinsMeta(obj), descs, values, obj, keys);
</ins><span class="cx">
</span><del>- for(key in values) {
- if (key === 'contructor' || !values.hasOwnProperty(key)) { continue; }
</del><ins>+ for(var i = 0, l = keys.length; i < l; i++) {
+ key = keys[i];
+ if (key === 'constructor' || !values.hasOwnProperty(key)) { continue; }
</ins><span class="cx">
</span><span class="cx"> desc = descs[key];
</span><span class="cx"> value = values[key];
</span><span class="lines">@@ -5418,7 +7453,7 @@
</span><span class="cx">
</span><span class="cx"> if (desc === undefined && value === undefined) { continue; }
</span><span class="cx">
</span><del>- replaceObservers(obj, key, value);
</del><ins>+ replaceObserversAndListeners(obj, key, value);
</ins><span class="cx"> detectBinding(obj, key, value, m);
</span><span class="cx"> defineProperty(obj, key, desc, value, m);
</span><span class="cx"> }
</span><span class="lines">@@ -5457,9 +7492,9 @@
</span><span class="cx"> });
</span><span class="cx">
</span><span class="cx"> // Mix mixins into classes by passing them as the first arguments to
</span><del>- // .extend or .create.
</del><ins>+ // .extend.
</ins><span class="cx"> App.CommentView = Ember.View.extend(App.Editable, {
</span><del>- template: Ember.Handlebars.compile('{{#if isEditing}}...{{else}}...{{/if}}')
</del><ins>+ template: Ember.Handlebars.compile('{{#if view.isEditing}}...{{else}}...{{/if}}')
</ins><span class="cx"> });
</span><span class="cx">
</span><span class="cx"> commentView = App.CommentView.create();
</span><span class="lines">@@ -5469,6 +7504,31 @@
</span><span class="cx"> Note that Mixins are created with `Ember.Mixin.create`, not
</span><span class="cx"> `Ember.Mixin.extend`.
</span><span class="cx">
</span><ins>+ Note that mixins extend a constructor's prototype so arrays and object literals
+ defined as properties will be shared amongst objects that implement the mixin.
+ If you want to define an property in a mixin that is not shared, you can define
+ it either as a computed property or have it be created on initialization of the object.
+
+ ```javascript
+ //filters array will be shared amongst any object implementing mixin
+ App.Filterable = Ember.Mixin.create({
+ filters: Ember.A()
+ });
+
+ //filters will be a separate array for every object implementing the mixin
+ App.Filterable = Ember.Mixin.create({
+ filters: Ember.computed(function(){return Ember.A();})
+ });
+
+ //filters will be created as a separate array during the object's initialization
+ App.Filterable = Ember.Mixin.create({
+ init: function() {
+ this._super();
+ this.set("filters", Ember.A());
+ }
+ });
+ ```
+
</ins><span class="cx"> @class Mixin
</span><span class="cx"> @namespace Ember
</span><span class="cx"> */
</span><span class="lines">@@ -5476,6 +7536,12 @@
</span><span class="cx">
</span><span class="cx"> Mixin = Ember.Mixin;
</span><span class="cx">
</span><ins>+Mixin.prototype = {
+ properties: null,
+ mixins: null,
+ ownerConstructor: null
+};
+
</ins><span class="cx"> Mixin._apply = applyMixin;
</span><span class="cx">
</span><span class="cx"> Mixin.applyPartial = function(obj) {
</span><span class="lines">@@ -5520,7 +7586,8 @@
</span><span class="cx">
</span><span class="cx"> for(idx=0; idx < len; idx++) {
</span><span class="cx"> mixin = arguments[idx];
</span><del>- Ember.assert('Expected hash or Mixin instance, got ' + Object.prototype.toString.call(mixin), typeof mixin === 'object' && mixin !== null && Object.prototype.toString.call(mixin) !== '[object Array]');
</del><ins>+ Ember.assert('Expected hash or Mixin instance, got ' + Object.prototype.toString.call(mixin),
+ typeof mixin === 'object' && mixin !== null && Object.prototype.toString.call(mixin) !== '[object Array]');
</ins><span class="cx">
</span><span class="cx"> if (mixin instanceof Mixin) {
</span><span class="cx"> mixins.push(mixin);
</span><span class="lines">@@ -5647,7 +7714,7 @@
</span><span class="cx"> App.PaintSample = Ember.Object.extend({
</span><span class="cx"> color: 'red',
</span><span class="cx"> colour: Ember.alias('color'),
</span><del>- name: function(){
</del><ins>+ name: function() {
</ins><span class="cx"> return "Zed";
</span><span class="cx"> },
</span><span class="cx"> moniker: Ember.alias("name")
</span><span class="lines">@@ -5665,17 +7732,16 @@
</span><span class="cx"> @deprecated Use `Ember.aliasMethod` or `Ember.computed.alias` instead
</span><span class="cx"> */
</span><span class="cx"> Ember.alias = function(methodName) {
</span><ins>+ Ember.deprecate("Ember.alias is deprecated. Please use Ember.aliasMethod or Ember.computed.alias instead.");
</ins><span class="cx"> return new Alias(methodName);
</span><span class="cx"> };
</span><span class="cx">
</span><del>-Ember.deprecateFunc("Ember.alias is deprecated. Please use Ember.aliasMethod or Ember.computed.alias instead.", Ember.alias);
-
</del><span class="cx"> /**
</span><span class="cx"> Makes a method available via an additional name.
</span><span class="cx">
</span><span class="cx"> ```javascript
</span><span class="cx"> App.Person = Ember.Object.extend({
</span><del>- name: function(){
</del><ins>+ name: function() {
</ins><span class="cx"> return 'Tomhuda Katzdale';
</span><span class="cx"> },
</span><span class="cx"> moniker: Ember.aliasMethod('name')
</span><span class="lines">@@ -5698,25 +7764,72 @@
</span><span class="cx"> //
</span><span class="cx">
</span><span class="cx"> /**
</span><ins>+ Specify a method that observes property changes.
+
+ ```javascript
+ Ember.Object.extend({
+ valueObserver: Ember.observer('value', function() {
+ // Executes whenever the "value" property changes
+ })
+ });
+ ```
+
+ In the future this method may become asynchronous. If you want to ensure
+ synchronous behavior, use `immediateObserver`.
+
+ Also available as `Function.prototype.observes` if prototype extensions are
+ enabled.
+
</ins><span class="cx"> @method observer
</span><span class="cx"> @for Ember
</span><ins>+ @param {String} propertyNames*
</ins><span class="cx"> @param {Function} func
</span><del>- @param {String} propertyNames*
</del><span class="cx"> @return func
</span><span class="cx"> */
</span><del>-Ember.observer = function(func) {
- var paths = a_slice.call(arguments, 1);
</del><ins>+Ember.observer = function() {
+ var func = a_slice.call(arguments, -1)[0];
+ var paths;
+
+
+ paths = a_slice.call(arguments, 0, -1);
+
+ if (typeof func !== "function") {
+ // revert to old, soft-deprecated argument ordering
+
+ func = arguments[0];
+ paths = a_slice.call(arguments, 1);
+ }
+
+
+ if (typeof func !== "function") {
+ throw new Ember.Error("Ember.observer called without a function");
+ }
+
</ins><span class="cx"> func.__ember_observes__ = paths;
</span><span class="cx"> return func;
</span><span class="cx"> };
</span><span class="cx">
</span><del>-// If observers ever become asynchronous, Ember.immediateObserver
-// must remain synchronous.
</del><span class="cx"> /**
</span><ins>+ Specify a method that observes property changes.
+
+ ```javascript
+ Ember.Object.extend({
+ valueObserver: Ember.immediateObserver('value', function() {
+ // Executes whenever the "value" property changes
+ })
+ });
+ ```
+
+ In the future, `Ember.observer` may become asynchronous. In this event,
+ `Ember.immediateObserver` will maintain the synchronous behavior.
+
+ Also available as `Function.prototype.observesImmediately` if prototype extensions are
+ enabled.
+
</ins><span class="cx"> @method immediateObserver
</span><span class="cx"> @for Ember
</span><ins>+ @param {String} propertyNames*
</ins><span class="cx"> @param {Function} func
</span><del>- @param {String} propertyNames*
</del><span class="cx"> @return func
</span><span class="cx"> */
</span><span class="cx"> Ember.immediateObserver = function() {
</span><span class="lines">@@ -5729,14 +7842,67 @@
</span><span class="cx"> };
</span><span class="cx">
</span><span class="cx"> /**
</span><ins>+ When observers fire, they are called with the arguments `obj`, `keyName`.
+
+ Note, `@each.property` observer is called per each add or replace of an element
+ and it's not called with a specific enumeration item.
+
+ A `beforeObserver` fires before a property changes.
+
+ A `beforeObserver` is an alternative form of `.observesBefore()`.
+
+ ```javascript
+ App.PersonView = Ember.View.extend({
+
+ friends: [{ name: 'Tom' }, { name: 'Stefan' }, { name: 'Kris' }],
+
+ valueWillChange: Ember.beforeObserver('content.value', function(obj, keyName) {
+ this.changingFrom = obj.get(keyName);
+ }),
+
+ valueDidChange: Ember.observer('content.value', function(obj, keyName) {
+ // only run if updating a value already in the DOM
+ if (this.get('state') === 'inDOM') {
+ var color = obj.get(keyName) > this.changingFrom ? 'green' : 'red';
+ // logic
+ }
+ }),
+
+ friendsDidChange: Ember.observer('friends.@each.name', function(obj, keyName) {
+ // some logic
+ // obj.get(keyName) returns friends array
+ })
+ });
+ ```
+
+ Also available as `Function.prototype.observesBefore` if prototype extensions are
+ enabled.
+
</ins><span class="cx"> @method beforeObserver
</span><span class="cx"> @for Ember
</span><ins>+ @param {String} propertyNames*
</ins><span class="cx"> @param {Function} func
</span><del>- @param {String} propertyNames*
</del><span class="cx"> @return func
</span><span class="cx"> */
</span><del>-Ember.beforeObserver = function(func) {
- var paths = a_slice.call(arguments, 1);
</del><ins>+Ember.beforeObserver = function() {
+ var func = a_slice.call(arguments, -1)[0];
+ var paths;
+
+
+ paths = a_slice.call(arguments, 0, -1);
+
+ if (typeof func !== "function") {
+ // revert to old, soft-deprecated argument ordering
+
+ func = arguments[0];
+ paths = a_slice.call(arguments, 1);
+ }
+
+
+ if (typeof func !== "function") {
+ throw new Ember.Error("Ember.beforeObserver called without a function");
+ }
+
</ins><span class="cx"> func.__ember_observesBefore__ = paths;
</span><span class="cx"> return func;
</span><span class="cx"> };
</span><span class="lines">@@ -5746,6 +7912,55 @@
</span><span class="cx">
</span><span class="cx">
</span><span class="cx"> (function() {
</span><ins>+// Provides a way to register library versions with ember.
+var forEach = Ember.EnumerableUtils.forEach,
+ indexOf = Ember.EnumerableUtils.indexOf;
+
+Ember.libraries = function() {
+ var libraries = [];
+ var coreLibIndex = 0;
+
+ var getLibrary = function(name) {
+ for (var i = 0; i < libraries.length; i++) {
+ if (libraries[i].name === name) {
+ return libraries[i];
+ }
+ }
+ };
+
+ libraries.register = function(name, version) {
+ if (!getLibrary(name)) {
+ libraries.push({name: name, version: version});
+ }
+ };
+
+ libraries.registerCoreLibrary = function(name, version) {
+ if (!getLibrary(name)) {
+ libraries.splice(coreLibIndex++, 0, {name: name, version: version});
+ }
+ };
+
+ libraries.deRegister = function(name) {
+ var lib = getLibrary(name);
+ if (lib) libraries.splice(indexOf(libraries, lib), 1);
+ };
+
+ libraries.each = function (callback) {
+ forEach(libraries, function(lib) {
+ callback(lib.name, lib.version);
+ });
+ };
+
+ return libraries;
+}();
+
+Ember.libraries.registerCoreLibrary('Ember', Ember.VERSION);
+
+})();
+
+
+
+(function() {
</ins><span class="cx"> /**
</span><span class="cx"> Ember Metal
</span><span class="cx">
</span><span class="lines">@@ -5756,69 +7971,232 @@
</span><span class="cx"> })();
</span><span class="cx">
</span><span class="cx"> (function() {
</span><del>-define("rsvp",
- [],
- function() {
</del><ins>+/**
+ @class RSVP
+ @module RSVP
+ */
+define("rsvp/all",
+ ["./promise","exports"],
+ function(__dependency1__, __exports__) {
</ins><span class="cx"> "use strict";
</span><del>- var browserGlobal = (typeof window !== 'undefined') ? window : {};
</del><ins>+ var Promise = __dependency1__["default"];
</ins><span class="cx">
</span><del>- var MutationObserver = browserGlobal.MutationObserver || browserGlobal.WebKitMutationObserver;
- var RSVP, async;
</del><ins>+ /**
+ This is a convenient alias for `RSVP.Promise.all`.
</ins><span class="cx">
</span><del>- if (typeof process !== 'undefined' &&
- {}.toString.call(process) === '[object process]') {
- async = function(callback, binding) {
- process.nextTick(function() {
- callback.call(binding);
- });
- };
- } else if (MutationObserver) {
- var queue = [];
</del><ins>+ @method all
+ @for RSVP
+ @param {Array} array Array of promises.
+ @param {String} label An optional label. This is useful
+ for tooling.
+ @static
+ */
+ __exports__["default"] = function all(array, label) {
+ return Promise.all(array, label);
+ };
+ });
+define("rsvp/all_settled",
+ ["./promise","./utils","exports"],
+ function(__dependency1__, __dependency2__, __exports__) {
+ "use strict";
+ var Promise = __dependency1__["default"];
+ var isArray = __dependency2__.isArray;
+ var isNonThenable = __dependency2__.isNonThenable;
</ins><span class="cx">
</span><del>- var observer = new MutationObserver(function() {
- var toProcess = queue.slice();
- queue = [];
</del><ins>+ /**
+ `RSVP.allSettled` is similar to `RSVP.all`, but instead of implementing
+ a fail-fast method, it waits until all the promises have returned and
+ shows you all the results. This is useful if you want to handle multiple
+ promises' failure states together as a set.
</ins><span class="cx">
</span><del>- toProcess.forEach(function(tuple) {
- var callback = tuple[0], binding = tuple[1];
- callback.call(binding);
- });
- });
</del><ins>+ Returns a promise that is fulfilled when all the given promises have been
+ settled. The return promise is fulfilled with an array of the states of
+ the promises passed into the `promises` array argument.
</ins><span class="cx">
</span><del>- var element = document.createElement('div');
- observer.observe(element, { attributes: true });
</del><ins>+ Each state object will either indicate fulfillment or rejection, and
+ provide the corresponding value or reason. The states will take one of
+ the following formats:
</ins><span class="cx">
</span><del>- // Chrome Memory Leak: https://bugs.webkit.org/show_bug.cgi?id=93661
- window.addEventListener('unload', function(){
- observer.disconnect();
- observer = null;
</del><ins>+ ```javascript
+ { state: 'fulfilled', value: value }
+ or
+ { state: 'rejected', reason: reason }
+ ```
+
+ Example:
+
+ ```javascript
+ var promise1 = RSVP.Promise.resolve(1);
+ var promise2 = RSVP.Promise.reject(new Error('2'));
+ var promise3 = RSVP.Promise.reject(new Error('3'));
+ var promises = [ promise1, promise2, promise3 ];
+
+ RSVP.allSettled(promises).then(function(array){
+ // array == [
+ // { state: 'fulfilled', value: 1 },
+ // { state: 'rejected', reason: Error },
+ // { state: 'rejected', reason: Error }
+ // ]
+ // Note that for the second item, reason.message will be "2", and for the
+ // third item, reason.message will be "3".
+ }, function(error) {
+ // Not run. (This block would only be called if allSettled had failed,
+ // for instance if passed an incorrect argument type.)
</ins><span class="cx"> });
</span><ins>+ ```
</ins><span class="cx">
</span><del>- async = function(callback, binding) {
- queue.push([callback, binding]);
- element.setAttribute('drainQueue', 'drainQueue');
- };
- } else {
- async = function(callback, binding) {
- setTimeout(function() {
- callback.call(binding);
- }, 1);
- };
</del><ins>+ @method allSettled
+ @for RSVP
+ @param {Array} promises
+ @param {String} label - optional string that describes the promise.
+ Useful for tooling.
+ @return {Promise} promise that is fulfilled with an array of the settled
+ states of the constituent promises.
+ @static
+ */
+
+ __exports__["default"] = function allSettled(entries, label) {
+ return new Promise(function(resolve, reject) {
+ if (!isArray(entries)) {
+ throw new TypeError('You must pass an array to allSettled.');
+ }
+
+ var remaining = entries.length;
+ var entry;
+
+ if (remaining === 0) {
+ resolve([]);
+ return;
+ }
+
+ var results = new Array(remaining);
+
+ function fulfilledResolver(index) {
+ return function(value) {
+ resolveAll(index, fulfilled(value));
+ };
+ }
+
+ function rejectedResolver(index) {
+ return function(reason) {
+ resolveAll(index, rejected(reason));
+ };
+ }
+
+ function resolveAll(index, value) {
+ results[index] = value;
+ if (--remaining === 0) {
+ resolve(results);
+ }
+ }
+
+ for (var index = 0; index < entries.length; index++) {
+ entry = entries[index];
+
+ if (isNonThenable(entry)) {
+ resolveAll(index, fulfilled(entry));
+ } else {
+ Promise.cast(entry).then(fulfilledResolver(index), rejectedResolver(index));
+ }
+ }
+ }, label);
+ };
+
+ function fulfilled(value) {
+ return { state: 'fulfilled', value: value };
</ins><span class="cx"> }
</span><span class="cx">
</span><del>- var Event = function(type, options) {
- this.type = type;
</del><ins>+ function rejected(reason) {
+ return { state: 'rejected', reason: reason };
+ }
+ });
+define("rsvp/config",
+ ["./events","exports"],
+ function(__dependency1__, __exports__) {
+ "use strict";
+ var EventTarget = __dependency1__["default"];
</ins><span class="cx">
</span><del>- for (var option in options) {
- if (!options.hasOwnProperty(option)) { continue; }
</del><ins>+ var config = {
+ instrument: false
+ };
</ins><span class="cx">
</span><del>- this[option] = options[option];
</del><ins>+ EventTarget.mixin(config);
+
+ function configure(name, value) {
+ if (name === 'onerror') {
+ // handle for legacy users that expect the actual
+ // error to be passed to their function added via
+ // `RSVP.configure('onerror', someFunctionHere);`
+ config.on('error', value);
+ return;
</ins><span class="cx"> }
</span><ins>+
+ if (arguments.length === 2) {
+ config[name] = value;
+ } else {
+ return config[name];
+ }
+ }
+
+ __exports__.config = config;
+ __exports__.configure = configure;
+ });
+define("rsvp/defer",
+ ["./promise","exports"],
+ function(__dependency1__, __exports__) {
+ "use strict";
+ var Promise = __dependency1__["default"];
+
+ /**
+ `RSVP.defer` returns an object similar to jQuery's `$.Deferred`.
+ `RSVP.defer` should be used when porting over code reliant on `$.Deferred`'s
+ interface. New code should use the `RSVP.Promise` constructor instead.
+
+ The object returned from `RSVP.defer` is a plain object with three properties:
+
+ * promise - an `RSVP.Promise`.
+ * reject - a function that causes the `promise` property on this object to
+ become rejected
+ * resolve - a function that causes the `promise` property on this object to
+ become fulfilled.
+
+ Example:
+
+ ```javascript
+ var deferred = RSVP.defer();
+
+ deferred.resolve("Success!");
+
+ defered.promise.then(function(value){
+ // value here is "Success!"
+ });
+ ```
+
+ @method defer
+ @for RSVP
+ @param {String} label optional string for labeling the promise.
+ Useful for tooling.
+ @return {Object}
+ */
+
+ __exports__["default"] = function defer(label) {
+ var deferred = { };
+
+ deferred.promise = new Promise(function(resolve, reject) {
+ deferred.resolve = resolve;
+ deferred.reject = reject;
+ }, label);
+
+ return deferred;
</ins><span class="cx"> };
</span><del>-
</del><ins>+ });
+define("rsvp/events",
+ ["exports"],
+ function(__exports__) {
+ "use strict";
</ins><span class="cx"> var indexOf = function(callbacks, callback) {
</span><span class="cx"> for (var i=0, l=callbacks.length; i<l; i++) {
</span><del>- if (callbacks[i][0] === callback) { return i; }
</del><ins>+ if (callbacks[i] === callback) { return i; }
</ins><span class="cx"> }
</span><span class="cx">
</span><span class="cx"> return -1;
</span><span class="lines">@@ -5834,231 +8212,1964 @@
</span><span class="cx"> return callbacks;
</span><span class="cx"> };
</span><span class="cx">
</span><del>- var EventTarget = {
</del><ins>+ /**
+ @class RSVP.EventTarget
+ */
+ __exports__["default"] = {
+
+ /**
+ `RSVP.EventTarget.mixin` extends an object with EventTarget methods. For
+ Example:
+
+ ```javascript
+ var object = {};
+
+ RSVP.EventTarget.mixin(object);
+
+ object.on("finished", function(event) {
+ // handle event
+ });
+
+ object.trigger("finished", { detail: value });
+ ```
+
+ `EventTarget.mixin` also works with prototypes:
+
+ ```javascript
+ var Person = function() {};
+ RSVP.EventTarget.mixin(Person.prototype);
+
+ var yehuda = new Person();
+ var tom = new Person();
+
+ yehuda.on("poke", function(event) {
+ console.log("Yehuda says OW");
+ });
+
+ tom.on("poke", function(event) {
+ console.log("Tom says OW");
+ });
+
+ yehuda.trigger("poke");
+ tom.trigger("poke");
+ ```
+
+ @method mixin
+ @param {Object} object object to extend with EventTarget methods
+ @private
+ */
</ins><span class="cx"> mixin: function(object) {
</span><span class="cx"> object.on = this.on;
</span><span class="cx"> object.off = this.off;
</span><span class="cx"> object.trigger = this.trigger;
</span><ins>+ object._promiseCallbacks = undefined;
</ins><span class="cx"> return object;
</span><span class="cx"> },
</span><span class="cx">
</span><del>- on: function(eventNames, callback, binding) {
- var allCallbacks = callbacksFor(this), callbacks, eventName;
- eventNames = eventNames.split(/\s+/);
- binding = binding || this;
</del><ins>+ /**
+ Registers a callback to be executed when `eventName` is triggered
</ins><span class="cx">
</span><del>- while (eventName = eventNames.shift()) {
- callbacks = allCallbacks[eventName];
</del><ins>+ ```javascript
+ object.on('event', function(eventInfo){
+ // handle the event
+ });
</ins><span class="cx">
</span><del>- if (!callbacks) {
- callbacks = allCallbacks[eventName] = [];
- }
</del><ins>+ object.trigger('event');
+ ```
</ins><span class="cx">
</span><del>- if (indexOf(callbacks, callback) === -1) {
- callbacks.push([callback, binding]);
- }
</del><ins>+ @method on
+ @param {String} eventName name of the event to listen for
+ @param {Function} callback function to be called when the event is triggered.
+ @private
+ */
+ on: function(eventName, callback) {
+ var allCallbacks = callbacksFor(this), callbacks;
+
+ callbacks = allCallbacks[eventName];
+
+ if (!callbacks) {
+ callbacks = allCallbacks[eventName] = [];
</ins><span class="cx"> }
</span><ins>+
+ if (indexOf(callbacks, callback) === -1) {
+ callbacks.push(callback);
+ }
</ins><span class="cx"> },
</span><span class="cx">
</span><del>- off: function(eventNames, callback) {
- var allCallbacks = callbacksFor(this), callbacks, eventName, index;
- eventNames = eventNames.split(/\s+/);
</del><ins>+ /**
+ You can use `off` to stop firing a particular callback for an event:
</ins><span class="cx">
</span><del>- while (eventName = eventNames.shift()) {
- if (!callback) {
- allCallbacks[eventName] = [];
- continue;
- }
</del><ins>+ ```javascript
+ function doStuff() { // do stuff! }
+ object.on('stuff', doStuff);
</ins><span class="cx">
</span><del>- callbacks = allCallbacks[eventName];
</del><ins>+ object.trigger('stuff'); // doStuff will be called
</ins><span class="cx">
</span><del>- index = indexOf(callbacks, callback);
</del><ins>+ // Unregister ONLY the doStuff callback
+ object.off('stuff', doStuff);
+ object.trigger('stuff'); // doStuff will NOT be called
+ ```
</ins><span class="cx">
</span><del>- if (index !== -1) { callbacks.splice(index, 1); }
</del><ins>+ If you don't pass a `callback` argument to `off`, ALL callbacks for the
+ event will not be executed when the event fires. For example:
+
+ ```javascript
+ var callback1 = function(){};
+ var callback2 = function(){};
+
+ object.on('stuff', callback1);
+ object.on('stuff', callback2);
+
+ object.trigger('stuff'); // callback1 and callback2 will be executed.
+
+ object.off('stuff');
+ object.trigger('stuff'); // callback1 and callback2 will not be executed!
+ ```
+
+ @method off
+ @param {String} eventName event to stop listening to
+ @param {Function} callback optional argument. If given, only the function
+ given will be removed from the event's callback queue. If no `callback`
+ argument is given, all callbacks will be removed from the event's callback
+ queue.
+ @private
+
+ */
+ off: function(eventName, callback) {
+ var allCallbacks = callbacksFor(this), callbacks, index;
+
+ if (!callback) {
+ allCallbacks[eventName] = [];
+ return;
</ins><span class="cx"> }
</span><ins>+
+ callbacks = allCallbacks[eventName];
+
+ index = indexOf(callbacks, callback);
+
+ if (index !== -1) { callbacks.splice(index, 1); }
</ins><span class="cx"> },
</span><span class="cx">
</span><ins>+ /**
+ Use `trigger` to fire custom events. For example:
+
+ ```javascript
+ object.on('foo', function(){
+ console.log('foo event happened!');
+ });
+ object.trigger('foo');
+ // 'foo event happened!' logged to the console
+ ```
+
+ You can also pass a value as a second argument to `trigger` that will be
+ passed as an argument to all event listeners for the event:
+
+ ```javascript
+ object.on('foo', function(value){
+ console.log(value.name);
+ });
+
+ object.trigger('foo', { name: 'bar' });
+ // 'bar' logged to the console
+ ```
+
+ @method trigger
+ @param {String} eventName name of the event to be triggered
+ @param {Any} options optional value to be passed to any event handlers for
+ the given `eventName`
+ @private
+ */
</ins><span class="cx"> trigger: function(eventName, options) {
</span><span class="cx"> var allCallbacks = callbacksFor(this),
</span><del>- callbacks, callbackTuple, callback, binding, event;
</del><ins>+ callbacks, callbackTuple, callback, binding;
</ins><span class="cx">
</span><span class="cx"> if (callbacks = allCallbacks[eventName]) {
</span><span class="cx"> // Don't cache the callbacks.length since it may grow
</span><span class="cx"> for (var i=0; i<callbacks.length; i++) {
</span><del>- callbackTuple = callbacks[i];
- callback = callbackTuple[0];
- binding = callbackTuple[1];
</del><ins>+ callback = callbacks[i];
</ins><span class="cx">
</span><del>- if (typeof options !== 'object') {
- options = { detail: options };
</del><ins>+ callback(options);
+ }
+ }
+ }
+ };
+ });
+define("rsvp/filter",
+ ["./all","./map","./utils","exports"],
+ function(__dependency1__, __dependency2__, __dependency3__, __exports__) {
+ "use strict";
+ var all = __dependency1__["default"];
+ var map = __dependency2__["default"];
+ var isFunction = __dependency3__.isFunction;
+ var isArray = __dependency3__.isArray;
+
+ /**
+ `RSVP.filter` is similar to JavaScript's native `filter` method, except that it
+ waits for all promises to become fulfilled before running the `filterFn` on
+ each item in given to `promises`. `RSVP.filter` returns a promise that will
+ become fulfilled with the result of running `filterFn` on the values the
+ promises become fulfilled with.
+
+ For example:
+
+ ```javascript
+
+ var promise1 = RSVP.resolve(1);
+ var promise2 = RSVP.resolve(2);
+ var promise3 = RSVP.resolve(3);
+
+ var filterFn = function(item){
+ return item > 1;
+ };
+
+ RSVP.filter(promises, filterFn).then(function(result){
+ // result is [ 2, 3 ]
+ });
+ ```
+
+ If any of the `promises` given to `RSVP.filter` are rejected, the first promise
+ that is rejected will be given as an argument to the returned promise's
+ rejection handler. For example:
+
+ ```javascript
+ var promise1 = RSVP.resolve(1);
+ var promise2 = RSVP.reject(new Error("2"));
+ var promise3 = RSVP.reject(new Error("3"));
+ var promises = [ promise1, promise2, promise3 ];
+
+ var filterFn = function(item){
+ return item > 1;
+ };
+
+ RSVP.filter(promises, filterFn).then(function(array){
+ // Code here never runs because there are rejected promises!
+ }, function(reason) {
+ // reason.message === "2"
+ });
+ ```
+
+ `RSVP.filter` will also wait for any promises returned from `filterFn`.
+ For instance, you may want to fetch a list of users then return a subset
+ of those users based on some asynchronous operation:
+
+ ```javascript
+
+ var alice = { name: 'alice' };
+ var bob = { name: 'bob' };
+ var users = [ alice, bob ];
+
+ var promises = users.map(function(user){
+ return RSVP.resolve(user);
+ });
+
+ var filterFn = function(user){
+ // Here, Alice has permissions to create a blog post, but Bob does not.
+ return getPrivilegesForUser(user).then(function(privs){
+ return privs.can_create_blog_post === true;
+ });
+ };
+ RSVP.filter(promises, filterFn).then(function(users){
+ // true, because the server told us only Alice can create a blog post.
+ users.length === 1;
+ // false, because Alice is the only user present in `users`
+ users[0] === bob;
+ });
+ ```
+
+ @method filter
+ @for RSVP
+ @param {Array} promises
+ @param {Function} filterFn - function to be called on each resolved value to
+ filter the final results.
+ @param {String} label optional string describing the promise. Useful for
+ tooling.
+ @return {Promise}
+ */
+ function filter(promises, filterFn, label) {
+ return all(promises, label).then(function(values){
+ if (!isArray(promises)) {
+ throw new TypeError('You must pass an array to filter.');
+ }
+
+ if (!isFunction(filterFn)){
+ throw new TypeError("You must pass a function to filter's second argument.");
+ }
+
+ return map(promises, filterFn, label).then(function(filterResults){
+ var i,
+ valuesLen = values.length,
+ filtered = [];
+
+ for (i = 0; i < valuesLen; i++){
+ if(filterResults[i]) filtered.push(values[i]);
+ }
+ return filtered;
+ });
+ });
+ }
+
+ __exports__["default"] = filter;
+ });
+define("rsvp/hash",
+ ["./promise","./utils","exports"],
+ function(__dependency1__, __dependency2__, __exports__) {
+ "use strict";
+ var Promise = __dependency1__["default"];
+ var isNonThenable = __dependency2__.isNonThenable;
+ var keysOf = __dependency2__.keysOf;
+
+ /**
+ `RSVP.hash` is similar to `RSVP.all`, but takes an object instead of an array
+ for its `promises` argument.
+
+ Returns a promise that is fulfilled when all the given promises have been
+ fulfilled, or rejected if any of them become rejected. The returned promise
+ is fulfilled with a hash that has the same key names as the `promises` object
+ argument. If any of the values in the object are not promises, they will
+ simply be copied over to the fulfilled object.
+
+ Example:
+
+ ```javascript
+ var promises = {
+ myPromise: RSVP.resolve(1),
+ yourPromise: RSVP.resolve(2),
+ theirPromise: RSVP.resolve(3),
+ notAPromise: 4
+ };
+
+ RSVP.hash(promises).then(function(hash){
+ // hash here is an object that looks like:
+ // {
+ // myPromise: 1,
+ // yourPromise: 2,
+ // theirPromise: 3,
+ // notAPromise: 4
+ // }
+ });
+ ````
+
+ If any of the `promises` given to `RSVP.hash` are rejected, the first promise
+ that is rejected will be given as the reason to the rejection handler.
+
+ Example:
+
+ ```javascript
+ var promises = {
+ myPromise: RSVP.resolve(1),
+ rejectedPromise: RSVP.reject(new Error("rejectedPromise")),
+ anotherRejectedPromise: RSVP.reject(new Error("anotherRejectedPromise")),
+ };
+
+ RSVP.hash(promises).then(function(hash){
+ // Code here never runs because there are rejected promises!
+ }, function(reason) {
+ // reason.message === "rejectedPromise"
+ });
+ ```
+
+ An important note: `RSVP.hash` is intended for plain JavaScript objects that
+ are just a set of keys and values. `RSVP.hash` will NOT preserve prototype
+ chains.
+
+ Example:
+
+ ```javascript
+ function MyConstructor(){
+ this.example = RSVP.resolve("Example");
+ }
+
+ MyConstructor.prototype = {
+ protoProperty: RSVP.resolve("Proto Property")
+ };
+
+ var myObject = new MyConstructor();
+
+ RSVP.hash(myObject).then(function(hash){
+ // protoProperty will not be present, instead you will just have an
+ // object that looks like:
+ // {
+ // example: "Example"
+ // }
+ //
+ // hash.hasOwnProperty('protoProperty'); // false
+ // 'undefined' === typeof hash.protoProperty
+ });
+ ```
+
+ @method hash
+ @for RSVP
+ @param {Object} promises
+ @param {String} label optional string that describes the promise.
+ Useful for tooling.
+ @return {Promise} promise that is fulfilled when all properties of `promises`
+ have been fulfilled, or rejected if any of them become rejected.
+ @static
+ */
+ __exports__["default"] = function hash(object, label) {
+ return new Promise(function(resolve, reject){
+ var results = {};
+ var keys = keysOf(object);
+ var remaining = keys.length;
+ var entry, property;
+
+ if (remaining === 0) {
+ resolve(results);
+ return;
+ }
+
+ function fulfilledTo(property) {
+ return function(value) {
+ results[property] = value;
+ if (--remaining === 0) {
+ resolve(results);
</ins><span class="cx"> }
</span><ins>+ };
+ }
</ins><span class="cx">
</span><del>- event = new Event(eventName, options);
- callback.call(binding, event);
</del><ins>+ function onRejection(reason) {
+ remaining = 0;
+ reject(reason);
+ }
+
+ for (var i = 0; i < keys.length; i++) {
+ property = keys[i];
+ entry = object[property];
+
+ if (isNonThenable(entry)) {
+ results[property] = entry;
+ if (--remaining === 0) {
+ resolve(results);
+ }
+ } else {
+ Promise.cast(entry).then(fulfilledTo(property), onRejection);
</ins><span class="cx"> }
</span><span class="cx"> }
</span><ins>+ });
+ };
+ });
+define("rsvp/instrument",
+ ["./config","./utils","exports"],
+ function(__dependency1__, __dependency2__, __exports__) {
+ "use strict";
+ var config = __dependency1__.config;
+ var now = __dependency2__.now;
+
+ __exports__["default"] = function instrument(eventName, promise, child) {
+ // instrumentation should not disrupt normal usage.
+ try {
+ config.trigger(eventName, {
+ guid: promise._guidKey + promise._id,
+ eventName: eventName,
+ detail: promise._detail,
+ childGuid: child && promise._guidKey + child._id,
+ label: promise._label,
+ timeStamp: now(),
+ stack: new Error(promise._label).stack
+ });
+ } catch(error) {
+ setTimeout(function(){
+ throw error;
+ }, 0);
</ins><span class="cx"> }
</span><span class="cx"> };
</span><ins>+ });
+define("rsvp/map",
+ ["./promise","./all","./utils","exports"],
+ function(__dependency1__, __dependency2__, __dependency3__, __exports__) {
+ "use strict";
+ var Promise = __dependency1__["default"];
+ var all = __dependency2__["default"];
+ var isArray = __dependency3__.isArray;
+ var isFunction = __dependency3__.isFunction;
</ins><span class="cx">
</span><del>- var Promise = function() {
- this.on('promise:resolved', function(event) {
- this.trigger('success', { detail: event.detail });
- }, this);
</del><ins>+ /**
+ `RSVP.map` is similar to JavaScript's native `map` method, except that it
+ waits for all promises to become fulfilled before running the `mapFn` on
+ each item in given to `promises`. `RSVP.map` returns a promise that will
+ become fulfilled with the result of running `mapFn` on the values the promises
+ become fulfilled with.
</ins><span class="cx">
</span><del>- this.on('promise:failed', function(event) {
- this.trigger('error', { detail: event.detail });
- }, this);
</del><ins>+ For example:
+
+ ```javascript
+
+ var promise1 = RSVP.resolve(1);
+ var promise2 = RSVP.resolve(2);
+ var promise3 = RSVP.resolve(3);
+ var promises = [ promise1, promise2, promise3 ];
+
+ var mapFn = function(item){
+ return item + 1;
+ };
+
+ RSVP.map(promises, mapFn).then(function(result){
+ // result is [ 2, 3, 4 ]
+ });
+ ```
+
+ If any of the `promises` given to `RSVP.map` are rejected, the first promise
+ that is rejected will be given as an argument to the returned promise's
+ rejection handler. For example:
+
+ ```javascript
+ var promise1 = RSVP.resolve(1);
+ var promise2 = RSVP.reject(new Error("2"));
+ var promise3 = RSVP.reject(new Error("3"));
+ var promises = [ promise1, promise2, promise3 ];
+
+ var mapFn = function(item){
+ return item + 1;
+ };
+
+ RSVP.map(promises, mapFn).then(function(array){
+ // Code here never runs because there are rejected promises!
+ }, function(reason) {
+ // reason.message === "2"
+ });
+ ```
+
+ `RSVP.map` will also wait if a promise is returned from `mapFn`. For example,
+ say you want to get all comments from a set of blog posts, but you need
+ the blog posts first becuase they contain a url to those comments.
+
+ ```javscript
+
+ var mapFn = function(blogPost){
+ // getComments does some ajax and returns an RSVP.Promise that is fulfilled
+ // with some comments data
+ return getComments(blogPost.comments_url);
+ };
+
+ // getBlogPosts does some ajax and returns an RSVP.Promise that is fulfilled
+ // with some blog post data
+ RSVP.map(getBlogPosts(), mapFn).then(function(comments){
+ // comments is the result of asking the server for the comments
+ // of all blog posts returned from getBlogPosts()
+ });
+ ```
+
+ @method map
+ @for RSVP
+ @param {Array} promises
+ @param {Function} mapFn function to be called on each fulfilled promise.
+ @param {String} label optional string for labeling the promise.
+ Useful for tooling.
+ @return {Promise} promise that is fulfilled with the result of calling
+ `mapFn` on each fulfilled promise or value when they become fulfilled.
+ The promise will be rejected if any of the given `promises` become rejected.
+ @static
+ */
+ __exports__["default"] = function map(promises, mapFn, label) {
+ return all(promises, label).then(function(results){
+ if (!isArray(promises)) {
+ throw new TypeError('You must pass an array to map.');
+ }
+
+ if (!isFunction(mapFn)){
+ throw new TypeError("You must pass a function to map's second argument.");
+ }
+
+
+ var resultLen = results.length,
+ mappedResults = [],
+ i;
+
+ for (i = 0; i < resultLen; i++){
+ mappedResults.push(mapFn(results[i]));
+ }
+
+ return all(mappedResults, label);
+ });
</ins><span class="cx"> };
</span><ins>+ });
+define("rsvp/node",
+ ["./promise","exports"],
+ function(__dependency1__, __exports__) {
+ "use strict";
+ var Promise = __dependency1__["default"];
</ins><span class="cx">
</span><del>- var noop = function() {};
</del><ins>+ var slice = Array.prototype.slice;
</ins><span class="cx">
</span><del>- var invokeCallback = function(type, promise, callback, event) {
- var hasCallback = typeof callback === 'function',
</del><ins>+ function makeNodeCallbackFor(resolve, reject) {
+ return function (error, value) {
+ if (error) {
+ reject(error);
+ } else if (arguments.length > 2) {
+ resolve(slice.call(arguments, 1));
+ } else {
+ resolve(value);
+ }
+ };
+ }
+
+ /**
+ `RSVP.denodeify` takes a "node-style" function and returns a function that
+ will return an `RSVP.Promise`. You can use `denodeify` in Node.js or the
+ browser when you'd prefer to use promises over using callbacks. For example,
+ `denodeify` transforms the following:
+
+ ```javascript
+ var fs = require('fs');
+
+ fs.readFile('myfile.txt', function(err, data){
+ if (err) return handleError(err);
+ handleData(data);
+ });
+ ```
+
+ into:
+
+ ```javascript
+ var fs = require('fs');
+
+ var readFile = RSVP.denodeify(fs.readFile);
+
+ readFile('myfile.txt').then(handleData, handleError);
+ ```
+
+ Using `denodeify` makes it easier to compose asynchronous operations instead
+ of using callbacks. For example, instead of:
+
+ ```javascript
+ var fs = require('fs');
+ var log = require('some-async-logger');
+
+ fs.readFile('myfile.txt', function(err, data){
+ if (err) return handleError(err);
+ fs.writeFile('myfile2.txt', data, function(err){
+ if (err) throw err;
+ log('success', function(err) {
+ if (err) throw err;
+ });
+ });
+ });
+ ```
+
+ You can chain the operations together using `then` from the returned promise:
+
+ ```javascript
+ var fs = require('fs');
+ var denodeify = RSVP.denodeify;
+ var readFile = denodeify(fs.readFile);
+ var writeFile = denodeify(fs.writeFile);
+ var log = denodeify(require('some-async-logger'));
+
+ readFile('myfile.txt').then(function(data){
+ return writeFile('myfile2.txt', data);
+ }).then(function(){
+ return log('SUCCESS');
+ }).then(function(){
+ // success handler
+ }, function(reason){
+ // rejection handler
+ });
+ ```
+
+ @method denodeify
+ @for RSVP
+ @param {Function} nodeFunc a "node-style" function that takes a callback as
+ its last argument. The callback expects an error to be passed as its first
+ argument (if an error occurred, otherwise null), and the value from the
+ operation as its second argument ("function(err, value){ }").
+ @param {Any} binding optional argument for binding the "this" value when
+ calling the `nodeFunc` function.
+ @return {Function} a function that wraps `nodeFunc` to return an
+ `RSVP.Promise`
+ @static
+ */
+ __exports__["default"] = function denodeify(nodeFunc, binding) {
+ return function() {
+ var nodeArgs = slice.call(arguments), resolve, reject;
+ var thisArg = this || binding;
+
+ return new Promise(function(resolve, reject) {
+ Promise.all(nodeArgs).then(function(nodeArgs) {
+ try {
+ nodeArgs.push(makeNodeCallbackFor(resolve, reject));
+ nodeFunc.apply(thisArg, nodeArgs);
+ } catch(e) {
+ reject(e);
+ }
+ });
+ });
+ };
+ };
+ });
+define("rsvp/promise",
+ ["./config","./events","./instrument","./utils","./promise/cast","./promise/all","./promise/race","./promise/resolve","./promise/reject","exports"],
+ function(__dependency1__, __dependency2__, __dependency3__, __dependency4__, __dependency5__, __dependency6__, __dependency7__, __dependency8__, __dependency9__, __exports__) {
+ "use strict";
+ var config = __dependency1__.config;
+ var EventTarget = __dependency2__["default"];
+ var instrument = __dependency3__["default"];
+ var objectOrFunction = __dependency4__.objectOrFunction;
+ var isFunction = __dependency4__.isFunction;
+ var now = __dependency4__.now;
+ var cast = __dependency5__["default"];
+ var all = __dependency6__["default"];
+ var race = __dependency7__["default"];
+ var Resolve = __dependency8__["default"];
+ var Reject = __dependency9__["default"];
+
+ var guidKey = 'rsvp_' + now() + '-';
+ var counter = 0;
+
+ function noop() {}
+
+ __exports__["default"] = Promise;
+
+
+ /**
+ Promise objects represent the eventual result of an asynchronous operation. The
+ primary way of interacting with a promise is through its `then` method, which
+ registers callbacks to receive either a promise’s eventual value or the reason
+ why the promise cannot be fulfilled.
+
+ Terminology
+ -----------
+
+ - `promise` is an object or function with a `then` method whose behavior conforms to this specification.
+ - `thenable` is an object or function that defines a `then` method.
+ - `value` is any legal JavaScript value (including undefined, a thenable, or a promise).
+ - `exception` is a value that is thrown using the throw statement.
+ - `reason` is a value that indicates why a promise was rejected.
+ - `settled` the final resting state of a promise, fulfilled or rejected.
+
+ A promise can be in one of three states: pending, fulfilled, or rejected.
+
+ Promises that are fulfilled have a fulfillment value and are in the fulfilled
+ state. Promises that are rejected have a rejection reason and are in the
+ rejected state. A fulfillment value is never a thenable. Similarly, a
+ rejection reason is never a thenable.
+
+ Promises can also be said to *resolve* a value. If this value is also a
+ promise, then the original promise's settled state will match the value's
+ settled state. So a promise that *resolves* a promise that rejects will
+ itself reject, and a promise that *resolves* a promise that fulfills will
+ itself fulfill.
+
+
+ Basic Usage:
+ ------------
+
+ ```js
+ var promise = new Promise(function(resolve, reject) {
+ // on success
+ resolve(value);
+
+ // on failure
+ reject(reason);
+ });
+
+ promise.then(function(value) {
+ // on fulfillment
+ }, function(reason) {
+ // on rejection
+ });
+ ```
+
+ Advanced Usage:
+ ---------------
+
+ Promises shine when abstracting away asynchronous interactions such as
+ `XMLHttpRequest`s.
+
+ ```js
+ function getJSON(url) {
+ return new Promise(function(resolve, reject){
+ var xhr = new XMLHttpRequest();
+
+ xhr.open('GET', url);
+ xhr.onreadystatechange = handler;
+ xhr.responseType = 'json';
+ xhr.setRequestHeader('Accept', 'application/json');
+ xhr.send();
+
+ function handler() {
+ if (this.readyState === this.DONE) {
+ if (this.status === 200) {
+ resolve(this.response);
+ } else {
+ reject(new Error("getJSON: `" + url + "` failed with status: [" + this.status + "]");
+ }
+ }
+ };
+ });
+ }
+
+ getJSON('/posts.json').then(function(json) {
+ // on fulfillment
+ }, function(reason) {
+ // on rejection
+ });
+ ```
+
+ Unlike callbacks, promises are great composable primitives.
+
+ ```js
+ Promise.all([
+ getJSON('/posts'),
+ getJSON('/comments')
+ ]).then(function(values){
+ values[0] // => postsJSON
+ values[1] // => commentsJSON
+
+ return values;
+ });
+ ```
+
+ @class RSVP.Promise
+ @param {function}
+ @param {String} label optional string for labeling the promise.
+ Useful for tooling.
+ @constructor
+ */
+ function Promise(resolver, label) {
+ if (!isFunction(resolver)) {
+ throw new TypeError('You must pass a resolver function as the first argument to the promise constructor');
+ }
+
+ if (!(this instanceof Promise)) {
+ throw new TypeError("Failed to construct 'Promise': Please use the 'new' operator, this object constructor cannot be called as a function.");
+ }
+
+ this._id = counter++;
+ this._label = label;
+ this._subscribers = [];
+
+ if (config.instrument) {
+ instrument('created', this);
+ }
+
+ if (noop !== resolver) {
+ invokeResolver(resolver, this);
+ }
+ }
+
+ function invokeResolver(resolver, promise) {
+ function resolvePromise(value) {
+ resolve(promise, value);
+ }
+
+ function rejectPromise(reason) {
+ reject(promise, reason);
+ }
+
+ try {
+ resolver(resolvePromise, rejectPromise);
+ } catch(e) {
+ rejectPromise(e);
+ }
+ }
+
+ Promise.cast = cast;
+ Promise.all = all;
+ Promise.race = race;
+ Promise.resolve = Resolve;
+ Promise.reject = Reject;
+
+ var PENDING = void 0;
+ var SEALED = 0;
+ var FULFILLED = 1;
+ var REJECTED = 2;
+
+ function subscribe(parent, child, onFulfillment, onRejection) {
+ var subscribers = parent._subscribers;
+ var length = subscribers.length;
+
+ subscribers[length] = child;
+ subscribers[length + FULFILLED] = onFulfillment;
+ subscribers[length + REJECTED] = onRejection;
+ }
+
+ function publish(promise, settled) {
+ var child, callback, subscribers = promise._subscribers, detail = promise._detail;
+
+ if (config.instrument) {
+ instrument(settled === FULFILLED ? 'fulfilled' : 'rejected', promise);
+ }
+
+ for (var i = 0; i < subscribers.length; i += 3) {
+ child = subscribers[i];
+ callback = subscribers[i + settled];
+
+ invokeCallback(settled, child, callback, detail);
+ }
+
+ promise._subscribers = null;
+ }
+
+ Promise.prototype = {
+ constructor: Promise,
+
+ _id: undefined,
+ _guidKey: guidKey,
+ _label: undefined,
+
+ _state: undefined,
+ _detail: undefined,
+ _subscribers: undefined,
+
+ _onerror: function (reason) {
+ config.trigger('error', reason);
+ },
+
+ /**
+ The primary way of interacting with a promise is through its `then` method,
+ which registers callbacks to receive either a promise's eventual value or the
+ reason why the promise cannot be fulfilled.
+
+ ```js
+ findUser().then(function(user){
+ // user is available
+ }, function(reason){
+ // user is unavailable, and you are given the reason why
+ });
+ ```
+
+ Chaining
+ --------
+
+ The return value of `then` is itself a promise. This second, "downstream"
+ promise is resolved with the return value of the first promise's fulfillment
+ or rejection handler, or rejected if the handler throws an exception.
+
+ ```js
+ findUser().then(function (user) {
+ return user.name;
+ }, function (reason) {
+ return "default name";
+ }).then(function (userName) {
+ // If `findUser` fulfilled, `userName` will be the user's name, otherwise it
+ // will be `"default name"`
+ });
+
+ findUser().then(function (user) {
+ throw new Error("Found user, but still unhappy");
+ }, function (reason) {
+ throw new Error("`findUser` rejected and we're unhappy");
+ }).then(function (value) {
+ // never reached
+ }, function (reason) {
+ // if `findUser` fulfilled, `reason` will be "Found user, but still unhappy".
+ // If `findUser` rejected, `reason` will be "`findUser` rejected and we're unhappy".
+ });
+ ```
+ If the downstream promise does not specify a rejection handler, rejection reasons will be propagated further downstream.
+
+ ```js
+ findUser().then(function (user) {
+ throw new PedagogicalException("Upstream error");
+ }).then(function (value) {
+ // never reached
+ }).then(function (value) {
+ // never reached
+ }, function (reason) {
+ // The `PedgagocialException` is propagated all the way down to here
+ });
+ ```
+
+ Assimilation
+ ------------
+
+ Sometimes the value you want to propagate to a downstream promise can only be
+ retrieved asynchronously. This can be achieved by returning a promise in the
+ fulfillment or rejection handler. The downstream promise will then be pending
+ until the returned promise is settled. This is called *assimilation*.
+
+ ```js
+ findUser().then(function (user) {
+ return findCommentsByAuthor(user);
+ }).then(function (comments) {
+ // The user's comments are now available
+ });
+ ```
+
+ If the assimliated promise rejects, then the downstream promise will also reject.
+
+ ```js
+ findUser().then(function (user) {
+ return findCommentsByAuthor(user);
+ }).then(function (comments) {
+ // If `findCommentsByAuthor` fulfills, we'll have the value here
+ }, function (reason) {
+ // If `findCommentsByAuthor` rejects, we'll have the reason here
+ });
+ ```
+
+ Simple Example
+ --------------
+
+ Synchronous Example
+
+ ```javascript
+ var result;
+
+ try {
+ result = findResult();
+ // success
+ } catch(reason) {
+ // failure
+ }
+ ```
+
+ Errback Example
+
+ ```js
+ findResult(function(result, err){
+ if (err) {
+ // failure
+ } else {
+ // success
+ }
+ });
+ ```
+
+ Promise Example;
+
+ ```javascript
+ findResult().then(function(result){
+ // success
+ }, function(reason){
+ // failure
+ });
+ ```
+
+ Advanced Example
+ --------------
+
+ Synchronous Example
+
+ ```javascript
+ var author, books;
+
+ try {
+ author = findAuthor();
+ books = findBooksByAuthor(author);
+ // success
+ } catch(reason) {
+ // failure
+ }
+ ```
+
+ Errback Example
+
+ ```js
+
+ function foundBooks(books) {
+
+ }
+
+ function failure(reason) {
+
+ }
+
+ findAuthor(function(author, err){
+ if (err) {
+ failure(err);
+ // failure
+ } else {
+ try {
+ findBoooksByAuthor(author, function(books, err) {
+ if (err) {
+ failure(err);
+ } else {
+ try {
+ foundBooks(books);
+ } catch(reason) {
+ failure(reason);
+ }
+ }
+ });
+ } catch(error) {
+ failure(err);
+ }
+ // success
+ }
+ });
+ ```
+
+ Promise Example;
+
+ ```javascript
+ findAuthor().
+ then(findBooksByAuthor).
+ then(function(books){
+ // found books
+ }).catch(function(reason){
+ // something went wrong
+ });
+ ```
+
+ @method then
+ @param {Function} onFulfilled
+ @param {Function} onRejected
+ @param {String} label optional string for labeling the promise.
+ Useful for tooling.
+ @return {Promise}
+ */
+ then: function(onFulfillment, onRejection, label) {
+ var promise = this;
+ this._onerror = null;
+
+ var thenPromise = new this.constructor(noop, label);
+
+ if (this._state) {
+ var callbacks = arguments;
+ config.async(function invokePromiseCallback() {
+ invokeCallback(promise._state, thenPromise, callbacks[promise._state - 1], promise._detail);
+ });
+ } else {
+ subscribe(this, thenPromise, onFulfillment, onRejection);
+ }
+
+ if (config.instrument) {
+ instrument('chained', promise, thenPromise);
+ }
+
+ return thenPromise;
+ },
+
+ /**
+ `catch` is simply sugar for `then(undefined, onRejection)` which makes it the same
+ as the catch block of a try/catch statement.
+
+ ```js
+ function findAuthor(){
+ throw new Error("couldn't find that author");
+ }
+
+ // synchronous
+ try {
+ findAuthor();
+ } catch(reason) {
+ // something went wrong
+ }
+
+ // async with promises
+ findAuthor().catch(function(reason){
+ // something went wrong
+ });
+ ```
+
+ @method catch
+ @param {Function} onRejection
+ @param {String} label optional string for labeling the promise.
+ Useful for tooling.
+ @return {Promise}
+ */
+ 'catch': function(onRejection, label) {
+ return this.then(null, onRejection, label);
+ },
+
+ /**
+ `finally` will be invoked regardless of the promise's fate just as native
+ try/catch/finally behaves
+
+ Synchronous example:
+
+ ```js
+ findAuthor() {
+ if (Math.random() > 0.5) {
+ throw new Error();
+ }
+ return new Author();
+ }
+
+ try {
+ return findAuthor(); // succeed or fail
+ } catch(error) {
+ return findOtherAuther();
+ } finally {
+ // always runs
+ // doesn't affect the return value
+ }
+ ```
+
+ Asynchronous example:
+
+ ```js
+ findAuthor().catch(function(reason){
+ return findOtherAuther();
+ }).finally(function(){
+ // author was either found, or not
+ });
+ ```
+
+ @method finally
+ @param {Function} callback
+ @param {String} label optional string for labeling the promise.
+ Useful for tooling.
+ @return {Promise}
+ */
+ 'finally': function(callback, label) {
+ var constructor = this.constructor;
+
+ return this.then(function(value) {
+ return constructor.cast(callback()).then(function(){
+ return value;
+ });
+ }, function(reason) {
+ return constructor.cast(callback()).then(function(){
+ throw reason;
+ });
+ }, label);
+ }
+ };
+
+ function invokeCallback(settled, promise, callback, detail) {
+ var hasCallback = isFunction(callback),
</ins><span class="cx"> value, error, succeeded, failed;
</span><span class="cx">
</span><span class="cx"> if (hasCallback) {
</span><span class="cx"> try {
</span><del>- value = callback(event.detail);
</del><ins>+ value = callback(detail);
</ins><span class="cx"> succeeded = true;
</span><span class="cx"> } catch(e) {
</span><span class="cx"> failed = true;
</span><span class="cx"> error = e;
</span><span class="cx"> }
</span><span class="cx"> } else {
</span><del>- value = event.detail;
</del><ins>+ value = detail;
</ins><span class="cx"> succeeded = true;
</span><span class="cx"> }
</span><span class="cx">
</span><del>- if (value && typeof value.then === 'function') {
- value.then(function(value) {
- promise.resolve(value);
- }, function(error) {
- promise.reject(error);
- });
</del><ins>+ if (handleThenable(promise, value)) {
+ return;
</ins><span class="cx"> } else if (hasCallback && succeeded) {
</span><del>- promise.resolve(value);
</del><ins>+ resolve(promise, value);
</ins><span class="cx"> } else if (failed) {
</span><del>- promise.reject(error);
- } else {
- promise[type](value);
</del><ins>+ reject(promise, error);
+ } else if (settled === FULFILLED) {
+ resolve(promise, value);
+ } else if (settled === REJECTED) {
+ reject(promise, value);
</ins><span class="cx"> }
</span><del>- };
</del><ins>+ }
</ins><span class="cx">
</span><del>- Promise.prototype = {
- then: function(done, fail) {
- var thenPromise = new Promise();
</del><ins>+ function handleThenable(promise, value) {
+ var then = null,
+ resolved;
</ins><span class="cx">
</span><del>- if (this.isResolved) {
- RSVP.async(function() {
- invokeCallback('resolve', thenPromise, done, { detail: this.resolvedValue });
- }, this);
</del><ins>+ try {
+ if (promise === value) {
+ throw new TypeError("A promises callback cannot return that same promise.");
</ins><span class="cx"> }
</span><span class="cx">
</span><del>- if (this.isRejected) {
- RSVP.async(function() {
- invokeCallback('reject', thenPromise, fail, { detail: this.rejectedValue });
- }, this);
</del><ins>+ if (objectOrFunction(value)) {
+ then = value.then;
+
+ if (isFunction(then)) {
+ then.call(value, function(val) {
+ if (resolved) { return true; }
+ resolved = true;
+
+ if (value !== val) {
+ resolve(promise, val);
+ } else {
+ fulfill(promise, val);
+ }
+ }, function(val) {
+ if (resolved) { return true; }
+ resolved = true;
+
+ reject(promise, val);
+ }, 'derived from: ' + (promise._label || ' unknown promise'));
+
+ return true;
+ }
</ins><span class="cx"> }
</span><ins>+ } catch (error) {
+ if (resolved) { return true; }
+ reject(promise, error);
+ return true;
+ }
</ins><span class="cx">
</span><del>- this.on('promise:resolved', function(event) {
- invokeCallback('resolve', thenPromise, done, event);
- });
</del><ins>+ return false;
+ }
</ins><span class="cx">
</span><del>- this.on('promise:failed', function(event) {
- invokeCallback('reject', thenPromise, fail, event);
- });
</del><ins>+ function resolve(promise, value) {
+ if (promise === value) {
+ fulfill(promise, value);
+ } else if (!handleThenable(promise, value)) {
+ fulfill(promise, value);
+ }
+ }
</ins><span class="cx">
</span><del>- return thenPromise;
- },
</del><ins>+ function fulfill(promise, value) {
+ if (promise._state !== PENDING) { return; }
+ promise._state = SEALED;
+ promise._detail = value;
</ins><span class="cx">
</span><del>- resolve: function(value) {
- resolve(this, value);
</del><ins>+ config.async(publishFulfillment, promise);
+ }
</ins><span class="cx">
</span><del>- this.resolve = noop;
- this.reject = noop;
- },
</del><ins>+ function reject(promise, reason) {
+ if (promise._state !== PENDING) { return; }
+ promise._state = SEALED;
+ promise._detail = reason;
</ins><span class="cx">
</span><del>- reject: function(value) {
- reject(this, value);
</del><ins>+ config.async(publishRejection, promise);
+ }
</ins><span class="cx">
</span><del>- this.resolve = noop;
- this.reject = noop;
</del><ins>+ function publishFulfillment(promise) {
+ publish(promise, promise._state = FULFILLED);
+ }
+
+ function publishRejection(promise) {
+ if (promise._onerror) {
+ promise._onerror(promise._detail);
</ins><span class="cx"> }
</span><ins>+
+ publish(promise, promise._state = REJECTED);
+ }
+ });
+define("rsvp/promise/all",
+ ["../utils","exports"],
+ function(__dependency1__, __exports__) {
+ "use strict";
+ var isArray = __dependency1__.isArray;
+ var isNonThenable = __dependency1__.isNonThenable;
+
+ /**
+ `RSVP.Promise.all` accepts an array of promises, and returns a new promise which
+ is fulfilled with an array of fulfillment values for the passed promises, or
+ rejected with the reason of the first passed promise to be rejected. It casts all
+ elements of the passed iterable to promises as it runs this algorithm.
+
+ Example:
+
+ ```javascript
+ var promise1 = RSVP.resolve(1);
+ var promise2 = RSVP.resolve(2);
+ var promise3 = RSVP.resolve(3);
+ var promises = [ promise1, promise2, promise3 ];
+
+ RSVP.Promise.all(promises).then(function(array){
+ // The array here would be [ 1, 2, 3 ];
+ });
+ ```
+
+ If any of the `promises` given to `RSVP.all` are rejected, the first promise
+ that is rejected will be given as an argument to the returned promises's
+ rejection handler. For example:
+
+ Example:
+
+ ```javascript
+ var promise1 = RSVP.resolve(1);
+ var promise2 = RSVP.reject(new Error("2"));
+ var promise3 = RSVP.reject(new Error("3"));
+ var promises = [ promise1, promise2, promise3 ];
+
+ RSVP.Promise.all(promises).then(function(array){
+ // Code here never runs because there are rejected promises!
+ }, function(error) {
+ // error.message === "2"
+ });
+ ```
+
+ @method all
+ @for RSVP.Promise
+ @param {Array} entries array of promises
+ @param {String} label optional string for labeling the promise.
+ Useful for tooling.
+ @return {Promise} promise that is fulfilled when all `promises` have been
+ fulfilled, or rejected if any of them become rejected.
+ @static
+ */
+ __exports__["default"] = function all(entries, label) {
+
+ /*jshint validthis:true */
+ var Constructor = this;
+
+ return new Constructor(function(resolve, reject) {
+ if (!isArray(entries)) {
+ throw new TypeError('You must pass an array to all.');
+ }
+
+ var remaining = entries.length;
+ var results = new Array(remaining);
+ var entry, pending = true;
+
+ if (remaining === 0) {
+ resolve(results);
+ return;
+ }
+
+ function fulfillmentAt(index) {
+ return function(value) {
+ results[index] = value;
+ if (--remaining === 0) {
+ resolve(results);
+ }
+ };
+ }
+
+ function onRejection(reason) {
+ remaining = 0;
+ reject(reason);
+ }
+
+ for (var index = 0; index < entries.length; index++) {
+ entry = entries[index];
+ if (isNonThenable(entry)) {
+ results[index] = entry;
+ if (--remaining === 0) {
+ resolve(results);
+ }
+ } else {
+ Constructor.cast(entry).then(fulfillmentAt(index), onRejection);
+ }
+ }
+ }, label);
</ins><span class="cx"> };
</span><ins>+ });
+define("rsvp/promise/cast",
+ ["exports"],
+ function(__exports__) {
+ "use strict";
+ /**
+ `RSVP.Promise.cast` coerces its argument to a promise, or returns the
+ argument if it is already a promise which shares a constructor with the caster.
</ins><span class="cx">
</span><del>- function resolve(promise, value) {
- RSVP.async(function() {
- promise.trigger('promise:resolved', { detail: value });
- promise.isResolved = true;
- promise.resolvedValue = value;
</del><ins>+ Example:
+
+ ```javascript
+ var promise = RSVP.Promise.resolve(1);
+ var casted = RSVP.Promise.cast(promise);
+
+ console.log(promise === casted); // true
+ ```
+
+ In the case of a promise whose constructor does not match, it is assimilated.
+ The resulting promise will fulfill or reject based on the outcome of the
+ promise being casted.
+
+ Example:
+
+ ```javascript
+ var thennable = $.getJSON('/api/foo');
+ var casted = RSVP.Promise.cast(thennable);
+
+ console.log(thennable === casted); // false
+ console.log(casted instanceof RSVP.Promise) // true
+
+ casted.then(function(data) {
+ // data is the value getJSON fulfills with
</ins><span class="cx"> });
</span><del>- }
</del><ins>+ ```
</ins><span class="cx">
</span><del>- function reject(promise, value) {
- RSVP.async(function() {
- promise.trigger('promise:failed', { detail: value });
- promise.isRejected = true;
- promise.rejectedValue = value;
</del><ins>+ In the case of a non-promise, a promise which will fulfill with that value is
+ returned.
+
+ Example:
+
+ ```javascript
+ var value = 1; // could be a number, boolean, string, undefined...
+ var casted = RSVP.Promise.cast(value);
+
+ console.log(value === casted); // false
+ console.log(casted instanceof RSVP.Promise) // true
+
+ casted.then(function(val) {
+ val === value // => true
</ins><span class="cx"> });
</span><del>- }
</del><ins>+ ```
</ins><span class="cx">
</span><del>- function all(promises) {
- var i, results = [];
- var allPromise = new Promise();
- var remaining = promises.length;
</del><ins>+ `RSVP.Promise.cast` is similar to `RSVP.Promise.resolve`, but `RSVP.Promise.cast` differs in the
+ following ways:
</ins><span class="cx">
</span><del>- if (remaining === 0) {
- allPromise.resolve([]);
</del><ins>+ * `RSVP.Promise.cast` serves as a memory-efficient way of getting a promise, when you
+ have something that could either be a promise or a value. RSVP.resolve
+ will have the same effect but will create a new promise wrapper if the
+ argument is a promise.
+ * `RSVP.Promise.cast` is a way of casting incoming thenables or promise subclasses to
+ promises of the exact class specified, so that the resulting object's `then` is
+ ensured to have the behavior of the constructor you are calling cast on (i.e., RSVP.Promise).
+
+ @method cast
+ @param {Object} object to be casted
+ @param {String} label optional string for labeling the promise.
+ Useful for tooling.
+ @return {Promise} promise
+ @static
+ */
+
+ __exports__["default"] = function cast(object, label) {
+ /*jshint validthis:true */
+ var Constructor = this;
+
+ if (object && typeof object === 'object' && object.constructor === Constructor) {
+ return object;
</ins><span class="cx"> }
</span><span class="cx">
</span><del>- var resolver = function(index) {
- return function(value) {
- resolve(index, value);
</del><ins>+ return new Constructor(function(resolve) {
+ resolve(object);
+ }, label);
</ins><span class="cx"> };
</span><ins>+ });
+define("rsvp/promise/race",
+ ["../utils","exports"],
+ function(__dependency1__, __exports__) {
+ "use strict";
+ /* global toString */
+
+ var isArray = __dependency1__.isArray;
+ var isFunction = __dependency1__.isFunction;
+ var isNonThenable = __dependency1__.isNonThenable;
+
+ /**
+ `RSVP.Promise.race` returns a new promise which is settled in the same way as the
+ first passed promise to settle.
+
+ Example:
+
+ ```javascript
+ var promise1 = new RSVP.Promise(function(resolve, reject){
+ setTimeout(function(){
+ resolve("promise 1");
+ }, 200);
+ });
+
+ var promise2 = new RSVP.Promise(function(resolve, reject){
+ setTimeout(function(){
+ resolve("promise 2");
+ }, 100);
+ });
+
+ RSVP.Promise.race([promise1, promise2]).then(function(result){
+ // result === "promise 2" because it was resolved before promise1
+ // was resolved.
+ });
+ ```
+
+ `RSVP.Promise.race` is deterministic in that only the state of the first
+ settled promise matters. For example, even if other promises given to the
+ `promises` array argument are resolved, but the first settled promise has
+ become rejected before the other promises became fulfilled, the returned
+ promise will become rejected:
+
+ ```javascript
+ var promise1 = new RSVP.Promise(function(resolve, reject){
+ setTimeout(function(){
+ resolve("promise 1");
+ }, 200);
+ });
+
+ var promise2 = new RSVP.Promise(function(resolve, reject){
+ setTimeout(function(){
+ reject(new Error("promise 2"));
+ }, 100);
+ });
+
+ RSVP.Promise.race([promise1, promise2]).then(function(result){
+ // Code here never runs
+ }, function(reason){
+ // reason.message === "promise2" because promise 2 became rejected before
+ // promise 1 became fulfilled
+ });
+ ```
+
+ An example real-world use case is implementing timeouts:
+
+ ```javascript
+ RSVP.Promise.race([ajax('foo.json'), timeout(5000)])
+ ```
+
+ @method race
+ @param {Array} promises array of promises to observe
+ @param {String} label optional string for describing the promise returned.
+ Useful for tooling.
+ @return {Promise} a promise which settles in the same way as the first passed
+ promise to settle.
+ @static
+ */
+ __exports__["default"] = function race(entries, label) {
+ /*jshint validthis:true */
+ var Constructor = this, entry;
+
+ return new Constructor(function(resolve, reject) {
+ if (!isArray(entries)) {
+ throw new TypeError('You must pass an array to race.');
+ }
+
+ var pending = true;
+
+ function onFulfillment(value) { if (pending) { pending = false; resolve(value); } }
+ function onRejection(reason) { if (pending) { pending = false; reject(reason); } }
+
+ for (var i = 0; i < entries.length; i++) {
+ entry = entries[i];
+ if (isNonThenable(entry)) {
+ pending = false;
+ resolve(entry);
+ return;
+ } else {
+ Constructor.cast(entry).then(onFulfillment, onRejection);
+ }
+ }
+ }, label);
</ins><span class="cx"> };
</span><ins>+ });
+define("rsvp/promise/reject",
+ ["exports"],
+ function(__exports__) {
+ "use strict";
+ /**
+ `RSVP.Promise.reject` returns a promise rejected with the passed `reason`.
+ It is shorthand for the following:
</ins><span class="cx">
</span><del>- var resolve = function(index, value) {
- results[index] = value;
- if (--remaining === 0) {
- allPromise.resolve(results);
- }
</del><ins>+ ```javascript
+ var promise = new RSVP.Promise(function(resolve, reject){
+ reject(new Error('WHOOPS'));
+ });
+
+ promise.then(function(value){
+ // Code here doesn't run because the promise is rejected!
+ }, function(reason){
+ // reason.message === 'WHOOPS'
+ });
+ ```
+
+ Instead of writing the above, your code now simply becomes the following:
+
+ ```javascript
+ var promise = RSVP.Promise.reject(new Error('WHOOPS'));
+
+ promise.then(function(value){
+ // Code here doesn't run because the promise is rejected!
+ }, function(reason){
+ // reason.message === 'WHOOPS'
+ });
+ ```
+
+ @method reject
+ @param {Any} reason value that the returned promise will be rejected with.
+ @param {String} label optional string for identifying the returned promise.
+ Useful for tooling.
+ @return {Promise} a promise rejected with the given `reason`.
+ @static
+ */
+ __exports__["default"] = function reject(reason, label) {
+ /*jshint validthis:true */
+ var Constructor = this;
+
+ return new Constructor(function (resolve, reject) {
+ reject(reason);
+ }, label);
</ins><span class="cx"> };
</span><ins>+ });
+define("rsvp/promise/resolve",
+ ["exports"],
+ function(__exports__) {
+ "use strict";
+ /**
+ `RSVP.Promise.resolve` returns a promise that will become resolved with the
+ passed `value`. It is shorthand for the following:
</ins><span class="cx">
</span><del>- var reject = function(error) {
- allPromise.reject(error);
</del><ins>+ ```javascript
+ var promise = new RSVP.Promise(function(resolve, reject){
+ resolve(1);
+ });
+
+ promise.then(function(value){
+ // value === 1
+ });
+ ```
+
+ Instead of writing the above, your code now simply becomes the following:
+
+ ```javascript
+ var promise = RSVP.Promise.resolve(1);
+
+ promise.then(function(value){
+ // value === 1
+ });
+ ```
+
+ @method resolve
+ @param {Any} value value that the returned promise will be resolved with
+ @param {String} label optional string for identifying the returned promise.
+ Useful for tooling.
+ @return {Promise} a promise that will become fulfilled with the given
+ `value`
+ @static
+ */
+ __exports__["default"] = function resolve(value, label) {
+ /*jshint validthis:true */
+ var Constructor = this;
+
+ return new Constructor(function(resolve, reject) {
+ resolve(value);
+ }, label);
</ins><span class="cx"> };
</span><ins>+ });
+define("rsvp/race",
+ ["./promise","exports"],
+ function(__dependency1__, __exports__) {
+ "use strict";
+ var Promise = __dependency1__["default"];
</ins><span class="cx">
</span><del>- for (i = 0; i < remaining; i++) {
- promises[i].then(resolver(i), reject);
</del><ins>+ /**
+ This is a convenient alias for `RSVP.Promise.race`.
+
+ @method race
+ @param {Array} array Array of promises.
+ @param {String} label An optional label. This is useful
+ for tooling.
+ @static
+ */
+ __exports__["default"] = function race(array, label) {
+ return Promise.race(array, label);
+ };
+ });
+define("rsvp/reject",
+ ["./promise","exports"],
+ function(__dependency1__, __exports__) {
+ "use strict";
+ var Promise = __dependency1__["default"];
+
+ /**
+ This is a convenient alias for `RSVP.Promise.reject`.
+
+ @method reject
+ @for RSVP
+ @param {Any} reason value that the returned promise will be rejected with.
+ @param {String} label optional string for identifying the returned promise.
+ Useful for tooling.
+ @return {Promise} a promise rejected with the given `reason`.
+ @static
+ */
+ __exports__["default"] = function reject(reason, label) {
+ return Promise.reject(reason, label);
+ };
+ });
+define("rsvp/resolve",
+ ["./promise","exports"],
+ function(__dependency1__, __exports__) {
+ "use strict";
+ var Promise = __dependency1__["default"];
+
+ /**
+ This is a convenient alias for `RSVP.Promise.resolve`.
+
+ @method resolve
+ @for RSVP
+ @param {Any} value value that the returned promise will be resolved with
+ @param {String} label optional string for identifying the returned promise.
+ Useful for tooling.
+ @return {Promise} a promise that will become fulfilled with the given
+ `value`
+ @static
+ */
+ __exports__["default"] = function resolve(value, label) {
+ return Promise.resolve(value, label);
+ };
+ });
+define("rsvp/rethrow",
+ ["exports"],
+ function(__exports__) {
+ "use strict";
+ /**
+ `RSVP.rethrow` will rethrow an error on the next turn of the JavaScript event
+ loop in order to aid debugging.
+
+ Promises A+ specifies that any exceptions that occur with a promise must be
+ caught by the promises implementation and bubbled to the last handler. For
+ this reason, it is recommended that you always specify a second rejection
+ handler function to `then`. However, `RSVP.rethrow` will throw the exception
+ outside of the promise, so it bubbles up to your console if in the browser,
+ or domain/cause uncaught exception in Node. `rethrow` will also throw the
+ error again so the error can be handled by the promise per the spec.
+
+ ```javascript
+ function throws(){
+ throw new Error('Whoops!');
+ }
+
+ var promise = new RSVP.Promise(function(resolve, reject){
+ throws();
+ });
+
+ promise.catch(RSVP.rethrow).then(function(){
+ // Code here doesn't run because the promise became rejected due to an
+ // error!
+ }, function (err){
+ // handle the error here
+ });
+ ```
+
+ The 'Whoops' error will be thrown on the next turn of the event loop
+ and you can watch for it in your console. You can also handle it using a
+ rejection handler given to `.then` or `.catch` on the returned promise.
+
+ @method rethrow
+ @for RSVP
+ @param {Error} reason reason the promise became rejected.
+ @throws Error
+ @static
+ */
+ __exports__["default"] = function rethrow(reason) {
+ setTimeout(function() {
+ throw reason;
+ });
+ throw reason;
+ };
+ });
+define("rsvp/utils",
+ ["exports"],
+ function(__exports__) {
+ "use strict";
+ function objectOrFunction(x) {
+ return typeof x === "function" || (typeof x === "object" && x !== null);
</ins><span class="cx"> }
</span><del>- return allPromise;
</del><ins>+
+ __exports__.objectOrFunction = objectOrFunction;function isFunction(x) {
+ return typeof x === "function";
</ins><span class="cx"> }
</span><span class="cx">
</span><del>- EventTarget.mixin(Promise.prototype);
</del><ins>+ __exports__.isFunction = isFunction;function isNonThenable(x) {
+ return !objectOrFunction(x);
+ }
</ins><span class="cx">
</span><del>- RSVP = { async: async, Promise: Promise, Event: Event, EventTarget: EventTarget, all: all, raiseOnUncaughtExceptions: true };
- return RSVP;
</del><ins>+ __exports__.isNonThenable = isNonThenable;function isArray(x) {
+ return Object.prototype.toString.call(x) === "[object Array]";
+ }
+
+ __exports__.isArray = isArray;// Date.now is not available in browsers < IE9
+ // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/now#Compatibility
+ var now = Date.now || function() { return new Date().getTime(); };
+ __exports__.now = now;
+ var keysOf = Object.keys || function(object) {
+ var result = [];
+
+ for (var prop in object) {
+ result.push(prop);
+ }
+
+ return result;
+ };
+ __exports__.keysOf = keysOf;
</ins><span class="cx"> });
</span><ins>+define("rsvp",
+ ["./rsvp/promise","./rsvp/events","./rsvp/node","./rsvp/all","./rsvp/all_settled","./rsvp/race","./rsvp/hash","./rsvp/rethrow","./rsvp/defer","./rsvp/config","./rsvp/map","./rsvp/resolve","./rsvp/reject","./rsvp/filter","exports"],
+ function(__dependency1__, __dependency2__, __dependency3__, __dependency4__, __dependency5__, __dependency6__, __dependency7__, __dependency8__, __dependency9__, __dependency10__, __dependency11__, __dependency12__, __dependency13__, __dependency14__, __exports__) {
+ "use strict";
+ var Promise = __dependency1__["default"];
+ var EventTarget = __dependency2__["default"];
+ var denodeify = __dependency3__["default"];
+ var all = __dependency4__["default"];
+ var allSettled = __dependency5__["default"];
+ var race = __dependency6__["default"];
+ var hash = __dependency7__["default"];
+ var rethrow = __dependency8__["default"];
+ var defer = __dependency9__["default"];
+ var config = __dependency10__.config;
+ var configure = __dependency10__.configure;
+ var map = __dependency11__["default"];
+ var resolve = __dependency12__["default"];
+ var reject = __dependency13__["default"];
+ var filter = __dependency14__["default"];
</ins><span class="cx">
</span><ins>+ function async(callback, arg) {
+ config.async(callback, arg);
+ }
+
+ function on() {
+ config.on.apply(config, arguments);
+ }
+
+ function off() {
+ config.off.apply(config, arguments);
+ }
+
+ // Set up instrumentation through `window.__PROMISE_INTRUMENTATION__`
+ if (typeof window !== 'undefined' && typeof window.__PROMISE_INSTRUMENTATION__ === 'object') {
+ var callbacks = window.__PROMISE_INSTRUMENTATION__;
+ configure('instrument', true);
+ for (var eventName in callbacks) {
+ if (callbacks.hasOwnProperty(eventName)) {
+ on(eventName, callbacks[eventName]);
+ }
+ }
+ }
+
+ __exports__.Promise = Promise;
+ __exports__.EventTarget = EventTarget;
+ __exports__.all = all;
+ __exports__.allSettled = allSettled;
+ __exports__.race = race;
+ __exports__.hash = hash;
+ __exports__.rethrow = rethrow;
+ __exports__.defer = defer;
+ __exports__.denodeify = denodeify;
+ __exports__.configure = configure;
+ __exports__.on = on;
+ __exports__.off = off;
+ __exports__.resolve = resolve;
+ __exports__.reject = reject;
+ __exports__.async = async;
+ __exports__.map = map;
+ __exports__.filter = filter;
+ });
+
</ins><span class="cx"> })();
</span><span class="cx">
</span><span class="cx"> (function() {
</span><ins>+/**
+Public api for the container is still in flux.
+The public api, specified on the application namespace should be considered the stable api.
+// @module container
+ @private
+*/
+
+/*
+ Flag to enable/disable model factory injections (disabled by default)
+ If model factory injections are enabled, models should not be
+ accessed globally (only through `container.lookupFactory('model:modelName'))`);
+*/
+Ember.MODEL_FACTORY_INJECTIONS = false || !!Ember.ENV.MODEL_FACTORY_INJECTIONS;
+
</ins><span class="cx"> define("container",
</span><span class="cx"> [],
</span><span class="cx"> function() {
</span><span class="cx">
</span><del>- var objectCreate = Object.create || function(parent) {
- function F() {}
- F.prototype = parent;
- return new F();
- };
-
</del><ins>+ // A safe and simple inheriting object.
</ins><span class="cx"> function InheritingDict(parent) {
</span><span class="cx"> this.parent = parent;
</span><span class="cx"> this.dict = {};
</span><span class="cx"> }
</span><span class="cx">
</span><span class="cx"> InheritingDict.prototype = {
</span><ins>+
+ /**
+ @property parent
+ @type InheritingDict
+ @default null
+ */
+
+ parent: null,
+
+ /**
+ Object used to store the current nodes data.
+
+ @property dict
+ @type Object
+ @default Object
+ */
+ dict: null,
+
+ /**
+ Retrieve the value given a key, if the value is present at the current
+ level use it, otherwise walk up the parent hierarchy and try again. If
+ no matching key is found, return undefined.
+
+ @method get
+ @param {String} key
+ @return {any}
+ */
</ins><span class="cx"> get: function(key) {
</span><span class="cx"> var dict = this.dict;
</span><span class="cx">
</span><span class="lines">@@ -6071,10 +10182,36 @@
</span><span class="cx"> }
</span><span class="cx"> },
</span><span class="cx">
</span><ins>+ /**
+ Set the given value for the given key, at the current level.
+
+ @method set
+ @param {String} key
+ @param {Any} value
+ */
</ins><span class="cx"> set: function(key, value) {
</span><span class="cx"> this.dict[key] = value;
</span><span class="cx"> },
</span><span class="cx">
</span><ins>+ /**
+ Delete the given key
+
+ @method remove
+ @param {String} key
+ */
+ remove: function(key) {
+ delete this.dict[key];
+ },
+
+ /**
+ Check for the existence of given a key, if the key is present at the current
+ level return true, otherwise walk up the parent hierarchy and try again. If
+ no matching key is found, return false.
+
+ @method has
+ @param {String} key
+ @return {Boolean}
+ */
</ins><span class="cx"> has: function(key) {
</span><span class="cx"> var dict = this.dict;
</span><span class="cx">
</span><span class="lines">@@ -6089,6 +10226,13 @@
</span><span class="cx"> return false;
</span><span class="cx"> },
</span><span class="cx">
</span><ins>+ /**
+ Iterate and invoke a callback for each local key-value pair.
+
+ @method eachLocal
+ @param {Function} callback
+ @param {Object} binding
+ */
</ins><span class="cx"> eachLocal: function(callback, binding) {
</span><span class="cx"> var dict = this.dict;
</span><span class="cx">
</span><span class="lines">@@ -6100,96 +10244,469 @@
</span><span class="cx"> }
</span><span class="cx"> };
</span><span class="cx">
</span><ins>+
+ // A lightweight container that helps to assemble and decouple components.
+ // Public api for the container is still in flux.
+ // The public api, specified on the application namespace should be considered the stable api.
</ins><span class="cx"> function Container(parent) {
</span><span class="cx"> this.parent = parent;
</span><span class="cx"> this.children = [];
</span><span class="cx">
</span><span class="cx"> this.resolver = parent && parent.resolver || function() {};
</span><ins>+
</ins><span class="cx"> this.registry = new InheritingDict(parent && parent.registry);
</span><span class="cx"> this.cache = new InheritingDict(parent && parent.cache);
</span><ins>+ this.factoryCache = new InheritingDict(parent && parent.cache);
</ins><span class="cx"> this.typeInjections = new InheritingDict(parent && parent.typeInjections);
</span><span class="cx"> this.injections = {};
</span><ins>+
+ this.factoryTypeInjections = new InheritingDict(parent && parent.factoryTypeInjections);
+ this.factoryInjections = {};
+
</ins><span class="cx"> this._options = new InheritingDict(parent && parent._options);
</span><span class="cx"> this._typeOptions = new InheritingDict(parent && parent._typeOptions);
</span><span class="cx"> }
</span><span class="cx">
</span><span class="cx"> Container.prototype = {
</span><ins>+
+ /**
+ @property parent
+ @type Container
+ @default null
+ */
+ parent: null,
+
+ /**
+ @property children
+ @type Array
+ @default []
+ */
+ children: null,
+
+ /**
+ @property resolver
+ @type function
+ */
+ resolver: null,
+
+ /**
+ @property registry
+ @type InheritingDict
+ */
+ registry: null,
+
+ /**
+ @property cache
+ @type InheritingDict
+ */
+ cache: null,
+
+ /**
+ @property typeInjections
+ @type InheritingDict
+ */
+ typeInjections: null,
+
+ /**
+ @property injections
+ @type Object
+ @default {}
+ */
+ injections: null,
+
+ /**
+ @private
+
+ @property _options
+ @type InheritingDict
+ @default null
+ */
+ _options: null,
+
+ /**
+ @private
+
+ @property _typeOptions
+ @type InheritingDict
+ */
+ _typeOptions: null,
+
+ /**
+ Returns a new child of the current container. These children are configured
+ to correctly inherit from the current container.
+
+ @method child
+ @return {Container}
+ */
</ins><span class="cx"> child: function() {
</span><span class="cx"> var container = new Container(this);
</span><span class="cx"> this.children.push(container);
</span><span class="cx"> return container;
</span><span class="cx"> },
</span><span class="cx">
</span><ins>+ /**
+ Sets a key-value pair on the current container. If a parent container,
+ has the same key, once set on a child, the parent and child will diverge
+ as expected.
+
+ @method set
+ @param {Object} object
+ @param {String} key
+ @param {any} value
+ */
</ins><span class="cx"> set: function(object, key, value) {
</span><span class="cx"> object[key] = value;
</span><span class="cx"> },
</span><span class="cx">
</span><del>- register: function(type, name, factory, options) {
- var fullName;
</del><ins>+ /**
+ Registers a factory for later injection.
</ins><span class="cx">
</span><ins>+ Example:
</ins><span class="cx">
</span><del>- if (type.indexOf(':') !== -1){
- options = factory;
- factory = name;
- fullName = type;
- } else {
- Ember.deprecate('register("'+type +'", "'+ name+'") is now deprecated in-favour of register("'+type+':'+name+'");', true);
- fullName = type + ":" + name;
</del><ins>+ ```javascript
+ var container = new Container();
+
+ container.register('model:user', Person, {singleton: false });
+ container.register('fruit:favorite', Orange);
+ container.register('communication:main', Email, {singleton: false});
+ ```
+
+ @method register
+ @param {String} fullName
+ @param {Function} factory
+ @param {Object} options
+ */
+ register: function(fullName, factory, options) {
+ if (fullName.indexOf(':') === -1) {
+ throw new TypeError("malformed fullName, expected: `type:name` got: " + fullName + "");
</ins><span class="cx"> }
</span><span class="cx">
</span><del>- this.registry.set(fullName, factory);
- this._options.set(fullName, options || {});
</del><ins>+ if (factory === undefined) {
+ throw new TypeError('Attempting to register an unknown factory: `' + fullName + '`');
+ }
+
+ var normalizedName = this.normalize(fullName);
+
+ if (this.cache.has(normalizedName)) {
+ throw new Error('Cannot re-register: `' + fullName +'`, as it has already been looked up.');
+ }
+
+ this.registry.set(normalizedName, factory);
+ this._options.set(normalizedName, options || {});
</ins><span class="cx"> },
</span><span class="cx">
</span><ins>+ /**
+ Unregister a fullName
+
+ ```javascript
+ var container = new Container();
+ container.register('model:user', User);
+
+ container.lookup('model:user') instanceof User //=> true
+
+ container.unregister('model:user')
+ container.lookup('model:user') === undefined //=> true
+ ```
+
+ @method unregister
+ @param {String} fullName
+ */
+ unregister: function(fullName) {
+ var normalizedName = this.normalize(fullName);
+
+ this.registry.remove(normalizedName);
+ this.cache.remove(normalizedName);
+ this.factoryCache.remove(normalizedName);
+ this._options.remove(normalizedName);
+ },
+
+ /**
+ Given a fullName return the corresponding factory.
+
+ By default `resolve` will retrieve the factory from
+ its container's registry.
+
+ ```javascript
+ var container = new Container();
+ container.register('api:twitter', Twitter);
+
+ container.resolve('api:twitter') // => Twitter
+ ```
+
+ Optionally the container can be provided with a custom resolver.
+ If provided, `resolve` will first provide the custom resolver
+ the oppertunity to resolve the fullName, otherwise it will fallback
+ to the registry.
+
+ ```javascript
+ var container = new Container();
+ container.resolver = function(fullName) {
+ // lookup via the module system of choice
+ };
+
+ // the twitter factory is added to the module system
+ container.resolve('api:twitter') // => Twitter
+ ```
+
+ @method resolve
+ @param {String} fullName
+ @return {Function} fullName's factory
+ */
</ins><span class="cx"> resolve: function(fullName) {
</span><span class="cx"> return this.resolver(fullName) || this.registry.get(fullName);
</span><span class="cx"> },
</span><span class="cx">
</span><del>- lookup: function(fullName) {
- if (this.cache.has(fullName)) {
</del><ins>+ /**
+ A hook that can be used to describe how the resolver will
+ attempt to find the factory.
+
+ For example, the default Ember `.describe` returns the full
+ class name (including namespace) where Ember's resolver expects
+ to find the `fullName`.
+
+ @method describe
+ @param {String} fullName
+ @return {string} described fullName
+ */
+ describe: function(fullName) {
+ return fullName;
+ },
+
+ /**
+ A hook to enable custom fullName normalization behaviour
+
+ @method normalize
+ @param {String} fullName
+ @return {string} normalized fullName
+ */
+ normalize: function(fullName) {
+ return fullName;
+ },
+
+ /**
+ @method makeToString
+
+ @param {any} factory
+ @param {string} fullName
+ @return {function} toString function
+ */
+ makeToString: function(factory, fullName) {
+ return factory.toString();
+ },
+
+ /**
+ Given a fullName return a corresponding instance.
+
+ The default behaviour is for lookup to return a singleton instance.
+ The singleton is scoped to the container, allowing multiple containers
+ to all have their own locally scoped singletons.
+
+ ```javascript
+ var container = new Container();
+ container.register('api:twitter', Twitter);
+
+ var twitter = container.lookup('api:twitter');
+
+ twitter instanceof Twitter; // => true
+
+ // by default the container will return singletons
+ var twitter2 = container.lookup('api:twitter');
+ twitter instanceof Twitter; // => true
+
+ twitter === twitter2; //=> true
+ ```
+
+ If singletons are not wanted an optional flag can be provided at lookup.
+
+ ```javascript
+ var container = new Container();
+ container.register('api:twitter', Twitter);
+
+ var twitter = container.lookup('api:twitter', { singleton: false });
+ var twitter2 = container.lookup('api:twitter', { singleton: false });
+
+ twitter === twitter2; //=> false
+ ```
+
+ @method lookup
+ @param {String} fullName
+ @param {Object} options
+ @return {any}
+ */
+ lookup: function(fullName, options) {
+ fullName = this.normalize(fullName);
+
+ options = options || {};
+
+ if (this.cache.has(fullName) && options.singleton !== false) {
</ins><span class="cx"> return this.cache.get(fullName);
</span><span class="cx"> }
</span><span class="cx">
</span><span class="cx"> var value = instantiate(this, fullName);
</span><span class="cx">
</span><del>- if (!value) { return; }
</del><ins>+ if (value === undefined) { return; }
</ins><span class="cx">
</span><del>- if (isSingleton(this, fullName)) {
</del><ins>+ if (isSingleton(this, fullName) && options.singleton !== false) {
</ins><span class="cx"> this.cache.set(fullName, value);
</span><span class="cx"> }
</span><span class="cx">
</span><span class="cx"> return value;
</span><span class="cx"> },
</span><span class="cx">
</span><ins>+ /**
+ Given a fullName return the corresponding factory.
+
+ @method lookupFactory
+ @param {String} fullName
+ @return {any}
+ */
+ lookupFactory: function(fullName) {
+ return factoryFor(this, fullName);
+ },
+
+ /**
+ Given a fullName check if the container is aware of its factory
+ or singleton instance.
+
+ @method has
+ @param {String} fullName
+ @return {Boolean}
+ */
</ins><span class="cx"> has: function(fullName) {
</span><span class="cx"> if (this.cache.has(fullName)) {
</span><span class="cx"> return true;
</span><span class="cx"> }
</span><span class="cx">
</span><del>- return !!factoryFor(this, fullName);
</del><ins>+ return !!this.resolve(fullName);
</ins><span class="cx"> },
</span><span class="cx">
</span><ins>+ /**
+ Allow registering options for all factories of a type.
+
+ ```javascript
+ var container = new Container();
+
+ // if all of type `connection` must not be singletons
+ container.optionsForType('connection', { singleton: false });
+
+ container.register('connection:twitter', TwitterConnection);
+ container.register('connection:facebook', FacebookConnection);
+
+ var twitter = container.lookup('connection:twitter');
+ var twitter2 = container.lookup('connection:twitter');
+
+ twitter === twitter2; // => false
+
+ var facebook = container.lookup('connection:facebook');
+ var facebook2 = container.lookup('connection:facebook');
+
+ facebook === facebook2; // => false
+ ```
+
+ @method optionsForType
+ @param {String} type
+ @param {Object} options
+ */
</ins><span class="cx"> optionsForType: function(type, options) {
</span><span class="cx"> if (this.parent) { illegalChildOperation('optionsForType'); }
</span><span class="cx">
</span><span class="cx"> this._typeOptions.set(type, options);
</span><span class="cx"> },
</span><span class="cx">
</span><ins>+ /**
+ @method options
+ @param {String} type
+ @param {Object} options
+ */
</ins><span class="cx"> options: function(type, options) {
</span><span class="cx"> this.optionsForType(type, options);
</span><span class="cx"> },
</span><span class="cx">
</span><ins>+ /**
+ Used only via `injection`.
+
+ Provides a specialized form of injection, specifically enabling
+ all objects of one type to be injected with a reference to another
+ object.
+
+ For example, provided each object of type `controller` needed a `router`.
+ one would do the following:
+
+ ```javascript
+ var container = new Container();
+
+ container.register('router:main', Router);
+ container.register('controller:user', UserController);
+ container.register('controller:post', PostController);
+
+ container.typeInjection('controller', 'router', 'router:main');
+
+ var user = container.lookup('controller:user');
+ var post = container.lookup('controller:post');
+
+ user.router instanceof Router; //=> true
+ post.router instanceof Router; //=> true
+
+ // both controllers share the same router
+ user.router === post.router; //=> true
+ ```
+
+ @private
+ @method typeInjection
+ @param {String} type
+ @param {String} property
+ @param {String} fullName
+ */
</ins><span class="cx"> typeInjection: function(type, property, fullName) {
</span><span class="cx"> if (this.parent) { illegalChildOperation('typeInjection'); }
</span><span class="cx">
</span><del>- var injections = this.typeInjections.get(type);
- if (!injections) {
- injections = [];
- this.typeInjections.set(type, injections);
- }
- injections.push({ property: property, fullName: fullName });
</del><ins>+ addTypeInjection(this.typeInjections, type, property, fullName);
</ins><span class="cx"> },
</span><span class="cx">
</span><ins>+ /**
+ Defines injection rules.
+
+ These rules are used to inject dependencies onto objects when they
+ are instantiated.
+
+ Two forms of injections are possible:
+
+ * Injecting one fullName on another fullName
+ * Injecting one fullName on a type
+
+ Example:
+
+ ```javascript
+ var container = new Container();
+
+ container.register('source:main', Source);
+ container.register('model:user', User);
+ container.register('model:post', Post);
+
+ // injecting one fullName on another fullName
+ // eg. each user model gets a post model
+ container.injection('model:user', 'post', 'model:post');
+
+ // injecting one fullName on another type
+ container.injection('model', 'source', 'source:main');
+
+ var user = container.lookup('model:user');
+ var post = container.lookup('model:post');
+
+ user.source instanceof Source; //=> true
+ post.source instanceof Source; //=> true
+
+ user.post instanceof Post; //=> true
+
+ // and both models share the same source
+ user.source === post.source; //=> true
+ ```
+
+ @method injection
+ @param {String} factoryName
+ @param {String} property
+ @param {String} injectionName
+ */
</ins><span class="cx"> injection: function(factoryName, property, injectionName) {
</span><span class="cx"> if (this.parent) { illegalChildOperation('injection'); }
</span><span class="cx">
</span><span class="lines">@@ -6197,12 +10714,111 @@
</span><span class="cx"> return this.typeInjection(factoryName, property, injectionName);
</span><span class="cx"> }
</span><span class="cx">
</span><del>- var injections = this.injections[factoryName] = this.injections[factoryName] || [];
- injections.push({ property: property, fullName: injectionName });
</del><ins>+ addInjection(this.injections, factoryName, property, injectionName);
</ins><span class="cx"> },
</span><span class="cx">
</span><ins>+
+ /**
+ Used only via `factoryInjection`.
+
+ Provides a specialized form of injection, specifically enabling
+ all factory of one type to be injected with a reference to another
+ object.
+
+ For example, provided each factory of type `model` needed a `store`.
+ one would do the following:
+
+ ```javascript
+ var container = new Container();
+
+ container.register('store:main', SomeStore);
+
+ container.factoryTypeInjection('model', 'store', 'store:main');
+
+ var store = container.lookup('store:main');
+ var UserFactory = container.lookupFactory('model:user');
+
+ UserFactory.store instanceof SomeStore; //=> true
+ ```
+
+ @private
+ @method factoryTypeInjection
+ @param {String} type
+ @param {String} property
+ @param {String} fullName
+ */
+ factoryTypeInjection: function(type, property, fullName) {
+ if (this.parent) { illegalChildOperation('factoryTypeInjection'); }
+
+ addTypeInjection(this.factoryTypeInjections, type, property, fullName);
+ },
+
+ /**
+ Defines factory injection rules.
+
+ Similar to regular injection rules, but are run against factories, via
+ `Container#lookupFactory`.
+
+ These rules are used to inject objects onto factories when they
+ are looked up.
+
+ Two forms of injections are possible:
+
+ * Injecting one fullName on another fullName
+ * Injecting one fullName on a type
+
+ Example:
+
+ ```javascript
+ var container = new Container();
+
+ container.register('store:main', Store);
+ container.register('store:secondary', OtherStore);
+ container.register('model:user', User);
+ container.register('model:post', Post);
+
+ // injecting one fullName on another type
+ container.factoryInjection('model', 'store', 'store:main');
+
+ // injecting one fullName on another fullName
+ container.factoryInjection('model:post', 'secondaryStore', 'store:secondary');
+
+ var UserFactory = container.lookupFactory('model:user');
+ var PostFactory = container.lookupFactory('model:post');
+ var store = container.lookup('store:main');
+
+ UserFactory.store instanceof Store; //=> true
+ UserFactory.secondaryStore instanceof OtherStore; //=> false
+
+ PostFactory.store instanceof Store; //=> true
+ PostFactory.secondaryStore instanceof OtherStore; //=> true
+
+ // and both models share the same source instance
+ UserFactory.store === PostFactory.store; //=> true
+ ```
+
+ @method factoryInjection
+ @param {String} factoryName
+ @param {String} property
+ @param {String} injectionName
+ */
+ factoryInjection: function(factoryName, property, injectionName) {
+ if (this.parent) { illegalChildOperation('injection'); }
+
+ if (factoryName.indexOf(':') === -1) {
+ return this.factoryTypeInjection(factoryName, property, injectionName);
+ }
+
+ addInjection(this.factoryInjections, factoryName, property, injectionName);
+ },
+
+ /**
+ A depth first traversal, destroying the container, its descendant containers and all
+ their managed objects.
+
+ @method destroy
+ */
</ins><span class="cx"> destroy: function() {
</span><del>- this.isDestroyed = true;
</del><span class="cx">
</span><span class="cx"> for (var i=0, l=this.children.length; i<l; i++) {
</span><span class="cx"> this.children[i].destroy();
</span><span class="lines">@@ -6211,17 +10827,16 @@
</span><span class="cx"> this.children = [];
</span><span class="cx">
</span><span class="cx"> eachDestroyable(this, function(item) {
</span><del>- item.isDestroying = true;
- });
-
- eachDestroyable(this, function(item) {
</del><span class="cx"> item.destroy();
</span><span class="cx"> });
</span><span class="cx">
</span><del>- delete this.parent;
</del><ins>+ this.parent = undefined;
</ins><span class="cx"> this.isDestroyed = true;
</span><span class="cx"> },
</span><span class="cx">
</span><ins>+ /**
+ @method reset
+ */
</ins><span class="cx"> reset: function() {
</span><span class="cx"> for (var i=0, l=this.children.length; i<l; i++) {
</span><span class="cx"> resetCache(this.children[i]);
</span><span class="lines">@@ -6250,7 +10865,12 @@
</span><span class="cx"> for (var i=0, l=injections.length; i<l; i++) {
</span><span class="cx"> injection = injections[i];
</span><span class="cx"> lookup = container.lookup(injection.fullName);
</span><del>- hash[injection.property] = lookup;
</del><ins>+
+ if (lookup !== undefined) {
+ hash[injection.property] = lookup;
+ } else {
+ throw new Error('Attempting to inject an unknown injection: `' + injection.fullName + '`');
+ }
</ins><span class="cx"> }
</span><span class="cx">
</span><span class="cx"> return hash;
</span><span class="lines">@@ -6272,32 +10892,84 @@
</span><span class="cx"> }
</span><span class="cx">
</span><span class="cx"> function factoryFor(container, fullName) {
</span><del>- return container.resolve(fullName);
</del><ins>+ var name = container.normalize(fullName);
+ var factory = container.resolve(name);
+ var injectedFactory;
+ var cache = container.factoryCache;
+ var type = fullName.split(":")[0];
+
+ if (factory === undefined) { return; }
+
+ if (cache.has(fullName)) {
+ return cache.get(fullName);
+ }
+
+ if (!factory || typeof factory.extend !== 'function' || (!Ember.MODEL_FACTORY_INJECTIONS && type === 'model')) {
+ // TODO: think about a 'safe' merge style extension
+ // for now just fallback to create time injection
+ return factory;
+ } else {
+
+ var injections = injectionsFor(container, fullName);
+ var factoryInjections = factoryInjectionsFor(container, fullName);
+
+ factoryInjections._toString = container.makeToString(factory, fullName);
+
+ injectedFactory = factory.extend(injections);
+ injectedFactory.reopenClass(factoryInjections);
+
+ cache.set(fullName, injectedFactory);
+
+ return injectedFactory;
+ }
</ins><span class="cx"> }
</span><span class="cx">
</span><ins>+ function injectionsFor(container ,fullName) {
+ var splitName = fullName.split(":"),
+ type = splitName[0],
+ injections = [];
+
+ injections = injections.concat(container.typeInjections.get(type) || []);
+ injections = injections.concat(container.injections[fullName] || []);
+
+ injections = buildInjections(container, injections);
+ injections._debugContainerKey = fullName;
+ injections.container = container;
+
+ return injections;
+ }
+
+ function factoryInjectionsFor(container, fullName) {
+ var splitName = fullName.split(":"),
+ type = splitName[0],
+ factoryInjections = [];
+
+ factoryInjections = factoryInjections.concat(container.factoryTypeInjections.get(type) || []);
+ factoryInjections = factoryInjections.concat(container.factoryInjections[fullName] || []);
+
+ factoryInjections = buildInjections(container, factoryInjections);
+ factoryInjections._debugContainerKey = fullName;
+
+ return factoryInjections;
+ }
+
</ins><span class="cx"> function instantiate(container, fullName) {
</span><span class="cx"> var factory = factoryFor(container, fullName);
</span><span class="cx">
</span><del>- var splitName = fullName.split(":"),
- type = splitName[0], name = splitName[1],
- value;
-
</del><span class="cx"> if (option(container, fullName, 'instantiate') === false) {
</span><span class="cx"> return factory;
</span><span class="cx"> }
</span><span class="cx">
</span><span class="cx"> if (factory) {
</span><del>- var injections = [];
- injections = injections.concat(container.typeInjections.get(type) || []);
- injections = injections.concat(container.injections[fullName] || []);
-
- var hash = buildInjections(container, injections);
- hash.container = container;
- hash._debugContainerKey = fullName;
-
- value = factory.create(hash);
-
- return value;
</del><ins>+ if (typeof factory.extend === 'function') {
+ // assume the factory was extendable and is already injected
+ return factory.create();
+ } else {
+ // assume the factory was extendable
+ // to create time injections
+ // TODO: support new'ing for instantiation and merge injections for pure JS Functions
+ return factory.create(injectionsFor(container, fullName));
+ }
</ins><span class="cx"> }
</span><span class="cx"> }
</span><span class="cx">
</span><span class="lines">@@ -6316,6 +10988,25 @@
</span><span class="cx"> container.cache.dict = {};
</span><span class="cx"> }
</span><span class="cx">
</span><ins>+ function addTypeInjection(rules, type, property, fullName) {
+ var injections = rules.get(type);
+
+ if (!injections) {
+ injections = [];
+ rules.set(type, injections);
+ }
+
+ injections.push({
+ property: property,
+ fullName: fullName
+ });
+ }
+
+ function addInjection(rules, factoryName, property, injectionName) {
+ var injections = rules[factoryName] = rules[factoryName] || [];
+ injections.push({ property: property, fullName: injectionName });
+ }
+
</ins><span class="cx"> return Container;
</span><span class="cx"> });
</span><span class="cx">
</span><span class="lines">@@ -6330,131 +11021,7 @@
</span><span class="cx">
</span><span class="cx"> var indexOf = Ember.EnumerableUtils.indexOf;
</span><span class="cx">
</span><del>-// ........................................
-// TYPING & ARRAY MESSAGING
-//
-
-var TYPE_MAP = {};
-var t = "Boolean Number String Function Array Date RegExp Object".split(" ");
-Ember.ArrayPolyfills.forEach.call(t, function(name) {
- TYPE_MAP[ "[object " + name + "]" ] = name.toLowerCase();
-});
-
-var toString = Object.prototype.toString;
-
</del><span class="cx"> /**
</span><del>- Returns a consistent type for the passed item.
-
- Use this instead of the built-in `typeof` to get the type of an item.
- It will return the same result across all browsers and includes a bit
- more detail. Here is what will be returned:
-
- | Return Value | Meaning |
- |---------------|------------------------------------------------------|
- | 'string' | String primitive |
- | 'number' | Number primitive |
- | 'boolean' | Boolean primitive |
- | 'null' | Null value |
- | 'undefined' | Undefined value |
- | 'function' | A function |
- | 'array' | An instance of Array |
- | 'class' | A Ember class (created using Ember.Object.extend()) |
- | 'instance' | A Ember object instance |
- | 'error' | An instance of the Error object |
- | 'object' | A JavaScript object not inheriting from Ember.Object |
-
- Examples:
-
- ```javascript
- Ember.typeOf(); // 'undefined'
- Ember.typeOf(null); // 'null'
- Ember.typeOf(undefined); // 'undefined'
- Ember.typeOf('michael'); // 'string'
- Ember.typeOf(101); // 'number'
- Ember.typeOf(true); // 'boolean'
- Ember.typeOf(Ember.makeArray); // 'function'
- Ember.typeOf([1,2,90]); // 'array'
- Ember.typeOf(Ember.Object.extend()); // 'class'
- Ember.typeOf(Ember.Object.create()); // 'instance'
- Ember.typeOf(new Error('teamocil')); // 'error'
-
- // "normal" JavaScript object
- Ember.typeOf({a: 'b'}); // 'object'
- ```
-
- @method typeOf
- @for Ember
- @param item {Object} the item to check
- @return {String} the type
-*/
-Ember.typeOf = function(item) {
- var ret;
-
- ret = (item === null || item === undefined) ? String(item) : TYPE_MAP[toString.call(item)] || 'object';
-
- if (ret === 'function') {
- if (Ember.Object && Ember.Object.detect(item)) ret = 'class';
- } else if (ret === 'object') {
- if (item instanceof Error) ret = 'error';
- else if (Ember.Object && item instanceof Ember.Object) ret = 'instance';
- else ret = 'object';
- }
-
- return ret;
-};
-
-/**
- Returns true if the passed value is null or undefined. This avoids errors
- from JSLint complaining about use of ==, which can be technically
- confusing.
-
- ```javascript
- Ember.isNone(); // true
- Ember.isNone(null); // true
- Ember.isNone(undefined); // true
- Ember.isNone(''); // false
- Ember.isNone([]); // false
- Ember.isNone(function(){}); // false
- ```
-
- @method isNone
- @for Ember
- @param {Object} obj Value to test
- @return {Boolean}
-*/
-Ember.isNone = function(obj) {
- return obj === null || obj === undefined;
-};
-Ember.none = Ember.deprecateFunc("Ember.none is deprecated. Please use Ember.isNone instead.", Ember.isNone);
-
-/**
- Verifies that a value is `null` or an empty string, empty array,
- or empty function.
-
- Constrains the rules on `Ember.isNone` by returning false for empty
- string and empty arrays.
-
- ```javascript
- Ember.isEmpty(); // true
- Ember.isEmpty(null); // true
- Ember.isEmpty(undefined); // true
- Ember.isEmpty(''); // true
- Ember.isEmpty([]); // true
- Ember.isEmpty('Adam Hawkins'); // false
- Ember.isEmpty([0,1,2]); // false
- ```
-
- @method isEmpty
- @for Ember
- @param {Object} obj Value to test
- @return {Boolean}
-*/
-Ember.isEmpty = function(obj) {
- return obj === null || obj === undefined || (obj.length === 0 && typeof obj !== 'function') || (typeof obj === 'object' && Ember.get(obj, 'length') === 0);
-};
-Ember.empty = Ember.deprecateFunc("Ember.empty is deprecated. Please use Ember.isEmpty instead.", Ember.isEmpty) ;
-
-/**
</del><span class="cx"> This will compare two javascript values of possibly different types.
</span><span class="cx"> It will tell you which one is greater than the other by returning:
</span><span class="cx">
</span><span class="lines">@@ -6618,7 +11185,7 @@
</span><span class="cx">
</span><span class="cx"> @method copy
</span><span class="cx"> @for Ember
</span><del>- @param {Object} object The object to clone
</del><ins>+ @param {Object} obj The object to clone
</ins><span class="cx"> @param {Boolean} deep If true, a deep copy of the object is made
</span><span class="cx"> @return {Object} The cloned object
</span><span class="cx"> */
</span><span class="lines">@@ -6642,7 +11209,11 @@
</span><span class="cx"> @return {String} A description of the object
</span><span class="cx"> */
</span><span class="cx"> Ember.inspect = function(obj) {
</span><del>- if (typeof obj !== 'object' || obj === null) {
</del><ins>+ var type = Ember.typeOf(obj);
+ if (type === 'array') {
+ return '[' + obj + ']';
+ }
+ if (type !== 'object') {
</ins><span class="cx"> return obj + '';
</span><span class="cx"> }
</span><span class="cx">
</span><span class="lines">@@ -6708,41 +11279,44 @@
</span><span class="cx"> */
</span><span class="cx"> Ember.keys = Object.keys;
</span><span class="cx">
</span><del>-if (!Ember.keys) {
</del><ins>+if (!Ember.keys || Ember.create.isSimulated) {
+ var prototypeProperties = [
+ 'constructor',
+ 'hasOwnProperty',
+ 'isPrototypeOf',
+ 'propertyIsEnumerable',
+ 'valueOf',
+ 'toLocaleString',
+ 'toString'
+ ],
+ pushPropertyName = function(obj, array, key) {
+ // Prevents browsers that don't respect non-enumerability from
+ // copying internal Ember properties
+ if (key.substring(0,2) === '__') return;
+ if (key === '_super') return;
+ if (indexOf(array, key) >= 0) return;
+ if (!obj.hasOwnProperty(key)) return;
+
+ array.push(key);
+ };
+
</ins><span class="cx"> Ember.keys = function(obj) {
</span><del>- var ret = [];
- for(var key in obj) {
- if (obj.hasOwnProperty(key)) { ret.push(key); }
</del><ins>+ var ret = [], key;
+ for (key in obj) {
+ pushPropertyName(obj, ret, key);
</ins><span class="cx"> }
</span><ins>+
+ // IE8 doesn't enumerate property that named the same as prototype properties.
+ for (var i = 0, l = prototypeProperties.length; i < l; i++) {
+ key = prototypeProperties[i];
+
+ pushPropertyName(obj, ret, key);
+ }
+
</ins><span class="cx"> return ret;
</span><span class="cx"> };
</span><span class="cx"> }
</span><span class="cx">
</span><del>-// ..........................................................
-// ERROR
-//
-
-var errorProps = ['description', 'fileName', 'lineNumber', 'message', 'name', 'number', 'stack'];
-
-/**
- A subclass of the JavaScript Error object for use in Ember.
-
- @class Error
- @namespace Ember
- @extends Error
- @constructor
-*/
-Ember.Error = function() {
- var tmp = Error.prototype.constructor.apply(this, arguments);
-
- // Unfortunately errors are not enumerable in Chrome (at least), so `for prop in tmp` doesn't work.
- for (var idx = 0; idx < errorProps.length; idx++) {
- this[errorProps[idx]] = tmp[errorProps[idx]];
- }
-};
-
-Ember.Error.prototype = Ember.create(Error.prototype);
-
</del><span class="cx"> })();
</span><span class="cx">
</span><span class="cx">
</span><span class="lines">@@ -6755,7 +11329,7 @@
</span><span class="cx">
</span><span class="cx"> var STRING_DASHERIZE_REGEXP = (/[ _]/g);
</span><span class="cx"> var STRING_DASHERIZE_CACHE = {};
</span><del>-var STRING_DECAMELIZE_REGEXP = (/([a-z])([A-Z])/g);
</del><ins>+var STRING_DECAMELIZE_REGEXP = (/([a-z\d])([A-Z])/g);
</ins><span class="cx"> var STRING_CAMELIZE_REGEXP = (/(\-|_|\.|\s)+(.)?/g);
</span><span class="cx"> var STRING_UNDERSCORE_REGEXP_1 = (/([a-z\d])([A-Z]+)/g);
</span><span class="cx"> var STRING_UNDERSCORE_REGEXP_2 = (/\-|\s+/g);
</span><span class="lines">@@ -6798,16 +11372,17 @@
</span><span class="cx"> ```
</span><span class="cx">
</span><span class="cx"> @method fmt
</span><del>- @param {Object...} [args]
</del><ins>+ @param {String} str The string to format
+ @param {Array} formats An array of parameters to interpolate into string.
</ins><span class="cx"> @return {String} formatted string
</span><span class="cx"> */
</span><span class="cx"> fmt: function(str, formats) {
</span><span class="cx"> // first, replace any ORDERED replacements.
</span><span class="cx"> var idx = 0; // the current index for non-numerical replacements
</span><span class="cx"> return str.replace(/%@([0-9]+)?/g, function(s, argIndex) {
</span><del>- argIndex = (argIndex) ? parseInt(argIndex,0) - 1 : idx++ ;
</del><ins>+ argIndex = (argIndex) ? parseInt(argIndex, 10) - 1 : idx++;
</ins><span class="cx"> s = formats[argIndex];
</span><del>- return ((s === null) ? '(null)' : (s === undefined) ? '' : s).toString();
</del><ins>+ return (s === null) ? '(null)' : (s === undefined) ? '' : Ember.inspect(s);
</ins><span class="cx"> }) ;
</span><span class="cx"> },
</span><span class="cx">
</span><span class="lines">@@ -6880,7 +11455,7 @@
</span><span class="cx"> },
</span><span class="cx">
</span><span class="cx"> /**
</span><del>- Replaces underscores or spaces with dashes.
</del><ins>+ Replaces underscores, spaces, or camelCase with dashes.
</ins><span class="cx">
</span><span class="cx"> ```javascript
</span><span class="cx"> 'innerHTML'.dasherize(); // 'inner-html'
</span><span class="lines">@@ -6895,10 +11470,11 @@
</span><span class="cx"> */
</span><span class="cx"> dasherize: function(str) {
</span><span class="cx"> var cache = STRING_DASHERIZE_CACHE,
</span><del>- ret = cache[str];
</del><ins>+ hit = cache.hasOwnProperty(str),
+ ret;
</ins><span class="cx">
</span><del>- if (ret) {
- return ret;
</del><ins>+ if (hit) {
+ return cache[str];
</ins><span class="cx"> } else {
</span><span class="cx"> ret = Ember.String.decamelize(str).replace(STRING_DASHERIZE_REGEXP,'-');
</span><span class="cx"> cache[str] = ret;
</span><span class="lines">@@ -6908,13 +11484,14 @@
</span><span class="cx"> },
</span><span class="cx">
</span><span class="cx"> /**
</span><del>- Returns the lowerCaseCamel form of a string.
</del><ins>+ Returns the lowerCamelCase form of a string.
</ins><span class="cx">
</span><span class="cx"> ```javascript
</span><span class="cx"> 'innerHTML'.camelize(); // 'innerHTML'
</span><span class="cx"> 'action_name'.camelize(); // 'actionName'
</span><span class="cx"> 'css-class-name'.camelize(); // 'cssClassName'
</span><span class="cx"> 'my favorite items'.camelize(); // 'myFavoriteItems'
</span><ins>+ 'My Favorite Items'.camelize(); // 'myFavoriteItems'
</ins><span class="cx"> ```
</span><span class="cx">
</span><span class="cx"> @method camelize
</span><span class="lines">@@ -6924,6 +11501,8 @@
</span><span class="cx"> camelize: function(str) {
</span><span class="cx"> return str.replace(STRING_CAMELIZE_REGEXP, function(match, separator, chr) {
</span><span class="cx"> return chr ? chr.toUpperCase() : '';
</span><ins>+ }).replace(/^([A-Z])/, function(match, separator, chr) {
+ return match.toLowerCase();
</ins><span class="cx"> });
</span><span class="cx"> },
</span><span class="cx">
</span><span class="lines">@@ -6935,7 +11514,7 @@
</span><span class="cx"> 'action_name'.classify(); // 'ActionName'
</span><span class="cx"> 'css-class-name'.classify(); // 'CssClassName'
</span><span class="cx"> 'my favorite items'.classify(); // 'MyFavoriteItems'
</span><del>- ```
</del><ins>+ ```
</ins><span class="cx">
</span><span class="cx"> @method classify
</span><span class="cx"> @param {String} str the string to classify
</span><span class="lines">@@ -6976,21 +11555,24 @@
</span><span class="cx"> /**
</span><span class="cx"> Returns the Capitalized form of a string
</span><span class="cx">
</span><del>- 'innerHTML'.capitalize() => 'InnerHTML'
- 'action_name'.capitalize() => 'Action_name'
- 'css-class-name'.capitalize() => 'Css-class-name'
- 'my favorite items'.capitalize() => 'My favorite items'
</del><ins>+ ```javascript
+ 'innerHTML'.capitalize() // 'InnerHTML'
+ 'action_name'.capitalize() // 'Action_name'
+ 'css-class-name'.capitalize() // 'Css-class-name'
+ 'my favorite items'.capitalize() // 'My favorite items'
+ ```
</ins><span class="cx">
</span><span class="cx"> @method capitalize
</span><del>- @param {String} str
- @return {String}
</del><ins>+ @param {String} str The string to capitalize.
+ @return {String} The capitalized string.
</ins><span class="cx"> */
</span><span class="cx"> capitalize: function(str) {
</span><span class="cx"> return str.charAt(0).toUpperCase() + str.substr(1);
</span><span class="cx"> }
</span><del>-
</del><span class="cx"> };
</span><span class="cx">
</span><ins>+
+
</ins><span class="cx"> })();
</span><span class="cx">
</span><span class="cx">
</span><span class="lines">@@ -7013,10 +11595,11 @@
</span><span class="cx"> capitalize = Ember.String.capitalize,
</span><span class="cx"> classify = Ember.String.classify;
</span><span class="cx">
</span><ins>+
</ins><span class="cx"> if (Ember.EXTEND_PROTOTYPES === true || Ember.EXTEND_PROTOTYPES.String) {
</span><span class="cx">
</span><span class="cx"> /**
</span><del>- See {{#crossLink "Ember.String/fmt"}}{{/crossLink}}
</del><ins>+ See [Ember.String.fmt](/api/classes/Ember.String.html#method_fmt).
</ins><span class="cx">
</span><span class="cx"> @method fmt
</span><span class="cx"> @for String
</span><span class="lines">@@ -7026,7 +11609,7 @@
</span><span class="cx"> };
</span><span class="cx">
</span><span class="cx"> /**
</span><del>- See {{#crossLink "Ember.String/w"}}{{/crossLink}}
</del><ins>+ See [Ember.String.w](/api/classes/Ember.String.html#method_w).
</ins><span class="cx">
</span><span class="cx"> @method w
</span><span class="cx"> @for String
</span><span class="lines">@@ -7036,7 +11619,7 @@
</span><span class="cx"> };
</span><span class="cx">
</span><span class="cx"> /**
</span><del>- See {{#crossLink "Ember.String/loc"}}{{/crossLink}}
</del><ins>+ See [Ember.String.loc](/api/classes/Ember.String.html#method_loc).
</ins><span class="cx">
</span><span class="cx"> @method loc
</span><span class="cx"> @for String
</span><span class="lines">@@ -7046,7 +11629,7 @@
</span><span class="cx"> };
</span><span class="cx">
</span><span class="cx"> /**
</span><del>- See {{#crossLink "Ember.String/camelize"}}{{/crossLink}}
</del><ins>+ See [Ember.String.camelize](/api/classes/Ember.String.html#method_camelize).
</ins><span class="cx">
</span><span class="cx"> @method camelize
</span><span class="cx"> @for String
</span><span class="lines">@@ -7056,7 +11639,7 @@
</span><span class="cx"> };
</span><span class="cx">
</span><span class="cx"> /**
</span><del>- See {{#crossLink "Ember.String/decamelize"}}{{/crossLink}}
</del><ins>+ See [Ember.String.decamelize](/api/classes/Ember.String.html#method_decamelize).
</ins><span class="cx">
</span><span class="cx"> @method decamelize
</span><span class="cx"> @for String
</span><span class="lines">@@ -7066,7 +11649,7 @@
</span><span class="cx"> };
</span><span class="cx">
</span><span class="cx"> /**
</span><del>- See {{#crossLink "Ember.String/dasherize"}}{{/crossLink}}
</del><ins>+ See [Ember.String.dasherize](/api/classes/Ember.String.html#method_dasherize).
</ins><span class="cx">
</span><span class="cx"> @method dasherize
</span><span class="cx"> @for String
</span><span class="lines">@@ -7076,7 +11659,7 @@
</span><span class="cx"> };
</span><span class="cx">
</span><span class="cx"> /**
</span><del>- See {{#crossLink "Ember.String/underscore"}}{{/crossLink}}
</del><ins>+ See [Ember.String.underscore](/api/classes/Ember.String.html#method_underscore).
</ins><span class="cx">
</span><span class="cx"> @method underscore
</span><span class="cx"> @for String
</span><span class="lines">@@ -7086,7 +11669,7 @@
</span><span class="cx"> };
</span><span class="cx">
</span><span class="cx"> /**
</span><del>- See {{#crossLink "Ember.String/classify"}}{{/crossLink}}
</del><ins>+ See [Ember.String.classify](/api/classes/Ember.String.html#method_classify).
</ins><span class="cx">
</span><span class="cx"> @method classify
</span><span class="cx"> @for String
</span><span class="lines">@@ -7096,7 +11679,7 @@
</span><span class="cx"> };
</span><span class="cx">
</span><span class="cx"> /**
</span><del>- See {{#crossLink "Ember.String/capitalize"}}{{/crossLink}}
</del><ins>+ See [Ember.String.capitalize](/api/classes/Ember.String.html#method_capitalize).
</ins><span class="cx">
</span><span class="cx"> @method capitalize
</span><span class="cx"> @for String
</span><span class="lines">@@ -7105,6 +11688,7 @@
</span><span class="cx"> return capitalize(this);
</span><span class="cx"> };
</span><span class="cx">
</span><ins>+
</ins><span class="cx"> }
</span><span class="cx">
</span><span class="cx">
</span><span class="lines">@@ -7118,134 +11702,1269 @@
</span><span class="cx"> @submodule ember-runtime
</span><span class="cx"> */
</span><span class="cx">
</span><del>-var a_slice = Array.prototype.slice;
</del><ins>+var get = Ember.get,
+ set = Ember.set,
+ slice = Array.prototype.slice,
+ getProperties = Ember.getProperties;
</ins><span class="cx">
</span><del>-if (Ember.EXTEND_PROTOTYPES === true || Ember.EXTEND_PROTOTYPES.Function) {
</del><ins>+/**
+ ## Overview
</ins><span class="cx">
</span><ins>+ This mixin provides properties and property observing functionality, core
+ features of the Ember object model.
+
+ Properties and observers allow one object to observe changes to a
+ property on another object. This is one of the fundamental ways that
+ models, controllers and views communicate with each other in an Ember
+ application.
+
+ Any object that has this mixin applied can be used in observer
+ operations. That includes `Ember.Object` and most objects you will
+ interact with as you write your Ember application.
+
+ Note that you will not generally apply this mixin to classes yourself,
+ but you will use the features provided by this module frequently, so it
+ is important to understand how to use it.
+
+ ## Using `get()` and `set()`
+
+ Because of Ember's support for bindings and observers, you will always
+ access properties using the get method, and set properties using the
+ set method. This allows the observing objects to be notified and
+ computed properties to be handled properly.
+
+ More documentation about `get` and `set` are below.
+
+ ## Observing Property Changes
+
+ You typically observe property changes simply by adding the `observes`
+ call to the end of your method declarations in classes that you write.
+ For example:
+
+ ```javascript
+ Ember.Object.extend({
+ valueObserver: function() {
+ // Executes whenever the "value" property changes
+ }.observes('value')
+ });
+ ```
+
+ Although this is the most common way to add an observer, this capability
+ is actually built into the `Ember.Object` class on top of two methods
+ defined in this mixin: `addObserver` and `removeObserver`. You can use
+ these two methods to add and remove observers yourself if you need to
+ do so at runtime.
+
+ To add an observer for a property, call:
+
+ ```javascript
+ object.addObserver('propertyKey', targetObject, targetAction)
+ ```
+
+ This will call the `targetAction` method on the `targetObject` whenever
+ the value of the `propertyKey` changes.
+
+ Note that if `propertyKey` is a computed property, the observer will be
+ called when any of the property dependencies are changed, even if the
+ resulting value of the computed property is unchanged. This is necessary
+ because computed properties are not computed until `get` is called.
+
+ @class Observable
+ @namespace Ember
+*/
+Ember.Observable = Ember.Mixin.create({
+
</ins><span class="cx"> /**
</span><del>- The `property` extension of Javascript's Function prototype is available
- when `Ember.EXTEND_PROTOTYPES` or `Ember.EXTEND_PROTOTYPES.Function` is
- `true`, which is the default.
</del><ins>+ Retrieves the value of a property from the object.
</ins><span class="cx">
</span><del>- Computed properties allow you to treat a function like a property:
</del><ins>+ This method is usually similar to using `object[keyName]` or `object.keyName`,
+ however it supports both computed properties and the unknownProperty
+ handler.
</ins><span class="cx">
</span><ins>+ Because `get` unifies the syntax for accessing all these kinds
+ of properties, it can make many refactorings easier, such as replacing a
+ simple property with a computed property, or vice versa.
+
+ ### Computed Properties
+
+ Computed properties are methods defined with the `property` modifier
+ declared at the end, such as:
+
</ins><span class="cx"> ```javascript
</span><del>- MyApp.president = Ember.Object.create({
- firstName: "Barack",
- lastName: "Obama",
</del><ins>+ fullName: function() {
+ return this.get('firstName') + ' ' + this.get('lastName');
+ }.property('firstName', 'lastName')
+ ```
</ins><span class="cx">
</span><del>- fullName: function() {
- return this.get('firstName') + ' ' + this.get('lastName');
</del><ins>+ When you call `get` on a computed property, the function will be
+ called and the return value will be returned instead of the function
+ itself.
</ins><span class="cx">
</span><del>- // Call this flag to mark the function as a property
- }.property()
</del><ins>+ ### Unknown Properties
+
+ Likewise, if you try to call `get` on a property whose value is
+ `undefined`, the `unknownProperty()` method will be called on the object.
+ If this method returns any value other than `undefined`, it will be returned
+ instead. This allows you to implement "virtual" properties that are
+ not defined upfront.
+
+ @method get
+ @param {String} keyName The property to retrieve
+ @return {Object} The property value or undefined.
+ */
+ get: function(keyName) {
+ return get(this, keyName);
+ },
+
+ /**
+ To get multiple properties at once, call `getProperties`
+ with a list of strings or an array:
+
+ ```javascript
+ record.getProperties('firstName', 'lastName', 'zipCode'); // { firstName: 'John', lastName: 'Doe', zipCode: '10011' }
+ ```
+
+ is equivalent to:
+
+ ```javascript
+ record.getProperties(['firstName', 'lastName', 'zipCode']); // { firstName: 'John', lastName: 'Doe', zipCode: '10011' }
+ ```
+
+ @method getProperties
+ @param {String...|Array} list of keys to get
+ @return {Hash}
+ */
+ getProperties: function() {
+ return getProperties.apply(null, [this].concat(slice.call(arguments)));
+ },
+
+ /**
+ Sets the provided key or path to the value.
+
+ This method is generally very similar to calling `object[key] = value` or
+ `object.key = value`, except that it provides support for computed
+ properties, the `setUnknownProperty()` method and property observers.
+
+ ### Computed Properties
+
+ If you try to set a value on a key that has a computed property handler
+ defined (see the `get()` method for an example), then `set()` will call
+ that method, passing both the value and key instead of simply changing
+ the value itself. This is useful for those times when you need to
+ implement a property that is composed of one or more member
+ properties.
+
+ ### Unknown Properties
+
+ If you try to set a value on a key that is undefined in the target
+ object, then the `setUnknownProperty()` handler will be called instead. This
+ gives you an opportunity to implement complex "virtual" properties that
+ are not predefined on the object. If `setUnknownProperty()` returns
+ undefined, then `set()` will simply set the value on the object.
+
+ ### Property Observers
+
+ In addition to changing the property, `set()` will also register a property
+ change with the object. Unless you have placed this call inside of a
+ `beginPropertyChanges()` and `endPropertyChanges(),` any "local" observers
+ (i.e. observer methods declared on the same object), will be called
+ immediately. Any "remote" observers (i.e. observer methods declared on
+ another object) will be placed in a queue and called at a later time in a
+ coalesced manner.
+
+ ### Chaining
+
+ In addition to property changes, `set()` returns the value of the object
+ itself so you can do chaining like this:
+
+ ```javascript
+ record.set('firstName', 'Charles').set('lastName', 'Jolley');
+ ```
+
+ @method set
+ @param {String} keyName The property to set
+ @param {Object} value The value to set or `null`.
+ @return {Ember.Observable}
+ */
+ set: function(keyName, value) {
+ set(this, keyName, value);
+ return this;
+ },
+
+
+ /**
+ Sets a list of properties at once. These properties are set inside
+ a single `beginPropertyChanges` and `endPropertyChanges` batch, so
+ observers will be buffered.
+
+ ```javascript
+ record.setProperties({ firstName: 'Charles', lastName: 'Jolley' });
+ ```
+
+ @method setProperties
+ @param {Hash} hash the hash of keys and values to set
+ @return {Ember.Observable}
+ */
+ setProperties: function(hash) {
+ return Ember.setProperties(this, hash);
+ },
+
+ /**
+ Begins a grouping of property changes.
+
+ You can use this method to group property changes so that notifications
+ will not be sent until the changes are finished. If you plan to make a
+ large number of changes to an object at one time, you should call this
+ method at the beginning of the changes to begin deferring change
+ notifications. When you are done making changes, call
+ `endPropertyChanges()` to deliver the deferred change notifications and end
+ deferring.
+
+ @method beginPropertyChanges
+ @return {Ember.Observable}
+ */
+ beginPropertyChanges: function() {
+ Ember.beginPropertyChanges();
+ return this;
+ },
+
+ /**
+ Ends a grouping of property changes.
+
+ You can use this method to group property changes so that notifications
+ will not be sent until the changes are finished. If you plan to make a
+ large number of changes to an object at one time, you should call
+ `beginPropertyChanges()` at the beginning of the changes to defer change
+ notifications. When you are done making changes, call this method to
+ deliver the deferred change notifications and end deferring.
+
+ @method endPropertyChanges
+ @return {Ember.Observable}
+ */
+ endPropertyChanges: function() {
+ Ember.endPropertyChanges();
+ return this;
+ },
+
+ /**
+ Notify the observer system that a property is about to change.
+
+ Sometimes you need to change a value directly or indirectly without
+ actually calling `get()` or `set()` on it. In this case, you can use this
+ method and `propertyDidChange()` instead. Calling these two methods
+ together will notify all observers that the property has potentially
+ changed value.
+
+ Note that you must always call `propertyWillChange` and `propertyDidChange`
+ as a pair. If you do not, it may get the property change groups out of
+ order and cause notifications to be delivered more often than you would
+ like.
+
+ @method propertyWillChange
+ @param {String} keyName The property key that is about to change.
+ @return {Ember.Observable}
+ */
+ propertyWillChange: function(keyName) {
+ Ember.propertyWillChange(this, keyName);
+ return this;
+ },
+
+ /**
+ Notify the observer system that a property has just changed.
+
+ Sometimes you need to change a value directly or indirectly without
+ actually calling `get()` or `set()` on it. In this case, you can use this
+ method and `propertyWillChange()` instead. Calling these two methods
+ together will notify all observers that the property has potentially
+ changed value.
+
+ Note that you must always call `propertyWillChange` and `propertyDidChange`
+ as a pair. If you do not, it may get the property change groups out of
+ order and cause notifications to be delivered more often than you would
+ like.
+
+ @method propertyDidChange
+ @param {String} keyName The property key that has just changed.
+ @return {Ember.Observable}
+ */
+ propertyDidChange: function(keyName) {
+ Ember.propertyDidChange(this, keyName);
+ return this;
+ },
+
+ /**
+ Convenience method to call `propertyWillChange` and `propertyDidChange` in
+ succession.
+
+ @method notifyPropertyChange
+ @param {String} keyName The property key to be notified about.
+ @return {Ember.Observable}
+ */
+ notifyPropertyChange: function(keyName) {
+ this.propertyWillChange(keyName);
+ this.propertyDidChange(keyName);
+ return this;
+ },
+
+ addBeforeObserver: function(key, target, method) {
+ Ember.addBeforeObserver(this, key, target, method);
+ },
+
+ /**
+ Adds an observer on a property.
+
+ This is the core method used to register an observer for a property.
+
+ Once you call this method, any time the key's value is set, your observer
+ will be notified. Note that the observers are triggered any time the
+ value is set, regardless of whether it has actually changed. Your
+ observer should be prepared to handle that.
+
+ You can also pass an optional context parameter to this method. The
+ context will be passed to your observer method whenever it is triggered.
+ Note that if you add the same target/method pair on a key multiple times
+ with different context parameters, your observer will only be called once
+ with the last context you passed.
+
+ ### Observer Methods
+
+ Observer methods you pass should generally have the following signature if
+ you do not pass a `context` parameter:
+
+ ```javascript
+ fooDidChange: function(sender, key, value, rev) { };
+ ```
+
+ The sender is the object that changed. The key is the property that
+ changes. The value property is currently reserved and unused. The rev
+ is the last property revision of the object when it changed, which you can
+ use to detect if the key value has really changed or not.
+
+ If you pass a `context` parameter, the context will be passed before the
+ revision like so:
+
+ ```javascript
+ fooDidChange: function(sender, key, value, context, rev) { };
+ ```
+
+ Usually you will not need the value, context or revision parameters at
+ the end. In this case, it is common to write observer methods that take
+ only a sender and key value as parameters or, if you aren't interested in
+ any of these values, to write an observer that has no parameters at all.
+
+ @method addObserver
+ @param {String} key The key to observer
+ @param {Object} target The target object to invoke
+ @param {String|Function} method The method to invoke.
+ @return {Ember.Object} self
+ */
+ addObserver: function(key, target, method) {
+ Ember.addObserver(this, key, target, method);
+ },
+
+ /**
+ Remove an observer you have previously registered on this object. Pass
+ the same key, target, and method you passed to `addObserver()` and your
+ target will no longer receive notifications.
+
+ @method removeObserver
+ @param {String} key The key to observer
+ @param {Object} target The target object to invoke
+ @param {String|Function} method The method to invoke.
+ @return {Ember.Observable} receiver
+ */
+ removeObserver: function(key, target, method) {
+ Ember.removeObserver(this, key, target, method);
+ },
+
+ /**
+ Returns `true` if the object currently has observers registered for a
+ particular key. You can use this method to potentially defer performing
+ an expensive action until someone begins observing a particular property
+ on the object.
+
+ @method hasObserverFor
+ @param {String} key Key to check
+ @return {Boolean}
+ */
+ hasObserverFor: function(key) {
+ return Ember.hasListeners(this, key+':change');
+ },
+
+ /**
+ Retrieves the value of a property, or a default value in the case that the
+ property returns `undefined`.
+
+ ```javascript
+ person.getWithDefault('lastName', 'Doe');
+ ```
+
+ @method getWithDefault
+ @param {String} keyName The name of the property to retrieve
+ @param {Object} defaultValue The value to return if the property value is undefined
+ @return {Object} The property value or the defaultValue.
+ */
+ getWithDefault: function(keyName, defaultValue) {
+ return Ember.getWithDefault(this, keyName, defaultValue);
+ },
+
+ /**
+ Set the value of a property to the current value plus some amount.
+
+ ```javascript
+ person.incrementProperty('age');
+ team.incrementProperty('score', 2);
+ ```
+
+ @method incrementProperty
+ @param {String} keyName The name of the property to increment
+ @param {Number} increment The amount to increment by. Defaults to 1
+ @return {Number} The new property value
+ */
+ incrementProperty: function(keyName, increment) {
+ if (Ember.isNone(increment)) { increment = 1; }
+ Ember.assert("Must pass a numeric value to incrementProperty", (!isNaN(parseFloat(increment)) && isFinite(increment)));
+ set(this, keyName, (get(this, keyName) || 0) + increment);
+ return get(this, keyName);
+ },
+
+ /**
+ Set the value of a property to the current value minus some amount.
+
+ ```javascript
+ player.decrementProperty('lives');
+ orc.decrementProperty('health', 5);
+ ```
+
+ @method decrementProperty
+ @param {String} keyName The name of the property to decrement
+ @param {Number} decrement The amount to decrement by. Defaults to 1
+ @return {Number} The new property value
+ */
+ decrementProperty: function(keyName, decrement) {
+ if (Ember.isNone(decrement)) { decrement = 1; }
+ Ember.assert("Must pass a numeric value to decrementProperty", (!isNaN(parseFloat(decrement)) && isFinite(decrement)));
+ set(this, keyName, (get(this, keyName) || 0) - decrement);
+ return get(this, keyName);
+ },
+
+ /**
+ Set the value of a boolean property to the opposite of it's
+ current value.
+
+ ```javascript
+ starship.toggleProperty('warpDriveEngaged');
+ ```
+
+ @method toggleProperty
+ @param {String} keyName The name of the property to toggle
+ @return {Object} The new property value
+ */
+ toggleProperty: function(keyName) {
+ set(this, keyName, !get(this, keyName));
+ return get(this, keyName);
+ },
+
+ /**
+ Returns the cached value of a computed property, if it exists.
+ This allows you to inspect the value of a computed property
+ without accidentally invoking it if it is intended to be
+ generated lazily.
+
+ @method cacheFor
+ @param {String} keyName
+ @return {Object} The cached value of the computed property, if any
+ */
+ cacheFor: function(keyName) {
+ return Ember.cacheFor(this, keyName);
+ },
+
+ // intended for debugging purposes
+ observersForKey: function(keyName) {
+ return Ember.observersFor(this, keyName);
+ }
+});
+
+})();
+
+
+
+(function() {
+/**
+ @module ember
+ @submodule ember-runtime
+*/
+
+
+// NOTE: this object should never be included directly. Instead use `Ember.Object`.
+// We only define this separately so that `Ember.Set` can depend on it.
+
+
+var set = Ember.set, get = Ember.get,
+ o_create = Ember.create,
+ o_defineProperty = Ember.platform.defineProperty,
+ GUID_KEY = Ember.GUID_KEY,
+ guidFor = Ember.guidFor,
+ generateGuid = Ember.generateGuid,
+ meta = Ember.meta,
+ rewatch = Ember.rewatch,
+ finishChains = Ember.finishChains,
+ sendEvent = Ember.sendEvent,
+ destroy = Ember.destroy,
+ schedule = Ember.run.schedule,
+ Mixin = Ember.Mixin,
+ applyMixin = Mixin._apply,
+ finishPartial = Mixin.finishPartial,
+ reopen = Mixin.prototype.reopen,
+ MANDATORY_SETTER = Ember.ENV.MANDATORY_SETTER,
+ indexOf = Ember.EnumerableUtils.indexOf;
+
+var undefinedDescriptor = {
+ configurable: true,
+ writable: true,
+ enumerable: false,
+ value: undefined
+};
+
+function makeCtor() {
+
+ // Note: avoid accessing any properties on the object since it makes the
+ // method a lot faster. This is glue code so we want it to be as fast as
+ // possible.
+
+ var wasApplied = false, initMixins, initProperties;
+
+ var Class = function() {
+ if (!wasApplied) {
+ Class.proto(); // prepare prototype...
+ }
+ o_defineProperty(this, GUID_KEY, undefinedDescriptor);
+ o_defineProperty(this, '_super', undefinedDescriptor);
+ var m = meta(this), proto = m.proto;
+ m.proto = this;
+ if (initMixins) {
+ // capture locally so we can clear the closed over variable
+ var mixins = initMixins;
+ initMixins = null;
+ this.reopen.apply(this, mixins);
+ }
+ if (initProperties) {
+ // capture locally so we can clear the closed over variable
+ var props = initProperties;
+ initProperties = null;
+
+ var concatenatedProperties = this.concatenatedProperties;
+
+ for (var i = 0, l = props.length; i < l; i++) {
+ var properties = props[i];
+
+ Ember.assert("Ember.Object.create no longer supports mixing in other definitions, use createWithMixins instead.", !(properties instanceof Ember.Mixin));
+
+ if (typeof properties !== 'object' && properties !== undefined) {
+ throw new Ember.Error("Ember.Object.create only accepts objects.");
+ }
+
+ if (!properties) { continue; }
+
+ var keyNames = Ember.keys(properties);
+
+ for (var j = 0, ll = keyNames.length; j < ll; j++) {
+ var keyName = keyNames[j];
+ if (!properties.hasOwnProperty(keyName)) { continue; }
+
+ var value = properties[keyName],
+ IS_BINDING = Ember.IS_BINDING;
+
+ if (IS_BINDING.test(keyName)) {
+ var bindings = m.bindings;
+ if (!bindings) {
+ bindings = m.bindings = {};
+ } else if (!m.hasOwnProperty('bindings')) {
+ bindings = m.bindings = o_create(m.bindings);
+ }
+ bindings[keyName] = value;
+ }
+
+ var desc = m.descs[keyName];
+
+ Ember.assert("Ember.Object.create no longer supports defining computed properties.", !(value instanceof Ember.ComputedProperty));
+ Ember.assert("Ember.Object.create no longer supports defining methods that call _super.", !(typeof value === 'function' && value.toString().indexOf('._super') !== -1));
+ Ember.assert("`actions` must be provided at extend time, not at create " +
+ "time, when Ember.ActionHandler is used (i.e. views, " +
+ "controllers & routes).", !((keyName === 'actions') && Ember.ActionHandler.detect(this)));
+
+ if (concatenatedProperties && indexOf(concatenatedProperties, keyName) >= 0) {
+ var baseValue = this[keyName];
+
+ if (baseValue) {
+ if ('function' === typeof baseValue.concat) {
+ value = baseValue.concat(value);
+ } else {
+ value = Ember.makeArray(baseValue).concat(value);
+ }
+ } else {
+ value = Ember.makeArray(value);
+ }
+ }
+
+ if (desc) {
+ desc.set(this, keyName, value);
+ } else {
+ if (typeof this.setUnknownProperty === 'function' && !(keyName in this)) {
+ this.setUnknownProperty(keyName, value);
+ } else if (MANDATORY_SETTER) {
+ Ember.defineProperty(this, keyName, null, value); // setup mandatory setter
+ } else {
+ this[keyName] = value;
+ }
+ }
+ }
+ }
+ }
+ finishPartial(this, m);
+ this.init.apply(this, arguments);
+ m.proto = proto;
+ finishChains(this);
+ sendEvent(this, "init");
+ };
+
+ Class.toString = Mixin.prototype.toString;
+ Class.willReopen = function() {
+ if (wasApplied) {
+ Class.PrototypeMixin = Mixin.create(Class.PrototypeMixin);
+ }
+
+ wasApplied = false;
+ };
+ Class._initMixins = function(args) { initMixins = args; };
+ Class._initProperties = function(args) { initProperties = args; };
+
+ Class.proto = function() {
+ var superclass = Class.superclass;
+ if (superclass) { superclass.proto(); }
+
+ if (!wasApplied) {
+ wasApplied = true;
+ Class.PrototypeMixin.applyPartial(Class.prototype);
+ rewatch(Class.prototype);
+ }
+
+ return this.prototype;
+ };
+
+ return Class;
+
+}
+
+/**
+ @class CoreObject
+ @namespace Ember
+*/
+var CoreObject = makeCtor();
+CoreObject.toString = function() { return "Ember.CoreObject"; };
+
+CoreObject.PrototypeMixin = Mixin.create({
+ reopen: function() {
+ applyMixin(this, arguments, true);
+ return this;
+ },
+
+ /**
+ An overridable method called when objects are instantiated. By default,
+ does nothing unless it is overridden during class definition.
+
+ Example:
+
+ ```javascript
+ App.Person = Ember.Object.extend({
+ init: function() {
+ alert('Name is ' + this.get('name'));
+ }
</ins><span class="cx"> });
</span><span class="cx">
</span><del>- MyApp.president.get('fullName'); // "Barack Obama"
</del><ins>+ var steve = App.Person.create({
+ name: "Steve"
+ });
+
+ // alerts 'Name is Steve'.
</ins><span class="cx"> ```
</span><span class="cx">
</span><del>- Treating a function like a property is useful because they can work with
- bindings, just like any other property.
</del><ins>+ NOTE: If you do override `init` for a framework class like `Ember.View` or
+ `Ember.ArrayController`, be sure to call `this._super()` in your
+ `init` declaration! If you don't, Ember may not have an opportunity to
+ do important setup work, and you'll see strange behavior in your
+ application.
</ins><span class="cx">
</span><del>- Many computed properties have dependencies on other properties. For
- example, in the above example, the `fullName` property depends on
- `firstName` and `lastName` to determine its value. You can tell Ember
- about these dependencies like this:
</del><ins>+ @method init
+ */
+ init: function() {},
</ins><span class="cx">
</span><ins>+ /**
+ Defines the properties that will be concatenated from the superclass
+ (instead of overridden).
+
+ By default, when you extend an Ember class a property defined in
+ the subclass overrides a property with the same name that is defined
+ in the superclass. However, there are some cases where it is preferable
+ to build up a property's value by combining the superclass' property
+ value with the subclass' value. An example of this in use within Ember
+ is the `classNames` property of `Ember.View`.
+
+ Here is some sample code showing the difference between a concatenated
+ property and a normal one:
+
</ins><span class="cx"> ```javascript
</span><del>- MyApp.president = Ember.Object.create({
- firstName: "Barack",
- lastName: "Obama",
</del><ins>+ App.BarView = Ember.View.extend({
+ someNonConcatenatedProperty: ['bar'],
+ classNames: ['bar']
+ });
</ins><span class="cx">
</span><del>- fullName: function() {
- return this.get('firstName') + ' ' + this.get('lastName');
</del><ins>+ App.FooBarView = App.BarView.extend({
+ someNonConcatenatedProperty: ['foo'],
+ classNames: ['foo'],
+ });
</ins><span class="cx">
</span><del>- // Tell Ember.js that this computed property depends on firstName
- // and lastName
- }.property('firstName', 'lastName')
</del><ins>+ var fooBarView = App.FooBarView.create();
+ fooBarView.get('someNonConcatenatedProperty'); // ['foo']
+ fooBarView.get('classNames'); // ['ember-view', 'bar', 'foo']
+ ```
+
+ This behavior extends to object creation as well. Continuing the
+ above example:
+
+ ```javascript
+ var view = App.FooBarView.create({
+ someNonConcatenatedProperty: ['baz'],
+ classNames: ['baz']
+ })
+ view.get('someNonConcatenatedProperty'); // ['baz']
+ view.get('classNames'); // ['ember-view', 'bar', 'foo', 'baz']
+ ```
+ Adding a single property that is not an array will just add it in the array:
+
+ ```javascript
+ var view = App.FooBarView.create({
+ classNames: 'baz'
+ })
+ view.get('classNames'); // ['ember-view', 'bar', 'foo', 'baz']
+ ```
+
+ Using the `concatenatedProperties` property, we can tell to Ember that mix
+ the content of the properties.
+
+ In `Ember.View` the `classNameBindings` and `attributeBindings` properties
+ are also concatenated, in addition to `classNames`.
+
+ This feature is available for you to use throughout the Ember object model,
+ although typical app developers are likely to use it infrequently. Since
+ it changes expectations about behavior of properties, you should properly
+ document its usage in each individual concatenated property (to not
+ mislead your users to think they can override the property in a subclass).
+
+ @property concatenatedProperties
+ @type Array
+ @default null
+ */
+ concatenatedProperties: null,
+
+ /**
+ Destroyed object property flag.
+
+ if this property is `true` the observers and bindings were already
+ removed by the effect of calling the `destroy()` method.
+
+ @property isDestroyed
+ @default false
+ */
+ isDestroyed: false,
+
+ /**
+ Destruction scheduled flag. The `destroy()` method has been called.
+
+ The object stays intact until the end of the run loop at which point
+ the `isDestroyed` flag is set.
+
+ @property isDestroying
+ @default false
+ */
+ isDestroying: false,
+
+ /**
+ Destroys an object by setting the `isDestroyed` flag and removing its
+ metadata, which effectively destroys observers and bindings.
+
+ If you try to set a property on a destroyed object, an exception will be
+ raised.
+
+ Note that destruction is scheduled for the end of the run loop and does not
+ happen immediately. It will set an isDestroying flag immediately.
+
+ @method destroy
+ @return {Ember.Object} receiver
+ */
+ destroy: function() {
+ if (this.isDestroying) { return; }
+ this.isDestroying = true;
+
+ schedule('actions', this, this.willDestroy);
+ schedule('destroy', this, this._scheduledDestroy);
+ return this;
+ },
+
+ /**
+ Override to implement teardown.
+
+ @method willDestroy
+ */
+ willDestroy: Ember.K,
+
+ /**
+ Invoked by the run loop to actually destroy the object. This is
+ scheduled for execution by the `destroy` method.
+
+ @private
+ @method _scheduledDestroy
+ */
+ _scheduledDestroy: function() {
+ if (this.isDestroyed) { return; }
+ destroy(this);
+ this.isDestroyed = true;
+ },
+
+ bind: function(to, from) {
+ if (!(from instanceof Ember.Binding)) { from = Ember.Binding.from(from); }
+ from.to(to).connect(this);
+ return from;
+ },
+
+ /**
+ Returns a string representation which attempts to provide more information
+ than Javascript's `toString` typically does, in a generic way for all Ember
+ objects.
+
+ ```javascript
+ App.Person = Em.Object.extend()
+ person = App.Person.create()
+ person.toString() //=> "<App.Person:ember1024>"
+ ```
+
+ If the object's class is not defined on an Ember namespace, it will
+ indicate it is a subclass of the registered superclass:
+
+ ```javascript
+ Student = App.Person.extend()
+ student = Student.create()
+ student.toString() //=> "<(subclass of App.Person):ember1025>"
+ ```
+
+ If the method `toStringExtension` is defined, its return value will be
+ included in the output.
+
+ ```javascript
+ App.Teacher = App.Person.extend({
+ toStringExtension: function() {
+ return this.get('fullName');
+ }
</ins><span class="cx"> });
</span><ins>+ teacher = App.Teacher.create()
+ teacher.toString(); //=> "<App.Teacher:ember1026:Tom Dale>"
</ins><span class="cx"> ```
</span><span class="cx">
</span><del>- Make sure you list these dependencies so Ember knows when to update
- bindings that connect to a computed property. Changing a dependency
- will not immediately trigger an update of the computed property, but
- will instead clear the cache so that it is updated when the next `get`
- is called on the property.
</del><ins>+ @method toString
+ @return {String} string representation
+ */
+ toString: function toString() {
+ var hasToStringExtension = typeof this.toStringExtension === 'function',
+ extension = hasToStringExtension ? ":" + this.toStringExtension() : '';
+ var ret = '<'+this.constructor.toString()+':'+guidFor(this)+extension+'>';
+ this.toString = makeToString(ret);
+ return ret;
+ }
+});
</ins><span class="cx">
</span><del>- See {{#crossLink "Ember.ComputedProperty"}}{{/crossLink}},
- {{#crossLink "Ember/computed"}}{{/crossLink}}
</del><ins>+CoreObject.PrototypeMixin.ownerConstructor = CoreObject;
</ins><span class="cx">
</span><del>- @method property
- @for Function
</del><ins>+function makeToString(ret) {
+ return function() { return ret; };
+}
+
+if (Ember.config.overridePrototypeMixin) {
+ Ember.config.overridePrototypeMixin(CoreObject.PrototypeMixin);
+}
+
+CoreObject.__super__ = null;
+
+var ClassMixin = Mixin.create({
+
+ ClassMixin: Ember.required(),
+
+ PrototypeMixin: Ember.required(),
+
+ isClass: true,
+
+ isMethod: false,
+
+ /**
+ Creates a new subclass.
+
+ ```javascript
+ App.Person = Ember.Object.extend({
+ say: function(thing) {
+ alert(thing);
+ }
+ });
+ ```
+
+ This defines a new subclass of Ember.Object: `App.Person`. It contains one method: `say()`.
+
+ You can also create a subclass from any existing class by calling its `extend()` method. For example, you might want to create a subclass of Ember's built-in `Ember.View` class:
+
+ ```javascript
+ App.PersonView = Ember.View.extend({
+ tagName: 'li',
+ classNameBindings: ['isAdministrator']
+ });
+ ```
+
+ When defining a subclass, you can override methods but still access the implementation of your parent class by calling the special `_super()` method:
+
+ ```javascript
+ App.Person = Ember.Object.extend({
+ say: function(thing) {
+ var name = this.get('name');
+ alert(name + ' says: ' + thing);
+ }
+ });
+
+ App.Soldier = App.Person.extend({
+ say: function(thing) {
+ this._super(thing + ", sir!");
+ },
+ march: function(numberOfHours) {
+ alert(this.get('name') + ' marches for ' + numberOfHours + ' hours.')
+ }
+ });
+
+ var yehuda = App.Soldier.create({
+ name: "Yehuda Katz"
+ });
+
+ yehuda.say("Yes"); // alerts "Yehuda Katz says: Yes, sir!"
+ ```
+
+ The `create()` on line #17 creates an *instance* of the `App.Soldier` class. The `extend()` on line #8 creates a *subclass* of `App.Person`. Any instance of the `App.Person` class will *not* have the `march()` method.
+
+ You can also pass `Ember.Mixin` classes to add additional properties to the subclass.
+
+ ```javascript
+ App.Person = Ember.Object.extend({
+ say: function(thing) {
+ alert(this.get('name') + ' says: ' + thing);
+ }
+ });
+
+ App.SingingMixin = Ember.Mixin.create({
+ sing: function(thing){
+ alert(this.get('name') + ' sings: la la la ' + thing);
+ }
+ });
+
+ App.BroadwayStar = App.Person.extend(App.SingingMixin, {
+ dance: function() {
+ alert(this.get('name') + ' dances: tap tap tap tap ');
+ }
+ });
+ ```
+
+ The `App.BroadwayStar` class contains three methods: `say()`, `sing()`, and `dance()`.
+
+ @method extend
+ @static
+
+ @param {Ember.Mixin} [mixins]* One or more Ember.Mixin classes
+ @param {Object} [arguments]* Object containing values to use within the new class
</ins><span class="cx"> */
</span><del>- Function.prototype.property = function() {
- var ret = Ember.computed(this);
- return ret.property.apply(ret, arguments);
- };
</del><ins>+ extend: function() {
+ var Class = makeCtor(), proto;
+ Class.ClassMixin = Mixin.create(this.ClassMixin);
+ Class.PrototypeMixin = Mixin.create(this.PrototypeMixin);
</ins><span class="cx">
</span><ins>+ Class.ClassMixin.ownerConstructor = Class;
+ Class.PrototypeMixin.ownerConstructor = Class;
+
+ reopen.apply(Class.PrototypeMixin, arguments);
+
+ Class.superclass = this;
+ Class.__super__ = this.prototype;
+
+ proto = Class.prototype = o_create(this.prototype);
+ proto.constructor = Class;
+ generateGuid(proto);
+ meta(proto).proto = proto; // this will disable observers on prototype
+
+ Class.ClassMixin.apply(Class);
+ return Class;
+ },
+
</ins><span class="cx"> /**
</span><del>- The `observes` extension of Javascript's Function prototype is available
- when `Ember.EXTEND_PROTOTYPES` or `Ember.EXTEND_PROTOTYPES.Function` is
- true, which is the default.
</del><ins>+ Equivalent to doing `extend(arguments).create()`.
+ If possible use the normal `create` method instead.
</ins><span class="cx">
</span><del>- You can observe property changes simply by adding the `observes`
- call to the end of your method declarations in classes that you write.
- For example:
</del><ins>+ @method createWithMixins
+ @static
+ @param [arguments]*
+ */
+ createWithMixins: function() {
+ var C = this;
+ if (arguments.length>0) { this._initMixins(arguments); }
+ return new C();
+ },
</ins><span class="cx">
</span><ins>+ /**
+ Creates an instance of a class. Accepts either no arguments, or an object
+ containing values to initialize the newly instantiated object with.
+
</ins><span class="cx"> ```javascript
</span><del>- Ember.Object.create({
- valueObserver: function() {
- // Executes whenever the "value" property changes
- }.observes('value')
</del><ins>+ App.Person = Ember.Object.extend({
+ helloWorld: function() {
+ alert("Hi, my name is " + this.get('name'));
+ }
</ins><span class="cx"> });
</span><ins>+
+ var tom = App.Person.create({
+ name: 'Tom Dale'
+ });
+
+ tom.helloWorld(); // alerts "Hi, my name is Tom Dale".
</ins><span class="cx"> ```
</span><span class="cx">
</span><del>- See {{#crossLink "Ember.Observable/observes"}}{{/crossLink}}
</del><ins>+ `create` will call the `init` function if defined during
+ `Ember.AnyObject.extend`
</ins><span class="cx">
</span><del>- @method observes
- @for Function
</del><ins>+ If no arguments are passed to `create`, it will not set values to the new
+ instance during initialization:
+
+ ```javascript
+ var noName = App.Person.create();
+ noName.helloWorld(); // alerts undefined
+ ```
+
+ NOTE: For performance reasons, you cannot declare methods or computed
+ properties during `create`. You should instead declare methods and computed
+ properties when using `extend` or use the `createWithMixins` shorthand.
+
+ @method create
+ @static
+ @param [arguments]*
</ins><span class="cx"> */
</span><del>- Function.prototype.observes = function() {
- this.__ember_observes__ = a_slice.call(arguments);
</del><ins>+ create: function() {
+ var C = this;
+ if (arguments.length>0) { this._initProperties(arguments); }
+ return new C();
+ },
+
+ /**
+ Augments a constructor's prototype with additional
+ properties and functions:
+
+ ```javascript
+ MyObject = Ember.Object.extend({
+ name: 'an object'
+ });
+
+ o = MyObject.create();
+ o.get('name'); // 'an object'
+
+ MyObject.reopen({
+ say: function(msg){
+ console.log(msg);
+ }
+ })
+
+ o2 = MyObject.create();
+ o2.say("hello"); // logs "hello"
+
+ o.say("goodbye"); // logs "goodbye"
+ ```
+
+ To add functions and properties to the constructor itself,
+ see `reopenClass`
+
+ @method reopen
+ */
+ reopen: function() {
+ this.willReopen();
+ reopen.apply(this.PrototypeMixin, arguments);
</ins><span class="cx"> return this;
</span><del>- };
</del><ins>+ },
</ins><span class="cx">
</span><span class="cx"> /**
</span><del>- The `observesBefore` extension of Javascript's Function prototype is
- available when `Ember.EXTEND_PROTOTYPES` or
- `Ember.EXTEND_PROTOTYPES.Function` is true, which is the default.
</del><ins>+ Augments a constructor's own properties and functions:
</ins><span class="cx">
</span><del>- You can get notified when a property changes is about to happen by
- by adding the `observesBefore` call to the end of your method
- declarations in classes that you write. For example:
</del><ins>+ ```javascript
+ MyObject = Ember.Object.extend({
+ name: 'an object'
+ });
</ins><span class="cx">
</span><ins>+ MyObject.reopenClass({
+ canBuild: false
+ });
+
+ MyObject.canBuild; // false
+ o = MyObject.create();
+ ```
+
+ In other words, this creates static properties and functions for the class. These are only available on the class
+ and not on any instance of that class.
+
</ins><span class="cx"> ```javascript
</span><del>- Ember.Object.create({
- valueObserver: function() {
- // Executes whenever the "value" property is about to change
- }.observesBefore('value')
</del><ins>+ App.Person = Ember.Object.extend({
+ name : "",
+ sayHello : function(){
+ alert("Hello. My name is " + this.get('name'));
+ }
</ins><span class="cx"> });
</span><ins>+
+ App.Person.reopenClass({
+ species : "Homo sapiens",
+ createPerson: function(newPersonsName){
+ return App.Person.create({
+ name:newPersonsName
+ });
+ }
+ });
+
+ var tom = App.Person.create({
+ name : "Tom Dale"
+ });
+ var yehuda = App.Person.createPerson("Yehuda Katz");
+
+ tom.sayHello(); // "Hello. My name is Tom Dale"
+ yehuda.sayHello(); // "Hello. My name is Yehuda Katz"
+ alert(App.Person.species); // "Homo sapiens"
</ins><span class="cx"> ```
</span><span class="cx">
</span><del>- See {{#crossLink "Ember.Observable/observesBefore"}}{{/crossLink}}
</del><ins>+ Note that `species` and `createPerson` are *not* valid on the `tom` and `yehuda`
+ variables. They are only valid on `App.Person`.
</ins><span class="cx">
</span><del>- @method observesBefore
- @for Function
</del><ins>+ To add functions and properties to instances of
+ a constructor by extending the constructor's prototype
+ see `reopen`
+
+ @method reopenClass
</ins><span class="cx"> */
</span><del>- Function.prototype.observesBefore = function() {
- this.__ember_observesBefore__ = a_slice.call(arguments);
</del><ins>+ reopenClass: function() {
+ reopen.apply(this.ClassMixin, arguments);
+ applyMixin(this, arguments, false);
</ins><span class="cx"> return this;
</span><del>- };
</del><ins>+ },
</ins><span class="cx">
</span><ins>+ detect: function(obj) {
+ if ('function' !== typeof obj) { return false; }
+ while(obj) {
+ if (obj===this) { return true; }
+ obj = obj.superclass;
+ }
+ return false;
+ },
+
+ detectInstance: function(obj) {
+ return obj instanceof this;
+ },
+
+ /**
+ In some cases, you may want to annotate computed properties with additional
+ metadata about how they function or what values they operate on. For
+ example, computed property functions may close over variables that are then
+ no longer available for introspection.
+
+ You can pass a hash of these values to a computed property like this:
+
+ ```javascript
+ person: function() {
+ var personId = this.get('personId');
+ return App.Person.create({ id: personId });
+ }.property().meta({ type: App.Person })
+ ```
+
+ Once you've done this, you can retrieve the values saved to the computed
+ property from your class like this:
+
+ ```javascript
+ MyClass.metaForProperty('person');
+ ```
+
+ This will return the original hash that was passed to `meta()`.
+
+ @method metaForProperty
+ @param key {String} property name
+ */
+ metaForProperty: function(key) {
+ var desc = meta(this.proto(), false).descs[key];
+
+ Ember.assert("metaForProperty() could not find a computed property with key '"+key+"'.", !!desc && desc instanceof Ember.ComputedProperty);
+ return desc._meta || {};
+ },
+
+ /**
+ Iterate over each computed property for the class, passing its name
+ and any associated metadata (see `metaForProperty`) to the callback.
+
+ @method eachComputedProperty
+ @param {Function} callback
+ @param {Object} binding
+ */
+ eachComputedProperty: function(callback, binding) {
+ var proto = this.proto(),
+ descs = meta(proto).descs,
+ empty = {},
+ property;
+
+ for (var name in descs) {
+ property = descs[name];
+
+ if (property instanceof Ember.ComputedProperty) {
+ callback.call(binding || this, name, property._meta || empty);
+ }
+ }
+ }
+
+});
+
+ClassMixin.ownerConstructor = CoreObject;
+
+if (Ember.config.overrideClassMixin) {
+ Ember.config.overrideClassMixin(ClassMixin);
</ins><span class="cx"> }
</span><span class="cx">
</span><ins>+CoreObject.ClassMixin = ClassMixin;
+ClassMixin.apply(CoreObject);
</ins><span class="cx">
</span><ins>+Ember.CoreObject = CoreObject;
+
</ins><span class="cx"> })();
</span><span class="cx">
</span><span class="cx">
</span><span class="cx">
</span><span class="cx"> (function() {
</span><ins>+/**
+@module ember
+@submodule ember-runtime
+*/
</ins><span class="cx">
</span><ins>+/**
+ `Ember.Object` is the main base class for all Ember objects. It is a subclass
+ of `Ember.CoreObject` with the `Ember.Observable` mixin applied. For details,
+ see the documentation for each of these.
+
+ @class Object
+ @namespace Ember
+ @extends Ember.CoreObject
+ @uses Ember.Observable
+*/
+Ember.Object = Ember.CoreObject.extend(Ember.Observable);
+Ember.Object.toString = function() { return "Ember.Object"; };
+
</ins><span class="cx"> })();
</span><span class="cx">
</span><span class="cx">
</span><span class="lines">@@ -7256,6 +12975,366 @@
</span><span class="cx"> @submodule ember-runtime
</span><span class="cx"> */
</span><span class="cx">
</span><ins>+var get = Ember.get, indexOf = Ember.ArrayPolyfills.indexOf;
+
+/**
+ A Namespace is an object usually used to contain other objects or methods
+ such as an application or framework. Create a namespace anytime you want
+ to define one of these new containers.
+
+ # Example Usage
+
+ ```javascript
+ MyFramework = Ember.Namespace.create({
+ VERSION: '1.0.0'
+ });
+ ```
+
+ @class Namespace
+ @namespace Ember
+ @extends Ember.Object
+*/
+var Namespace = Ember.Namespace = Ember.Object.extend({
+ isNamespace: true,
+
+ init: function() {
+ Ember.Namespace.NAMESPACES.push(this);
+ Ember.Namespace.PROCESSED = false;
+ },
+
+ toString: function() {
+ var name = get(this, 'name');
+ if (name) { return name; }
+
+ findNamespaces();
+ return this[Ember.GUID_KEY+'_name'];
+ },
+
+ nameClasses: function() {
+ processNamespace([this.toString()], this, {});
+ },
+
+ destroy: function() {
+ var namespaces = Ember.Namespace.NAMESPACES;
+ Ember.lookup[this.toString()] = undefined;
+ namespaces.splice(indexOf.call(namespaces, this), 1);
+ this._super();
+ }
+});
+
+Namespace.reopenClass({
+ NAMESPACES: [Ember],
+ NAMESPACES_BY_ID: {},
+ PROCESSED: false,
+ processAll: processAllNamespaces,
+ byName: function(name) {
+ if (!Ember.BOOTED) {
+ processAllNamespaces();
+ }
+
+ return NAMESPACES_BY_ID[name];
+ }
+});
+
+var NAMESPACES_BY_ID = Namespace.NAMESPACES_BY_ID;
+
+var hasOwnProp = ({}).hasOwnProperty,
+ guidFor = Ember.guidFor;
+
+function processNamespace(paths, root, seen) {
+ var idx = paths.length;
+
+ NAMESPACES_BY_ID[paths.join('.')] = root;
+
+ // Loop over all of the keys in the namespace, looking for classes
+ for(var key in root) {
+ if (!hasOwnProp.call(root, key)) { continue; }
+ var obj = root[key];
+
+ // If we are processing the `Ember` namespace, for example, the
+ // `paths` will start with `["Ember"]`. Every iteration through
+ // the loop will update the **second** element of this list with
+ // the key, so processing `Ember.View` will make the Array
+ // `['Ember', 'View']`.
+ paths[idx] = key;
+
+ // If we have found an unprocessed class
+ if (obj && obj.toString === classToString) {
+ // Replace the class' `toString` with the dot-separated path
+ // and set its `NAME_KEY`
+ obj.toString = makeToString(paths.join('.'));
+ obj[NAME_KEY] = paths.join('.');
+
+ // Support nested namespaces
+ } else if (obj && obj.isNamespace) {
+ // Skip aliased namespaces
+ if (seen[guidFor(obj)]) { continue; }
+ seen[guidFor(obj)] = true;
+
+ // Process the child namespace
+ processNamespace(paths, obj, seen);
+ }
+ }
+
+ paths.length = idx; // cut out last item
+}
+
+function findNamespaces() {
+ var Namespace = Ember.Namespace, lookup = Ember.lookup, obj, isNamespace;
+
+ if (Namespace.PROCESSED) { return; }
+
+ for (var prop in lookup) {
+ // These don't raise exceptions but can cause warnings
+ if (prop === "parent" || prop === "top" || prop === "frameElement" || prop === "webkitStorageInfo") { continue; }
+
+ // get(window.globalStorage, 'isNamespace') would try to read the storage for domain isNamespace and cause exception in Firefox.
+ // globalStorage is a storage obsoleted by the WhatWG storage specification. See https://developer.mozilla.org/en/DOM/Storage#globalStorage
+ if (prop === "globalStorage" && lookup.StorageList && lookup.globalStorage instanceof lookup.StorageList) { continue; }
+ // Unfortunately, some versions of IE don't support window.hasOwnProperty
+ if (lookup.hasOwnProperty && !lookup.hasOwnProperty(prop)) { continue; }
+
+ // At times we are not allowed to access certain properties for security reasons.
+ // There are also times where even if we can access them, we are not allowed to access their properties.
+ try {
+ obj = Ember.lookup[prop];
+ isNamespace = obj && obj.isNamespace;
+ } catch (e) {
+ continue;
+ }
+
+ if (isNamespace) {
+ Ember.deprecate("Namespaces should not begin with lowercase.", /^[A-Z]/.test(prop));
+ obj[NAME_KEY] = prop;
+ }
+ }
+}
+
+var NAME_KEY = Ember.NAME_KEY = Ember.GUID_KEY + '_name';
+
+function superClassString(mixin) {
+ var superclass = mixin.superclass;
+ if (superclass) {
+ if (superclass[NAME_KEY]) { return superclass[NAME_KEY]; }
+ else { return superClassString(superclass); }
+ } else {
+ return;
+ }
+}
+
+function classToString() {
+ if (!Ember.BOOTED && !this[NAME_KEY]) {
+ processAllNamespaces();
+ }
+
+ var ret;
+
+ if (this[NAME_KEY]) {
+ ret = this[NAME_KEY];
+ } else if (this._toString) {
+ ret = this._toString;
+ } else {
+ var str = superClassString(this);
+ if (str) {
+ ret = "(subclass of " + str + ")";
+ } else {
+ ret = "(unknown mixin)";
+ }
+ this.toString = makeToString(ret);
+ }
+
+ return ret;
+}
+
+function processAllNamespaces() {
+ var unprocessedNamespaces = !Namespace.PROCESSED,
+ unprocessedMixins = Ember.anyUnprocessedMixins;
+
+ if (unprocessedNamespaces) {
+ findNamespaces();
+ Namespace.PROCESSED = true;
+ }
+
+ if (unprocessedNamespaces || unprocessedMixins) {
+ var namespaces = Namespace.NAMESPACES, namespace;
+ for (var i=0, l=namespaces.length; i<l; i++) {
+ namespace = namespaces[i];
+ processNamespace([namespace.toString()], namespace, {});
+ }
+
+ Ember.anyUnprocessedMixins = false;
+ }
+}
+
+function makeToString(ret) {
+ return function() { return ret; };
+}
+
+Ember.Mixin.prototype.toString = classToString;
+
+})();
+
+
+
+(function() {
+/**
+@module ember
+@submodule ember-runtime
+*/
+
+var get = Ember.get,
+ set = Ember.set,
+ fmt = Ember.String.fmt,
+ addBeforeObserver = Ember.addBeforeObserver,
+ addObserver = Ember.addObserver,
+ removeBeforeObserver = Ember.removeBeforeObserver,
+ removeObserver = Ember.removeObserver,
+ propertyWillChange = Ember.propertyWillChange,
+ propertyDidChange = Ember.propertyDidChange,
+ meta = Ember.meta,
+ defineProperty = Ember.defineProperty;
+
+function contentPropertyWillChange(content, contentKey) {
+ var key = contentKey.slice(8); // remove "content."
+ if (key in this) { return; } // if shadowed in proxy
+ propertyWillChange(this, key);
+}
+
+function contentPropertyDidChange(content, contentKey) {
+ var key = contentKey.slice(8); // remove "content."
+ if (key in this) { return; } // if shadowed in proxy
+ propertyDidChange(this, key);
+}
+
+/**
+ `Ember.ObjectProxy` forwards all properties not defined by the proxy itself
+ to a proxied `content` object.
+
+ ```javascript
+ object = Ember.Object.create({
+ name: 'Foo'
+ });
+
+ proxy = Ember.ObjectProxy.create({
+ content: object
+ });
+
+ // Access and change existing properties
+ proxy.get('name') // 'Foo'
+ proxy.set('name', 'Bar');
+ object.get('name') // 'Bar'
+
+ // Create new 'description' property on `object`
+ proxy.set('description', 'Foo is a whizboo baz');
+ object.get('description') // 'Foo is a whizboo baz'
+ ```
+
+ While `content` is unset, setting a property to be delegated will throw an
+ Error.
+
+ ```javascript
+ proxy = Ember.ObjectProxy.create({
+ content: null,
+ flag: null
+ });
+ proxy.set('flag', true);
+ proxy.get('flag'); // true
+ proxy.get('foo'); // undefined
+ proxy.set('foo', 'data'); // throws Error
+ ```
+
+ Delegated properties can be bound to and will change when content is updated.
+
+ Computed properties on the proxy itself can depend on delegated properties.
+
+ ```javascript
+ ProxyWithComputedProperty = Ember.ObjectProxy.extend({
+ fullName: function () {
+ var firstName = this.get('firstName'),
+ lastName = this.get('lastName');
+ if (firstName && lastName) {
+ return firstName + ' ' + lastName;
+ }
+ return firstName || lastName;
+ }.property('firstName', 'lastName')
+ });
+
+ proxy = ProxyWithComputedProperty.create();
+
+ proxy.get('fullName'); // undefined
+ proxy.set('content', {
+ firstName: 'Tom', lastName: 'Dale'
+ }); // triggers property change for fullName on proxy
+
+ proxy.get('fullName'); // 'Tom Dale'
+ ```
+
+ @class ObjectProxy
+ @namespace Ember
+ @extends Ember.Object
+*/
+Ember.ObjectProxy = Ember.Object.extend({
+ /**
+ The object whose properties will be forwarded.
+
+ @property content
+ @type Ember.Object
+ @default null
+ */
+ content: null,
+ _contentDidChange: Ember.observer('content', function() {
+ Ember.assert("Can't set ObjectProxy's content to itself", this.get('content') !== this);
+ }),
+
+ isTruthy: Ember.computed.bool('content'),
+
+ _debugContainerKey: null,
+
+ willWatchProperty: function (key) {
+ var contentKey = 'content.' + key;
+ addBeforeObserver(this, contentKey, null, contentPropertyWillChange);
+ addObserver(this, contentKey, null, contentPropertyDidChange);
+ },
+
+ didUnwatchProperty: function (key) {
+ var contentKey = 'content.' + key;
+ removeBeforeObserver(this, contentKey, null, contentPropertyWillChange);
+ removeObserver(this, contentKey, null, contentPropertyDidChange);
+ },
+
+ unknownProperty: function (key) {
+ var content = get(this, 'content');
+ if (content) {
+ return get(content, key);
+ }
+ },
+
+ setUnknownProperty: function (key, value) {
+ var m = meta(this);
+ if (m.proto === this) {
+ // if marked as prototype then just defineProperty
+ // rather than delegate
+ defineProperty(this, key, null, value);
+ return value;
+ }
+
+ var content = get(this, 'content');
+ Ember.assert(fmt("Cannot delegate set('%@', %@) to the 'content' property of object proxy %@: its 'content' is undefined.", [key, value, this]), content);
+ return set(content, key, value);
+ }
+
+});
+
+})();
+
+
+
+(function() {
+/**
+@module ember
+@submodule ember-runtime
+*/
+
</ins><span class="cx"> // ..........................................................
</span><span class="cx"> // HELPERS
</span><span class="cx"> //
</span><span class="lines">@@ -7304,7 +13383,7 @@
</span><span class="cx"> with an `Ember.Object` subclass, you should be sure to change the length
</span><span class="cx"> property using `set().`
</span><span class="cx">
</span><del>- 2. If you must implement `nextObject().` See documentation.
</del><ins>+ 2. You must implement `nextObject().` See documentation.
</ins><span class="cx">
</span><span class="cx"> Once you have these two methods implement, apply the `Ember.Enumerable` mixin
</span><span class="cx"> to your class and you will be able to enumerate the contents of your object
</span><span class="lines">@@ -7320,15 +13399,10 @@
</span><span class="cx">
</span><span class="cx"> @class Enumerable
</span><span class="cx"> @namespace Ember
</span><del>- @extends Ember.Mixin
</del><span class="cx"> @since Ember 0.9
</span><span class="cx"> */
</span><del>-Ember.Enumerable = Ember.Mixin.create(
- /** @scope Ember.Enumerable.prototype */ {
</del><ins>+Ember.Enumerable = Ember.Mixin.create({
</ins><span class="cx">
</span><del>- // compatibility
- isEnumerable: true,
-
</del><span class="cx"> /**
</span><span class="cx"> Implement this method to make your class enumerable.
</span><span class="cx">
</span><span class="lines">@@ -7357,7 +13431,7 @@
</span><span class="cx">
</span><span class="cx"> @method nextObject
</span><span class="cx"> @param {Number} index the current index of the iteration
</span><del>- @param {Object} previousObject the value returned by the last call to
</del><ins>+ @param {Object} previousObject the value returned by the last call to
</ins><span class="cx"> `nextObject`.
</span><span class="cx"> @param {Object} context a context object you can use to maintain state.
</span><span class="cx"> @return {Object} the next object in the iteration or undefined
</span><span class="lines">@@ -7376,10 +13450,10 @@
</span><span class="cx">
</span><span class="cx"> ```javascript
</span><span class="cx"> var arr = ["a", "b", "c"];
</span><del>- arr.firstObject(); // "a"
</del><ins>+ arr.get('firstObject'); // "a"
</ins><span class="cx">
</span><span class="cx"> var arr = [];
</span><del>- arr.firstObject(); // undefined
</del><ins>+ arr.get('firstObject'); // undefined
</ins><span class="cx"> ```
</span><span class="cx">
</span><span class="cx"> @property firstObject
</span><span class="lines">@@ -7402,10 +13476,10 @@
</span><span class="cx">
</span><span class="cx"> ```javascript
</span><span class="cx"> var arr = ["a", "b", "c"];
</span><del>- arr.lastObject(); // "c"
</del><ins>+ arr.get('lastObject'); // "c"
</ins><span class="cx">
</span><span class="cx"> var arr = [];
</span><del>- arr.lastObject(); // undefined
</del><ins>+ arr.get('lastObject'); // undefined
</ins><span class="cx"> ```
</span><span class="cx">
</span><span class="cx"> @property lastObject
</span><span class="lines">@@ -7484,14 +13558,14 @@
</span><span class="cx"> },
</span><span class="cx">
</span><span class="cx"> /**
</span><del>- Alias for `mapProperty`
</del><ins>+ Alias for `mapBy`
</ins><span class="cx">
</span><span class="cx"> @method getEach
</span><span class="cx"> @param {String} key name of the property
</span><span class="cx"> @return {Array} The mapped array.
</span><span class="cx"> */
</span><span class="cx"> getEach: function(key) {
</span><del>- return this.mapProperty(key);
</del><ins>+ return this.mapBy(key);
</ins><span class="cx"> },
</span><span class="cx">
</span><span class="cx"> /**
</span><span class="lines">@@ -7538,7 +13612,7 @@
</span><span class="cx"> @return {Array} The mapped array.
</span><span class="cx"> */
</span><span class="cx"> map: function(callback, target) {
</span><del>- var ret = [];
</del><ins>+ var ret = Ember.A();
</ins><span class="cx"> this.forEach(function(x, idx, i) {
</span><span class="cx"> ret[idx] = callback.call(target, x, idx,i);
</span><span class="cx"> });
</span><span class="lines">@@ -7549,17 +13623,29 @@
</span><span class="cx"> Similar to map, this specialized function returns the value of the named
</span><span class="cx"> property on all items in the enumeration.
</span><span class="cx">
</span><del>- @method mapProperty
</del><ins>+ @method mapBy
</ins><span class="cx"> @param {String} key name of the property
</span><span class="cx"> @return {Array} The mapped array.
</span><span class="cx"> */
</span><del>- mapProperty: function(key) {
</del><ins>+ mapBy: function(key) {
</ins><span class="cx"> return this.map(function(next) {
</span><span class="cx"> return get(next, key);
</span><span class="cx"> });
</span><span class="cx"> },
</span><span class="cx">
</span><span class="cx"> /**
</span><ins>+ Similar to map, this specialized function returns the value of the named
+ property on all items in the enumeration.
+
+ @method mapProperty
+ @param {String} key name of the property
+ @return {Array} The mapped array.
+ @deprecated Use `mapBy` instead
+ */
+
+ mapProperty: Ember.aliasMethod('mapBy'),
+
+ /**
</ins><span class="cx"> Returns an array with all of the items in the enumeration that the passed
</span><span class="cx"> function returns true for. This method corresponds to `filter()` defined in
</span><span class="cx"> JavaScript 1.6.
</span><span class="lines">@@ -7588,7 +13674,7 @@
</span><span class="cx"> @return {Array} A filtered array.
</span><span class="cx"> */
</span><span class="cx"> filter: function(callback, target) {
</span><del>- var ret = [];
</del><ins>+ var ret = Ember.A();
</ins><span class="cx"> this.forEach(function(x, idx, i) {
</span><span class="cx"> if (callback.call(target, x, idx, i)) ret.push(x);
</span><span class="cx"> });
</span><span class="lines">@@ -7602,7 +13688,9 @@
</span><span class="cx"> The callback method you provide should have the following signature (all
</span><span class="cx"> parameters are optional):
</span><span class="cx">
</span><del>- function(item, index, enumerable);
</del><ins>+ ```javascript
+ function(item, index, enumerable);
+ ```
</ins><span class="cx">
</span><span class="cx"> - *item* is the current item in the iteration.
</span><span class="cx"> - *index* is the current index in the iteration
</span><span class="lines">@@ -7630,26 +13718,39 @@
</span><span class="cx"> can pass an optional second argument with the target value. Otherwise
</span><span class="cx"> this will match any property that evaluates to `true`.
</span><span class="cx">
</span><del>- @method filterProperty
</del><ins>+ @method filterBy
</ins><span class="cx"> @param {String} key the property to test
</span><span class="cx"> @param {String} [value] optional value to test against.
</span><span class="cx"> @return {Array} filtered array
</span><span class="cx"> */
</span><del>- filterProperty: function(key, value) {
</del><ins>+ filterBy: function(key, value) {
</ins><span class="cx"> return this.filter(iter.apply(this, arguments));
</span><span class="cx"> },
</span><span class="cx">
</span><span class="cx"> /**
</span><ins>+ Returns an array with just the items with the matched property. You
+ can pass an optional second argument with the target value. Otherwise
+ this will match any property that evaluates to `true`.
+
+ @method filterProperty
+ @param {String} key the property to test
+ @param {String} [value] optional value to test against.
+ @return {Array} filtered array
+ @deprecated Use `filterBy` instead
+ */
+ filterProperty: Ember.aliasMethod('filterBy'),
+
+ /**
</ins><span class="cx"> Returns an array with the items that do not have truthy values for
</span><span class="cx"> key. You can pass an optional second argument with the target value. Otherwise
</span><span class="cx"> this will match any property that evaluates to false.
</span><span class="cx">
</span><del>- @method rejectProperty
</del><ins>+ @method rejectBy
</ins><span class="cx"> @param {String} key the property to test
</span><span class="cx"> @param {String} [value] optional value to test against.
</span><span class="cx"> @return {Array} rejected array
</span><span class="cx"> */
</span><del>- rejectProperty: function(key, value) {
</del><ins>+ rejectBy: function(key, value) {
</ins><span class="cx"> var exactValue = function(item) { return get(item, key) === value; },
</span><span class="cx"> hasValue = function(item) { return !!get(item, key); },
</span><span class="cx"> use = (arguments.length === 2 ? exactValue : hasValue);
</span><span class="lines">@@ -7658,6 +13759,19 @@
</span><span class="cx"> },
</span><span class="cx">
</span><span class="cx"> /**
</span><ins>+ Returns an array with the items that do not have truthy values for
+ key. You can pass an optional second argument with the target value. Otherwise
+ this will match any property that evaluates to false.
+
+ @method rejectProperty
+ @param {String} key the property to test
+ @param {String} [value] optional value to test against.
+ @return {Array} rejected array
+ @deprecated Use `rejectBy` instead
+ */
+ rejectProperty: Ember.aliasMethod('rejectBy'),
+
+ /**
</ins><span class="cx"> Returns the first item in the array for which the callback returns true.
</span><span class="cx"> This method works similar to the `filter()` method defined in JavaScript 1.6
</span><span class="cx"> except that it will stop working on the array once a match is found.
</span><span class="lines">@@ -7708,16 +13822,31 @@
</span><span class="cx">
</span><span class="cx"> This method works much like the more generic `find()` method.
</span><span class="cx">
</span><del>- @method findProperty
</del><ins>+ @method findBy
</ins><span class="cx"> @param {String} key the property to test
</span><span class="cx"> @param {String} [value] optional value to test against.
</span><span class="cx"> @return {Object} found item or `undefined`
</span><span class="cx"> */
</span><del>- findProperty: function(key, value) {
</del><ins>+ findBy: function(key, value) {
</ins><span class="cx"> return this.find(iter.apply(this, arguments));
</span><span class="cx"> },
</span><span class="cx">
</span><span class="cx"> /**
</span><ins>+ Returns the first item with a property matching the passed value. You
+ can pass an optional second argument with the target value. Otherwise
+ this will match any property that evaluates to `true`.
+
+ This method works much like the more generic `find()` method.
+
+ @method findProperty
+ @param {String} key the property to test
+ @param {String} [value] optional value to test against.
+ @return {Object} found item or `undefined`
+ @deprecated Use `findBy` instead
+ */
+ findProperty: Ember.aliasMethod('findBy'),
+
+ /**
</ins><span class="cx"> Returns `true` if the passed function returns true for every item in the
</span><span class="cx"> enumeration. This corresponds with the `every()` method in JavaScript 1.6.
</span><span class="cx">
</span><span class="lines">@@ -7756,19 +13885,36 @@
</span><span class="cx"> },
</span><span class="cx">
</span><span class="cx"> /**
</span><ins>+ @method everyBy
+ @param {String} key the property to test
+ @param {String} [value] optional value to test against.
+ @deprecated Use `isEvery` instead
+ @return {Boolean}
+ */
+ everyBy: Ember.aliasMethod('isEvery'),
+
+ /**
+ @method everyProperty
+ @param {String} key the property to test
+ @param {String} [value] optional value to test against.
+ @deprecated Use `isEvery` instead
+ @return {Boolean}
+ */
+ everyProperty: Ember.aliasMethod('isEvery'),
+
+ /**
</ins><span class="cx"> Returns `true` if the passed property resolves to `true` for all items in
</span><span class="cx"> the enumerable. This method is often simpler/faster than using a callback.
</span><span class="cx">
</span><del>- @method everyProperty
</del><ins>+ @method isEvery
</ins><span class="cx"> @param {String} key the property to test
</span><span class="cx"> @param {String} [value] optional value to test against.
</span><span class="cx"> @return {Boolean}
</span><span class="cx"> */
</span><del>- everyProperty: function(key, value) {
</del><ins>+ isEvery: function(key, value) {
</ins><span class="cx"> return this.every(iter.apply(this, arguments));
</span><span class="cx"> },
</span><span class="cx">
</span><del>-
</del><span class="cx"> /**
</span><span class="cx"> Returns `true` if the passed function returns true for any item in the
</span><span class="cx"> enumeration. This corresponds with the `some()` method in JavaScript 1.6.
</span><span class="lines">@@ -7794,34 +13940,90 @@
</span><span class="cx"> Usage Example:
</span><span class="cx">
</span><span class="cx"> ```javascript
</span><del>- if (people.some(isManager)) { Paychecks.addBiggerBonus(); }
</del><ins>+ if (people.any(isManager)) { Paychecks.addBiggerBonus(); }
</ins><span class="cx"> ```
</span><span class="cx">
</span><del>- @method some
</del><ins>+ @method any
</ins><span class="cx"> @param {Function} callback The callback to execute
</span><span class="cx"> @param {Object} [target] The target object to use
</span><del>- @return {Array} A filtered array.
</del><ins>+ @return {Boolean} `true` if the passed function returns `true` for any item
</ins><span class="cx"> */
</span><del>- some: function(callback, target) {
- return !!this.find(function(x, idx, i) {
</del><ins>+ any: function(callback, target) {
+ var found = this.find(function(x, idx, i) {
</ins><span class="cx"> return !!callback.call(target, x, idx, i);
</span><span class="cx"> });
</span><ins>+
+ return typeof found !== 'undefined';
</ins><span class="cx"> },
</span><span class="cx">
</span><span class="cx"> /**
</span><ins>+ Returns `true` if the passed function returns true for any item in the
+ enumeration. This corresponds with the `some()` method in JavaScript 1.6.
+
+ The callback method you provide should have the following signature (all
+ parameters are optional):
+
+ ```javascript
+ function(item, index, enumerable);
+ ```
+
+ - `item` is the current item in the iteration.
+ - `index` is the current index in the iteration.
+ - `enumerable` is the enumerable object itself.
+
+ It should return the `true` to include the item in the results, `false`
+ otherwise.
+
+ Note that in addition to a callback, you can also pass an optional target
+ object that will be set as `this` on the context. This is a good way
+ to give your iterator function access to the current object.
+
+ Usage Example:
+
+ ```javascript
+ if (people.some(isManager)) { Paychecks.addBiggerBonus(); }
+ ```
+
+ @method some
+ @param {Function} callback The callback to execute
+ @param {Object} [target] The target object to use
+ @return {Boolean} `true` if the passed function returns `true` for any item
+ @deprecated Use `any` instead
+ */
+ some: Ember.aliasMethod('any'),
+
+ /**
</ins><span class="cx"> Returns `true` if the passed property resolves to `true` for any item in
</span><span class="cx"> the enumerable. This method is often simpler/faster than using a callback.
</span><span class="cx">
</span><del>- @method someProperty
</del><ins>+ @method isAny
</ins><span class="cx"> @param {String} key the property to test
</span><span class="cx"> @param {String} [value] optional value to test against.
</span><del>- @return {Boolean} `true`
</del><ins>+ @return {Boolean} `true` if the passed function returns `true` for any item
</ins><span class="cx"> */
</span><del>- someProperty: function(key, value) {
- return this.some(iter.apply(this, arguments));
</del><ins>+ isAny: function(key, value) {
+ return this.any(iter.apply(this, arguments));
</ins><span class="cx"> },
</span><span class="cx">
</span><span class="cx"> /**
</span><ins>+ @method anyBy
+ @param {String} key the property to test
+ @param {String} [value] optional value to test against.
+ @return {Boolean} `true` if the passed function returns `true` for any item
+ @deprecated Use `isAny` instead
+ */
+ anyBy: Ember.aliasMethod('isAny'),
+
+ /**
+ @method someProperty
+ @param {String} key the property to test
+ @param {String} [value] optional value to test against.
+ @return {Boolean} `true` if the passed function returns `true` for any item
+ @deprecated Use `isAny` instead
+ */
+ someProperty: Ember.aliasMethod('isAny'),
+
+ /**
</ins><span class="cx"> This will combine the values of the enumerator into a single value. It
</span><span class="cx"> is a useful way to collect a summary value from an enumeration. This
</span><span class="cx"> corresponds to the `reduce()` method defined in JavaScript 1.8.
</span><span class="lines">@@ -7877,7 +14079,7 @@
</span><span class="cx"> @return {Array} return values from calling invoke.
</span><span class="cx"> */
</span><span class="cx"> invoke: function(methodName) {
</span><del>- var args, ret = [];
</del><ins>+ var args, ret = Ember.A();
</ins><span class="cx"> if (arguments.length>1) args = a_slice.call(arguments, 1);
</span><span class="cx">
</span><span class="cx"> this.forEach(function(x, idx) {
</span><span class="lines">@@ -7898,23 +14100,25 @@
</span><span class="cx"> @return {Array} the enumerable as an array.
</span><span class="cx"> */
</span><span class="cx"> toArray: function() {
</span><del>- var ret = [];
</del><ins>+ var ret = Ember.A();
</ins><span class="cx"> this.forEach(function(o, idx) { ret[idx] = o; });
</span><span class="cx"> return ret ;
</span><span class="cx"> },
</span><span class="cx">
</span><span class="cx"> /**
</span><del>- Returns a copy of the array with all null elements removed.
</del><ins>+ Returns a copy of the array with all null and undefined elements removed.
</ins><span class="cx">
</span><span class="cx"> ```javascript
</span><del>- var arr = ["a", null, "c", null];
</del><ins>+ var arr = ["a", null, "c", undefined];
</ins><span class="cx"> arr.compact(); // ["a", "c"]
</span><span class="cx"> ```
</span><span class="cx">
</span><span class="cx"> @method compact
</span><del>- @return {Array} the array without null elements.
</del><ins>+ @return {Array} the array without null and undefined elements.
</ins><span class="cx"> */
</span><del>- compact: function() { return this.without(null); },
</del><ins>+ compact: function() {
+ return this.filter(function(value) { return value != null; });
+ },
</ins><span class="cx">
</span><span class="cx"> /**
</span><span class="cx"> Returns a new enumerable that excludes the passed value. The default
</span><span class="lines">@@ -7932,7 +14136,7 @@
</span><span class="cx"> */
</span><span class="cx"> without: function(value) {
</span><span class="cx"> if (!this.contains(value)) return this; // nothing to do
</span><del>- var ret = [] ;
</del><ins>+ var ret = Ember.A();
</ins><span class="cx"> this.forEach(function(k) {
</span><span class="cx"> if (k !== value) ret[ret.length] = k;
</span><span class="cx"> }) ;
</span><span class="lines">@@ -7952,8 +14156,8 @@
</span><span class="cx"> @return {Ember.Enumerable}
</span><span class="cx"> */
</span><span class="cx"> uniq: function() {
</span><del>- var ret = [];
- this.forEach(function(k){
</del><ins>+ var ret = Ember.A();
+ this.forEach(function(k) {
</ins><span class="cx"> if (a_indexOf(ret, k)<0) ret.push(k);
</span><span class="cx"> });
</span><span class="cx"> return ret;
</span><span class="lines">@@ -7969,6 +14173,7 @@
</span><span class="cx">
</span><span class="cx"> @property []
</span><span class="cx"> @type Ember.Array
</span><ins>+ @return this
</ins><span class="cx"> */
</span><span class="cx"> '[]': Ember.computed(function(key, value) {
</span><span class="cx"> return this;
</span><span class="lines">@@ -7983,8 +14188,9 @@
</span><span class="cx"> mixin.
</span><span class="cx">
</span><span class="cx"> @method addEnumerableObserver
</span><del>- @param target {Object}
- @param opts {Hash}
</del><ins>+ @param {Object} target
+ @param {Hash} [opts]
+ @return this
</ins><span class="cx"> */
</span><span class="cx"> addEnumerableObserver: function(target, opts) {
</span><span class="cx"> var willChange = (opts && opts.willChange) || 'enumerableWillChange',
</span><span class="lines">@@ -8002,8 +14208,9 @@
</span><span class="cx"> Removes a registered enumerable observer.
</span><span class="cx">
</span><span class="cx"> @method removeEnumerableObserver
</span><del>- @param target {Object}
- @param [opts] {Hash}
</del><ins>+ @param {Object} target
+ @param {Hash} [opts]
+ @return this
</ins><span class="cx"> */
</span><span class="cx"> removeEnumerableObserver: function(target, opts) {
</span><span class="cx"> var willChange = (opts && opts.willChange) || 'enumerableWillChange',
</span><span class="lines">@@ -8082,7 +14289,7 @@
</span><span class="cx"> @chainable
</span><span class="cx"> */
</span><span class="cx"> enumerableContentDidChange: function(removing, adding) {
</span><del>- var notify = this.propertyDidChange, removeCnt, addCnt, hasDelta;
</del><ins>+ var removeCnt, addCnt, hasDelta;
</ins><span class="cx">
</span><span class="cx"> if ('number' === typeof removing) removeCnt = removing;
</span><span class="cx"> else if (removing) removeCnt = get(removing, 'length');
</span><span class="lines">@@ -8102,10 +14309,34 @@
</span><span class="cx"> Ember.propertyDidChange(this, '[]');
</span><span class="cx">
</span><span class="cx"> return this ;
</span><ins>+ },
+
+ /**
+ Converts the enumerable into an array and sorts by the keys
+ specified in the argument.
+
+ You may provide multiple arguments to sort by multiple properties.
+
+ @method sortBy
+ @param {String} property name(s) to sort on
+ @return {Array} The sorted array.
+ */
+ sortBy: function() {
+ var sortKeys = arguments;
+ return this.toArray().sort(function(a, b){
+ for(var i = 0; i < sortKeys.length; i++) {
+ var key = sortKeys[i],
+ propA = get(a, key),
+ propB = get(b, key);
+ // return 1 or -1 else continue to the next sortKey
+ var compareValue = Ember.compare(propA, propB);
+ if (compareValue) { return compareValue; }
+ }
+ return 0;
+ });
</ins><span class="cx"> }
</span><ins>+});
</ins><span class="cx">
</span><del>-}) ;
-
</del><span class="cx"> })();
</span><span class="cx">
</span><span class="cx">
</span><span class="lines">@@ -8120,10 +14351,8 @@
</span><span class="cx"> // HELPERS
</span><span class="cx"> //
</span><span class="cx">
</span><del>-var get = Ember.get, set = Ember.set, meta = Ember.meta, map = Ember.EnumerableUtils.map, cacheFor = Ember.cacheFor;
</del><ins>+var get = Ember.get, set = Ember.set, isNone = Ember.isNone, map = Ember.EnumerableUtils.map, cacheFor = Ember.cacheFor;
</ins><span class="cx">
</span><del>-function none(obj) { return obj===null || obj===undefined; }
-
</del><span class="cx"> // ..........................................................
</span><span class="cx"> // ARRAY
</span><span class="cx"> //
</span><span class="lines">@@ -8145,7 +14374,7 @@
</span><span class="cx">
</span><span class="cx"> You can use the methods defined in this module to access and modify array
</span><span class="cx"> contents in a KVO-friendly way. You can also be notified whenever the
</span><del>- membership if an array changes by changing the syntax of the property to
</del><ins>+ membership of an array changes by changing the syntax of the property to
</ins><span class="cx"> `.observes('*myProperty.[]')`.
</span><span class="cx">
</span><span class="cx"> To support `Ember.Array` in your own class, you must override two
</span><span class="lines">@@ -8156,15 +14385,11 @@
</span><span class="cx">
</span><span class="cx"> @class Array
</span><span class="cx"> @namespace Ember
</span><del>- @extends Ember.Mixin
</del><span class="cx"> @uses Ember.Enumerable
</span><span class="cx"> @since Ember 0.9.0
</span><span class="cx"> */
</span><del>-Ember.Array = Ember.Mixin.create(Ember.Enumerable, /** @scope Ember.Array.prototype */ {
</del><ins>+Ember.Array = Ember.Mixin.create(Ember.Enumerable, {
</ins><span class="cx">
</span><del>- // compatibility
- isSCArray: true,
-
</del><span class="cx"> /**
</span><span class="cx"> Your array must support the `length` property. Your replace methods should
</span><span class="cx"> set this property whenever it changes.
</span><span class="lines">@@ -8193,6 +14418,7 @@
</span><span class="cx">
</span><span class="cx"> @method objectAt
</span><span class="cx"> @param {Number} idx The index of the item to return.
</span><ins>+ @return {*} item at index or undefined
</ins><span class="cx"> */
</span><span class="cx"> objectAt: function(idx) {
</span><span class="cx"> if ((idx < 0) || (idx>=get(this, 'length'))) return undefined ;
</span><span class="lines">@@ -8210,10 +14436,11 @@
</span><span class="cx">
</span><span class="cx"> @method objectsAt
</span><span class="cx"> @param {Array} indexes An array of indexes of items to return.
</span><ins>+ @return {Array}
</ins><span class="cx"> */
</span><span class="cx"> objectsAt: function(indexes) {
</span><span class="cx"> var self = this;
</span><del>- return map(indexes, function(idx){ return self.objectAt(idx); });
</del><ins>+ return map(indexes, function(idx) { return self.objectAt(idx); });
</ins><span class="cx"> },
</span><span class="cx">
</span><span class="cx"> // overrides Ember.Enumerable version
</span><span class="lines">@@ -8229,6 +14456,7 @@
</span><span class="cx"> This property overrides the default property defined in `Ember.Enumerable`.
</span><span class="cx">
</span><span class="cx"> @property []
</span><ins>+ @return this
</ins><span class="cx"> */
</span><span class="cx"> '[]': Ember.computed(function(key, value) {
</span><span class="cx"> if (value !== undefined) this.replace(0, get(this, 'length'), value) ;
</span><span class="lines">@@ -8244,7 +14472,7 @@
</span><span class="cx"> }),
</span><span class="cx">
</span><span class="cx"> // optimized version from Enumerable
</span><del>- contains: function(obj){
</del><ins>+ contains: function(obj) {
</ins><span class="cx"> return this.indexOf(obj) >= 0;
</span><span class="cx"> },
</span><span class="cx">
</span><span class="lines">@@ -8262,15 +14490,19 @@
</span><span class="cx"> ```
</span><span class="cx">
</span><span class="cx"> @method slice
</span><del>- @param beginIndex {Integer} (Optional) index to begin slicing from.
- @param endIndex {Integer} (Optional) index to end the slice at.
</del><ins>+ @param {Integer} beginIndex (Optional) index to begin slicing from.
+ @param {Integer} endIndex (Optional) index to end the slice at.
</ins><span class="cx"> @return {Array} New array with specified slice
</span><span class="cx"> */
</span><span class="cx"> slice: function(beginIndex, endIndex) {
</span><del>- var ret = [];
</del><ins>+ var ret = Ember.A();
</ins><span class="cx"> var length = get(this, 'length') ;
</span><del>- if (none(beginIndex)) beginIndex = 0 ;
- if (none(endIndex) || (endIndex > length)) endIndex = length ;
</del><ins>+ if (isNone(beginIndex)) beginIndex = 0 ;
+ if (isNone(endIndex) || (endIndex > length)) endIndex = length ;
+
+ if (beginIndex < 0) beginIndex = length + beginIndex;
+ if (endIndex < 0) endIndex = length + endIndex;
+
</ins><span class="cx"> while(beginIndex < endIndex) {
</span><span class="cx"> ret[ret.length] = this.objectAt(beginIndex++) ;
</span><span class="cx"> }
</span><span class="lines">@@ -8305,7 +14537,7 @@
</span><span class="cx"> if (startAt < 0) startAt += len;
</span><span class="cx">
</span><span class="cx"> for(idx=startAt;idx<len;idx++) {
</span><del>- if (this.objectAt(idx, true) === object) return idx ;
</del><ins>+ if (this.objectAt(idx) === object) return idx ;
</ins><span class="cx"> }
</span><span class="cx"> return -1;
</span><span class="cx"> },
</span><span class="lines">@@ -8351,15 +14583,15 @@
</span><span class="cx"> Adds an array observer to the receiving array. The array observer object
</span><span class="cx"> normally must implement two methods:
</span><span class="cx">
</span><del>- * `arrayWillChange(start, removeCount, addCount)` - This method will be
</del><ins>+ * `arrayWillChange(observedObj, start, removeCount, addCount)` - This method will be
</ins><span class="cx"> called just before the array is modified.
</span><del>- * `arrayDidChange(start, removeCount, addCount)` - This method will be
</del><ins>+ * `arrayDidChange(observedObj, start, removeCount, addCount)` - This method will be
</ins><span class="cx"> called just after the array is modified.
</span><span class="cx">
</span><del>- Both callbacks will be passed the starting index of the change as well a
- a count of the items to be removed and added. You can use these callbacks
- to optionally inspect the array during the change, clear caches, or do
- any other bookkeeping necessary.
</del><ins>+ Both callbacks will be passed the observed object, starting index of the
+ change as well a a count of the items to be removed and added. You can use
+ these callbacks to optionally inspect the array during the change, clear
+ caches, or do any other bookkeeping necessary.
</ins><span class="cx">
</span><span class="cx"> In addition to passing a target, you can also include an options hash
</span><span class="cx"> which you can use to override the method names that will be invoked on the
</span><span class="lines">@@ -8368,7 +14600,7 @@
</span><span class="cx"> @method addArrayObserver
</span><span class="cx"> @param {Object} target The observer object.
</span><span class="cx"> @param {Hash} opts Optional hash of configuration options including
</span><del>- `willChange`, `didChange`, and a `context` option.
</del><ins>+ `willChange` and `didChange` option.
</ins><span class="cx"> @return {Ember.Array} receiver
</span><span class="cx"> */
</span><span class="cx"> addArrayObserver: function(target, opts) {
</span><span class="lines">@@ -8390,6 +14622,8 @@
</span><span class="cx">
</span><span class="cx"> @method removeArrayObserver
</span><span class="cx"> @param {Object} target The object observing the array.
</span><ins>+ @param {Hash} opts Optional hash of configuration options including
+ `willChange` and `didChange` option.
</ins><span class="cx"> @return {Ember.Array} receiver
</span><span class="cx"> */
</span><span class="cx"> removeArrayObserver: function(target, opts) {
</span><span class="lines">@@ -8422,9 +14656,9 @@
</span><span class="cx">
</span><span class="cx"> @method arrayContentWillChange
</span><span class="cx"> @param {Number} startIdx The starting index in the array that will change.
</span><del>- @param {Number} removeAmt The number of items that will be removed. If you
</del><ins>+ @param {Number} removeAmt The number of items that will be removed. If you
</ins><span class="cx"> pass `null` assumes 0
</span><del>- @param {Number} addAmt The number of items that will be added If you
</del><ins>+ @param {Number} addAmt The number of items that will be added. If you
</ins><span class="cx"> pass `null` assumes 0.
</span><span class="cx"> @return {Ember.Array} receiver
</span><span class="cx"> */
</span><span class="lines">@@ -8458,6 +14692,20 @@
</span><span class="cx"> return this;
</span><span class="cx"> },
</span><span class="cx">
</span><ins>+ /**
+ If you are implementing an object that supports `Ember.Array`, call this
+ method just after the array content changes to notify any observers and
+ invalidate any related properties. Pass the starting index of the change
+ as well as a delta of the amounts to change.
+
+ @method arrayContentDidChange
+ @param {Number} startIdx The starting index in the array that did change.
+ @param {Number} removeAmt The number of items that were removed. If you
+ pass `null` assumes 0
+ @param {Number} addAmt The number of items that were added. If you
+ pass `null` assumes 0.
+ @return {Ember.Array} receiver
+ */
</ins><span class="cx"> arrayContentDidChange: function(startIdx, removeAmt, addAmt) {
</span><span class="cx">
</span><span class="cx"> // if no args are passed assume everything changes
</span><span class="lines">@@ -8506,6 +14754,9 @@
</span><span class="cx"> return an enumerable that maps automatically to the named key on the
</span><span class="cx"> member objects.
</span><span class="cx">
</span><ins>+ If you merely want to watch for any items being added or removed to the array,
+ use the `[]` property instead of `@each`.
+
</ins><span class="cx"> @property @each
</span><span class="cx"> */
</span><span class="cx"> '@each': Ember.computed(function() {
</span><span class="lines">@@ -8520,13 +14771,1982 @@
</span><span class="cx">
</span><span class="cx">
</span><span class="cx"> (function() {
</span><ins>+var e_get = Ember.get,
+ set = Ember.set,
+ guidFor = Ember.guidFor,
+ metaFor = Ember.meta,
+ propertyWillChange = Ember.propertyWillChange,
+ propertyDidChange = Ember.propertyDidChange,
+ addBeforeObserver = Ember.addBeforeObserver,
+ removeBeforeObserver = Ember.removeBeforeObserver,
+ addObserver = Ember.addObserver,
+ removeObserver = Ember.removeObserver,
+ ComputedProperty = Ember.ComputedProperty,
+ a_slice = [].slice,
+ o_create = Ember.create,
+ forEach = Ember.EnumerableUtils.forEach,
+ // Here we explicitly don't allow `@each.foo`; it would require some special
+ // testing, but there's no particular reason why it should be disallowed.
+ eachPropertyPattern = /^(.*)\.@each\.(.*)/,
+ doubleEachPropertyPattern = /(.*\.@each){2,}/,
+ arrayBracketPattern = /\.\[\]$/;
+
+
+function get(obj, key) {
+ if (key === '@this') {
+ return obj;
+ }
+
+ return e_get(obj, key);
+}
+
+/*
+ Tracks changes to dependent arrays, as well as to properties of items in
+ dependent arrays.
+
+ @class DependentArraysObserver
+*/
+function DependentArraysObserver(callbacks, cp, instanceMeta, context, propertyName, sugarMeta) {
+ // user specified callbacks for `addedItem` and `removedItem`
+ this.callbacks = callbacks;
+
+ // the computed property: remember these are shared across instances
+ this.cp = cp;
+
+ // the ReduceComputedPropertyInstanceMeta this DependentArraysObserver is
+ // associated with
+ this.instanceMeta = instanceMeta;
+
+ // A map of array guids to dependentKeys, for the given context. We track
+ // this because we want to set up the computed property potentially before the
+ // dependent array even exists, but when the array observer fires, we lack
+ // enough context to know what to update: we can recover that context by
+ // getting the dependentKey.
+ this.dependentKeysByGuid = {};
+
+ // a map of dependent array guids -> Ember.TrackedArray instances. We use
+ // this to lazily recompute indexes for item property observers.
+ this.trackedArraysByGuid = {};
+
+ // We suspend observers to ignore replacements from `reset` when totally
+ // recomputing. Unfortunately we cannot properly suspend the observers
+ // because we only have the key; instead we make the observers no-ops
+ this.suspended = false;
+
+ // This is used to coalesce item changes from property observers.
+ this.changedItems = {};
+}
+
+function ItemPropertyObserverContext (dependentArray, index, trackedArray) {
+ Ember.assert("Internal error: trackedArray is null or undefined", trackedArray);
+
+ this.dependentArray = dependentArray;
+ this.index = index;
+ this.item = dependentArray.objectAt(index);
+ this.trackedArray = trackedArray;
+ this.beforeObserver = null;
+ this.observer = null;
+
+ this.destroyed = false;
+}
+
+DependentArraysObserver.prototype = {
+ setValue: function (newValue) {
+ this.instanceMeta.setValue(newValue, true);
+ },
+ getValue: function () {
+ return this.instanceMeta.getValue();
+ },
+
+ setupObservers: function (dependentArray, dependentKey) {
+ Ember.assert("dependent array must be an `Ember.Array`", Ember.Array.detect(dependentArray));
+
+ this.dependentKeysByGuid[guidFor(dependentArray)] = dependentKey;
+
+ dependentArray.addArrayObserver(this, {
+ willChange: 'dependentArrayWillChange',
+ didChange: 'dependentArrayDidChange'
+ });
+
+ if (this.cp._itemPropertyKeys[dependentKey]) {
+ this.setupPropertyObservers(dependentKey, this.cp._itemPropertyKeys[dependentKey]);
+ }
+ },
+
+ teardownObservers: function (dependentArray, dependentKey) {
+ var itemPropertyKeys = this.cp._itemPropertyKeys[dependentKey] || [];
+
+ delete this.dependentKeysByGuid[guidFor(dependentArray)];
+
+ this.teardownPropertyObservers(dependentKey, itemPropertyKeys);
+
+ dependentArray.removeArrayObserver(this, {
+ willChange: 'dependentArrayWillChange',
+ didChange: 'dependentArrayDidChange'
+ });
+ },
+
+ suspendArrayObservers: function (callback, binding) {
+ var oldSuspended = this.suspended;
+ this.suspended = true;
+ callback.call(binding);
+ this.suspended = oldSuspended;
+ },
+
+ setupPropertyObservers: function (dependentKey, itemPropertyKeys) {
+ var dependentArray = get(this.instanceMeta.context, dependentKey),
+ length = get(dependentArray, 'length'),
+ observerContexts = new Array(length);
+
+ this.resetTransformations(dependentKey, observerContexts);
+
+ forEach(dependentArray, function (item, index) {
+ var observerContext = this.createPropertyObserverContext(dependentArray, index, this.trackedArraysByGuid[dependentKey]);
+ observerContexts[index] = observerContext;
+
+ forEach(itemPropertyKeys, function (propertyKey) {
+ addBeforeObserver(item, propertyKey, this, observerContext.beforeObserver);
+ addObserver(item, propertyKey, this, observerContext.observer);
+ }, this);
+ }, this);
+ },
+
+ teardownPropertyObservers: function (dependentKey, itemPropertyKeys) {
+ var dependentArrayObserver = this,
+ trackedArray = this.trackedArraysByGuid[dependentKey],
+ beforeObserver,
+ observer,
+ item;
+
+ if (!trackedArray) { return; }
+
+ trackedArray.apply(function (observerContexts, offset, operation) {
+ if (operation === Ember.TrackedArray.DELETE) { return; }
+
+ forEach(observerContexts, function (observerContext) {
+ observerContext.destroyed = true;
+ beforeObserver = observerContext.beforeObserver;
+ observer = observerContext.observer;
+ item = observerContext.item;
+
+ forEach(itemPropertyKeys, function (propertyKey) {
+ removeBeforeObserver(item, propertyKey, dependentArrayObserver, beforeObserver);
+ removeObserver(item, propertyKey, dependentArrayObserver, observer);
+ });
+ });
+ });
+ },
+
+ createPropertyObserverContext: function (dependentArray, index, trackedArray) {
+ var observerContext = new ItemPropertyObserverContext(dependentArray, index, trackedArray);
+
+ this.createPropertyObserver(observerContext);
+
+ return observerContext;
+ },
+
+ createPropertyObserver: function (observerContext) {
+ var dependentArrayObserver = this;
+
+ observerContext.beforeObserver = function (obj, keyName) {
+ return dependentArrayObserver.itemPropertyWillChange(obj, keyName, observerContext.dependentArray, observerContext);
+ };
+ observerContext.observer = function (obj, keyName) {
+ return dependentArrayObserver.itemPropertyDidChange(obj, keyName, observerContext.dependentArray, observerContext);
+ };
+ },
+
+ resetTransformations: function (dependentKey, observerContexts) {
+ this.trackedArraysByGuid[dependentKey] = new Ember.TrackedArray(observerContexts);
+ },
+
+ trackAdd: function (dependentKey, index, newItems) {
+ var trackedArray = this.trackedArraysByGuid[dependentKey];
+ if (trackedArray) {
+ trackedArray.addItems(index, newItems);
+ }
+ },
+
+ trackRemove: function (dependentKey, index, removedCount) {
+ var trackedArray = this.trackedArraysByGuid[dependentKey];
+
+ if (trackedArray) {
+ return trackedArray.removeItems(index, removedCount);
+ }
+
+ return [];
+ },
+
+ updateIndexes: function (trackedArray, array) {
+ var length = get(array, 'length');
+ // OPTIMIZE: we could stop updating once we hit the object whose observer
+ // fired; ie partially apply the transformations
+ trackedArray.apply(function (observerContexts, offset, operation) {
+ // we don't even have observer contexts for removed items, even if we did,
+ // they no longer have any index in the array
+ if (operation === Ember.TrackedArray.DELETE) { return; }
+ if (operation === Ember.TrackedArray.RETAIN && observerContexts.length === length && offset === 0) {
+ // If we update many items we don't want to walk the array each time: we
+ // only need to update the indexes at most once per run loop.
+ return;
+ }
+
+ forEach(observerContexts, function (context, index) {
+ context.index = index + offset;
+ });
+ });
+ },
+
+ dependentArrayWillChange: function (dependentArray, index, removedCount, addedCount) {
+ if (this.suspended) { return; }
+
+ var removedItem = this.callbacks.removedItem,
+ changeMeta,
+ guid = guidFor(dependentArray),
+ dependentKey = this.dependentKeysByGuid[guid],
+ itemPropertyKeys = this.cp._itemPropertyKeys[dependentKey] || [],
+ length = get(dependentArray, 'length'),
+ normalizedIndex = normalizeIndex(index, length, 0),
+ normalizedRemoveCount = normalizeRemoveCount(normalizedIndex, length, removedCount),
+ item,
+ itemIndex,
+ sliceIndex,
+ observerContexts;
+
+ observerContexts = this.trackRemove(dependentKey, normalizedIndex, normalizedRemoveCount);
+
+ function removeObservers(propertyKey) {
+ observerContexts[sliceIndex].destroyed = true;
+ removeBeforeObserver(item, propertyKey, this, observerContexts[sliceIndex].beforeObserver);
+ removeObserver(item, propertyKey, this, observerContexts[sliceIndex].observer);
+ }
+
+ for (sliceIndex = normalizedRemoveCount - 1; sliceIndex >= 0; --sliceIndex) {
+ itemIndex = normalizedIndex + sliceIndex;
+ if (itemIndex >= length) { break; }
+
+ item = dependentArray.objectAt(itemIndex);
+
+ forEach(itemPropertyKeys, removeObservers, this);
+
+ changeMeta = createChangeMeta(dependentArray, item, itemIndex, this.instanceMeta.propertyName, this.cp);
+ this.setValue( removedItem.call(
+ this.instanceMeta.context, this.getValue(), item, changeMeta, this.instanceMeta.sugarMeta));
+ }
+ },
+
+ dependentArrayDidChange: function (dependentArray, index, removedCount, addedCount) {
+ if (this.suspended) { return; }
+
+ var addedItem = this.callbacks.addedItem,
+ guid = guidFor(dependentArray),
+ dependentKey = this.dependentKeysByGuid[guid],
+ observerContexts = new Array(addedCount),
+ itemPropertyKeys = this.cp._itemPropertyKeys[dependentKey],
+ length = get(dependentArray, 'length'),
+ normalizedIndex = normalizeIndex(index, length, addedCount),
+ changeMeta,
+ observerContext;
+
+ forEach(dependentArray.slice(normalizedIndex, normalizedIndex + addedCount), function (item, sliceIndex) {
+ if (itemPropertyKeys) {
+ observerContext =
+ observerContexts[sliceIndex] =
+ this.createPropertyObserverContext(dependentArray, normalizedIndex + sliceIndex, this.trackedArraysByGuid[dependentKey]);
+ forEach(itemPropertyKeys, function (propertyKey) {
+ addBeforeObserver(item, propertyKey, this, observerContext.beforeObserver);
+ addObserver(item, propertyKey, this, observerContext.observer);
+ }, this);
+ }
+
+ changeMeta = createChangeMeta(dependentArray, item, normalizedIndex + sliceIndex, this.instanceMeta.propertyName, this.cp);
+ this.setValue( addedItem.call(
+ this.instanceMeta.context, this.getValue(), item, changeMeta, this.instanceMeta.sugarMeta));
+ }, this);
+
+ this.trackAdd(dependentKey, normalizedIndex, observerContexts);
+ },
+
+ itemPropertyWillChange: function (obj, keyName, array, observerContext) {
+ var guid = guidFor(obj);
+
+ if (!this.changedItems[guid]) {
+ this.changedItems[guid] = {
+ array: array,
+ observerContext: observerContext,
+ obj: obj,
+ previousValues: {}
+ };
+ }
+
+ this.changedItems[guid].previousValues[keyName] = get(obj, keyName);
+ },
+
+ itemPropertyDidChange: function(obj, keyName, array, observerContext) {
+ this.flushChanges();
+ },
+
+ flushChanges: function() {
+ var changedItems = this.changedItems, key, c, changeMeta;
+
+ for (key in changedItems) {
+ c = changedItems[key];
+ if (c.observerContext.destroyed) { continue; }
+
+ this.updateIndexes(c.observerContext.trackedArray, c.observerContext.dependentArray);
+
+ changeMeta = createChangeMeta(c.array, c.obj, c.observerContext.index, this.instanceMeta.propertyName, this.cp, c.previousValues);
+ this.setValue(
+ this.callbacks.removedItem.call(this.instanceMeta.context, this.getValue(), c.obj, changeMeta, this.instanceMeta.sugarMeta));
+ this.setValue(
+ this.callbacks.addedItem.call(this.instanceMeta.context, this.getValue(), c.obj, changeMeta, this.instanceMeta.sugarMeta));
+ }
+ this.changedItems = {};
+ }
+};
+
+function normalizeIndex(index, length, newItemsOffset) {
+ if (index < 0) {
+ return Math.max(0, length + index);
+ } else if (index < length) {
+ return index;
+ } else /* index > length */ {
+ return Math.min(length - newItemsOffset, index);
+ }
+}
+
+function normalizeRemoveCount(index, length, removedCount) {
+ return Math.min(removedCount, length - index);
+}
+
+function createChangeMeta(dependentArray, item, index, propertyName, property, previousValues) {
+ var meta = {
+ arrayChanged: dependentArray,
+ index: index,
+ item: item,
+ propertyName: propertyName,
+ property: property
+ };
+
+ if (previousValues) {
+ // previous values only available for item property changes
+ meta.previousValues = previousValues;
+ }
+
+ return meta;
+}
+
+function addItems (dependentArray, callbacks, cp, propertyName, meta) {
+ forEach(dependentArray, function (item, index) {
+ meta.setValue( callbacks.addedItem.call(
+ this, meta.getValue(), item, createChangeMeta(dependentArray, item, index, propertyName, cp), meta.sugarMeta));
+ }, this);
+}
+
+function reset(cp, propertyName) {
+ var callbacks = cp._callbacks(),
+ meta;
+
+ if (cp._hasInstanceMeta(this, propertyName)) {
+ meta = cp._instanceMeta(this, propertyName);
+ meta.setValue(cp.resetValue(meta.getValue()));
+ } else {
+ meta = cp._instanceMeta(this, propertyName);
+ }
+
+ if (cp.options.initialize) {
+ cp.options.initialize.call(this, meta.getValue(), { property: cp, propertyName: propertyName }, meta.sugarMeta);
+ }
+}
+
+function partiallyRecomputeFor(obj, dependentKey) {
+ if (arrayBracketPattern.test(dependentKey)) {
+ return false;
+ }
+
+ var value = get(obj, dependentKey);
+ return Ember.Array.detect(value);
+}
+
+function ReduceComputedPropertyInstanceMeta(context, propertyName, initialValue) {
+ this.context = context;
+ this.propertyName = propertyName;
+ this.cache = metaFor(context).cache;
+
+ this.dependentArrays = {};
+ this.sugarMeta = {};
+
+ this.initialValue = initialValue;
+}
+
+ReduceComputedPropertyInstanceMeta.prototype = {
+ getValue: function () {
+ if (this.propertyName in this.cache) {
+ return this.cache[this.propertyName];
+ } else {
+ return this.initialValue;
+ }
+ },
+
+ setValue: function(newValue, triggerObservers) {
+ // This lets sugars force a recomputation, handy for very simple
+ // implementations of eg max.
+ if (newValue !== undefined) {
+ var fireObservers = triggerObservers && (newValue !== this.cache[this.propertyName]);
+
+ if (fireObservers) {
+ propertyWillChange(this.context, this.propertyName);
+ }
+
+ this.cache[this.propertyName] = newValue;
+
+ if (fireObservers) {
+ propertyDidChange(this.context, this.propertyName);
+ }
+ } else {
+ delete this.cache[this.propertyName];
+ }
+ }
+};
+
</ins><span class="cx"> /**
</span><ins>+ A computed property whose dependent keys are arrays and which is updated with
+ "one at a time" semantics.
+
+ @class ReduceComputedProperty
+ @namespace Ember
+ @extends Ember.ComputedProperty
+ @constructor
+*/
+function ReduceComputedProperty(options) {
+ var cp = this;
+
+ this.options = options;
+ this._instanceMetas = {};
+
+ this._dependentKeys = null;
+ // A map of dependentKey -> [itemProperty, ...] that tracks what properties of
+ // items in the array we must track to update this property.
+ this._itemPropertyKeys = {};
+ this._previousItemPropertyKeys = {};
+
+ this.readOnly();
+ this.cacheable();
+
+ this.recomputeOnce = function(propertyName) {
+ // What we really want to do is coalesce by <cp, propertyName>.
+ // We need a form of `scheduleOnce` that accepts an arbitrary token to
+ // coalesce by, in addition to the target and method.
+ Ember.run.once(this, recompute, propertyName);
+ };
+ var recompute = function(propertyName) {
+ var dependentKeys = cp._dependentKeys,
+ meta = cp._instanceMeta(this, propertyName),
+ callbacks = cp._callbacks();
+
+ reset.call(this, cp, propertyName);
+
+ meta.dependentArraysObserver.suspendArrayObservers(function () {
+ forEach(cp._dependentKeys, function (dependentKey) {
+ if (!partiallyRecomputeFor(this, dependentKey)) { return; }
+
+ var dependentArray = get(this, dependentKey),
+ previousDependentArray = meta.dependentArrays[dependentKey];
+
+ if (dependentArray === previousDependentArray) {
+ // The array may be the same, but our item property keys may have
+ // changed, so we set them up again. We can't easily tell if they've
+ // changed: the array may be the same object, but with different
+ // contents.
+ if (cp._previousItemPropertyKeys[dependentKey]) {
+ delete cp._previousItemPropertyKeys[dependentKey];
+ meta.dependentArraysObserver.setupPropertyObservers(dependentKey, cp._itemPropertyKeys[dependentKey]);
+ }
+ } else {
+ meta.dependentArrays[dependentKey] = dependentArray;
+
+ if (previousDependentArray) {
+ meta.dependentArraysObserver.teardownObservers(previousDependentArray, dependentKey);
+ }
+
+ if (dependentArray) {
+ meta.dependentArraysObserver.setupObservers(dependentArray, dependentKey);
+ }
+ }
+ }, this);
+ }, this);
+
+ forEach(cp._dependentKeys, function(dependentKey) {
+ if (!partiallyRecomputeFor(this, dependentKey)) { return; }
+
+ var dependentArray = get(this, dependentKey);
+ if (dependentArray) {
+ addItems.call(this, dependentArray, callbacks, cp, propertyName, meta);
+ }
+ }, this);
+ };
+
+ this.func = function (propertyName) {
+ Ember.assert("Computed reduce values require at least one dependent key", cp._dependentKeys);
+
+ recompute.call(this, propertyName);
+
+ return cp._instanceMeta(this, propertyName).getValue();
+ };
+}
+
+Ember.ReduceComputedProperty = ReduceComputedProperty;
+ReduceComputedProperty.prototype = o_create(ComputedProperty.prototype);
+
+function defaultCallback(computedValue) {
+ return computedValue;
+}
+
+ReduceComputedProperty.prototype._callbacks = function () {
+ if (!this.callbacks) {
+ var options = this.options;
+ this.callbacks = {
+ removedItem: options.removedItem || defaultCallback,
+ addedItem: options.addedItem || defaultCallback
+ };
+ }
+ return this.callbacks;
+};
+
+ReduceComputedProperty.prototype._hasInstanceMeta = function (context, propertyName) {
+ var guid = guidFor(context),
+ key = guid + ':' + propertyName;
+
+ return !!this._instanceMetas[key];
+};
+
+ReduceComputedProperty.prototype._instanceMeta = function (context, propertyName) {
+ var guid = guidFor(context),
+ key = guid + ':' + propertyName,
+ meta = this._instanceMetas[key];
+
+ if (!meta) {
+ meta = this._instanceMetas[key] = new ReduceComputedPropertyInstanceMeta(context, propertyName, this.initialValue());
+ meta.dependentArraysObserver = new DependentArraysObserver(this._callbacks(), this, meta, context, propertyName, meta.sugarMeta);
+ }
+
+ return meta;
+};
+
+ReduceComputedProperty.prototype.initialValue = function () {
+ if (typeof this.options.initialValue === 'function') {
+ return this.options.initialValue();
+ }
+ else {
+ return this.options.initialValue;
+ }
+};
+
+ReduceComputedProperty.prototype.resetValue = function (value) {
+ return this.initialValue();
+};
+
+ReduceComputedProperty.prototype.itemPropertyKey = function (dependentArrayKey, itemPropertyKey) {
+ this._itemPropertyKeys[dependentArrayKey] = this._itemPropertyKeys[dependentArrayKey] || [];
+ this._itemPropertyKeys[dependentArrayKey].push(itemPropertyKey);
+};
+
+ReduceComputedProperty.prototype.clearItemPropertyKeys = function (dependentArrayKey) {
+ if (this._itemPropertyKeys[dependentArrayKey]) {
+ this._previousItemPropertyKeys[dependentArrayKey] = this._itemPropertyKeys[dependentArrayKey];
+ this._itemPropertyKeys[dependentArrayKey] = [];
+ }
+};
+
+ReduceComputedProperty.prototype.property = function () {
+ var cp = this,
+ args = a_slice.call(arguments),
+ propertyArgs = new Ember.Set(),
+ match,
+ dependentArrayKey,
+ itemPropertyKey;
+
+ forEach(a_slice.call(arguments), function (dependentKey) {
+ if (doubleEachPropertyPattern.test(dependentKey)) {
+ throw new Ember.Error("Nested @each properties not supported: " + dependentKey);
+ } else if (match = eachPropertyPattern.exec(dependentKey)) {
+ dependentArrayKey = match[1];
+
+
+ itemPropertyKey = match[2];
+ cp.itemPropertyKey(dependentArrayKey, itemPropertyKey);
+
+ propertyArgs.add(dependentArrayKey);
+ } else {
+ propertyArgs.add(dependentKey);
+ }
+ });
+
+ return ComputedProperty.prototype.property.apply(this, propertyArgs.toArray());
+
+};
+
+/**
+ Creates a computed property which operates on dependent arrays and
+ is updated with "one at a time" semantics. When items are added or
+ removed from the dependent array(s) a reduce computed only operates
+ on the change instead of re-evaluating the entire array.
+
+ If there are more than one arguments the first arguments are
+ considered to be dependent property keys. The last argument is
+ required to be an options object. The options object can have the
+ following four properties:
+
+ `initialValue` - A value or function that will be used as the initial
+ value for the computed. If this property is a function the result of calling
+ the function will be used as the initial value. This property is required.
+
+ `initialize` - An optional initialize function. Typically this will be used
+ to set up state on the instanceMeta object.
+
+ `removedItem` - A function that is called each time an element is removed
+ from the array.
+
+ `addedItem` - A function that is called each time an element is added to
+ the array.
+
+
+ The `initialize` function has the following signature:
+
+ ```javascript
+ function (initialValue, changeMeta, instanceMeta)
+ ```
+
+ `initialValue` - The value of the `initialValue` property from the
+ options object.
+
+ `changeMeta` - An object which contains meta information about the
+ computed. It contains the following properties:
+
+ - `property` the computed property
+ - `propertyName` the name of the property on the object
+
+ `instanceMeta` - An object that can be used to store meta
+ information needed for calculating your computed. For example a
+ unique computed might use this to store the number of times a given
+ element is found in the dependent array.
+
+
+ The `removedItem` and `addedItem` functions both have the following signature:
+
+ ```javascript
+ function (accumulatedValue, item, changeMeta, instanceMeta)
+ ```
+
+ `accumulatedValue` - The value returned from the last time
+ `removedItem` or `addedItem` was called or `initialValue`.
+
+ `item` - the element added or removed from the array
+
+ `changeMeta` - An object which contains meta information about the
+ change. It contains the following properties:
+
+ - `property` the computed property
+ - `propertyName` the name of the property on the object
+ - `index` the index of the added or removed item
+ - `item` the added or removed item: this is exactly the same as
+ the second arg
+ - `arrayChanged` the array that triggered the change. Can be
+ useful when depending on multiple arrays.
+
+ For property changes triggered on an item property change (when
+ depKey is something like `someArray.@each.someProperty`),
+ `changeMeta` will also contain the following property:
+
+ - `previousValues` an object whose keys are the properties that changed on
+ the item, and whose values are the item's previous values.
+
+ `previousValues` is important Ember coalesces item property changes via
+ Ember.run.once. This means that by the time removedItem gets called, item has
+ the new values, but you may need the previous value (eg for sorting &
+ filtering).
+
+ `instanceMeta` - An object that can be used to store meta
+ information needed for calculating your computed. For example a
+ unique computed might use this to store the number of times a given
+ element is found in the dependent array.
+
+ The `removedItem` and `addedItem` functions should return the accumulated
+ value. It is acceptable to not return anything (ie return undefined)
+ to invalidate the computation. This is generally not a good idea for
+ arrayComputed but it's used in eg max and min.
+
+ Note that observers will be fired if either of these functions return a value
+ that differs from the accumulated value. When returning an object that
+ mutates in response to array changes, for example an array that maps
+ everything from some other array (see `Ember.computed.map`), it is usually
+ important that the *same* array be returned to avoid accidentally triggering observers.
+
+ Example
+
+ ```javascript
+ Ember.computed.max = function (dependentKey) {
+ return Ember.reduceComputed.call(null, dependentKey, {
+ initialValue: -Infinity,
+
+ addedItem: function (accumulatedValue, item, changeMeta, instanceMeta) {
+ return Math.max(accumulatedValue, item);
+ },
+
+ removedItem: function (accumulatedValue, item, changeMeta, instanceMeta) {
+ if (item < accumulatedValue) {
+ return accumulatedValue;
+ }
+ }
+ });
+ };
+ ```
+
+ Dependent keys may refer to `@this` to observe changes to the object itself,
+ which must be array-like, rather than a property of the object. This is
+ mostly useful for array proxies, to ensure objects are retrieved via
+ `objectAtContent`. This is how you could sort items by properties defined on an item controller.
+
+ Example
+
+ ```javascript
+ App.PeopleController = Ember.ArrayController.extend({
+ itemController: 'person',
+
+ sortedPeople: Ember.computed.sort('@this.@each.reversedName', function(personA, personB) {
+ // `reversedName` isn't defined on Person, but we have access to it via
+ // the item controller App.PersonController. If we'd used
+ // `content.@each.reversedName` above, we would be getting the objects
+ // directly and not have access to `reversedName`.
+ //
+ var reversedNameA = get(personA, 'reversedName'),
+ reversedNameB = get(personB, 'reversedName');
+
+ return Ember.compare(reversedNameA, reversedNameB);
+ })
+ });
+
+ App.PersonController = Ember.ObjectController.extend({
+ reversedName: function () {
+ return reverse(get(this, 'name'));
+ }.property('name')
+ })
+ ```
+
+ Dependent keys whose values are not arrays are treated as regular
+ dependencies: when they change, the computed property is completely
+ recalculated. It is sometimes useful to have dependent arrays with similar
+ semantics. Dependent keys which end in `.[]` do not use "one at a time"
+ semantics. When an item is added or removed from such a dependency, the
+ computed property is completely recomputed.
+
+ Example
+
+ ```javascript
+ Ember.Object.extend({
+ // When `string` is changed, `computed` is completely recomputed.
+ string: 'a string',
+
+ // When an item is added to `array`, `addedItem` is called.
+ array: [],
+
+ // When an item is added to `anotherArray`, `computed` is completely
+ // recomputed.
+ anotherArray: [],
+
+ computed: Ember.reduceComputed('string', 'array', 'anotherArray.[]', {
+ addedItem: addedItemCallback,
+ removedItem: removedItemCallback
+ })
+ });
+ ```
+
+ @method reduceComputed
+ @for Ember
+ @param {String} [dependentKeys*]
+ @param {Object} options
+ @return {Ember.ComputedProperty}
+*/
+Ember.reduceComputed = function (options) {
+ var args;
+
+ if (arguments.length > 1) {
+ args = a_slice.call(arguments, 0, -1);
+ options = a_slice.call(arguments, -1)[0];
+ }
+
+ if (typeof options !== "object") {
+ throw new Ember.Error("Reduce Computed Property declared without an options hash");
+ }
+
+ if (!('initialValue' in options)) {
+ throw new Ember.Error("Reduce Computed Property declared without an initial value");
+ }
+
+ var cp = new ReduceComputedProperty(options);
+
+ if (args) {
+ cp.property.apply(cp, args);
+ }
+
+ return cp;
+};
+
+})();
+
+
+
+(function() {
+var ReduceComputedProperty = Ember.ReduceComputedProperty,
+ a_slice = [].slice,
+ o_create = Ember.create,
+ forEach = Ember.EnumerableUtils.forEach;
+
+function ArrayComputedProperty() {
+ var cp = this;
+
+ ReduceComputedProperty.apply(this, arguments);
+
+ this.func = (function(reduceFunc) {
+ return function (propertyName) {
+ if (!cp._hasInstanceMeta(this, propertyName)) {
+ // When we recompute an array computed property, we need already
+ // retrieved arrays to be updated; we can't simply empty the cache and
+ // hope the array is re-retrieved.
+ forEach(cp._dependentKeys, function(dependentKey) {
+ Ember.addObserver(this, dependentKey, function() {
+ cp.recomputeOnce.call(this, propertyName);
+ });
+ }, this);
+ }
+
+ return reduceFunc.apply(this, arguments);
+ };
+ })(this.func);
+
+ return this;
+}
+Ember.ArrayComputedProperty = ArrayComputedProperty;
+ArrayComputedProperty.prototype = o_create(ReduceComputedProperty.prototype);
+ArrayComputedProperty.prototype.initialValue = function () {
+ return Ember.A();
+};
+ArrayComputedProperty.prototype.resetValue = function (array) {
+ array.clear();
+ return array;
+};
+
+// This is a stopgap to keep the reference counts correct with lazy CPs.
+ArrayComputedProperty.prototype.didChange = function (obj, keyName) {
+ return;
+};
+
+/**
+ Creates a computed property which operates on dependent arrays and
+ is updated with "one at a time" semantics. When items are added or
+ removed from the dependent array(s) an array computed only operates
+ on the change instead of re-evaluating the entire array. This should
+ return an array, if you'd like to use "one at a time" semantics and
+ compute some value other then an array look at
+ `Ember.reduceComputed`.
+
+ If there are more than one arguments the first arguments are
+ considered to be dependent property keys. The last argument is
+ required to be an options object. The options object can have the
+ following three properties.
+
+ `initialize` - An optional initialize function. Typically this will be used
+ to set up state on the instanceMeta object.
+
+ `removedItem` - A function that is called each time an element is
+ removed from the array.
+
+ `addedItem` - A function that is called each time an element is
+ added to the array.
+
+
+ The `initialize` function has the following signature:
+
+ ```javascript
+ function (array, changeMeta, instanceMeta)
+ ```
+
+ `array` - The initial value of the arrayComputed, an empty array.
+
+ `changeMeta` - An object which contains meta information about the
+ computed. It contains the following properties:
+
+ - `property` the computed property
+ - `propertyName` the name of the property on the object
+
+ `instanceMeta` - An object that can be used to store meta
+ information needed for calculating your computed. For example a
+ unique computed might use this to store the number of times a given
+ element is found in the dependent array.
+
+
+ The `removedItem` and `addedItem` functions both have the following signature:
+
+ ```javascript
+ function (accumulatedValue, item, changeMeta, instanceMeta)
+ ```
+
+ `accumulatedValue` - The value returned from the last time
+ `removedItem` or `addedItem` was called or an empty array.
+
+ `item` - the element added or removed from the array
+
+ `changeMeta` - An object which contains meta information about the
+ change. It contains the following properties:
+
+ - `property` the computed property
+ - `propertyName` the name of the property on the object
+ - `index` the index of the added or removed item
+ - `item` the added or removed item: this is exactly the same as
+ the second arg
+ - `arrayChanged` the array that triggered the change. Can be
+ useful when depending on multiple arrays.
+
+ For property changes triggered on an item property change (when
+ depKey is something like `someArray.@each.someProperty`),
+ `changeMeta` will also contain the following property:
+
+ - `previousValues` an object whose keys are the properties that changed on
+ the item, and whose values are the item's previous values.
+
+ `previousValues` is important Ember coalesces item property changes via
+ Ember.run.once. This means that by the time removedItem gets called, item has
+ the new values, but you may need the previous value (eg for sorting &
+ filtering).
+
+ `instanceMeta` - An object that can be used to store meta
+ information needed for calculating your computed. For example a
+ unique computed might use this to store the number of times a given
+ element is found in the dependent array.
+
+ The `removedItem` and `addedItem` functions should return the accumulated
+ value. It is acceptable to not return anything (ie return undefined)
+ to invalidate the computation. This is generally not a good idea for
+ arrayComputed but it's used in eg max and min.
+
+ Example
+
+ ```javascript
+ Ember.computed.map = function(dependentKey, callback) {
+ var options = {
+ addedItem: function(array, item, changeMeta, instanceMeta) {
+ var mapped = callback(item);
+ array.insertAt(changeMeta.index, mapped);
+ return array;
+ },
+ removedItem: function(array, item, changeMeta, instanceMeta) {
+ array.removeAt(changeMeta.index, 1);
+ return array;
+ }
+ };
+
+ return Ember.arrayComputed(dependentKey, options);
+ };
+ ```
+
+ @method arrayComputed
+ @for Ember
+ @param {String} [dependentKeys*]
+ @param {Object} options
+ @return {Ember.ComputedProperty}
+*/
+Ember.arrayComputed = function (options) {
+ var args;
+
+ if (arguments.length > 1) {
+ args = a_slice.call(arguments, 0, -1);
+ options = a_slice.call(arguments, -1)[0];
+ }
+
+ if (typeof options !== "object") {
+ throw new Ember.Error("Array Computed Property declared without an options hash");
+ }
+
+ var cp = new ArrayComputedProperty(options);
+
+ if (args) {
+ cp.property.apply(cp, args);
+ }
+
+ return cp;
+};
+
+})();
+
+
+
+(function() {
+/**
</ins><span class="cx"> @module ember
</span><span class="cx"> @submodule ember-runtime
</span><span class="cx"> */
</span><span class="cx">
</span><ins>+var get = Ember.get,
+ set = Ember.set,
+ guidFor = Ember.guidFor,
+ merge = Ember.merge,
+ a_slice = [].slice,
+ forEach = Ember.EnumerableUtils.forEach,
+ map = Ember.EnumerableUtils.map,
+ SearchProxy;
</ins><span class="cx">
</span><span class="cx"> /**
</span><ins>+ A computed property that calculates the maximum value in the
+ dependent array. This will return `-Infinity` when the dependent
+ array is empty.
+
+ ```javascript
+ App.Person = Ember.Object.extend({
+ childAges: Ember.computed.mapBy('children', 'age'),
+ maxChildAge: Ember.computed.max('childAges')
+ });
+
+ var lordByron = App.Person.create({children: []});
+ lordByron.get('maxChildAge'); // -Infinity
+ lordByron.get('children').pushObject({
+ name: 'Augusta Ada Byron', age: 7
+ });
+ lordByron.get('maxChildAge'); // 7
+ lordByron.get('children').pushObjects([{
+ name: 'Allegra Byron',
+ age: 5
+ }, {
+ name: 'Elizabeth Medora Leigh',
+ age: 8
+ }]);
+ lordByron.get('maxChildAge'); // 8
+ ```
+
+ @method computed.max
+ @for Ember
+ @param {String} dependentKey
+ @return {Ember.ComputedProperty} computes the largest value in the dependentKey's array
+*/
+Ember.computed.max = function (dependentKey) {
+ return Ember.reduceComputed.call(null, dependentKey, {
+ initialValue: -Infinity,
+
+ addedItem: function (accumulatedValue, item, changeMeta, instanceMeta) {
+ return Math.max(accumulatedValue, item);
+ },
+
+ removedItem: function (accumulatedValue, item, changeMeta, instanceMeta) {
+ if (item < accumulatedValue) {
+ return accumulatedValue;
+ }
+ }
+ });
+};
+
+/**
+ A computed property that calculates the minimum value in the
+ dependent array. This will return `Infinity` when the dependent
+ array is empty.
+
+ ```javascript
+ App.Person = Ember.Object.extend({
+ childAges: Ember.computed.mapBy('children', 'age'),
+ minChildAge: Ember.computed.min('childAges')
+ });
+
+ var lordByron = App.Person.create({children: []});
+ lordByron.get('minChildAge'); // Infinity
+ lordByron.get('children').pushObject({
+ name: 'Augusta Ada Byron', age: 7
+ });
+ lordByron.get('minChildAge'); // 7
+ lordByron.get('children').pushObjects([{
+ name: 'Allegra Byron',
+ age: 5
+ }, {
+ name: 'Elizabeth Medora Leigh',
+ age: 8
+ }]);
+ lordByron.get('minChildAge'); // 5
+ ```
+
+ @method computed.min
+ @for Ember
+ @param {String} dependentKey
+ @return {Ember.ComputedProperty} computes the smallest value in the dependentKey's array
+*/
+Ember.computed.min = function (dependentKey) {
+ return Ember.reduceComputed.call(null, dependentKey, {
+ initialValue: Infinity,
+
+ addedItem: function (accumulatedValue, item, changeMeta, instanceMeta) {
+ return Math.min(accumulatedValue, item);
+ },
+
+ removedItem: function (accumulatedValue, item, changeMeta, instanceMeta) {
+ if (item > accumulatedValue) {
+ return accumulatedValue;
+ }
+ }
+ });
+};
+
+/**
+ Returns an array mapped via the callback
+
+ The callback method you provide should have the following signature.
+ `item` is the current item in the iteration.
+
+ ```javascript
+ function(item);
+ ```
+
+ Example
+
+ ```javascript
+ App.Hamster = Ember.Object.extend({
+ excitingChores: Ember.computed.map('chores', function(chore) {
+ return chore.toUpperCase() + '!';
+ })
+ });
+
+ var hamster = App.Hamster.create({
+ chores: ['clean', 'write more unit tests']
+ });
+ hamster.get('excitingChores'); // ['CLEAN!', 'WRITE MORE UNIT TESTS!']
+ ```
+
+ @method computed.map
+ @for Ember
+ @param {String} dependentKey
+ @param {Function} callback
+ @return {Ember.ComputedProperty} an array mapped via the callback
+*/
+Ember.computed.map = function(dependentKey, callback) {
+ var options = {
+ addedItem: function(array, item, changeMeta, instanceMeta) {
+ var mapped = callback.call(this, item);
+ array.insertAt(changeMeta.index, mapped);
+ return array;
+ },
+ removedItem: function(array, item, changeMeta, instanceMeta) {
+ array.removeAt(changeMeta.index, 1);
+ return array;
+ }
+ };
+
+ return Ember.arrayComputed(dependentKey, options);
+};
+
+/**
+ Returns an array mapped to the specified key.
+
+ ```javascript
+ App.Person = Ember.Object.extend({
+ childAges: Ember.computed.mapBy('children', 'age')
+ });
+
+ var lordByron = App.Person.create({children: []});
+ lordByron.get('childAges'); // []
+ lordByron.get('children').pushObject({name: 'Augusta Ada Byron', age: 7});
+ lordByron.get('childAges'); // [7]
+ lordByron.get('children').pushObjects([{
+ name: 'Allegra Byron',
+ age: 5
+ }, {
+ name: 'Elizabeth Medora Leigh',
+ age: 8
+ }]);
+ lordByron.get('childAges'); // [7, 5, 8]
+ ```
+
+ @method computed.mapBy
+ @for Ember
+ @param {String} dependentKey
+ @param {String} propertyKey
+ @return {Ember.ComputedProperty} an array mapped to the specified key
+*/
+Ember.computed.mapBy = function(dependentKey, propertyKey) {
+ var callback = function(item) { return get(item, propertyKey); };
+ return Ember.computed.map(dependentKey + '.@each.' + propertyKey, callback);
+};
+
+/**
+ @method computed.mapProperty
+ @for Ember
+ @deprecated Use `Ember.computed.mapBy` instead
+ @param dependentKey
+ @param propertyKey
+*/
+Ember.computed.mapProperty = Ember.computed.mapBy;
+
+/**
+ Filters the array by the callback.
+
+ The callback method you provide should have the following signature.
+ `item` is the current item in the iteration.
+
+ ```javascript
+ function(item);
+ ```
+
+ ```javascript
+ App.Hamster = Ember.Object.extend({
+ remainingChores: Ember.computed.filter('chores', function(chore) {
+ return !chore.done;
+ })
+ });
+
+ var hamster = App.Hamster.create({chores: [
+ {name: 'cook', done: true},
+ {name: 'clean', done: true},
+ {name: 'write more unit tests', done: false}
+ ]});
+ hamster.get('remainingChores'); // [{name: 'write more unit tests', done: false}]
+ ```
+
+ @method computed.filter
+ @for Ember
+ @param {String} dependentKey
+ @param {Function} callback
+ @return {Ember.ComputedProperty} the filtered array
+*/
+Ember.computed.filter = function(dependentKey, callback) {
+ var options = {
+ initialize: function (array, changeMeta, instanceMeta) {
+ instanceMeta.filteredArrayIndexes = new Ember.SubArray();
+ },
+
+ addedItem: function(array, item, changeMeta, instanceMeta) {
+ var match = !!callback.call(this, item),
+ filterIndex = instanceMeta.filteredArrayIndexes.addItem(changeMeta.index, match);
+
+ if (match) {
+ array.insertAt(filterIndex, item);
+ }
+
+ return array;
+ },
+
+ removedItem: function(array, item, changeMeta, instanceMeta) {
+ var filterIndex = instanceMeta.filteredArrayIndexes.removeItem(changeMeta.index);
+
+ if (filterIndex > -1) {
+ array.removeAt(filterIndex);
+ }
+
+ return array;
+ }
+ };
+
+ return Ember.arrayComputed(dependentKey, options);
+};
+
+/**
+ Filters the array by the property and value
+
+ ```javascript
+ App.Hamster = Ember.Object.extend({
+ remainingChores: Ember.computed.filterBy('chores', 'done', false)
+ });
+
+ var hamster = App.Hamster.create({chores: [
+ {name: 'cook', done: true},
+ {name: 'clean', done: true},
+ {name: 'write more unit tests', done: false}
+ ]});
+ hamster.get('remainingChores'); // [{name: 'write more unit tests', done: false}]
+ ```
+
+ @method computed.filterBy
+ @for Ember
+ @param {String} dependentKey
+ @param {String} propertyKey
+ @param {String} value
+ @return {Ember.ComputedProperty} the filtered array
+*/
+Ember.computed.filterBy = function(dependentKey, propertyKey, value) {
+ var callback;
+
+ if (arguments.length === 2) {
+ callback = function(item) {
+ return get(item, propertyKey);
+ };
+ } else {
+ callback = function(item) {
+ return get(item, propertyKey) === value;
+ };
+ }
+
+ return Ember.computed.filter(dependentKey + '.@each.' + propertyKey, callback);
+};
+
+/**
+ @method computed.filterProperty
+ @for Ember
+ @param dependentKey
+ @param propertyKey
+ @param value
+ @deprecated Use `Ember.computed.filterBy` instead
+*/
+Ember.computed.filterProperty = Ember.computed.filterBy;
+
+/**
+ A computed property which returns a new array with all the unique
+ elements from one or more dependent arrays.
+
+ Example
+
+ ```javascript
+ App.Hamster = Ember.Object.extend({
+ uniqueFruits: Ember.computed.uniq('fruits')
+ });
+
+ var hamster = App.Hamster.create({fruits: [
+ 'banana',
+ 'grape',
+ 'kale',
+ 'banana'
+ ]});
+ hamster.get('uniqueFruits'); // ['banana', 'grape', 'kale']
+ ```
+
+ @method computed.uniq
+ @for Ember
+ @param {String} propertyKey*
+ @return {Ember.ComputedProperty} computes a new array with all the
+ unique elements from the dependent array
+*/
+Ember.computed.uniq = function() {
+ var args = a_slice.call(arguments);
+ args.push({
+ initialize: function(array, changeMeta, instanceMeta) {
+ instanceMeta.itemCounts = {};
+ },
+
+ addedItem: function(array, item, changeMeta, instanceMeta) {
+ var guid = guidFor(item);
+
+ if (!instanceMeta.itemCounts[guid]) {
+ instanceMeta.itemCounts[guid] = 1;
+ } else {
+ ++instanceMeta.itemCounts[guid];
+ }
+ array.addObject(item);
+ return array;
+ },
+ removedItem: function(array, item, _, instanceMeta) {
+ var guid = guidFor(item),
+ itemCounts = instanceMeta.itemCounts;
+
+ if (--itemCounts[guid] === 0) {
+ array.removeObject(item);
+ }
+ return array;
+ }
+ });
+ return Ember.arrayComputed.apply(null, args);
+};
+
+/**
+ Alias for [Ember.computed.uniq](/api/#method_computed_uniq).
+
+ @method computed.union
+ @for Ember
+ @param {String} propertyKey*
+ @return {Ember.ComputedProperty} computes a new array with all the
+ unique elements from the dependent array
+*/
+Ember.computed.union = Ember.computed.uniq;
+
+/**
+ A computed property which returns a new array with all the duplicated
+ elements from two or more dependeny arrays.
+
+ Example
+
+ ```javascript
+ var obj = Ember.Object.createWithMixins({
+ adaFriends: ['Charles Babbage', 'John Hobhouse', 'William King', 'Mary Somerville'],
+ charlesFriends: ['William King', 'Mary Somerville', 'Ada Lovelace', 'George Peacock'],
+ friendsInCommon: Ember.computed.intersect('adaFriends', 'charlesFriends')
+ });
+
+ obj.get('friendsInCommon'); // ['William King', 'Mary Somerville']
+ ```
+
+ @method computed.intersect
+ @for Ember
+ @param {String} propertyKey*
+ @return {Ember.ComputedProperty} computes a new array with all the
+ duplicated elements from the dependent arrays
+*/
+Ember.computed.intersect = function () {
+ var getDependentKeyGuids = function (changeMeta) {
+ return map(changeMeta.property._dependentKeys, function (dependentKey) {
+ return guidFor(dependentKey);
+ });
+ };
+
+ var args = a_slice.call(arguments);
+ args.push({
+ initialize: function (array, changeMeta, instanceMeta) {
+ instanceMeta.itemCounts = {};
+ },
+
+ addedItem: function(array, item, changeMeta, instanceMeta) {
+ var itemGuid = guidFor(item),
+ dependentGuids = getDependentKeyGuids(changeMeta),
+ dependentGuid = guidFor(changeMeta.arrayChanged),
+ numberOfDependentArrays = changeMeta.property._dependentKeys.length,
+ itemCounts = instanceMeta.itemCounts;
+
+ if (!itemCounts[itemGuid]) { itemCounts[itemGuid] = {}; }
+ if (itemCounts[itemGuid][dependentGuid] === undefined) { itemCounts[itemGuid][dependentGuid] = 0; }
+
+ if (++itemCounts[itemGuid][dependentGuid] === 1 &&
+ numberOfDependentArrays === Ember.keys(itemCounts[itemGuid]).length) {
+
+ array.addObject(item);
+ }
+ return array;
+ },
+ removedItem: function(array, item, changeMeta, instanceMeta) {
+ var itemGuid = guidFor(item),
+ dependentGuids = getDependentKeyGuids(changeMeta),
+ dependentGuid = guidFor(changeMeta.arrayChanged),
+ numberOfDependentArrays = changeMeta.property._dependentKeys.length,
+ numberOfArraysItemAppearsIn,
+ itemCounts = instanceMeta.itemCounts;
+
+ if (itemCounts[itemGuid][dependentGuid] === undefined) { itemCounts[itemGuid][dependentGuid] = 0; }
+ if (--itemCounts[itemGuid][dependentGuid] === 0) {
+ delete itemCounts[itemGuid][dependentGuid];
+ numberOfArraysItemAppearsIn = Ember.keys(itemCounts[itemGuid]).length;
+
+ if (numberOfArraysItemAppearsIn === 0) {
+ delete itemCounts[itemGuid];
+ }
+ array.removeObject(item);
+ }
+ return array;
+ }
+ });
+ return Ember.arrayComputed.apply(null, args);
+};
+
+/**
+ A computed property which returns a new array with all the
+ properties from the first dependent array that are not in the second
+ dependent array.
+
+ Example
+
+ ```javascript
+ App.Hamster = Ember.Object.extend({
+ likes: ['banana', 'grape', 'kale'],
+ wants: Ember.computed.setDiff('likes', 'fruits')
+ });
+
+ var hamster = App.Hamster.create({fruits: [
+ 'grape',
+ 'kale',
+ ]});
+ hamster.get('wants'); // ['banana']
+ ```
+
+ @method computed.setDiff
+ @for Ember
+ @param {String} setAProperty
+ @param {String} setBProperty
+ @return {Ember.ComputedProperty} computes a new array with all the
+ items from the first dependent array that are not in the second
+ dependent array
+*/
+Ember.computed.setDiff = function (setAProperty, setBProperty) {
+ if (arguments.length !== 2) {
+ throw new Ember.Error("setDiff requires exactly two dependent arrays.");
+ }
+ return Ember.arrayComputed.call(null, setAProperty, setBProperty, {
+ addedItem: function (array, item, changeMeta, instanceMeta) {
+ var setA = get(this, setAProperty),
+ setB = get(this, setBProperty);
+
+ if (changeMeta.arrayChanged === setA) {
+ if (!setB.contains(item)) {
+ array.addObject(item);
+ }
+ } else {
+ array.removeObject(item);
+ }
+ return array;
+ },
+
+ removedItem: function (array, item, changeMeta, instanceMeta) {
+ var setA = get(this, setAProperty),
+ setB = get(this, setBProperty);
+
+ if (changeMeta.arrayChanged === setB) {
+ if (setA.contains(item)) {
+ array.addObject(item);
+ }
+ } else {
+ array.removeObject(item);
+ }
+ return array;
+ }
+ });
+};
+
+function binarySearch(array, item, low, high) {
+ var mid, midItem, res, guidMid, guidItem;
+
+ if (arguments.length < 4) { high = get(array, 'length'); }
+ if (arguments.length < 3) { low = 0; }
+
+ if (low === high) {
+ return low;
+ }
+
+ mid = low + Math.floor((high - low) / 2);
+ midItem = array.objectAt(mid);
+
+ guidMid = _guidFor(midItem);
+ guidItem = _guidFor(item);
+
+ if (guidMid === guidItem) {
+ return mid;
+ }
+
+ res = this.order(midItem, item);
+ if (res === 0) {
+ res = guidMid < guidItem ? -1 : 1;
+ }
+
+
+ if (res < 0) {
+ return this.binarySearch(array, item, mid+1, high);
+ } else if (res > 0) {
+ return this.binarySearch(array, item, low, mid);
+ }
+
+ return mid;
+
+ function _guidFor(item) {
+ if (SearchProxy.detectInstance(item)) {
+ return guidFor(get(item, 'content'));
+ }
+ return guidFor(item);
+ }
+}
+
+
+SearchProxy = Ember.ObjectProxy.extend();
+
+/**
+ A computed property which returns a new array with all the
+ properties from the first dependent array sorted based on a property
+ or sort function.
+
+ The callback method you provide should have the following signature:
+
+ ```javascript
+ function(itemA, itemB);
+ ```
+
+ - `itemA` the first item to compare.
+ - `itemB` the second item to compare.
+
+ This function should return `-1` when `itemA` should come before
+ `itemB`. It should return `1` when `itemA` should come after
+ `itemB`. If the `itemA` and `itemB` are equal this function should return `0`.
+
+ Example
+
+ ```javascript
+ var ToDoList = Ember.Object.extend({
+ todosSorting: ['name'],
+ sortedTodos: Ember.computed.sort('todos', 'todosSorting'),
+ priorityTodos: Ember.computed.sort('todos', function(a, b){
+ if (a.priority > b.priority) {
+ return 1;
+ } else if (a.priority < b.priority) {
+ return -1;
+ }
+ return 0;
+ }),
+ });
+ var todoList = ToDoList.create({todos: [
+ {name: 'Unit Test', priority: 2},
+ {name: 'Documentation', priority: 3},
+ {name: 'Release', priority: 1}
+ ]});
+
+ todoList.get('sortedTodos'); // [{name:'Documentation', priority:3}, {name:'Release', priority:1}, {name:'Unit Test', priority:2}]
+ todoList.get('priorityTodos'); // [{name:'Release', priority:1}, {name:'Unit Test', priority:2}, {name:'Documentation', priority:3}]
+ ```
+
+ @method computed.sort
+ @for Ember
+ @param {String} dependentKey
+ @param {String or Function} sortDefinition a dependent key to an
+ array of sort properties or a function to use when sorting
+ @return {Ember.ComputedProperty} computes a new sorted array based
+ on the sort property array or callback function
+*/
+Ember.computed.sort = function (itemsKey, sortDefinition) {
+ Ember.assert("Ember.computed.sort requires two arguments: an array key to sort and either a sort properties key or sort function", arguments.length === 2);
+
+ var initFn, sortPropertiesKey;
+
+ if (typeof sortDefinition === 'function') {
+ initFn = function (array, changeMeta, instanceMeta) {
+ instanceMeta.order = sortDefinition;
+ instanceMeta.binarySearch = binarySearch;
+ };
+ } else {
+ sortPropertiesKey = sortDefinition;
+ initFn = function (array, changeMeta, instanceMeta) {
+ function setupSortProperties() {
+ var sortPropertyDefinitions = get(this, sortPropertiesKey),
+ sortProperty,
+ sortProperties = instanceMeta.sortProperties = [],
+ sortPropertyAscending = instanceMeta.sortPropertyAscending = {},
+ idx,
+ asc;
+
+ Ember.assert("Cannot sort: '" + sortPropertiesKey + "' is not an array.", Ember.isArray(sortPropertyDefinitions));
+
+ changeMeta.property.clearItemPropertyKeys(itemsKey);
+
+ forEach(sortPropertyDefinitions, function (sortPropertyDefinition) {
+ if ((idx = sortPropertyDefinition.indexOf(':')) !== -1) {
+ sortProperty = sortPropertyDefinition.substring(0, idx);
+ asc = sortPropertyDefinition.substring(idx+1).toLowerCase() !== 'desc';
+ } else {
+ sortProperty = sortPropertyDefinition;
+ asc = true;
+ }
+
+ sortProperties.push(sortProperty);
+ sortPropertyAscending[sortProperty] = asc;
+ changeMeta.property.itemPropertyKey(itemsKey, sortProperty);
+ });
+
+ sortPropertyDefinitions.addObserver('@each', this, updateSortPropertiesOnce);
+ }
+
+ function updateSortPropertiesOnce() {
+ Ember.run.once(this, updateSortProperties, changeMeta.propertyName);
+ }
+
+ function updateSortProperties(propertyName) {
+ setupSortProperties.call(this);
+ changeMeta.property.recomputeOnce.call(this, propertyName);
+ }
+
+ Ember.addObserver(this, sortPropertiesKey, updateSortPropertiesOnce);
+
+ setupSortProperties.call(this);
+
+
+ instanceMeta.order = function (itemA, itemB) {
+ var sortProperty, result, asc;
+ for (var i = 0; i < this.sortProperties.length; ++i) {
+ sortProperty = this.sortProperties[i];
+ result = Ember.compare(get(itemA, sortProperty), get(itemB, sortProperty));
+
+ if (result !== 0) {
+ asc = this.sortPropertyAscending[sortProperty];
+ return asc ? result : (-1 * result);
+ }
+ }
+
+ return 0;
+ };
+
+ instanceMeta.binarySearch = binarySearch;
+ };
+ }
+
+ return Ember.arrayComputed.call(null, itemsKey, {
+ initialize: initFn,
+
+ addedItem: function (array, item, changeMeta, instanceMeta) {
+ var index = instanceMeta.binarySearch(array, item);
+ array.insertAt(index, item);
+ return array;
+ },
+
+ removedItem: function (array, item, changeMeta, instanceMeta) {
+ var proxyProperties, index, searchItem;
+
+ if (changeMeta.previousValues) {
+ proxyProperties = merge({ content: item }, changeMeta.previousValues);
+
+ searchItem = SearchProxy.create(proxyProperties);
+ } else {
+ searchItem = item;
+ }
+
+ index = instanceMeta.binarySearch(array, searchItem);
+ array.removeAt(index);
+ return array;
+ }
+ });
+};
+
+})();
+
+
+
+(function() {
+Ember.RSVP = requireModule('rsvp');
+
+Ember.RSVP.onerrorDefault = function(error) {
+ if (error instanceof Error) {
+ if (Ember.testing) {
+ if (Ember.Test && Ember.Test.adapter) {
+ Ember.Test.adapter.exception(error);
+ } else {
+ throw error;
+ }
+ } else {
+ Ember.Logger.error(error.stack);
+ Ember.assert(error, false);
+ }
+ }
+};
+
+Ember.RSVP.on('error', Ember.RSVP.onerrorDefault);
+
+})();
+
+
+
+(function() {
+/**
+@module ember
+@submodule ember-runtime
+*/
+
+var a_slice = Array.prototype.slice;
+
+
+if (Ember.EXTEND_PROTOTYPES === true || Ember.EXTEND_PROTOTYPES.Function) {
+
+ /**
+ The `property` extension of Javascript's Function prototype is available
+ when `Ember.EXTEND_PROTOTYPES` or `Ember.EXTEND_PROTOTYPES.Function` is
+ `true`, which is the default.
+
+ Computed properties allow you to treat a function like a property:
+
+ ```javascript
+ MyApp.President = Ember.Object.extend({
+ firstName: '',
+ lastName: '',
+
+ fullName: function() {
+ return this.get('firstName') + ' ' + this.get('lastName');
+
+ // Call this flag to mark the function as a property
+ }.property()
+ });
+
+ var president = MyApp.President.create({
+ firstName: "Barack",
+ lastName: "Obama"
+ });
+
+ president.get('fullName'); // "Barack Obama"
+ ```
+
+ Treating a function like a property is useful because they can work with
+ bindings, just like any other property.
+
+ Many computed properties have dependencies on other properties. For
+ example, in the above example, the `fullName` property depends on
+ `firstName` and `lastName` to determine its value. You can tell Ember
+ about these dependencies like this:
+
+ ```javascript
+ MyApp.President = Ember.Object.extend({
+ firstName: '',
+ lastName: '',
+
+ fullName: function() {
+ return this.get('firstName') + ' ' + this.get('lastName');
+
+ // Tell Ember.js that this computed property depends on firstName
+ // and lastName
+ }.property('firstName', 'lastName')
+ });
+ ```
+
+ Make sure you list these dependencies so Ember knows when to update
+ bindings that connect to a computed property. Changing a dependency
+ will not immediately trigger an update of the computed property, but
+ will instead clear the cache so that it is updated when the next `get`
+ is called on the property.
+
+ See [Ember.ComputedProperty](/api/classes/Ember.ComputedProperty.html), [Ember.computed](/api/#method_computed).
+
+ @method property
+ @for Function
+ */
+ Function.prototype.property = function() {
+ var ret = Ember.computed(this);
+ // ComputedProperty.prototype.property expands properties; no need for us to
+ // do so here.
+ return ret.property.apply(ret, arguments);
+ };
+
+ /**
+ The `observes` extension of Javascript's Function prototype is available
+ when `Ember.EXTEND_PROTOTYPES` or `Ember.EXTEND_PROTOTYPES.Function` is
+ true, which is the default.
+
+ You can observe property changes simply by adding the `observes`
+ call to the end of your method declarations in classes that you write.
+ For example:
+
+ ```javascript
+ Ember.Object.extend({
+ valueObserver: function() {
+ // Executes whenever the "value" property changes
+ }.observes('value')
+ });
+ ```
+
+ In the future this method may become asynchronous. If you want to ensure
+ synchronous behavior, use `observesImmediately`.
+
+ See `Ember.observer`.
+
+ @method observes
+ @for Function
+ */
+ Function.prototype.observes = function() {
+
+ this.__ember_observes__ = a_slice.call(arguments);
+
+
+ return this;
+ };
+
+ /**
+ The `observesImmediately` extension of Javascript's Function prototype is
+ available when `Ember.EXTEND_PROTOTYPES` or
+ `Ember.EXTEND_PROTOTYPES.Function` is true, which is the default.
+
+ You can observe property changes simply by adding the `observesImmediately`
+ call to the end of your method declarations in classes that you write.
+ For example:
+
+ ```javascript
+ Ember.Object.extend({
+ valueObserver: function() {
+ // Executes immediately after the "value" property changes
+ }.observesImmediately('value')
+ });
+ ```
+
+ In the future, `observes` may become asynchronous. In this event,
+ `observesImmediately` will maintain the synchronous behavior.
+
+ See `Ember.immediateObserver`.
+
+ @method observesImmediately
+ @for Function
+ */
+ Function.prototype.observesImmediately = function() {
+ for (var i=0, l=arguments.length; i<l; i++) {
+ var arg = arguments[i];
+ Ember.assert("Immediate observers must observe internal properties only, not properties on other objects.", arg.indexOf('.') === -1);
+ }
+
+ // observes handles property expansion
+ return this.observes.apply(this, arguments);
+ };
+
+ /**
+ The `observesBefore` extension of Javascript's Function prototype is
+ available when `Ember.EXTEND_PROTOTYPES` or
+ `Ember.EXTEND_PROTOTYPES.Function` is true, which is the default.
+
+ You can get notified when a property change is about to happen by
+ by adding the `observesBefore` call to the end of your method
+ declarations in classes that you write. For example:
+
+ ```javascript
+ Ember.Object.extend({
+ valueObserver: function() {
+ // Executes whenever the "value" property is about to change
+ }.observesBefore('value')
+ });
+ ```
+
+ See `Ember.beforeObserver`.
+
+ @method observesBefore
+ @for Function
+ */
+ Function.prototype.observesBefore = function() {
+
+ this.__ember_observesBefore__ = a_slice.call(arguments);
+
+
+ return this;
+ };
+
+ /**
+ The `on` extension of Javascript's Function prototype is available
+ when `Ember.EXTEND_PROTOTYPES` or `Ember.EXTEND_PROTOTYPES.Function` is
+ true, which is the default.
+
+ You can listen for events simply by adding the `on` call to the end of
+ your method declarations in classes or mixins that you write. For example:
+
+ ```javascript
+ Ember.Mixin.create({
+ doSomethingWithElement: function() {
+ // Executes whenever the "didInsertElement" event fires
+ }.on('didInsertElement')
+ });
+ ```
+
+ See `Ember.on`.
+
+ @method on
+ @for Function
+ */
+ Function.prototype.on = function() {
+ var events = a_slice.call(arguments);
+ this.__ember_listens__ = events;
+ return this;
+ };
+}
+
+
+})();
+
+
+
+(function() {
+
+})();
+
+
+
+(function() {
+/**
+@module ember
+@submodule ember-runtime
+*/
+
+
+/**
</ins><span class="cx"> Implements some standard methods for comparing objects. Add this mixin to
</span><span class="cx"> any class you create that can compare its instances.
</span><span class="cx">
</span><span class="lines">@@ -8534,21 +16754,11 @@
</span><span class="cx">
</span><span class="cx"> @class Comparable
</span><span class="cx"> @namespace Ember
</span><del>- @extends Ember.Mixin
</del><span class="cx"> @since Ember 0.9
</span><span class="cx"> */
</span><del>-Ember.Comparable = Ember.Mixin.create( /** @scope Ember.Comparable.prototype */{
</del><ins>+Ember.Comparable = Ember.Mixin.create({
</ins><span class="cx">
</span><span class="cx"> /**
</span><del>- walk like a duck. Indicates that the object can be compared.
-
- @property isComparable
- @type Boolean
- @default true
- */
- isComparable: true,
-
- /**
</del><span class="cx"> Override to return the result of the comparison of the two parameters. The
</span><span class="cx"> compare method should return:
</span><span class="cx">
</span><span class="lines">@@ -8595,18 +16805,16 @@
</span><span class="cx">
</span><span class="cx"> @class Copyable
</span><span class="cx"> @namespace Ember
</span><del>- @extends Ember.Mixin
</del><span class="cx"> @since Ember 0.9
</span><span class="cx"> */
</span><del>-Ember.Copyable = Ember.Mixin.create(
-/** @scope Ember.Copyable.prototype */ {
</del><ins>+Ember.Copyable = Ember.Mixin.create({
</ins><span class="cx">
</span><span class="cx"> /**
</span><span class="cx"> Override to return a copy of the receiver. Default implementation raises
</span><span class="cx"> an exception.
</span><span class="cx">
</span><span class="cx"> @method copy
</span><del>- @param deep {Boolean} if `true`, a deep copy of the object should be made
</del><ins>+ @param {Boolean} deep if `true`, a deep copy of the object should be made
</ins><span class="cx"> @return {Object} copy of receiver
</span><span class="cx"> */
</span><span class="cx"> copy: Ember.required(Function),
</span><span class="lines">@@ -8629,7 +16837,7 @@
</span><span class="cx"> if (Ember.Freezable && Ember.Freezable.detect(this)) {
</span><span class="cx"> return get(this, 'isFrozen') ? this : this.copy().freeze();
</span><span class="cx"> } else {
</span><del>- throw new Error(Ember.String.fmt("%@ does not support freezing", [this]));
</del><ins>+ throw new Ember.Error(Ember.String.fmt("%@ does not support freezing", [this]));
</ins><span class="cx"> }
</span><span class="cx"> }
</span><span class="cx"> });
</span><span class="lines">@@ -8687,7 +16895,7 @@
</span><span class="cx">
</span><span class="cx"> });
</span><span class="cx">
</span><del>- c = Context.create({ firstName: "John", lastName: "Doe" });
</del><ins>+ c = Contact.create({ firstName: "John", lastName: "Doe" });
</ins><span class="cx"> c.swapNames(); // returns c
</span><span class="cx"> c.freeze();
</span><span class="cx"> c.swapNames(); // EXCEPTION
</span><span class="lines">@@ -8701,11 +16909,9 @@
</span><span class="cx">
</span><span class="cx"> @class Freezable
</span><span class="cx"> @namespace Ember
</span><del>- @extends Ember.Mixin
</del><span class="cx"> @since Ember 0.9
</span><span class="cx"> */
</span><del>-Ember.Freezable = Ember.Mixin.create(
-/** @scope Ember.Freezable.prototype */ {
</del><ins>+Ember.Freezable = Ember.Mixin.create({
</ins><span class="cx">
</span><span class="cx"> /**
</span><span class="cx"> Set to `true` when the object is frozen. Use this property to detect
</span><span class="lines">@@ -8758,7 +16964,7 @@
</span><span class="cx">
</span><span class="cx"> To add an object to an enumerable, use the `addObject()` method. This
</span><span class="cx"> method will only add the object to the enumerable if the object is not
</span><del>- already present and the object if of a type supported by the enumerable.
</del><ins>+ already present and is of a type supported by the enumerable.
</ins><span class="cx">
</span><span class="cx"> ```javascript
</span><span class="cx"> set.addObject(contact);
</span><span class="lines">@@ -8766,8 +16972,8 @@
</span><span class="cx">
</span><span class="cx"> ## Removing Objects
</span><span class="cx">
</span><del>- To remove an object form an enumerable, use the `removeObject()` method. This
- will only remove the object if it is already in the enumerable, otherwise
</del><ins>+ To remove an object from an enumerable, use the `removeObject()` method. This
+ will only remove the object if it is present in the enumerable, otherwise
</ins><span class="cx"> this method has no effect.
</span><span class="cx">
</span><span class="cx"> ```javascript
</span><span class="lines">@@ -8782,11 +16988,9 @@
</span><span class="cx">
</span><span class="cx"> @class MutableEnumerable
</span><span class="cx"> @namespace Ember
</span><del>- @extends Ember.Mixin
</del><span class="cx"> @uses Ember.Enumerable
</span><span class="cx"> */
</span><del>-Ember.MutableEnumerable = Ember.Mixin.create(Ember.Enumerable,
- /** @scope Ember.MutableEnumerable.prototype */ {
</del><ins>+Ember.MutableEnumerable = Ember.Mixin.create(Ember.Enumerable, {
</ins><span class="cx">
</span><span class="cx"> /**
</span><span class="cx"> __Required.__ You must implement this method to apply this mixin.
</span><span class="lines">@@ -8795,7 +16999,7 @@
</span><span class="cx"> already present in the collection. If the object is present, this method
</span><span class="cx"> has no effect.
</span><span class="cx">
</span><del>- If the passed object is of a type not supported by the receiver
</del><ins>+ If the passed object is of a type not supported by the receiver,
</ins><span class="cx"> then this method should raise an exception.
</span><span class="cx">
</span><span class="cx"> @method addObject
</span><span class="lines">@@ -8822,10 +17026,10 @@
</span><span class="cx"> __Required.__ You must implement this method to apply this mixin.
</span><span class="cx">
</span><span class="cx"> Attempts to remove the passed object from the receiver collection if the
</span><del>- object is in present in the collection. If the object is not present,
</del><ins>+ object is present in the collection. If the object is not present,
</ins><span class="cx"> this method has no effect.
</span><span class="cx">
</span><del>- If the passed object is of a type not supported by the receiver
</del><ins>+ If the passed object is of a type not supported by the receiver,
</ins><span class="cx"> then this method should raise an exception.
</span><span class="cx">
</span><span class="cx"> @method removeObject
</span><span class="lines">@@ -8836,7 +17040,7 @@
</span><span class="cx">
</span><span class="cx">
</span><span class="cx"> /**
</span><del>- Removes each objects in the passed enumerable from the receiver.
</del><ins>+ Removes each object in the passed enumerable from the receiver.
</ins><span class="cx">
</span><span class="cx"> @method removeObjects
</span><span class="cx"> @param {Ember.Enumerable} objects the objects to remove
</span><span class="lines">@@ -8871,7 +17075,7 @@
</span><span class="cx"> // HELPERS
</span><span class="cx"> //
</span><span class="cx">
</span><del>-var get = Ember.get, set = Ember.set, forEach = Ember.EnumerableUtils.forEach;
</del><ins>+var get = Ember.get, set = Ember.set;
</ins><span class="cx">
</span><span class="cx"> /**
</span><span class="cx"> This mixin defines the API for modifying array-like objects. These methods
</span><span class="lines">@@ -8883,12 +17087,10 @@
</span><span class="cx">
</span><span class="cx"> @class MutableArray
</span><span class="cx"> @namespace Ember
</span><del>- @extends Ember.Mixin
</del><span class="cx"> @uses Ember.Array
</span><span class="cx"> @uses Ember.MutableEnumerable
</span><span class="cx"> */
</span><del>-Ember.MutableArray = Ember.Mixin.create(Ember.Array, Ember.MutableEnumerable,
- /** @scope Ember.MutableArray.prototype */ {
</del><ins>+Ember.MutableArray = Ember.Mixin.create(Ember.Array, Ember.MutableEnumerable, {
</ins><span class="cx">
</span><span class="cx"> /**
</span><span class="cx"> __Required.__ You must implement this method to apply this mixin.
</span><span class="lines">@@ -8898,11 +17100,11 @@
</span><span class="cx"> passed array. You should also call `this.enumerableContentDidChange()`
</span><span class="cx">
</span><span class="cx"> @method replace
</span><del>- @param {Number} idx Starting index in the array to replace. If
</del><ins>+ @param {Number} idx Starting index in the array to replace. If
</ins><span class="cx"> idx >= length, then append to the end of the array.
</span><del>- @param {Number} amt Number of elements that should be removed from
</del><ins>+ @param {Number} amt Number of elements that should be removed from
</ins><span class="cx"> the array, starting at *idx*.
</span><del>- @param {Array} objects An array of zero or more objects that should be
</del><ins>+ @param {Array} objects An array of zero or more objects that should be
</ins><span class="cx"> inserted into the array at *idx*
</span><span class="cx"> */
</span><span class="cx"> replace: Ember.required(),
</span><span class="lines">@@ -8941,9 +17143,10 @@
</span><span class="cx"> @method insertAt
</span><span class="cx"> @param {Number} idx index of insert the object at.
</span><span class="cx"> @param {Object} object object to insert
</span><ins>+ @return this
</ins><span class="cx"> */
</span><span class="cx"> insertAt: function(idx, object) {
</span><del>- if (idx > get(this, 'length')) throw new Error(OUT_OF_RANGE_EXCEPTION) ;
</del><ins>+ if (idx > get(this, 'length')) throw new Ember.Error(OUT_OF_RANGE_EXCEPTION) ;
</ins><span class="cx"> this.replace(idx, 0, [object]) ;
</span><span class="cx"> return this ;
</span><span class="cx"> },
</span><span class="lines">@@ -8953,7 +17156,7 @@
</span><span class="cx"> method. You can pass either a single index, or a start and a length.
</span><span class="cx">
</span><span class="cx"> If you pass a start and length that is beyond the
</span><del>- length this method will throw an `Ember.OUT_OF_RANGE_EXCEPTION`
</del><ins>+ length this method will throw an `OUT_OF_RANGE_EXCEPTION`.
</ins><span class="cx">
</span><span class="cx"> ```javascript
</span><span class="cx"> var colors = ["red", "green", "blue", "yellow", "orange"];
</span><span class="lines">@@ -8971,7 +17174,7 @@
</span><span class="cx"> if ('number' === typeof start) {
</span><span class="cx">
</span><span class="cx"> if ((start < 0) || (start >= get(this, 'length'))) {
</span><del>- throw new Error(OUT_OF_RANGE_EXCEPTION);
</del><ins>+ throw new Ember.Error(OUT_OF_RANGE_EXCEPTION);
</ins><span class="cx"> }
</span><span class="cx">
</span><span class="cx"> // fast case
</span><span class="lines">@@ -8987,17 +17190,18 @@
</span><span class="cx"> is KVO-compliant.
</span><span class="cx">
</span><span class="cx"> ```javascript
</span><del>- var colors = ["red", "green", "blue"];
- colors.pushObject("black"); // ["red", "green", "blue", "black"]
- colors.pushObject(["yellow", "orange"]); // ["red", "green", "blue", "black", ["yellow", "orange"]]
</del><ins>+ var colors = ["red", "green"];
+ colors.pushObject("black"); // ["red", "green", "black"]
+ colors.pushObject(["yellow"]); // ["red", "green", ["yellow"]]
</ins><span class="cx"> ```
</span><span class="cx">
</span><span class="cx"> @method pushObject
</span><del>- @param {anything} obj object to push
</del><ins>+ @param {*} obj object to push
+ @return The same obj passed as param
</ins><span class="cx"> */
</span><span class="cx"> pushObject: function(obj) {
</span><span class="cx"> this.insertAt(get(this, 'length'), obj) ;
</span><del>- return obj ;
</del><ins>+ return obj;
</ins><span class="cx"> },
</span><span class="cx">
</span><span class="cx"> /**
</span><span class="lines">@@ -9005,9 +17209,8 @@
</span><span class="cx"> notifying observers of the change until all objects are added.
</span><span class="cx">
</span><span class="cx"> ```javascript
</span><del>- var colors = ["red", "green", "blue"];
- colors.pushObjects("black"); // ["red", "green", "blue", "black"]
- colors.pushObjects(["yellow", "orange"]); // ["red", "green", "blue", "black", "yellow", "orange"]
</del><ins>+ var colors = ["red"];
+ colors.pushObjects(["yellow", "orange"]); // ["red", "yellow", "orange"]
</ins><span class="cx"> ```
</span><span class="cx">
</span><span class="cx"> @method pushObjects
</span><span class="lines">@@ -9015,6 +17218,9 @@
</span><span class="cx"> @return {Ember.Array} receiver
</span><span class="cx"> */
</span><span class="cx"> pushObjects: function(objects) {
</span><ins>+ if (!(Ember.Enumerable.detect(objects) || Ember.isArray(objects))) {
+ throw new TypeError("Must pass Ember.Enumerable to Ember.MutableArray#pushObjects");
+ }
</ins><span class="cx"> this.replace(get(this, 'length'), 0, objects);
</span><span class="cx"> return this;
</span><span class="cx"> },
</span><span class="lines">@@ -9066,13 +17272,14 @@
</span><span class="cx"> KVO-compliant.
</span><span class="cx">
</span><span class="cx"> ```javascript
</span><del>- var colors = ["red", "green", "blue"];
- colors.unshiftObject("yellow"); // ["yellow", "red", "green", "blue"]
- colors.unshiftObject(["black", "white"]); // [["black", "white"], "yellow", "red", "green", "blue"]
</del><ins>+ var colors = ["red"];
+ colors.unshiftObject("yellow"); // ["yellow", "red"]
+ colors.unshiftObject(["black"]); // [["black"], "yellow", "red"]
</ins><span class="cx"> ```
</span><span class="cx">
</span><span class="cx"> @method unshiftObject
</span><del>- @param {anything} obj object to unshift
</del><ins>+ @param {*} obj object to unshift
+ @return The same obj passed as param
</ins><span class="cx"> */
</span><span class="cx"> unshiftObject: function(obj) {
</span><span class="cx"> this.insertAt(0, obj) ;
</span><span class="lines">@@ -9084,9 +17291,9 @@
</span><span class="cx"> observers until all objects have been added.
</span><span class="cx">
</span><span class="cx"> ```javascript
</span><del>- var colors = ["red", "green", "blue"];
- colors.unshiftObjects(["black", "white"]); // ["black", "white", "red", "green", "blue"]
- colors.unshiftObjects("yellow"); // Type Error: 'undefined' is not a function
</del><ins>+ var colors = ["red"];
+ colors.unshiftObjects(["black", "white"]); // ["black", "white", "red"]
+ colors.unshiftObjects("yellow"); // Type Error: 'undefined' is not a function
</ins><span class="cx"> ```
</span><span class="cx">
</span><span class="cx"> @method unshiftObjects
</span><span class="lines">@@ -9156,7 +17363,6 @@
</span><span class="cx">
</span><span class="cx"> });
</span><span class="cx">
</span><del>-
</del><span class="cx"> })();
</span><span class="cx">
</span><span class="cx">
</span><span class="lines">@@ -9167,563 +17373,130 @@
</span><span class="cx"> @submodule ember-runtime
</span><span class="cx"> */
</span><span class="cx">
</span><del>-var get = Ember.get, set = Ember.set, defineProperty = Ember.defineProperty;
</del><ins>+var get = Ember.get, set = Ember.set;
</ins><span class="cx">
</span><span class="cx"> /**
</span><del>- ## Overview
</del><ins>+`Ember.TargetActionSupport` is a mixin that can be included in a class
+to add a `triggerAction` method with semantics similar to the Handlebars
+`{{action}}` helper. In normal Ember usage, the `{{action}}` helper is
+usually the best choice. This mixin is most often useful when you are
+doing more complex event handling in View objects.
</ins><span class="cx">
</span><del>- This mixin provides properties and property observing functionality, core
- features of the Ember object model.
</del><ins>+See also `Ember.ViewTargetActionSupport`, which has
+view-aware defaults for target and actionContext.
</ins><span class="cx">
</span><del>- Properties and observers allow one object to observe changes to a
- property on another object. This is one of the fundamental ways that
- models, controllers and views communicate with each other in an Ember
- application.
</del><ins>+@class TargetActionSupport
+@namespace Ember
+@extends Ember.Mixin
+*/
+Ember.TargetActionSupport = Ember.Mixin.create({
+ target: null,
+ action: null,
+ actionContext: null,
</ins><span class="cx">
</span><del>- Any object that has this mixin applied can be used in observer
- operations. That includes `Ember.Object` and most objects you will
- interact with as you write your Ember application.
</del><ins>+ targetObject: Ember.computed(function() {
+ var target = get(this, 'target');
</ins><span class="cx">
</span><del>- Note that you will not generally apply this mixin to classes yourself,
- but you will use the features provided by this module frequently, so it
- is important to understand how to use it.
</del><ins>+ if (Ember.typeOf(target) === "string") {
+ var value = get(this, target);
+ if (value === undefined) { value = get(Ember.lookup, target); }
+ return value;
+ } else {
+ return target;
+ }
+ }).property('target'),
</ins><span class="cx">
</span><del>- ## Using `get()` and `set()`
</del><ins>+ actionContextObject: Ember.computed(function() {
+ var actionContext = get(this, 'actionContext');
</ins><span class="cx">
</span><del>- Because of Ember's support for bindings and observers, you will always
- access properties using the get method, and set properties using the
- set method. This allows the observing objects to be notified and
- computed properties to be handled properly.
</del><ins>+ if (Ember.typeOf(actionContext) === "string") {
+ var value = get(this, actionContext);
+ if (value === undefined) { value = get(Ember.lookup, actionContext); }
+ return value;
+ } else {
+ return actionContext;
+ }
+ }).property('actionContext'),
</ins><span class="cx">
</span><del>- More documentation about `get` and `set` are below.
</del><ins>+ /**
+ Send an "action" with an "actionContext" to a "target". The action, actionContext
+ and target will be retrieved from properties of the object. For example:
</ins><span class="cx">
</span><del>- ## Observing Property Changes
</del><ins>+ ```javascript
+ App.SaveButtonView = Ember.View.extend(Ember.TargetActionSupport, {
+ target: Ember.computed.alias('controller'),
+ action: 'save',
+ actionContext: Ember.computed.alias('context'),
+ click: function() {
+ this.triggerAction(); // Sends the `save` action, along with the current context
+ // to the current controller
+ }
+ });
+ ```
</ins><span class="cx">
</span><del>- You typically observe property changes simply by adding the `observes`
- call to the end of your method declarations in classes that you write.
- For example:
</del><ins>+ The `target`, `action`, and `actionContext` can be provided as properties of
+ an optional object argument to `triggerAction` as well.
</ins><span class="cx">
</span><span class="cx"> ```javascript
</span><del>- Ember.Object.create({
- valueObserver: function() {
- // Executes whenever the "value" property changes
- }.observes('value')
</del><ins>+ App.SaveButtonView = Ember.View.extend(Ember.TargetActionSupport, {
+ click: function() {
+ this.triggerAction({
+ action: 'save',
+ target: this.get('controller'),
+ actionContext: this.get('context'),
+ }); // Sends the `save` action, along with the current context
+ // to the current controller
+ }
</ins><span class="cx"> });
</span><span class="cx"> ```
</span><span class="cx">
</span><del>- Although this is the most common way to add an observer, this capability
- is actually built into the `Ember.Object` class on top of two methods
- defined in this mixin: `addObserver` and `removeObserver`. You can use
- these two methods to add and remove observers yourself if you need to
- do so at runtime.
</del><ins>+ The `actionContext` defaults to the object you mixing `TargetActionSupport` into.
+ But `target` and `action` must be specified either as properties or with the argument
+ to `triggerAction`, or a combination:
</ins><span class="cx">
</span><del>- To add an observer for a property, call:
-
</del><span class="cx"> ```javascript
</span><del>- object.addObserver('propertyKey', targetObject, targetAction)
</del><ins>+ App.SaveButtonView = Ember.View.extend(Ember.TargetActionSupport, {
+ target: Ember.computed.alias('controller'),
+ click: function() {
+ this.triggerAction({
+ action: 'save'
+ }); // Sends the `save` action, along with a reference to `this`,
+ // to the current controller
+ }
+ });
</ins><span class="cx"> ```
</span><span class="cx">
</span><del>- This will call the `targetAction` method on the `targetObject` to be called
- whenever the value of the `propertyKey` changes.
-
- Note that if `propertyKey` is a computed property, the observer will be
- called when any of the property dependencies are changed, even if the
- resulting value of the computed property is unchanged. This is necessary
- because computed properties are not computed until `get` is called.
-
- @class Observable
- @namespace Ember
- @extends Ember.Mixin
-*/
-Ember.Observable = Ember.Mixin.create(/** @scope Ember.Observable.prototype */ {
-
- /**
- Retrieves the value of a property from the object.
-
- This method is usually similar to using `object[keyName]` or `object.keyName`,
- however it supports both computed properties and the unknownProperty
- handler.
-
- Because `get` unifies the syntax for accessing all these kinds
- of properties, it can make many refactorings easier, such as replacing a
- simple property with a computed property, or vice versa.
-
- ### Computed Properties
-
- Computed properties are methods defined with the `property` modifier
- declared at the end, such as:
-
- ```javascript
- fullName: function() {
- return this.getEach('firstName', 'lastName').compact().join(' ');
- }.property('firstName', 'lastName')
- ```
-
- When you call `get` on a computed property, the function will be
- called and the return value will be returned instead of the function
- itself.
-
- ### Unknown Properties
-
- Likewise, if you try to call `get` on a property whose value is
- `undefined`, the `unknownProperty()` method will be called on the object.
- If this method returns any value other than `undefined`, it will be returned
- instead. This allows you to implement "virtual" properties that are
- not defined upfront.
-
- @method get
- @param {String} key The property to retrieve
- @return {Object} The property value or undefined.
</del><ins>+ @method triggerAction
+ @param opts {Hash} (optional, with the optional keys action, target and/or actionContext)
+ @return {Boolean} true if the action was sent successfully and did not return false
</ins><span class="cx"> */
</span><del>- get: function(keyName) {
- return get(this, keyName);
- },
</del><ins>+ triggerAction: function(opts) {
+ opts = opts || {};
+ var action = opts.action || get(this, 'action'),
+ target = opts.target || get(this, 'targetObject'),
+ actionContext = opts.actionContext;
</ins><span class="cx">
</span><del>- /**
- To get multiple properties at once, call `getProperties`
- with a list of strings or an array:
</del><ins>+ function args(options, actionName) {
+ var ret = [];
+ if (actionName) { ret.push(actionName); }
</ins><span class="cx">
</span><del>- ```javascript
- record.getProperties('firstName', 'lastName', 'zipCode'); // { firstName: 'John', lastName: 'Doe', zipCode: '10011' }
- ```
-
- is equivalent to:
-
- ```javascript
- record.getProperties(['firstName', 'lastName', 'zipCode']); // { firstName: 'John', lastName: 'Doe', zipCode: '10011' }
- ```
-
- @method getProperties
- @param {String...|Array} list of keys to get
- @return {Hash}
- */
- getProperties: function() {
- var ret = {};
- var propertyNames = arguments;
- if (arguments.length === 1 && Ember.typeOf(arguments[0]) === 'array') {
- propertyNames = arguments[0];
</del><ins>+ return ret.concat(options);
</ins><span class="cx"> }
</span><del>- for(var i = 0; i < propertyNames.length; i++) {
- ret[propertyNames[i]] = get(this, propertyNames[i]);
- }
- return ret;
- },
</del><span class="cx">
</span><del>- /**
- Sets the provided key or path to the value.
-
- This method is generally very similar to calling `object[key] = value` or
- `object.key = value`, except that it provides support for computed
- properties, the `unknownProperty()` method and property observers.
-
- ### Computed Properties
-
- If you try to set a value on a key that has a computed property handler
- defined (see the `get()` method for an example), then `set()` will call
- that method, passing both the value and key instead of simply changing
- the value itself. This is useful for those times when you need to
- implement a property that is composed of one or more member
- properties.
-
- ### Unknown Properties
-
- If you try to set a value on a key that is undefined in the target
- object, then the `unknownProperty()` handler will be called instead. This
- gives you an opportunity to implement complex "virtual" properties that
- are not predefined on the object. If `unknownProperty()` returns
- undefined, then `set()` will simply set the value on the object.
-
- ### Property Observers
-
- In addition to changing the property, `set()` will also register a property
- change with the object. Unless you have placed this call inside of a
- `beginPropertyChanges()` and `endPropertyChanges(),` any "local" observers
- (i.e. observer methods declared on the same object), will be called
- immediately. Any "remote" observers (i.e. observer methods declared on
- another object) will be placed in a queue and called at a later time in a
- coalesced manner.
-
- ### Chaining
-
- In addition to property changes, `set()` returns the value of the object
- itself so you can do chaining like this:
-
- ```javascript
- record.set('firstName', 'Charles').set('lastName', 'Jolley');
- ```
-
- @method set
- @param {String} key The property to set
- @param {Object} value The value to set or `null`.
- @return {Ember.Observable}
- */
- set: function(keyName, value) {
- set(this, keyName, value);
- return this;
- },
-
- /**
- To set multiple properties at once, call `setProperties`
- with a Hash:
-
- ```javascript
- record.setProperties({ firstName: 'Charles', lastName: 'Jolley' });
- ```
-
- @method setProperties
- @param {Hash} hash the hash of keys and values to set
- @return {Ember.Observable}
- */
- setProperties: function(hash) {
- return Ember.setProperties(this, hash);
- },
-
- /**
- Begins a grouping of property changes.
-
- You can use this method to group property changes so that notifications
- will not be sent until the changes are finished. If you plan to make a
- large number of changes to an object at one time, you should call this
- method at the beginning of the changes to begin deferring change
- notifications. When you are done making changes, call
- `endPropertyChanges()` to deliver the deferred change notifications and end
- deferring.
-
- @method beginPropertyChanges
- @return {Ember.Observable}
- */
- beginPropertyChanges: function() {
- Ember.beginPropertyChanges();
- return this;
- },
-
- /**
- Ends a grouping of property changes.
-
- You can use this method to group property changes so that notifications
- will not be sent until the changes are finished. If you plan to make a
- large number of changes to an object at one time, you should call
- `beginPropertyChanges()` at the beginning of the changes to defer change
- notifications. When you are done making changes, call this method to
- deliver the deferred change notifications and end deferring.
-
- @method endPropertyChanges
- @return {Ember.Observable}
- */
- endPropertyChanges: function() {
- Ember.endPropertyChanges();
- return this;
- },
-
- /**
- Notify the observer system that a property is about to change.
-
- Sometimes you need to change a value directly or indirectly without
- actually calling `get()` or `set()` on it. In this case, you can use this
- method and `propertyDidChange()` instead. Calling these two methods
- together will notify all observers that the property has potentially
- changed value.
-
- Note that you must always call `propertyWillChange` and `propertyDidChange`
- as a pair. If you do not, it may get the property change groups out of
- order and cause notifications to be delivered more often than you would
- like.
-
- @method propertyWillChange
- @param {String} key The property key that is about to change.
- @return {Ember.Observable}
- */
- propertyWillChange: function(keyName){
- Ember.propertyWillChange(this, keyName);
- return this;
- },
-
- /**
- Notify the observer system that a property has just changed.
-
- Sometimes you need to change a value directly or indirectly without
- actually calling `get()` or `set()` on it. In this case, you can use this
- method and `propertyWillChange()` instead. Calling these two methods
- together will notify all observers that the property has potentially
- changed value.
-
- Note that you must always call `propertyWillChange` and `propertyDidChange`
- as a pair. If you do not, it may get the property change groups out of
- order and cause notifications to be delivered more often than you would
- like.
-
- @method propertyDidChange
- @param {String} keyName The property key that has just changed.
- @return {Ember.Observable}
- */
- propertyDidChange: function(keyName) {
- Ember.propertyDidChange(this, keyName);
- return this;
- },
-
- /**
- Convenience method to call `propertyWillChange` and `propertyDidChange` in
- succession.
-
- @method notifyPropertyChange
- @param {String} keyName The property key to be notified about.
- @return {Ember.Observable}
- */
- notifyPropertyChange: function(keyName) {
- this.propertyWillChange(keyName);
- this.propertyDidChange(keyName);
- return this;
- },
-
- addBeforeObserver: function(key, target, method) {
- Ember.addBeforeObserver(this, key, target, method);
- },
-
- /**
- Adds an observer on a property.
-
- This is the core method used to register an observer for a property.
-
- Once you call this method, anytime the key's value is set, your observer
- will be notified. Note that the observers are triggered anytime the
- value is set, regardless of whether it has actually changed. Your
- observer should be prepared to handle that.
-
- You can also pass an optional context parameter to this method. The
- context will be passed to your observer method whenever it is triggered.
- Note that if you add the same target/method pair on a key multiple times
- with different context parameters, your observer will only be called once
- with the last context you passed.
-
- ### Observer Methods
-
- Observer methods you pass should generally have the following signature if
- you do not pass a `context` parameter:
-
- ```javascript
- fooDidChange: function(sender, key, value, rev) { };
- ```
-
- The sender is the object that changed. The key is the property that
- changes. The value property is currently reserved and unused. The rev
- is the last property revision of the object when it changed, which you can
- use to detect if the key value has really changed or not.
-
- If you pass a `context` parameter, the context will be passed before the
- revision like so:
-
- ```javascript
- fooDidChange: function(sender, key, value, context, rev) { };
- ```
-
- Usually you will not need the value, context or revision parameters at
- the end. In this case, it is common to write observer methods that take
- only a sender and key value as parameters or, if you aren't interested in
- any of these values, to write an observer that has no parameters at all.
-
- @method addObserver
- @param {String} key The key to observer
- @param {Object} target The target object to invoke
- @param {String|Function} method The method to invoke.
- @return {Ember.Object} self
- */
- addObserver: function(key, target, method) {
- Ember.addObserver(this, key, target, method);
- },
-
- /**
- Remove an observer you have previously registered on this object. Pass
- the same key, target, and method you passed to `addObserver()` and your
- target will no longer receive notifications.
-
- @method removeObserver
- @param {String} key The key to observer
- @param {Object} target The target object to invoke
- @param {String|Function} method The method to invoke.
- @return {Ember.Observable} receiver
- */
- removeObserver: function(key, target, method) {
- Ember.removeObserver(this, key, target, method);
- },
-
- /**
- Returns `true` if the object currently has observers registered for a
- particular key. You can use this method to potentially defer performing
- an expensive action until someone begins observing a particular property
- on the object.
-
- @method hasObserverFor
- @param {String} key Key to check
- @return {Boolean}
- */
- hasObserverFor: function(key) {
- return Ember.hasListeners(this, key+':change');
- },
-
- /**
- @deprecated
- @method getPath
- @param {String} path The property path to retrieve
- @return {Object} The property value or undefined.
- */
- getPath: function(path) {
- Ember.deprecate("getPath is deprecated since get now supports paths");
- return this.get(path);
- },
-
- /**
- @deprecated
- @method setPath
- @param {String} path The path to the property that will be set
- @param {Object} value The value to set or `null`.
- @return {Ember.Observable}
- */
- setPath: function(path, value) {
- Ember.deprecate("setPath is deprecated since set now supports paths");
- return this.set(path, value);
- },
-
- /**
- Retrieves the value of a property, or a default value in the case that the
- property returns `undefined`.
-
- ```javascript
- person.getWithDefault('lastName', 'Doe');
- ```
-
- @method getWithDefault
- @param {String} keyName The name of the property to retrieve
- @param {Object} defaultValue The value to return if the property value is undefined
- @return {Object} The property value or the defaultValue.
- */
- getWithDefault: function(keyName, defaultValue) {
- return Ember.getWithDefault(this, keyName, defaultValue);
- },
-
- /**
- Set the value of a property to the current value plus some amount.
-
- ```javascript
- person.incrementProperty('age');
- team.incrementProperty('score', 2);
- ```
-
- @method incrementProperty
- @param {String} keyName The name of the property to increment
- @param {Object} increment The amount to increment by. Defaults to 1
- @return {Object} The new property value
- */
- incrementProperty: function(keyName, increment) {
- if (!increment) { increment = 1; }
- set(this, keyName, (get(this, keyName) || 0)+increment);
- return get(this, keyName);
- },
-
- /**
- Set the value of a property to the current value minus some amount.
-
- ```javascript
- player.decrementProperty('lives');
- orc.decrementProperty('health', 5);
- ```
-
- @method decrementProperty
- @param {String} keyName The name of the property to decrement
- @param {Object} increment The amount to decrement by. Defaults to 1
- @return {Object} The new property value
- */
- decrementProperty: function(keyName, increment) {
- if (!increment) { increment = 1; }
- set(this, keyName, (get(this, keyName) || 0)-increment);
- return get(this, keyName);
- },
-
- /**
- Set the value of a boolean property to the opposite of it's
- current value.
-
- ```javascript
- starship.toggleProperty('warpDriveEnaged');
- ```
-
- @method toggleProperty
- @param {String} keyName The name of the property to toggle
- @return {Object} The new property value
- */
- toggleProperty: function(keyName) {
- set(this, keyName, !get(this, keyName));
- return get(this, keyName);
- },
-
- /**
- Returns the cached value of a computed property, if it exists.
- This allows you to inspect the value of a computed property
- without accidentally invoking it if it is intended to be
- generated lazily.
-
- @method cacheFor
- @param {String} keyName
- @return {Object} The cached value of the computed property, if any
- */
- cacheFor: function(keyName) {
- return Ember.cacheFor(this, keyName);
- },
-
- // intended for debugging purposes
- observersForKey: function(keyName) {
- return Ember.observersFor(this, keyName);
- }
-});
-
-
-})();
-
-
-
-(function() {
-/**
-@module ember
-@submodule ember-runtime
-*/
-
-var get = Ember.get, set = Ember.set;
-
-/**
-@class TargetActionSupport
-@namespace Ember
-@extends Ember.Mixin
-*/
-Ember.TargetActionSupport = Ember.Mixin.create({
- target: null,
- action: null,
-
- targetObject: Ember.computed(function() {
- var target = get(this, 'target');
-
- if (Ember.typeOf(target) === "string") {
- var value = get(this, target);
- if (value === undefined) { value = get(Ember.lookup, target); }
- return value;
- } else {
- return target;
</del><ins>+ if (typeof actionContext === 'undefined') {
+ actionContext = get(this, 'actionContextObject') || this;
</ins><span class="cx"> }
</span><del>- }).property('target'),
</del><span class="cx">
</span><del>- triggerAction: function() {
- var action = get(this, 'action'),
- target = get(this, 'targetObject');
-
</del><span class="cx"> if (target && action) {
</span><span class="cx"> var ret;
</span><span class="cx">
</span><del>- if (typeof target.send === 'function') {
- ret = target.send(action, this);
</del><ins>+ if (target.send) {
+ ret = target.send.apply(target, args(actionContext, action));
</ins><span class="cx"> } else {
</span><del>- if (typeof action === 'string') {
- action = target[action];
- }
- ret = action.call(target, this);
</del><ins>+ Ember.assert("The action '" + action + "' did not exist on " + target, typeof target[action] === 'function');
+ ret = target[action].apply(target, args(actionContext));
</ins><span class="cx"> }
</span><ins>+
</ins><span class="cx"> if (ret !== false) ret = true;
</span><span class="cx">
</span><span class="cx"> return ret;
</span><span class="lines">@@ -9765,9 +17538,18 @@
</span><span class="cx"> // outputs: 'Our person has greeted'
</span><span class="cx"> ```
</span><span class="cx">
</span><ins>+ You can also chain multiple event subscriptions:
+
+ ```javascript
+ person.on('greet', function() {
+ console.log('Our person has greeted');
+ }).one('greet', function() {
+ console.log('Offer one-time special');
+ }).off('event', this, forgetThis);
+ ```
+
</ins><span class="cx"> @class Evented
</span><span class="cx"> @namespace Ember
</span><del>- @extends Ember.Mixin
</del><span class="cx"> */
</span><span class="cx"> Ember.Evented = Ember.Mixin.create({
</span><span class="cx">
</span><span class="lines">@@ -9789,9 +17571,11 @@
</span><span class="cx"> @param {String} name The name of the event
</span><span class="cx"> @param {Object} [target] The "this" binding for the callback
</span><span class="cx"> @param {Function} method The callback to execute
</span><ins>+ @return this
</ins><span class="cx"> */
</span><span class="cx"> on: function(name, target, method) {
</span><span class="cx"> Ember.addListener(this, name, target, method);
</span><ins>+ return this;
</ins><span class="cx"> },
</span><span class="cx">
</span><span class="cx"> /**
</span><span class="lines">@@ -9807,6 +17591,7 @@
</span><span class="cx"> @param {String} name The name of the event
</span><span class="cx"> @param {Object} [target] The "this" binding for the callback
</span><span class="cx"> @param {Function} method The callback to execute
</span><ins>+ @return this
</ins><span class="cx"> */
</span><span class="cx"> one: function(name, target, method) {
</span><span class="cx"> if (!method) {
</span><span class="lines">@@ -9815,6 +17600,7 @@
</span><span class="cx"> }
</span><span class="cx">
</span><span class="cx"> Ember.addListener(this, name, target, method, true);
</span><ins>+ return this;
</ins><span class="cx"> },
</span><span class="cx">
</span><span class="cx"> /**
</span><span class="lines">@@ -9843,21 +17629,18 @@
</span><span class="cx"> Ember.sendEvent(this, name, args);
</span><span class="cx"> },
</span><span class="cx">
</span><del>- fire: function(name) {
- Ember.deprecate("Ember.Evented#fire() has been deprecated in favor of trigger() for compatibility with jQuery. It will be removed in 1.0. Please update your code to call trigger() instead.");
- this.trigger.apply(this, arguments);
- },
-
</del><span class="cx"> /**
</span><del>- Cancels subscription for give name, target, and method.
</del><ins>+ Cancels subscription for given name, target, and method.
</ins><span class="cx">
</span><span class="cx"> @method off
</span><span class="cx"> @param {String} name The name of the event
</span><span class="cx"> @param {Object} target The target of the subscription
</span><span class="cx"> @param {Function} method The function of the subscription
</span><ins>+ @return this
</ins><span class="cx"> */
</span><span class="cx"> off: function(name, target, method) {
</span><span class="cx"> Ember.removeListener(this, name, target, method);
</span><ins>+ return this;
</ins><span class="cx"> },
</span><span class="cx">
</span><span class="cx"> /**
</span><span class="lines">@@ -9879,8 +17662,13 @@
</span><span class="cx"> (function() {
</span><span class="cx"> var RSVP = requireModule("rsvp");
</span><span class="cx">
</span><del>-RSVP.async = function(callback, binding) {
- Ember.run.schedule('actions', binding, callback);
</del><ins>+RSVP.configure('async', function(callback, promise) {
+ Ember.run.schedule('actions', promise, callback, promise);
+});
+
+RSVP.Promise.prototype.fail = function(callback, label){
+ Ember.deprecate('RSVP.Promise.fail has been renamed as RSVP.Promise.catch');
+ return this['catch'](callback, label);
</ins><span class="cx"> };
</span><span class="cx">
</span><span class="cx"> /**
</span><span class="lines">@@ -9888,25 +17676,36 @@
</span><span class="cx"> @submodule ember-runtime
</span><span class="cx"> */
</span><span class="cx">
</span><del>-var get = Ember.get,
- slice = Array.prototype.slice;
</del><ins>+var get = Ember.get;
</ins><span class="cx">
</span><span class="cx"> /**
</span><span class="cx"> @class Deferred
</span><span class="cx"> @namespace Ember
</span><del>- @extends Ember.Mixin
</del><span class="cx"> */
</span><span class="cx"> Ember.DeferredMixin = Ember.Mixin.create({
</span><span class="cx"> /**
</span><span class="cx"> Add handlers to be called when the Deferred object is resolved or rejected.
</span><span class="cx">
</span><span class="cx"> @method then
</span><del>- @param {Function} doneCallback a callback function to be called when done
- @param {Function} failCallback a callback function to be called when failed
</del><ins>+ @param {Function} resolve a callback function to be called when done
+ @param {Function} reject a callback function to be called when failed
</ins><span class="cx"> */
</span><del>- then: function(doneCallback, failCallback) {
- var promise = get(this, 'promise');
- return promise.then.apply(promise, arguments);
</del><ins>+ then: function(resolve, reject, label) {
+ var deferred, promise, entity;
+
+ entity = this;
+ deferred = get(this, '_deferred');
+ promise = deferred.promise;
+
+ function fulfillmentHandler(fulfillment) {
+ if (fulfillment === promise) {
+ return resolve(entity);
+ } else {
+ return resolve(fulfillment);
+ }
+ }
+
+ return promise.then(resolve && fulfillmentHandler, reject, label);
</ins><span class="cx"> },
</span><span class="cx">
</span><span class="cx"> /**
</span><span class="lines">@@ -9915,7 +17714,16 @@
</span><span class="cx"> @method resolve
</span><span class="cx"> */
</span><span class="cx"> resolve: function(value) {
</span><del>- get(this, 'promise').resolve(value);
</del><ins>+ var deferred, promise;
+
+ deferred = get(this, '_deferred');
+ promise = deferred.promise;
+
+ if (value === this) {
+ deferred.resolve(promise);
+ } else {
+ deferred.resolve(value);
+ }
</ins><span class="cx"> },
</span><span class="cx">
</span><span class="cx"> /**
</span><span class="lines">@@ -9924,11 +17732,11 @@
</span><span class="cx"> @method reject
</span><span class="cx"> */
</span><span class="cx"> reject: function(value) {
</span><del>- get(this, 'promise').reject(value);
</del><ins>+ get(this, '_deferred').reject(value);
</ins><span class="cx"> },
</span><span class="cx">
</span><del>- promise: Ember.computed(function() {
- return new RSVP.Promise();
</del><ins>+ _deferred: Ember.computed(function() {
+ return RSVP.defer('Ember: DeferredMixin - ' + this);
</ins><span class="cx"> })
</span><span class="cx"> });
</span><span class="cx">
</span><span class="lines">@@ -9938,1224 +17746,941 @@
</span><span class="cx">
</span><span class="cx">
</span><span class="cx"> (function() {
</span><del>-
-})();
-
-
-
-(function() {
-Ember.Container = requireModule('container');
-Ember.Container.set = Ember.set;
-
-})();
-
-
-
-(function() {
</del><span class="cx"> /**
</span><span class="cx"> @module ember
</span><span class="cx"> @submodule ember-runtime
</span><span class="cx"> */
</span><span class="cx">
</span><ins>+var get = Ember.get, typeOf = Ember.typeOf;
</ins><span class="cx">
</span><del>-// NOTE: this object should never be included directly. Instead use Ember.
-// Ember.Object. We only define this separately so that Ember.Set can depend on it
</del><ins>+/**
+ The `Ember.ActionHandler` mixin implements support for moving an `actions`
+ property to an `_actions` property at extend time, and adding `_actions`
+ to the object's mergedProperties list.
</ins><span class="cx">
</span><ins>+ `Ember.ActionHandler` is used internally by Ember in `Ember.View`,
+ `Ember.Controller`, and `Ember.Route`.
</ins><span class="cx">
</span><del>-var set = Ember.set, get = Ember.get,
- o_create = Ember.create,
- o_defineProperty = Ember.platform.defineProperty,
- a_slice = Array.prototype.slice,
- GUID_KEY = Ember.GUID_KEY,
- guidFor = Ember.guidFor,
- generateGuid = Ember.generateGuid,
- meta = Ember.meta,
- rewatch = Ember.rewatch,
- finishChains = Ember.finishChains,
- destroy = Ember.destroy,
- schedule = Ember.run.schedule,
- Mixin = Ember.Mixin,
- applyMixin = Mixin._apply,
- finishPartial = Mixin.finishPartial,
- reopen = Mixin.prototype.reopen,
- MANDATORY_SETTER = Ember.ENV.MANDATORY_SETTER,
- indexOf = Ember.EnumerableUtils.indexOf;
</del><ins>+ @class ActionHandler
+ @namespace Ember
+*/
+Ember.ActionHandler = Ember.Mixin.create({
+ mergedProperties: ['_actions'],
</ins><span class="cx">
</span><del>-var undefinedDescriptor = {
- configurable: true,
- writable: true,
- enumerable: false,
- value: undefined
-};
</del><ins>+ /**
+ The collection of functions, keyed by name, available on this
+ `ActionHandler` as action targets.
</ins><span class="cx">
</span><del>-function makeCtor() {
</del><ins>+ These functions will be invoked when a matching `{{action}}` is triggered
+ from within a template and the application's current route is this route.
</ins><span class="cx">
</span><del>- // Note: avoid accessing any properties on the object since it makes the
- // method a lot faster. This is glue code so we want it to be as fast as
- // possible.
</del><ins>+ Actions can also be invoked from other parts of your application
+ via `ActionHandler#send`.
</ins><span class="cx">
</span><del>- var wasApplied = false, initMixins, initProperties;
</del><ins>+ The `actions` hash will inherit action handlers from
+ the `actions` hash defined on extended parent classes
+ or mixins rather than just replace the entire hash, e.g.:
</ins><span class="cx">
</span><del>- var Class = function() {
- if (!wasApplied) {
- Class.proto(); // prepare prototype...
- }
- o_defineProperty(this, GUID_KEY, undefinedDescriptor);
- o_defineProperty(this, '_super', undefinedDescriptor);
- var m = meta(this);
- m.proto = this;
- if (initMixins) {
- // capture locally so we can clear the closed over variable
- var mixins = initMixins;
- initMixins = null;
- this.reopen.apply(this, mixins);
- }
- if (initProperties) {
- // capture locally so we can clear the closed over variable
- var props = initProperties;
- initProperties = null;
</del><ins>+ ```js
+ App.CanDisplayBanner = Ember.Mixin.create({
+ actions: {
+ displayBanner: function(msg) {
+ // ...
+ }
+ }
+ });
</ins><span class="cx">
</span><del>- var concatenatedProperties = this.concatenatedProperties;
</del><ins>+ App.WelcomeRoute = Ember.Route.extend(App.CanDisplayBanner, {
+ actions: {
+ playMusic: function() {
+ // ...
+ }
+ }
+ });
</ins><span class="cx">
</span><del>- for (var i = 0, l = props.length; i < l; i++) {
- var properties = props[i];
- for (var keyName in properties) {
- if (!properties.hasOwnProperty(keyName)) { continue; }
</del><ins>+ // `WelcomeRoute`, when active, will be able to respond
+ // to both actions, since the actions hash is merged rather
+ // then replaced when extending mixins / parent classes.
+ this.send('displayBanner');
+ this.send('playMusic');
+ ```
</ins><span class="cx">
</span><del>- var value = properties[keyName],
- IS_BINDING = Ember.IS_BINDING;
</del><ins>+ Within a Controller, Route, View or Component's action handler,
+ the value of the `this` context is the Controller, Route, View or
+ Component object:
</ins><span class="cx">
</span><del>- if (IS_BINDING.test(keyName)) {
- var bindings = m.bindings;
- if (!bindings) {
- bindings = m.bindings = {};
- } else if (!m.hasOwnProperty('bindings')) {
- bindings = m.bindings = o_create(m.bindings);
- }
- bindings[keyName] = value;
- }
</del><ins>+ ```js
+ App.SongRoute = Ember.Route.extend({
+ actions: {
+ myAction: function() {
+ this.controllerFor("song");
+ this.transitionTo("other.route");
+ ...
+ }
+ }
+ });
+ ```
</ins><span class="cx">
</span><del>- var desc = m.descs[keyName];
</del><ins>+ It is also possible to call `this._super()` from within an
+ action handler if it overrides a handler defined on a parent
+ class or mixin:
</ins><span class="cx">
</span><del>- Ember.assert("Ember.Object.create no longer supports defining computed properties.", !(value instanceof Ember.ComputedProperty));
- Ember.assert("Ember.Object.create no longer supports defining methods that call _super.", !(typeof value === 'function' && value.toString().indexOf('._super') !== -1));
</del><ins>+ Take for example the following routes:
</ins><span class="cx">
</span><del>- if (concatenatedProperties && indexOf(concatenatedProperties, keyName) >= 0) {
- var baseValue = this[keyName];
</del><ins>+ ```js
+ App.DebugRoute = Ember.Mixin.create({
+ actions: {
+ debugRouteInformation: function() {
+ console.debug("trololo");
+ }
+ }
+ });
</ins><span class="cx">
</span><del>- if (baseValue) {
- if ('function' === typeof baseValue.concat) {
- value = baseValue.concat(value);
- } else {
- value = Ember.makeArray(baseValue).concat(value);
- }
- } else {
- value = Ember.makeArray(value);
- }
- }
</del><ins>+ App.AnnoyingDebugRoute = Ember.Route.extend(App.DebugRoute, {
+ actions: {
+ debugRouteInformation: function() {
+ // also call the debugRouteInformation of mixed in App.DebugRoute
+ this._super();
</ins><span class="cx">
</span><del>- if (desc) {
- desc.set(this, keyName, value);
- } else {
- if (typeof this.setUnknownProperty === 'function' && !(keyName in this)) {
- this.setUnknownProperty(keyName, value);
- } else if (MANDATORY_SETTER) {
- Ember.defineProperty(this, keyName, null, value); // setup mandatory setter
- } else {
- this[keyName] = value;
- }
- }
</del><ins>+ // show additional annoyance
+ window.alert(...);
</ins><span class="cx"> }
</span><span class="cx"> }
</span><del>- }
- finishPartial(this, m);
- delete m.proto;
- finishChains(this);
- this.init.apply(this, arguments);
- };
</del><ins>+ });
+ ```
</ins><span class="cx">
</span><del>- Class.toString = Mixin.prototype.toString;
- Class.willReopen = function() {
- if (wasApplied) {
- Class.PrototypeMixin = Mixin.create(Class.PrototypeMixin);
- }
</del><ins>+ ## Bubbling
</ins><span class="cx">
</span><del>- wasApplied = false;
- };
- Class._initMixins = function(args) { initMixins = args; };
- Class._initProperties = function(args) { initProperties = args; };
</del><ins>+ By default, an action will stop bubbling once a handler defined
+ on the `actions` hash handles it. To continue bubbling the action,
+ you must return `true` from the handler:
</ins><span class="cx">
</span><del>- Class.proto = function() {
- var superclass = Class.superclass;
- if (superclass) { superclass.proto(); }
-
- if (!wasApplied) {
- wasApplied = true;
- Class.PrototypeMixin.applyPartial(Class.prototype);
- rewatch(Class.prototype);
- }
-
- return this.prototype;
- };
-
- return Class;
-
-}
-
-var CoreObject = makeCtor();
-CoreObject.toString = function() { return "Ember.CoreObject"; };
-
-CoreObject.PrototypeMixin = Mixin.create({
- reopen: function() {
- applyMixin(this, arguments, true);
- return this;
- },
-
- isInstance: true,
-
- init: function() {},
-
- /**
- Defines the properties that will be concatenated from the superclass
- (instead of overridden).
-
- By default, when you extend an Ember class a property defined in
- the subclass overrides a property with the same name that is defined
- in the superclass. However, there are some cases where it is preferable
- to build up a property's value by combining the superclass' property
- value with the subclass' value. An example of this in use within Ember
- is the `classNames` property of `Ember.View`.
-
- Here is some sample code showing the difference between a concatenated
- property and a normal one:
-
- ```javascript
- App.BarView = Ember.View.extend({
- someNonConcatenatedProperty: ['bar'],
- classNames: ['bar']
</del><ins>+ ```js
+ App.Router.map(function() {
+ this.resource("album", function() {
+ this.route("song");
+ });
</ins><span class="cx"> });
</span><span class="cx">
</span><del>- App.FooBarView = App.BarView.extend({
- someNonConcatenatedProperty: ['foo'],
- classNames: ['foo'],
</del><ins>+ App.AlbumRoute = Ember.Route.extend({
+ actions: {
+ startPlaying: function() {
+ }
+ }
</ins><span class="cx"> });
</span><span class="cx">
</span><del>- var fooBarView = App.FooBarView.create();
- fooBarView.get('someNonConcatenatedProperty'); // ['foo']
- fooBarView.get('classNames'); // ['ember-view', 'bar', 'foo']
- ```
</del><ins>+ App.AlbumSongRoute = Ember.Route.extend({
+ actions: {
+ startPlaying: function() {
+ // ...
</ins><span class="cx">
</span><del>- This behavior extends to object creation as well. Continuing the
- above example:
-
- ```javascript
- var view = App.FooBarView.create({
- someNonConcatenatedProperty: ['baz'],
- classNames: ['baz']
- })
- view.get('someNonConcatenatedProperty'); // ['baz']
- view.get('classNames'); // ['ember-view', 'bar', 'foo', 'baz']
</del><ins>+ if (actionShouldAlsoBeTriggeredOnParentRoute) {
+ return true;
+ }
+ }
+ }
+ });
</ins><span class="cx"> ```
</span><del>- Adding a single property that is not an array will just add it in the array:
-
- ```javascript
- var view = App.FooBarView.create({
- classNames: 'baz'
- })
- view.get('classNames'); // ['ember-view', 'bar', 'foo', 'baz']
- ```
-
- Using the `concatenatedProperties` property, we can tell to Ember that mix
- the content of the properties.
</del><span class="cx">
</span><del>- In `Ember.View` the `classNameBindings` and `attributeBindings` properties
- are also concatenated, in addition to `classNames`.
-
- This feature is available for you to use throughout the Ember object model,
- although typical app developers are likely to use it infrequently.
-
- @property concatenatedProperties
- @type Array
</del><ins>+ @property actions
+ @type Hash
</ins><span class="cx"> @default null
</span><span class="cx"> */
</span><del>- concatenatedProperties: null,
</del><span class="cx">
</span><span class="cx"> /**
</span><del>- @property isDestroyed
- @default false
- */
- isDestroyed: false,
</del><ins>+ Moves `actions` to `_actions` at extend time. Note that this currently
+ modifies the mixin themselves, which is technically dubious but
+ is practically of little consequence. This may change in the future.
</ins><span class="cx">
</span><del>- /**
- @property isDestroying
- @default false
</del><ins>+ @private
+ @method willMergeMixin
</ins><span class="cx"> */
</span><del>- isDestroying: false,
</del><ins>+ willMergeMixin: function(props) {
+ var hashName;
</ins><span class="cx">
</span><del>- /**
- Destroys an object by setting the `isDestroyed` flag and removing its
- metadata, which effectively destroys observers and bindings.
</del><ins>+ if (!props._actions) {
+ if (typeOf(props.actions) === 'object') {
+ hashName = 'actions';
+ } else if (typeOf(props.events) === 'object') {
+ Ember.deprecate('Action handlers contained in an `events` object are deprecated in favor of putting them in an `actions` object', false);
+ hashName = 'events';
+ }
</ins><span class="cx">
</span><del>- If you try to set a property on a destroyed object, an exception will be
- raised.
</del><ins>+ if (hashName) {
+ props._actions = Ember.merge(props._actions || {}, props[hashName]);
+ }
</ins><span class="cx">
</span><del>- Note that destruction is scheduled for the end of the run loop and does not
- happen immediately.
-
- @method destroy
- @return {Ember.Object} receiver
- */
- destroy: function() {
- if (this._didCallDestroy) { return; }
-
- this.isDestroying = true;
- this._didCallDestroy = true;
-
- if (this.willDestroy) { this.willDestroy(); }
-
- schedule('destroy', this, this._scheduledDestroy);
- return this;
</del><ins>+ delete props[hashName];
+ }
</ins><span class="cx"> },
</span><span class="cx">
</span><del>- /**
- @private
</del><ins>+ send: function(actionName) {
+ var args = [].slice.call(arguments, 1), target;
</ins><span class="cx">
</span><del>- Invoked by the run loop to actually destroy the object. This is
- scheduled for execution by the `destroy` method.
</del><ins>+ if (this._actions && this._actions[actionName]) {
+ if (this._actions[actionName].apply(this, args) === true) {
+ // handler returned true, so this action will bubble
+ } else {
+ return;
+ }
+ } else if (this.deprecatedSend && this.deprecatedSendHandles && this.deprecatedSendHandles(actionName)) {
+ if (this.deprecatedSend.apply(this, [].slice.call(arguments)) === true) {
+ // handler return true, so this action will bubble
+ } else {
+ return;
+ }
+ }
</ins><span class="cx">
</span><del>- @method _scheduledDestroy
- */
- _scheduledDestroy: function() {
- destroy(this);
- set(this, 'isDestroyed', true);
</del><ins>+ if (target = get(this, 'target')) {
+ Ember.assert("The `target` for " + this + " (" + target + ") does not have a `send` method", typeof target.send === 'function');
+ target.send.apply(target, arguments);
+ }
+ }
</ins><span class="cx">
</span><del>- if (this.didDestroy) { this.didDestroy(); }
- },
</del><ins>+});
</ins><span class="cx">
</span><del>- bind: function(to, from) {
- if (!(from instanceof Ember.Binding)) { from = Ember.Binding.from(from); }
- from.to(to).connect(this);
- return from;
- },
</del><ins>+})();
</ins><span class="cx">
</span><del>- /**
- Returns a string representation which attempts to provide more information
- than Javascript's `toString` typically does, in a generic way for all Ember
- objects.
</del><span class="cx">
</span><del>- App.Person = Em.Object.extend()
- person = App.Person.create()
- person.toString() //=> "<App.Person:ember1024>"
</del><span class="cx">
</span><del>- If the object's class is not defined on an Ember namespace, it will
- indicate it is a subclass of the registered superclass:
</del><ins>+(function() {
+var set = Ember.set, get = Ember.get,
+ resolve = Ember.RSVP.resolve,
+ rethrow = Ember.RSVP.rethrow,
+ not = Ember.computed.not,
+ or = Ember.computed.or;
</ins><span class="cx">
</span><del>- Student = App.Person.extend()
- student = Student.create()
- student.toString() //=> "<(subclass of App.Person):ember1025>"
</del><ins>+/**
+ @module ember
+ @submodule ember-runtime
+ */
</ins><span class="cx">
</span><del>- If the method `toStringExtension` is defined, its return value will be
- included in the output.
</del><ins>+function observePromise(proxy, promise) {
+ promise.then(function(value) {
+ set(proxy, 'isFulfilled', true);
+ set(proxy, 'content', value);
+ }, function(reason) {
+ set(proxy, 'isRejected', true);
+ set(proxy, 'reason', reason);
+ // don't re-throw, as we are merely observing
+ }, "Ember: PromiseProxy");
+}
</ins><span class="cx">
</span><del>- App.Teacher = App.Person.extend({
- toStringExtension: function(){
- return this.get('fullName');
- }
- });
- teacher = App.Teacher.create()
- teacher.toString(); // #=> "<App.Teacher:ember1026:Tom Dale>"
</del><ins>+/**
+ A low level mixin making ObjectProxy, ObjectController or ArrayController's promise aware.
</ins><span class="cx">
</span><del>- @method toString
- @return {String} string representation
- */
- toString: function toString() {
- var hasToStringExtension = typeof this.toStringExtension === 'function',
- extension = hasToStringExtension ? ":" + this.toStringExtension() : '';
- var ret = '<'+this.constructor.toString()+':'+guidFor(this)+extension+'>';
- this.toString = makeToString(ret);
- return ret;
- }
-});
</del><ins>+ ```javascript
+ var ObjectPromiseController = Ember.ObjectController.extend(Ember.PromiseProxyMixin);
</ins><span class="cx">
</span><del>-CoreObject.PrototypeMixin.ownerConstructor = CoreObject;
</del><ins>+ var controller = ObjectPromiseController.create({
+ promise: $.getJSON('/some/remote/data.json')
+ });
</ins><span class="cx">
</span><del>-function makeToString(ret) {
- return function() { return ret; };
-}
</del><ins>+ controller.then(function(json){
+ // the json
+ }, function(reason) {
+ // the reason why you have no json
+ });
+ ```
</ins><span class="cx">
</span><del>-if (Ember.config.overridePrototypeMixin) {
- Ember.config.overridePrototypeMixin(CoreObject.PrototypeMixin);
-}
</del><ins>+ the controller has bindable attributes which
+ track the promises life cycle
</ins><span class="cx">
</span><del>-CoreObject.__super__ = null;
</del><ins>+ ```javascript
+ controller.get('isPending') //=> true
+ controller.get('isSettled') //=> false
+ controller.get('isRejected') //=> false
+ controller.get('isFulfilled') //=> false
+ ```
</ins><span class="cx">
</span><del>-var ClassMixin = Mixin.create({
</del><ins>+ When the the $.getJSON completes, and the promise is fulfilled
+ with json, the life cycle attributes will update accordingly.
</ins><span class="cx">
</span><del>- ClassMixin: Ember.required(),
</del><ins>+ ```javascript
+ controller.get('isPending') //=> false
+ controller.get('isSettled') //=> true
+ controller.get('isRejected') //=> false
+ controller.get('isFulfilled') //=> true
+ ```
</ins><span class="cx">
</span><del>- PrototypeMixin: Ember.required(),
</del><ins>+ As the controller is an ObjectController, and the json now its content,
+ all the json properties will be available directly from the controller.
</ins><span class="cx">
</span><del>- isClass: true,
</del><ins>+ ```javascript
+ // Assuming the following json:
+ {
+ firstName: 'Stefan',
+ lastName: 'Penner'
+ }
</ins><span class="cx">
</span><del>- isMethod: false,
</del><ins>+ // both properties will accessible on the controller
+ controller.get('firstName') //=> 'Stefan'
+ controller.get('lastName') //=> 'Penner'
+ ```
</ins><span class="cx">
</span><del>- extend: function() {
- var Class = makeCtor(), proto;
- Class.ClassMixin = Mixin.create(this.ClassMixin);
- Class.PrototypeMixin = Mixin.create(this.PrototypeMixin);
</del><ins>+ If the controller is backing a template, the attributes are
+ bindable from within that template
</ins><span class="cx">
</span><del>- Class.ClassMixin.ownerConstructor = Class;
- Class.PrototypeMixin.ownerConstructor = Class;
</del><ins>+ ```handlebars
+ {{#if isPending}}
+ loading...
+ {{else}}
+ firstName: {{firstName}}
+ lastName: {{lastName}}
+ {{/if}}
+ ```
+ @class Ember.PromiseProxyMixin
+*/
+Ember.PromiseProxyMixin = Ember.Mixin.create({
+ /**
+ If the proxied promise is rejected this will contain the reason
+ provided.
</ins><span class="cx">
</span><del>- reopen.apply(Class.PrototypeMixin, arguments);
</del><ins>+ @property reason
+ @default null
+ */
+ reason: null,
</ins><span class="cx">
</span><del>- Class.superclass = this;
- Class.__super__ = this.prototype;
</del><ins>+ /**
+ Once the proxied promise has settled this will become `false`.
</ins><span class="cx">
</span><del>- proto = Class.prototype = o_create(this.prototype);
- proto.constructor = Class;
- generateGuid(proto, 'ember');
- meta(proto).proto = proto; // this will disable observers on prototype
</del><ins>+ @property isPending
+ @default true
+ */
+ isPending: not('isSettled').readOnly(),
</ins><span class="cx">
</span><del>- Class.ClassMixin.apply(Class);
- return Class;
- },
</del><ins>+ /**
+ Once the proxied promise has settled this will become `true`.
</ins><span class="cx">
</span><del>- createWithMixins: function() {
- var C = this;
- if (arguments.length>0) { this._initMixins(arguments); }
- return new C();
- },
</del><ins>+ @property isSettled
+ @default false
+ */
+ isSettled: or('isRejected', 'isFulfilled').readOnly(),
</ins><span class="cx">
</span><del>- create: function() {
- var C = this;
- if (arguments.length>0) { this._initProperties(arguments); }
- return new C();
- },
</del><ins>+ /**
+ Will become `true` if the proxied promise is rejected.
</ins><span class="cx">
</span><del>- reopen: function() {
- this.willReopen();
- reopen.apply(this.PrototypeMixin, arguments);
- return this;
- },
</del><ins>+ @property isRejected
+ @default false
+ */
+ isRejected: false,
</ins><span class="cx">
</span><del>- reopenClass: function() {
- reopen.apply(this.ClassMixin, arguments);
- applyMixin(this, arguments, false);
- return this;
- },
</del><ins>+ /**
+ Will become `true` if the proxied promise is fulfilled.
</ins><span class="cx">
</span><del>- detect: function(obj) {
- if ('function' !== typeof obj) { return false; }
- while(obj) {
- if (obj===this) { return true; }
- obj = obj.superclass;
- }
- return false;
- },
</del><ins>+ @property isFullfilled
+ @default false
+ */
+ isFulfilled: false,
</ins><span class="cx">
</span><del>- detectInstance: function(obj) {
- return obj instanceof this;
- },
-
</del><span class="cx"> /**
</span><del>- In some cases, you may want to annotate computed properties with additional
- metadata about how they function or what values they operate on. For
- example, computed property functions may close over variables that are then
- no longer available for introspection.
</del><ins>+ The promise whose fulfillment value is being proxied by this object.
</ins><span class="cx">
</span><del>- You can pass a hash of these values to a computed property like this:
</del><ins>+ This property must be specified upon creation, and should not be
+ changed once created.
</ins><span class="cx">
</span><ins>+ Example:
+
</ins><span class="cx"> ```javascript
</span><del>- person: function() {
- var personId = this.get('personId');
- return App.Person.create({ id: personId });
- }.property().meta({ type: App.Person })
</del><ins>+ Ember.ObjectController.extend(Ember.PromiseProxyMixin).create({
+ promise: <thenable>
+ });
</ins><span class="cx"> ```
</span><span class="cx">
</span><del>- Once you've done this, you can retrieve the values saved to the computed
- property from your class like this:
</del><ins>+ @property promise
+ */
+ promise: Ember.computed(function(key, promise) {
+ if (arguments.length === 2) {
+ promise = resolve(promise);
+ observePromise(this, promise);
+ return promise.then(); // fork the promise.
+ } else {
+ throw new Ember.Error("PromiseProxy's promise must be set");
+ }
+ }),
</ins><span class="cx">
</span><del>- ```javascript
- MyClass.metaForProperty('person');
- ```
</del><ins>+ /**
+ An alias to the proxied promise's `then`.
</ins><span class="cx">
</span><del>- This will return the original hash that was passed to `meta()`.
</del><ins>+ See RSVP.Promise.then.
</ins><span class="cx">
</span><del>- @method metaForProperty
- @param key {String} property name
</del><ins>+ @method then
+ @param {Function} callback
+ @return {RSVP.Promise}
</ins><span class="cx"> */
</span><del>- metaForProperty: function(key) {
- var desc = meta(this.proto(), false).descs[key];
</del><ins>+ then: promiseAlias('then'),
</ins><span class="cx">
</span><del>- Ember.assert("metaForProperty() could not find a computed property with key '"+key+"'.", !!desc && desc instanceof Ember.ComputedProperty);
- return desc._meta || {};
- },
-
</del><span class="cx"> /**
</span><del>- Iterate over each computed property for the class, passing its name
- and any associated metadata (see `metaForProperty`) to the callback.
</del><ins>+ An alias to the proxied promise's `catch`.
</ins><span class="cx">
</span><del>- @method eachComputedProperty
</del><ins>+ See RSVP.Promise.catch.
+
+ @method catch
</ins><span class="cx"> @param {Function} callback
</span><del>- @param {Object} binding
</del><ins>+ @return {RSVP.Promise}
</ins><span class="cx"> */
</span><del>- eachComputedProperty: function(callback, binding) {
- var proto = this.proto(),
- descs = meta(proto).descs,
- empty = {},
- property;
</del><ins>+ 'catch': promiseAlias('catch'),
</ins><span class="cx">
</span><del>- for (var name in descs) {
- property = descs[name];
</del><ins>+ /**
+ An alias to the proxied promise's `finally`.
</ins><span class="cx">
</span><del>- if (property instanceof Ember.ComputedProperty) {
- callback.call(binding || this, name, property._meta || empty);
- }
- }
- }
</del><ins>+ See RSVP.Promise.finally.
</ins><span class="cx">
</span><ins>+ @method finally
+ @param {Function} callback
+ @return {RSVP.Promise}
+ */
+ 'finally': promiseAlias('finally')
+
</ins><span class="cx"> });
</span><span class="cx">
</span><del>-ClassMixin.ownerConstructor = CoreObject;
-
-if (Ember.config.overrideClassMixin) {
- Ember.config.overrideClassMixin(ClassMixin);
</del><ins>+function promiseAlias(name) {
+ return function () {
+ var promise = get(this, 'promise');
+ return promise[name].apply(promise, arguments);
+ };
</ins><span class="cx"> }
</span><span class="cx">
</span><del>-CoreObject.ClassMixin = ClassMixin;
-ClassMixin.apply(CoreObject);
-
-/**
- @class CoreObject
- @namespace Ember
-*/
-Ember.CoreObject = CoreObject;
-
</del><span class="cx"> })();
</span><span class="cx">
</span><span class="cx">
</span><span class="cx">
</span><span class="cx"> (function() {
</span><del>-/**
-@module ember
-@submodule ember-runtime
-*/
</del><span class="cx">
</span><del>-var get = Ember.get, set = Ember.set, guidFor = Ember.guidFor, none = Ember.isNone;
</del><ins>+})();
</ins><span class="cx">
</span><del>-/**
- An unordered collection of objects.
</del><span class="cx">
</span><del>- A Set works a bit like an array except that its items are not ordered. You
- can create a set to efficiently test for membership for an object. You can
- also iterate through a set just like an array, even accessing objects by
- index, however there is no guarantee as to their order.
</del><span class="cx">
</span><del>- All Sets are observable via the Enumerable Observer API - which works
- on any enumerable object including both Sets and Arrays.
</del><ins>+(function() {
+var get = Ember.get,
+ forEach = Ember.EnumerableUtils.forEach,
+ RETAIN = 'r',
+ INSERT = 'i',
+ DELETE = 'd';
</ins><span class="cx">
</span><del>- ## Creating a Set
</del><ins>+/**
+ An `Ember.TrackedArray` tracks array operations. It's useful when you want to
+ lazily compute the indexes of items in an array after they've been shifted by
+ subsequent operations.
</ins><span class="cx">
</span><del>- You can create a set like you would most objects using
- `new Ember.Set()`. Most new sets you create will be empty, but you can
- also initialize the set with some content by passing an array or other
- enumerable of objects to the constructor.
-
- Finally, you can pass in an existing set and the set will be copied. You
- can also create a copy of a set by calling `Ember.Set#copy()`.
-
- ```javascript
- // creates a new empty set
- var foundNames = new Ember.Set();
-
- // creates a set with four names in it.
- var names = new Ember.Set(["Charles", "Tom", "Juan", "Alex"]); // :P
-
- // creates a copy of the names set.
- var namesCopy = new Ember.Set(names);
-
- // same as above.
- var anotherNamesCopy = names.copy();
- ```
-
- ## Adding/Removing Objects
-
- You generally add or remove objects from a set using `add()` or
- `remove()`. You can add any type of object including primitives such as
- numbers, strings, and booleans.
-
- Unlike arrays, objects can only exist one time in a set. If you call `add()`
- on a set with the same object multiple times, the object will only be added
- once. Likewise, calling `remove()` with the same object multiple times will
- remove the object the first time and have no effect on future calls until
- you add the object to the set again.
-
- NOTE: You cannot add/remove `null` or `undefined` to a set. Any attempt to do
- so will be ignored.
-
- In addition to add/remove you can also call `push()`/`pop()`. Push behaves
- just like `add()` but `pop()`, unlike `remove()` will pick an arbitrary
- object, remove it and return it. This is a good way to use a set as a job
- queue when you don't care which order the jobs are executed in.
-
- ## Testing for an Object
-
- To test for an object's presence in a set you simply call
- `Ember.Set#contains()`.
-
- ## Observing changes
-
- When using `Ember.Set`, you can observe the `"[]"` property to be
- alerted whenever the content changes. You can also add an enumerable
- observer to the set to be notified of specific objects that are added and
- removed from the set. See `Ember.Enumerable` for more information on
- enumerables.
-
- This is often unhelpful. If you are filtering sets of objects, for instance,
- it is very inefficient to re-filter all of the items each time the set
- changes. It would be better if you could just adjust the filtered set based
- on what was changed on the original set. The same issue applies to merging
- sets, as well.
-
- ## Other Methods
-
- `Ember.Set` primary implements other mixin APIs. For a complete reference
- on the methods you will use with `Ember.Set`, please consult these mixins.
- The most useful ones will be `Ember.Enumerable` and
- `Ember.MutableEnumerable` which implement most of the common iterator
- methods you are used to on Array.
-
- Note that you can also use the `Ember.Copyable` and `Ember.Freezable`
- APIs on `Ember.Set` as well. Once a set is frozen it can no longer be
- modified. The benefit of this is that when you call `frozenCopy()` on it,
- Ember will avoid making copies of the set. This allows you to write
- code that can know with certainty when the underlying set data will or
- will not be modified.
-
- @class Set
</del><ins>+ @class TrackedArray
</ins><span class="cx"> @namespace Ember
</span><del>- @extends Ember.CoreObject
- @uses Ember.MutableEnumerable
- @uses Ember.Copyable
- @uses Ember.Freezable
- @since Ember 0.9
</del><ins>+ @param {array} [items=[]] The array to be tracked. This is used just to get
+ the initial items for the starting state of retain:n.
</ins><span class="cx"> */
</span><del>-Ember.Set = Ember.CoreObject.extend(Ember.MutableEnumerable, Ember.Copyable, Ember.Freezable,
- /** @scope Ember.Set.prototype */ {
</del><ins>+Ember.TrackedArray = function (items) {
+ if (arguments.length < 1) { items = []; }
</ins><span class="cx">
</span><del>- // ..........................................................
- // IMPLEMENT ENUMERABLE APIS
- //
</del><ins>+ var length = get(items, 'length');
</ins><span class="cx">
</span><del>- /**
- This property will change as the number of objects in the set changes.
</del><ins>+ if (length) {
+ this._operations = [new ArrayOperation(RETAIN, length, items)];
+ } else {
+ this._operations = [];
+ }
+};
</ins><span class="cx">
</span><del>- @property length
- @type number
- @default 0
- */
- length: 0,
</del><ins>+Ember.TrackedArray.RETAIN = RETAIN;
+Ember.TrackedArray.INSERT = INSERT;
+Ember.TrackedArray.DELETE = DELETE;
</ins><span class="cx">
</span><ins>+Ember.TrackedArray.prototype = {
+
</ins><span class="cx"> /**
</span><del>- Clears the set. This is useful if you want to reuse an existing set
- without having to recreate it.
</del><ins>+ Track that `newItems` were added to the tracked array at `index`.
</ins><span class="cx">
</span><del>- ```javascript
- var colors = new Ember.Set(["red", "green", "blue"]);
- colors.length; // 3
- colors.clear();
- colors.length; // 0
- ```
-
- @method clear
- @return {Ember.Set} An empty Set
</del><ins>+ @method addItems
+ @param index
+ @param newItems
</ins><span class="cx"> */
</span><del>- clear: function() {
- if (this.isFrozen) { throw new Error(Ember.FROZEN_ERROR); }
</del><ins>+ addItems: function (index, newItems) {
+ var count = get(newItems, 'length');
+ if (count < 1) { return; }
</ins><span class="cx">
</span><del>- var len = get(this, 'length');
- if (len === 0) { return this; }
</del><ins>+ var match = this._findArrayOperation(index),
+ arrayOperation = match.operation,
+ arrayOperationIndex = match.index,
+ arrayOperationRangeStart = match.rangeStart,
+ composeIndex,
+ splitIndex,
+ splitItems,
+ splitArrayOperation,
+ newArrayOperation;
</ins><span class="cx">
</span><del>- var guid;
</del><ins>+ newArrayOperation = new ArrayOperation(INSERT, count, newItems);
</ins><span class="cx">
</span><del>- this.enumerableContentWillChange(len, 0);
- Ember.propertyWillChange(this, 'firstObject');
- Ember.propertyWillChange(this, 'lastObject');
-
- for (var i=0; i < len; i++){
- guid = guidFor(this[i]);
- delete this[guid];
- delete this[i];
</del><ins>+ if (arrayOperation) {
+ if (!match.split) {
+ // insert left of arrayOperation
+ this._operations.splice(arrayOperationIndex, 0, newArrayOperation);
+ composeIndex = arrayOperationIndex;
+ } else {
+ this._split(arrayOperationIndex, index - arrayOperationRangeStart, newArrayOperation);
+ composeIndex = arrayOperationIndex + 1;
+ }
+ } else {
+ // insert at end
+ this._operations.push(newArrayOperation);
+ composeIndex = arrayOperationIndex;
</ins><span class="cx"> }
</span><span class="cx">
</span><del>- set(this, 'length', 0);
-
- Ember.propertyDidChange(this, 'firstObject');
- Ember.propertyDidChange(this, 'lastObject');
- this.enumerableContentDidChange(len, 0);
-
- return this;
</del><ins>+ this._composeInsert(composeIndex);
</ins><span class="cx"> },
</span><span class="cx">
</span><span class="cx"> /**
</span><del>- Returns true if the passed object is also an enumerable that contains the
- same objects as the receiver.
</del><ins>+ Track that `count` items were removed at `index`.
</ins><span class="cx">
</span><del>- ```javascript
- var colors = ["red", "green", "blue"],
- same_colors = new Ember.Set(colors);
-
- same_colors.isEqual(colors); // true
- same_colors.isEqual(["purple", "brown"]); // false
- ```
-
- @method isEqual
- @param {Ember.Set} obj the other object.
- @return {Boolean}
</del><ins>+ @method removeItems
+ @param index
+ @param count
</ins><span class="cx"> */
</span><del>- isEqual: function(obj) {
- // fail fast
- if (!Ember.Enumerable.detect(obj)) return false;
</del><ins>+ removeItems: function (index, count) {
+ if (count < 1) { return; }
</ins><span class="cx">
</span><del>- var loc = get(this, 'length');
- if (get(obj, 'length') !== loc) return false;
</del><ins>+ var match = this._findArrayOperation(index),
+ arrayOperation = match.operation,
+ arrayOperationIndex = match.index,
+ arrayOperationRangeStart = match.rangeStart,
+ newArrayOperation,
+ composeIndex;
</ins><span class="cx">
</span><del>- while(--loc >= 0) {
- if (!obj.contains(this[loc])) return false;
</del><ins>+ newArrayOperation = new ArrayOperation(DELETE, count);
+ if (!match.split) {
+ // insert left of arrayOperation
+ this._operations.splice(arrayOperationIndex, 0, newArrayOperation);
+ composeIndex = arrayOperationIndex;
+ } else {
+ this._split(arrayOperationIndex, index - arrayOperationRangeStart, newArrayOperation);
+ composeIndex = arrayOperationIndex + 1;
</ins><span class="cx"> }
</span><span class="cx">
</span><del>- return true;
</del><ins>+ return this._composeDelete(composeIndex);
</ins><span class="cx"> },
</span><span class="cx">
</span><span class="cx"> /**
</span><del>- Adds an object to the set. Only non-`null` objects can be added to a set
- and those can only be added once. If the object is already in the set or
- the passed value is null this method will have no effect.
</del><ins>+ Apply all operations, reducing them to retain:n, for `n`, the number of
+ items in the array.
</ins><span class="cx">
</span><del>- This is an alias for `Ember.MutableEnumerable.addObject()`.
</del><ins>+ `callback` will be called for each operation and will be passed the following arguments:
</ins><span class="cx">
</span><del>- ```javascript
- var colors = new Ember.Set();
- colors.add("blue"); // ["blue"]
- colors.add("blue"); // ["blue"]
- colors.add("red"); // ["blue", "red"]
- colors.add(null); // ["blue", "red"]
- colors.add(undefined); // ["blue", "red"]
- ```
</del><ins>+ * {array} items The items for the given operation
+ * {number} offset The computed offset of the items, ie the index in the
+ array of the first item for this operation.
+ * {string} operation The type of the operation. One of
+ `Ember.TrackedArray.{RETAIN, DELETE, INSERT}`
</ins><span class="cx">
</span><del>- @method add
- @param {Object} obj The object to add.
- @return {Ember.Set} The set itself.
</del><ins>+ @method apply
+ @param {function} callback
</ins><span class="cx"> */
</span><del>- add: Ember.aliasMethod('addObject'),
</del><ins>+ apply: function (callback) {
+ var items = [],
+ offset = 0;
</ins><span class="cx">
</span><del>- /**
- Removes the object from the set if it is found. If you pass a `null` value
- or an object that is already not in the set, this method will have no
- effect. This is an alias for `Ember.MutableEnumerable.removeObject()`.
</del><ins>+ forEach(this._operations, function (arrayOperation) {
+ callback(arrayOperation.items, offset, arrayOperation.type);
</ins><span class="cx">
</span><del>- ```javascript
- var colors = new Ember.Set(["red", "green", "blue"]);
- colors.remove("red"); // ["blue", "green"]
- colors.remove("purple"); // ["blue", "green"]
- colors.remove(null); // ["blue", "green"]
- ```
</del><ins>+ if (arrayOperation.type !== DELETE) {
+ offset += arrayOperation.count;
+ items = items.concat(arrayOperation.items);
+ }
+ });
</ins><span class="cx">
</span><del>- @method remove
- @param {Object} obj The object to remove
- @return {Ember.Set} The set itself.
- */
- remove: Ember.aliasMethod('removeObject'),
-
- /**
- Removes the last element from the set and returns it, or `null` if it's empty.
-
- ```javascript
- var colors = new Ember.Set(["green", "blue"]);
- colors.pop(); // "blue"
- colors.pop(); // "green"
- colors.pop(); // null
- ```
-
- @method pop
- @return {Object} The removed object from the set or null.
- */
- pop: function() {
- if (get(this, 'isFrozen')) throw new Error(Ember.FROZEN_ERROR);
- var obj = this.length > 0 ? this[this.length-1] : null;
- this.remove(obj);
- return obj;
</del><ins>+ this._operations = [new ArrayOperation(RETAIN, items.length, items)];
</ins><span class="cx"> },
</span><span class="cx">
</span><span class="cx"> /**
</span><del>- Inserts the given object on to the end of the set. It returns
- the set itself.
</del><ins>+ Return an `ArrayOperationMatch` for the operation that contains the item at `index`.
</ins><span class="cx">
</span><del>- This is an alias for `Ember.MutableEnumerable.addObject()`.
</del><ins>+ @method _findArrayOperation
</ins><span class="cx">
</span><del>- ```javascript
- var colors = new Ember.Set();
- colors.push("red"); // ["red"]
- colors.push("green"); // ["red", "green"]
- colors.push("blue"); // ["red", "green", "blue"]
- ```
-
- @method push
- @return {Ember.Set} The set itself.
</del><ins>+ @param {number} index the index of the item whose operation information
+ should be returned.
+ @private
</ins><span class="cx"> */
</span><del>- push: Ember.aliasMethod('addObject'),
</del><ins>+ _findArrayOperation: function (index) {
+ var arrayOperationIndex,
+ len,
+ split = false,
+ arrayOperation,
+ arrayOperationRangeStart,
+ arrayOperationRangeEnd;
</ins><span class="cx">
</span><del>- /**
- Removes the last element from the set and returns it, or `null` if it's empty.
</del><ins>+ // OPTIMIZE: we could search these faster if we kept a balanced tree.
+ // find leftmost arrayOperation to the right of `index`
+ for (arrayOperationIndex = arrayOperationRangeStart = 0, len = this._operations.length; arrayOperationIndex < len; ++arrayOperationIndex) {
+ arrayOperation = this._operations[arrayOperationIndex];
</ins><span class="cx">
</span><del>- This is an alias for `Ember.Set.pop()`.
</del><ins>+ if (arrayOperation.type === DELETE) { continue; }
</ins><span class="cx">
</span><del>- ```javascript
- var colors = new Ember.Set(["green", "blue"]);
- colors.shift(); // "blue"
- colors.shift(); // "green"
- colors.shift(); // null
- ```
</del><ins>+ arrayOperationRangeEnd = arrayOperationRangeStart + arrayOperation.count - 1;
</ins><span class="cx">
</span><del>- @method shift
- @return {Object} The removed object from the set or null.
- */
- shift: Ember.aliasMethod('pop'),
</del><ins>+ if (index === arrayOperationRangeStart) {
+ break;
+ } else if (index > arrayOperationRangeStart && index <= arrayOperationRangeEnd) {
+ split = true;
+ break;
+ } else {
+ arrayOperationRangeStart = arrayOperationRangeEnd + 1;
+ }
+ }
</ins><span class="cx">
</span><del>- /**
- Inserts the given object on to the end of the set. It returns
- the set itself.
</del><ins>+ return new ArrayOperationMatch(arrayOperation, arrayOperationIndex, split, arrayOperationRangeStart);
+ },
</ins><span class="cx">
</span><del>- This is an alias of `Ember.Set.push()`
</del><ins>+ _split: function (arrayOperationIndex, splitIndex, newArrayOperation) {
+ var arrayOperation = this._operations[arrayOperationIndex],
+ splitItems = arrayOperation.items.slice(splitIndex),
+ splitArrayOperation = new ArrayOperation(arrayOperation.type, splitItems.length, splitItems);
</ins><span class="cx">
</span><del>- ```javascript
- var colors = new Ember.Set();
- colors.unshift("red"); // ["red"]
- colors.unshift("green"); // ["red", "green"]
- colors.unshift("blue"); // ["red", "green", "blue"]
- ```
</del><ins>+ // truncate LHS
+ arrayOperation.count = splitIndex;
+ arrayOperation.items = arrayOperation.items.slice(0, splitIndex);
</ins><span class="cx">
</span><del>- @method unshift
- @return {Ember.Set} The set itself.
- */
- unshift: Ember.aliasMethod('push'),
</del><ins>+ this._operations.splice(arrayOperationIndex + 1, 0, newArrayOperation, splitArrayOperation);
+ },
</ins><span class="cx">
</span><del>- /**
- Adds each object in the passed enumerable to the set.
</del><ins>+ // see SubArray for a better implementation.
+ _composeInsert: function (index) {
+ var newArrayOperation = this._operations[index],
+ leftArrayOperation = this._operations[index-1], // may be undefined
+ rightArrayOperation = this._operations[index+1], // may be undefined
+ leftOp = leftArrayOperation && leftArrayOperation.type,
+ rightOp = rightArrayOperation && rightArrayOperation.type;
</ins><span class="cx">
</span><del>- This is an alias of `Ember.MutableEnumerable.addObjects()`
</del><ins>+ if (leftOp === INSERT) {
+ // merge left
+ leftArrayOperation.count += newArrayOperation.count;
+ leftArrayOperation.items = leftArrayOperation.items.concat(newArrayOperation.items);
</ins><span class="cx">
</span><del>- ```javascript
- var colors = new Ember.Set();
- colors.addEach(["red", "green", "blue"]); // ["red", "green", "blue"]
- ```
-
- @method addEach
- @param {Ember.Enumerable} objects the objects to add.
- @return {Ember.Set} The set itself.
- */
- addEach: Ember.aliasMethod('addObjects'),
-
- /**
- Removes each object in the passed enumerable to the set.
-
- This is an alias of `Ember.MutableEnumerable.removeObjects()`
-
- ```javascript
- var colors = new Ember.Set(["red", "green", "blue"]);
- colors.removeEach(["red", "blue"]); // ["green"]
- ```
-
- @method removeEach
- @param {Ember.Enumerable} objects the objects to remove.
- @return {Ember.Set} The set itself.
- */
- removeEach: Ember.aliasMethod('removeObjects'),
-
- // ..........................................................
- // PRIVATE ENUMERABLE SUPPORT
- //
-
- init: function(items) {
- this._super();
- if (items) this.addObjects(items);
</del><ins>+ if (rightOp === INSERT) {
+ // also merge right (we have split an insert with an insert)
+ leftArrayOperation.count += rightArrayOperation.count;
+ leftArrayOperation.items = leftArrayOperation.items.concat(rightArrayOperation.items);
+ this._operations.splice(index, 2);
+ } else {
+ // only merge left
+ this._operations.splice(index, 1);
+ }
+ } else if (rightOp === INSERT) {
+ // merge right
+ newArrayOperation.count += rightArrayOperation.count;
+ newArrayOperation.items = newArrayOperation.items.concat(rightArrayOperation.items);
+ this._operations.splice(index + 1, 1);
+ }
</ins><span class="cx"> },
</span><span class="cx">
</span><del>- // implement Ember.Enumerable
- nextObject: function(idx) {
- return this[idx];
- },
</del><ins>+ _composeDelete: function (index) {
+ var arrayOperation = this._operations[index],
+ deletesToGo = arrayOperation.count,
+ leftArrayOperation = this._operations[index-1], // may be undefined
+ leftOp = leftArrayOperation && leftArrayOperation.type,
+ nextArrayOperation,
+ nextOp,
+ nextCount,
+ removeNewAndNextOp = false,
+ removedItems = [];
</ins><span class="cx">
</span><del>- // more optimized version
- firstObject: Ember.computed(function() {
- return this.length > 0 ? this[0] : undefined;
- }),
</del><ins>+ if (leftOp === DELETE) {
+ arrayOperation = leftArrayOperation;
+ index -= 1;
+ }
</ins><span class="cx">
</span><del>- // more optimized version
- lastObject: Ember.computed(function() {
- return this.length > 0 ? this[this.length-1] : undefined;
- }),
</del><ins>+ for (var i = index + 1; deletesToGo > 0; ++i) {
+ nextArrayOperation = this._operations[i];
+ nextOp = nextArrayOperation.type;
+ nextCount = nextArrayOperation.count;
</ins><span class="cx">
</span><del>- // implements Ember.MutableEnumerable
- addObject: function(obj) {
- if (get(this, 'isFrozen')) throw new Error(Ember.FROZEN_ERROR);
- if (none(obj)) return this; // nothing to do
</del><ins>+ if (nextOp === DELETE) {
+ arrayOperation.count += nextCount;
+ continue;
+ }
</ins><span class="cx">
</span><del>- var guid = guidFor(obj),
- idx = this[guid],
- len = get(this, 'length'),
- added ;
</del><ins>+ if (nextCount > deletesToGo) {
+ // d:2 {r,i}:5 we reduce the retain or insert, but it stays
+ removedItems = removedItems.concat(nextArrayOperation.items.splice(0, deletesToGo));
+ nextArrayOperation.count -= deletesToGo;
</ins><span class="cx">
</span><del>- if (idx>=0 && idx<len && (this[idx] === obj)) return this; // added
</del><ins>+ // In the case where we truncate the last arrayOperation, we don't need to
+ // remove it; also the deletesToGo reduction is not the entirety of
+ // nextCount
+ i -= 1;
+ nextCount = deletesToGo;
</ins><span class="cx">
</span><del>- added = [obj];
</del><ins>+ deletesToGo = 0;
+ } else {
+ if (nextCount === deletesToGo) {
+ // Handle edge case of d:2 i:2 in which case both operations go away
+ // during composition.
+ removeNewAndNextOp = true;
+ }
+ removedItems = removedItems.concat(nextArrayOperation.items);
+ deletesToGo -= nextCount;
+ }
</ins><span class="cx">
</span><del>- this.enumerableContentWillChange(null, added);
- Ember.propertyWillChange(this, 'lastObject');
-
- len = get(this, 'length');
- this[guid] = len;
- this[len] = obj;
- set(this, 'length', len+1);
-
- Ember.propertyDidChange(this, 'lastObject');
- this.enumerableContentDidChange(null, added);
-
- return this;
- },
-
- // implements Ember.MutableEnumerable
- removeObject: function(obj) {
- if (get(this, 'isFrozen')) throw new Error(Ember.FROZEN_ERROR);
- if (none(obj)) return this; // nothing to do
-
- var guid = guidFor(obj),
- idx = this[guid],
- len = get(this, 'length'),
- isFirst = idx === 0,
- isLast = idx === len-1,
- last, removed;
-
-
- if (idx>=0 && idx<len && (this[idx] === obj)) {
- removed = [obj];
-
- this.enumerableContentWillChange(removed, null);
- if (isFirst) { Ember.propertyWillChange(this, 'firstObject'); }
- if (isLast) { Ember.propertyWillChange(this, 'lastObject'); }
-
- // swap items - basically move the item to the end so it can be removed
- if (idx < len-1) {
- last = this[len-1];
- this[idx] = last;
- this[guidFor(last)] = idx;
</del><ins>+ if (nextOp === INSERT) {
+ // d:2 i:3 will result in delete going away
+ arrayOperation.count -= nextCount;
</ins><span class="cx"> }
</span><ins>+ }
</ins><span class="cx">
</span><del>- delete this[guid];
- delete this[len-1];
- set(this, 'length', len-1);
-
- if (isFirst) { Ember.propertyDidChange(this, 'firstObject'); }
- if (isLast) { Ember.propertyDidChange(this, 'lastObject'); }
- this.enumerableContentDidChange(removed, null);
</del><ins>+ if (arrayOperation.count > 0) {
+ // compose our new delete with possibly several operations to the right of
+ // disparate types
+ this._operations.splice(index+1, i-1-index);
+ } else {
+ // The delete operation can go away; it has merely reduced some other
+ // operation, as in d:3 i:4; it may also have eliminated that operation,
+ // as in d:3 i:3.
+ this._operations.splice(index, removeNewAndNextOp ? 2 : 1);
</ins><span class="cx"> }
</span><span class="cx">
</span><del>- return this;
</del><ins>+ return removedItems;
</ins><span class="cx"> },
</span><span class="cx">
</span><del>- // optimized version
- contains: function(obj) {
- return this[guidFor(obj)]>=0;
- },
-
- copy: function() {
- var C = this.constructor, ret = new C(), loc = get(this, 'length');
- set(ret, 'length', loc);
- while(--loc>=0) {
- ret[loc] = this[loc];
- ret[guidFor(this[loc])] = loc;
- }
- return ret;
- },
-
- toString: function() {
- var len = this.length, idx, array = [];
- for(idx = 0; idx < len; idx++) {
- array[idx] = this[idx];
- }
- return "Ember.Set<%@>".fmt(array.join(','));
</del><ins>+ toString: function () {
+ var str = "";
+ forEach(this._operations, function (operation) {
+ str += " " + operation.type + ":" + operation.count;
+ });
+ return str.substring(1);
</ins><span class="cx"> }
</span><ins>+};
</ins><span class="cx">
</span><del>-});
</del><ins>+/**
+ Internal data structure to represent an array operation.
</ins><span class="cx">
</span><del>-})();
-
-
-
-(function() {
-/**
-@module ember
-@submodule ember-runtime
</del><ins>+ @method ArrayOperation
+ @private
+ @param {string} type The type of the operation. One of
+ `Ember.TrackedArray.{RETAIN, INSERT, DELETE}`
+ @param {number} count The number of items in this operation.
+ @param {array} items The items of the operation, if included. RETAIN and
+ INSERT include their items, DELETE does not.
</ins><span class="cx"> */
</span><ins>+function ArrayOperation (operation, count, items) {
+ this.type = operation; // RETAIN | INSERT | DELETE
+ this.count = count;
+ this.items = items;
+}
</ins><span class="cx">
</span><span class="cx"> /**
</span><del>- `Ember.Object` is the main base class for all Ember objects. It is a subclass
- of `Ember.CoreObject` with the `Ember.Observable` mixin applied. For details,
- see the documentation for each of these.
</del><ins>+ Internal data structure used to include information when looking up operations
+ by item index.
</ins><span class="cx">
</span><del>- @class Object
- @namespace Ember
- @extends Ember.CoreObject
- @uses Ember.Observable
</del><ins>+ @method ArrayOperationMatch
+ @private
+ @param {ArrayOperation} operation
+ @param {number} index The index of `operation` in the array of operations.
+ @param {boolean} split Whether or not the item index searched for would
+ require a split for a new operation type.
+ @param {number} rangeStart The index of the first item in the operation,
+ with respect to the tracked array. The index of the last item can be computed
+ from `rangeStart` and `operation.count`.
</ins><span class="cx"> */
</span><del>-Ember.Object = Ember.CoreObject.extend(Ember.Observable);
-Ember.Object.toString = function() { return "Ember.Object"; };
</del><ins>+function ArrayOperationMatch(operation, index, split, rangeStart) {
+ this.operation = operation;
+ this.index = index;
+ this.split = split;
+ this.rangeStart = rangeStart;
+}
</ins><span class="cx">
</span><span class="cx"> })();
</span><span class="cx">
</span><span class="cx">
</span><span class="cx">
</span><span class="cx"> (function() {
</span><del>-/**
-@module ember
-@submodule ember-runtime
-*/
</del><ins>+var get = Ember.get,
+ forEach = Ember.EnumerableUtils.forEach,
+ RETAIN = 'r',
+ FILTER = 'f';
</ins><span class="cx">
</span><del>-var get = Ember.get, indexOf = Ember.ArrayPolyfills.indexOf;
</del><ins>+function Operation (type, count) {
+ this.type = type;
+ this.count = count;
+}
</ins><span class="cx">
</span><span class="cx"> /**
</span><del>- A Namespace is an object usually used to contain other objects or methods
- such as an application or framework. Create a namespace anytime you want
- to define one of these new containers.
</del><ins>+ An `Ember.SubArray` tracks an array in a way similar to, but more specialized
+ than, `Ember.TrackedArray`. It is useful for keeping track of the indexes of
+ items within a filtered array.
</ins><span class="cx">
</span><del>- # Example Usage
-
- ```javascript
- MyFramework = Ember.Namespace.create({
- VERSION: '1.0.0'
- });
- ```
-
- @class Namespace
</del><ins>+ @class SubArray
</ins><span class="cx"> @namespace Ember
</span><del>- @extends Ember.Object
</del><span class="cx"> */
</span><del>-var Namespace = Ember.Namespace = Ember.Object.extend({
- isNamespace: true,
</del><ins>+Ember.SubArray = function (length) {
+ if (arguments.length < 1) { length = 0; }
</ins><span class="cx">
</span><del>- init: function() {
- Ember.Namespace.NAMESPACES.push(this);
- Ember.Namespace.PROCESSED = false;
- },
-
- toString: function() {
- var name = get(this, 'name');
- if (name) { return name; }
-
- findNamespaces();
- return this[Ember.GUID_KEY+'_name'];
- },
-
- nameClasses: function() {
- processNamespace([this.toString()], this, {});
- },
-
- destroy: function() {
- var namespaces = Ember.Namespace.NAMESPACES;
- Ember.lookup[this.toString()] = undefined;
- namespaces.splice(indexOf.call(namespaces, this), 1);
- this._super();
</del><ins>+ if (length > 0) {
+ this._operations = [new Operation(RETAIN, length)];
+ } else {
+ this._operations = [];
</ins><span class="cx"> }
</span><del>-});
</del><ins>+};
</ins><span class="cx">
</span><del>-Namespace.reopenClass({
- NAMESPACES: [Ember],
- NAMESPACES_BY_ID: {},
- PROCESSED: false,
- processAll: processAllNamespaces,
- byName: function(name) {
- if (!Ember.BOOTED) {
- processAllNamespaces();
- }
</del><ins>+Ember.SubArray.prototype = {
+ /**
+ Track that an item was added to the tracked array.
</ins><span class="cx">
</span><del>- return NAMESPACES_BY_ID[name];
- }
-});
</del><ins>+ @method addItem
</ins><span class="cx">
</span><del>-var NAMESPACES_BY_ID = Namespace.NAMESPACES_BY_ID;
</del><ins>+ @param {number} index The index of the item in the tracked array.
+ @param {boolean} match `true` iff the item is included in the subarray.
</ins><span class="cx">
</span><del>-var hasOwnProp = ({}).hasOwnProperty,
- guidFor = Ember.guidFor;
</del><ins>+ @return {number} The index of the item in the subarray.
+ */
+ addItem: function(index, match) {
+ var returnValue = -1,
+ itemType = match ? RETAIN : FILTER,
+ self = this;
</ins><span class="cx">
</span><del>-function processNamespace(paths, root, seen) {
- var idx = paths.length;
</del><ins>+ this._findOperation(index, function(operation, operationIndex, rangeStart, rangeEnd, seenInSubArray) {
+ var newOperation, splitOperation;
</ins><span class="cx">
</span><del>- NAMESPACES_BY_ID[paths.join('.')] = root;
</del><ins>+ if (itemType === operation.type) {
+ ++operation.count;
+ } else if (index === rangeStart) {
+ // insert to the left of `operation`
+ self._operations.splice(operationIndex, 0, new Operation(itemType, 1));
+ } else {
+ newOperation = new Operation(itemType, 1);
+ splitOperation = new Operation(operation.type, rangeEnd - index + 1);
+ operation.count = index - rangeStart;
</ins><span class="cx">
</span><del>- // Loop over all of the keys in the namespace, looking for classes
- for(var key in root) {
- if (!hasOwnProp.call(root, key)) { continue; }
- var obj = root[key];
</del><ins>+ self._operations.splice(operationIndex + 1, 0, newOperation, splitOperation);
+ }
</ins><span class="cx">
</span><del>- // If we are processing the `Ember` namespace, for example, the
- // `paths` will start with `["Ember"]`. Every iteration through
- // the loop will update the **second** element of this list with
- // the key, so processing `Ember.View` will make the Array
- // `['Ember', 'View']`.
- paths[idx] = key;
</del><ins>+ if (match) {
+ if (operation.type === RETAIN) {
+ returnValue = seenInSubArray + (index - rangeStart);
+ } else {
+ returnValue = seenInSubArray;
+ }
+ }
</ins><span class="cx">
</span><del>- // If we have found an unprocessed class
- if (obj && obj.toString === classToString) {
- // Replace the class' `toString` with the dot-separated path
- // and set its `NAME_KEY`
- obj.toString = makeToString(paths.join('.'));
- obj[NAME_KEY] = paths.join('.');
</del><ins>+ self._composeAt(operationIndex);
+ }, function(seenInSubArray) {
+ self._operations.push(new Operation(itemType, 1));
</ins><span class="cx">
</span><del>- // Support nested namespaces
- } else if (obj && obj.isNamespace) {
- // Skip aliased namespaces
- if (seen[guidFor(obj)]) { continue; }
- seen[guidFor(obj)] = true;
</del><ins>+ if (match) {
+ returnValue = seenInSubArray;
+ }
</ins><span class="cx">
</span><del>- // Process the child namespace
- processNamespace(paths, obj, seen);
- }
- }
</del><ins>+ self._composeAt(self._operations.length-1);
+ });
</ins><span class="cx">
</span><del>- paths.length = idx; // cut out last item
-}
</del><ins>+ return returnValue;
+ },
</ins><span class="cx">
</span><del>-function findNamespaces() {
- var Namespace = Ember.Namespace, lookup = Ember.lookup, obj, isNamespace;
</del><ins>+ /**
+ Track that an item was removed from the tracked array.
</ins><span class="cx">
</span><del>- if (Namespace.PROCESSED) { return; }
</del><ins>+ @method removeItem
</ins><span class="cx">
</span><del>- for (var prop in lookup) {
- // These don't raise exceptions but can cause warnings
- if (prop === "parent" || prop === "top" || prop === "frameElement") { continue; }
</del><ins>+ @param {number} index The index of the item in the tracked array.
</ins><span class="cx">
</span><del>- // get(window.globalStorage, 'isNamespace') would try to read the storage for domain isNamespace and cause exception in Firefox.
- // globalStorage is a storage obsoleted by the WhatWG storage specification. See https://developer.mozilla.org/en/DOM/Storage#globalStorage
- if (prop === "globalStorage" && lookup.StorageList && lookup.globalStorage instanceof lookup.StorageList) { continue; }
- // Unfortunately, some versions of IE don't support window.hasOwnProperty
- if (lookup.hasOwnProperty && !lookup.hasOwnProperty(prop)) { continue; }
</del><ins>+ @return {number} The index of the item in the subarray, or `-1` if the item
+ was not in the subarray.
+ */
+ removeItem: function(index) {
+ var returnValue = -1,
+ self = this;
</ins><span class="cx">
</span><del>- // At times we are not allowed to access certain properties for security reasons.
- // There are also times where even if we can access them, we are not allowed to access their properties.
- try {
- obj = Ember.lookup[prop];
- isNamespace = obj && obj.isNamespace;
- } catch (e) {
- continue;
- }
</del><ins>+ this._findOperation(index, function (operation, operationIndex, rangeStart, rangeEnd, seenInSubArray) {
+ if (operation.type === RETAIN) {
+ returnValue = seenInSubArray + (index - rangeStart);
+ }
</ins><span class="cx">
</span><del>- if (isNamespace) {
- Ember.deprecate("Namespaces should not begin with lowercase.", /^[A-Z]/.test(prop));
- obj[NAME_KEY] = prop;
- }
- }
-}
</del><ins>+ if (operation.count > 1) {
+ --operation.count;
+ } else {
+ self._operations.splice(operationIndex, 1);
+ self._composeAt(operationIndex);
+ }
+ }, function() {
+ throw new Ember.Error("Can't remove an item that has never been added.");
+ });
</ins><span class="cx">
</span><del>-var NAME_KEY = Ember.NAME_KEY = Ember.GUID_KEY + '_name';
</del><ins>+ return returnValue;
+ },
</ins><span class="cx">
</span><del>-function superClassString(mixin) {
- var superclass = mixin.superclass;
- if (superclass) {
- if (superclass[NAME_KEY]) { return superclass[NAME_KEY]; }
- else { return superClassString(superclass); }
- } else {
- return;
- }
-}
</del><span class="cx">
</span><del>-function classToString() {
- if (!Ember.BOOTED && !this[NAME_KEY]) {
- processAllNamespaces();
- }
</del><ins>+ _findOperation: function (index, foundCallback, notFoundCallback) {
+ var operationIndex,
+ len,
+ operation,
+ rangeStart,
+ rangeEnd,
+ seenInSubArray = 0;
</ins><span class="cx">
</span><del>- var ret;
</del><ins>+ // OPTIMIZE: change to balanced tree
+ // find leftmost operation to the right of `index`
+ for (operationIndex = rangeStart = 0, len = this._operations.length; operationIndex < len; rangeStart = rangeEnd + 1, ++operationIndex) {
+ operation = this._operations[operationIndex];
+ rangeEnd = rangeStart + operation.count - 1;
</ins><span class="cx">
</span><del>- if (this[NAME_KEY]) {
- ret = this[NAME_KEY];
- } else {
- var str = superClassString(this);
- if (str) {
- ret = "(subclass of " + str + ")";
- } else {
- ret = "(unknown mixin)";
</del><ins>+ if (index >= rangeStart && index <= rangeEnd) {
+ foundCallback(operation, operationIndex, rangeStart, rangeEnd, seenInSubArray);
+ return;
+ } else if (operation.type === RETAIN) {
+ seenInSubArray += operation.count;
+ }
</ins><span class="cx"> }
</span><del>- this.toString = makeToString(ret);
- }
</del><span class="cx">
</span><del>- return ret;
-}
</del><ins>+ notFoundCallback(seenInSubArray);
+ },
</ins><span class="cx">
</span><del>-function processAllNamespaces() {
- var unprocessedNamespaces = !Namespace.PROCESSED,
- unprocessedMixins = Ember.anyUnprocessedMixins;
</del><ins>+ _composeAt: function(index) {
+ var op = this._operations[index],
+ otherOp;
</ins><span class="cx">
</span><del>- if (unprocessedNamespaces) {
- findNamespaces();
- Namespace.PROCESSED = true;
- }
</del><ins>+ if (!op) {
+ // Composing out of bounds is a no-op, as when removing the last operation
+ // in the list.
+ return;
+ }
</ins><span class="cx">
</span><del>- if (unprocessedNamespaces || unprocessedMixins) {
- var namespaces = Namespace.NAMESPACES, namespace;
- for (var i=0, l=namespaces.length; i<l; i++) {
- namespace = namespaces[i];
- processNamespace([namespace.toString()], namespace, {});
</del><ins>+ if (index > 0) {
+ otherOp = this._operations[index-1];
+ if (otherOp.type === op.type) {
+ op.count += otherOp.count;
+ this._operations.splice(index-1, 1);
+ --index;
+ }
</ins><span class="cx"> }
</span><span class="cx">
</span><del>- Ember.anyUnprocessedMixins = false;
</del><ins>+ if (index < this._operations.length-1) {
+ otherOp = this._operations[index+1];
+ if (otherOp.type === op.type) {
+ op.count += otherOp.count;
+ this._operations.splice(index+1, 1);
+ }
+ }
+ },
+
+ toString: function () {
+ var str = "";
+ forEach(this._operations, function (operation) {
+ str += " " + operation.type + ":" + operation.count;
+ });
+ return str.substring(1);
</ins><span class="cx"> }
</span><del>-}
</del><ins>+};
</ins><span class="cx">
</span><del>-function makeToString(ret) {
- return function() { return ret; };
-}
-
-Ember.Mixin.prototype.toString = classToString;
-
</del><span class="cx"> })();
</span><span class="cx">
</span><span class="cx">
</span><span class="cx">
</span><span class="cx"> (function() {
</span><del>-/**
-@module ember
-@submodule ember-runtime
-*/
</del><ins>+Ember.Container = requireModule('container');
+Ember.Container.set = Ember.set;
</ins><span class="cx">
</span><del>-/**
- Defines a namespace that will contain an executable application. This is
- very similar to a normal namespace except that it is expected to include at
- least a 'ready' function which can be run to initialize the application.
</del><ins>+})();
</ins><span class="cx">
</span><del>- Currently `Ember.Application` is very similar to `Ember.Namespace.` However,
- this class may be augmented by additional frameworks so it is important to
- use this instance when building new applications.
</del><span class="cx">
</span><del>- # Example Usage
</del><span class="cx">
</span><del>- ```javascript
- MyApp = Ember.Application.create({
- VERSION: '1.0.0',
- store: Ember.Store.create().from(Ember.fixtures)
- });
-
- MyApp.ready = function() {
- //..init code goes here...
- }
- ```
-
- @class Application
- @namespace Ember
- @extends Ember.Namespace
-*/
</del><ins>+(function() {
</ins><span class="cx"> Ember.Application = Ember.Namespace.extend();
</span><span class="cx">
</span><del>-
</del><span class="cx"> })();
</span><span class="cx">
</span><span class="cx">
</span><span class="lines">@@ -11166,6 +18691,8 @@
</span><span class="cx"> @submodule ember-runtime
</span><span class="cx"> */
</span><span class="cx">
</span><ins>+var OUT_OF_RANGE_EXCEPTION = "Index out of range";
+var EMPTY = [];
</ins><span class="cx">
</span><span class="cx"> var get = Ember.get, set = Ember.set;
</span><span class="cx">
</span><span class="lines">@@ -11207,8 +18734,7 @@
</span><span class="cx"> @extends Ember.Object
</span><span class="cx"> @uses Ember.MutableArray
</span><span class="cx"> */
</span><del>-Ember.ArrayProxy = Ember.Object.extend(Ember.MutableArray,
-/** @scope Ember.ArrayProxy.prototype */ {
</del><ins>+Ember.ArrayProxy = Ember.Object.extend(Ember.MutableArray, {
</ins><span class="cx">
</span><span class="cx"> /**
</span><span class="cx"> The content array. Must be an object that implements `Ember.Array` and/or
</span><span class="lines">@@ -11262,16 +18788,15 @@
</span><span class="cx"> },
</span><span class="cx">
</span><span class="cx"> /**
</span><del>- @private
-
</del><span class="cx"> Invoked when the content property is about to change. Notifies observers that the
</span><span class="cx"> entire array content will change.
</span><span class="cx">
</span><ins>+ @private
</ins><span class="cx"> @method _contentWillChange
</span><span class="cx"> */
</span><del>- _contentWillChange: Ember.beforeObserver(function() {
</del><ins>+ _contentWillChange: Ember.beforeObserver('content', function() {
</ins><span class="cx"> this._teardownContent();
</span><del>- }, 'content'),
</del><ins>+ }),
</ins><span class="cx">
</span><span class="cx"> _teardownContent: function() {
</span><span class="cx"> var content = get(this, 'content');
</span><span class="lines">@@ -11288,20 +18813,19 @@
</span><span class="cx"> contentArrayDidChange: Ember.K,
</span><span class="cx">
</span><span class="cx"> /**
</span><del>- @private
-
</del><span class="cx"> Invoked when the content property changes. Notifies observers that the
</span><span class="cx"> entire array content has changed.
</span><span class="cx">
</span><ins>+ @private
</ins><span class="cx"> @method _contentDidChange
</span><span class="cx"> */
</span><del>- _contentDidChange: Ember.observer(function() {
</del><ins>+ _contentDidChange: Ember.observer('content', function() {
</ins><span class="cx"> var content = get(this, 'content');
</span><span class="cx">
</span><span class="cx"> Ember.assert("Can't set ArrayProxy's content to itself", content !== this);
</span><span class="cx">
</span><span class="cx"> this._setupContent();
</span><del>- }, 'content'),
</del><ins>+ }),
</ins><span class="cx">
</span><span class="cx"> _setupContent: function() {
</span><span class="cx"> var content = get(this, 'content');
</span><span class="lines">@@ -11314,7 +18838,7 @@
</span><span class="cx"> }
</span><span class="cx"> },
</span><span class="cx">
</span><del>- _arrangedContentWillChange: Ember.beforeObserver(function() {
</del><ins>+ _arrangedContentWillChange: Ember.beforeObserver('arrangedContent', function() {
</ins><span class="cx"> var arrangedContent = get(this, 'arrangedContent'),
</span><span class="cx"> len = arrangedContent ? get(arrangedContent, 'length') : 0;
</span><span class="cx">
</span><span class="lines">@@ -11322,9 +18846,9 @@
</span><span class="cx"> this.arrangedContentWillChange(this);
</span><span class="cx">
</span><span class="cx"> this._teardownArrangedContent(arrangedContent);
</span><del>- }, 'arrangedContent'),
</del><ins>+ }),
</ins><span class="cx">
</span><del>- _arrangedContentDidChange: Ember.observer(function() {
</del><ins>+ _arrangedContentDidChange: Ember.observer('arrangedContent', function() {
</ins><span class="cx"> var arrangedContent = get(this, 'arrangedContent'),
</span><span class="cx"> len = arrangedContent ? get(arrangedContent, 'length') : 0;
</span><span class="cx">
</span><span class="lines">@@ -11334,7 +18858,7 @@
</span><span class="cx">
</span><span class="cx"> this.arrangedContentDidChange(this);
</span><span class="cx"> this.arrangedContentArrayDidChange(this, 0, undefined, len);
</span><del>- }, 'arrangedContent'),
</del><ins>+ }),
</ins><span class="cx">
</span><span class="cx"> _setupArrangedContent: function() {
</span><span class="cx"> var arrangedContent = get(this, 'arrangedContent');
</span><span class="lines">@@ -11371,173 +18895,119 @@
</span><span class="cx"> // No dependencies since Enumerable notifies length of change
</span><span class="cx"> }),
</span><span class="cx">
</span><del>- replace: function(idx, amt, objects) {
- Ember.assert('The content property of '+ this.constructor + ' should be set before modifying it', this.get('content'));
- if (get(this, 'content')) this.replaceContent(idx, amt, objects);
</del><ins>+ _replace: function(idx, amt, objects) {
+ var content = get(this, 'content');
+ Ember.assert('The content property of '+ this.constructor + ' should be set before modifying it', content);
+ if (content) this.replaceContent(idx, amt, objects);
</ins><span class="cx"> return this;
</span><span class="cx"> },
</span><span class="cx">
</span><del>- arrangedContentArrayWillChange: function(item, idx, removedCnt, addedCnt) {
- this.arrayContentWillChange(idx, removedCnt, addedCnt);
</del><ins>+ replace: function() {
+ if (get(this, 'arrangedContent') === get(this, 'content')) {
+ this._replace.apply(this, arguments);
+ } else {
+ throw new Ember.Error("Using replace on an arranged ArrayProxy is not allowed.");
+ }
</ins><span class="cx"> },
</span><span class="cx">
</span><del>- arrangedContentArrayDidChange: function(item, idx, removedCnt, addedCnt) {
- this.arrayContentDidChange(idx, removedCnt, addedCnt);
</del><ins>+ _insertAt: function(idx, object) {
+ if (idx > get(this, 'content.length')) throw new Ember.Error(OUT_OF_RANGE_EXCEPTION);
+ this._replace(idx, 0, [object]);
+ return this;
</ins><span class="cx"> },
</span><span class="cx">
</span><del>- init: function() {
- this._super();
- this._setupContent();
- this._setupArrangedContent();
</del><ins>+ insertAt: function(idx, object) {
+ if (get(this, 'arrangedContent') === get(this, 'content')) {
+ return this._insertAt(idx, object);
+ } else {
+ throw new Ember.Error("Using insertAt on an arranged ArrayProxy is not allowed.");
+ }
</ins><span class="cx"> },
</span><span class="cx">
</span><del>- willDestroy: function() {
- this._teardownArrangedContent();
- this._teardownContent();
- }
-});
</del><ins>+ removeAt: function(start, len) {
+ if ('number' === typeof start) {
+ var content = get(this, 'content'),
+ arrangedContent = get(this, 'arrangedContent'),
+ indices = [], i;
</ins><span class="cx">
</span><ins>+ if ((start < 0) || (start >= get(this, 'length'))) {
+ throw new Ember.Error(OUT_OF_RANGE_EXCEPTION);
+ }
</ins><span class="cx">
</span><del>-})();
</del><ins>+ if (len === undefined) len = 1;
</ins><span class="cx">
</span><ins>+ // Get a list of indices in original content to remove
+ for (i=start; i<start+len; i++) {
+ // Use arrangedContent here so we avoid confusion with objects transformed by objectAtContent
+ indices.push(content.indexOf(arrangedContent.objectAt(i)));
+ }
</ins><span class="cx">
</span><ins>+ // Replace in reverse order since indices will change
+ indices.sort(function(a,b) { return b - a; });
</ins><span class="cx">
</span><del>-(function() {
-/**
-@module ember
-@submodule ember-runtime
-*/
-
-var get = Ember.get,
- set = Ember.set,
- fmt = Ember.String.fmt,
- addBeforeObserver = Ember.addBeforeObserver,
- addObserver = Ember.addObserver,
- removeBeforeObserver = Ember.removeBeforeObserver,
- removeObserver = Ember.removeObserver,
- propertyWillChange = Ember.propertyWillChange,
- propertyDidChange = Ember.propertyDidChange;
-
-function contentPropertyWillChange(content, contentKey) {
- var key = contentKey.slice(8); // remove "content."
- if (key in this) { return; } // if shadowed in proxy
- propertyWillChange(this, key);
-}
-
-function contentPropertyDidChange(content, contentKey) {
- var key = contentKey.slice(8); // remove "content."
- if (key in this) { return; } // if shadowed in proxy
- propertyDidChange(this, key);
-}
-
-/**
- `Ember.ObjectProxy` forwards all properties not defined by the proxy itself
- to a proxied `content` object.
-
- ```javascript
- object = Ember.Object.create({
- name: 'Foo'
- });
-
- proxy = Ember.ObjectProxy.create({
- content: object
- });
-
- // Access and change existing properties
- proxy.get('name') // 'Foo'
- proxy.set('name', 'Bar');
- object.get('name') // 'Bar'
-
- // Create new 'description' property on `object`
- proxy.set('description', 'Foo is a whizboo baz');
- object.get('description') // 'Foo is a whizboo baz'
- ```
-
- While `content` is unset, setting a property to be delegated will throw an
- Error.
-
- ```javascript
- proxy = Ember.ObjectProxy.create({
- content: null,
- flag: null
- });
- proxy.set('flag', true);
- proxy.get('flag'); // true
- proxy.get('foo'); // undefined
- proxy.set('foo', 'data'); // throws Error
- ```
-
- Delegated properties can be bound to and will change when content is updated.
-
- Computed properties on the proxy itself can depend on delegated properties.
-
- ```javascript
- ProxyWithComputedProperty = Ember.ObjectProxy.extend({
- fullName: function () {
- var firstName = this.get('firstName'),
- lastName = this.get('lastName');
- if (firstName && lastName) {
- return firstName + ' ' + lastName;
</del><ins>+ Ember.beginPropertyChanges();
+ for (i=0; i<indices.length; i++) {
+ this._replace(indices[i], 1, EMPTY);
</ins><span class="cx"> }
</span><del>- return firstName || lastName;
- }.property('firstName', 'lastName')
- });
</del><ins>+ Ember.endPropertyChanges();
+ }
</ins><span class="cx">
</span><del>- proxy = ProxyWithComputedProperty.create();
</del><ins>+ return this ;
+ },
</ins><span class="cx">
</span><del>- proxy.get('fullName'); // undefined
- proxy.set('content', {
- firstName: 'Tom', lastName: 'Dale'
- }); // triggers property change for fullName on proxy
</del><ins>+ pushObject: function(obj) {
+ this._insertAt(get(this, 'content.length'), obj) ;
+ return obj ;
+ },
</ins><span class="cx">
</span><del>- proxy.get('fullName'); // 'Tom Dale'
- ```
</del><ins>+ pushObjects: function(objects) {
+ if (!(Ember.Enumerable.detect(objects) || Ember.isArray(objects))) {
+ throw new TypeError("Must pass Ember.Enumerable to Ember.MutableArray#pushObjects");
+ }
+ this._replace(get(this, 'length'), 0, objects);
+ return this;
+ },
</ins><span class="cx">
</span><del>- @class ObjectProxy
- @namespace Ember
- @extends Ember.Object
-*/
-Ember.ObjectProxy = Ember.Object.extend(
-/** @scope Ember.ObjectProxy.prototype */ {
- /**
- The object whose properties will be forwarded.
</del><ins>+ setObjects: function(objects) {
+ if (objects.length === 0) return this.clear();
</ins><span class="cx">
</span><del>- @property content
- @type Ember.Object
- @default null
- */
- content: null,
- _contentDidChange: Ember.observer(function() {
- Ember.assert("Can't set ObjectProxy's content to itself", this.get('content') !== this);
- }, 'content'),
</del><ins>+ var len = get(this, 'length');
+ this._replace(0, len, objects);
+ return this;
+ },
</ins><span class="cx">
</span><del>- isTruthy: Ember.computed.bool('content'),
</del><ins>+ unshiftObject: function(obj) {
+ this._insertAt(0, obj) ;
+ return obj ;
+ },
</ins><span class="cx">
</span><del>- _debugContainerKey: null,
</del><ins>+ unshiftObjects: function(objects) {
+ this._replace(0, 0, objects);
+ return this;
+ },
</ins><span class="cx">
</span><del>- willWatchProperty: function (key) {
- var contentKey = 'content.' + key;
- addBeforeObserver(this, contentKey, null, contentPropertyWillChange);
- addObserver(this, contentKey, null, contentPropertyDidChange);
</del><ins>+ slice: function() {
+ var arr = this.toArray();
+ return arr.slice.apply(arr, arguments);
</ins><span class="cx"> },
</span><span class="cx">
</span><del>- didUnwatchProperty: function (key) {
- var contentKey = 'content.' + key;
- removeBeforeObserver(this, contentKey, null, contentPropertyWillChange);
- removeObserver(this, contentKey, null, contentPropertyDidChange);
</del><ins>+ arrangedContentArrayWillChange: function(item, idx, removedCnt, addedCnt) {
+ this.arrayContentWillChange(idx, removedCnt, addedCnt);
</ins><span class="cx"> },
</span><span class="cx">
</span><del>- unknownProperty: function (key) {
- var content = get(this, 'content');
- if (content) {
- return get(content, key);
- }
</del><ins>+ arrangedContentArrayDidChange: function(item, idx, removedCnt, addedCnt) {
+ this.arrayContentDidChange(idx, removedCnt, addedCnt);
</ins><span class="cx"> },
</span><span class="cx">
</span><del>- setUnknownProperty: function (key, value) {
- var content = get(this, 'content');
- Ember.assert(fmt("Cannot delegate set('%@', %@) to the 'content' property of object proxy %@: its 'content' is undefined.", [key, value, this]), content);
- return set(content, key, value);
</del><ins>+ init: function() {
+ this._super();
+ this._setupContent();
+ this._setupArrangedContent();
+ },
+
+ willDestroy: function() {
+ this._teardownArrangedContent();
+ this._teardownContent();
</ins><span class="cx"> }
</span><span class="cx"> });
</span><span class="cx">
</span><span class="lines">@@ -11553,7 +19023,8 @@
</span><span class="cx">
</span><span class="cx">
</span><span class="cx"> var set = Ember.set, get = Ember.get, guidFor = Ember.guidFor;
</span><del>-var forEach = Ember.EnumerableUtils.forEach;
</del><ins>+var forEach = Ember.EnumerableUtils.forEach,
+ indexOf = Ember.ArrayPolyfills.indexOf;
</ins><span class="cx">
</span><span class="cx"> var EachArray = Ember.Object.extend(Ember.Array, {
</span><span class="cx">
</span><span class="lines">@@ -11585,10 +19056,11 @@
</span><span class="cx"> while(--loc>=idx) {
</span><span class="cx"> var item = content.objectAt(loc);
</span><span class="cx"> if (item) {
</span><ins>+ Ember.assert('When using @each to observe the array ' + content + ', the array must return an object', Ember.typeOf(item) === 'instance' || Ember.typeOf(item) === 'object');
</ins><span class="cx"> Ember.addBeforeObserver(item, keyName, proxy, 'contentKeyWillChange');
</span><span class="cx"> Ember.addObserver(item, keyName, proxy, 'contentKeyDidChange');
</span><span class="cx">
</span><del>- // keep track of the indicies each item was found at so we can map
</del><ins>+ // keep track of the index each item was found at so we can map
</ins><span class="cx"> // it back when the obj changes.
</span><span class="cx"> guid = guidFor(item);
</span><span class="cx"> if (!objects[guid]) objects[guid] = [];
</span><span class="lines">@@ -11610,7 +19082,7 @@
</span><span class="cx">
</span><span class="cx"> guid = guidFor(item);
</span><span class="cx"> indicies = objects[guid];
</span><del>- indicies[indicies.indexOf(loc)] = null;
</del><ins>+ indicies[indexOf.call(indicies, loc)] = null;
</ins><span class="cx"> }
</span><span class="cx"> }
</span><span class="cx"> }
</span><span class="lines">@@ -11645,7 +19117,7 @@
</span><span class="cx">
</span><span class="cx"> @method unknownProperty
</span><span class="cx"> @param keyName {String}
</span><del>- @param value {anything}
</del><ins>+ @param value {*}
</ins><span class="cx"> */
</span><span class="cx"> unknownProperty: function(keyName, value) {
</span><span class="cx"> var ret;
</span><span class="lines">@@ -11660,7 +19132,7 @@
</span><span class="cx"> // Invokes whenever the content array itself changes.
</span><span class="cx">
</span><span class="cx"> arrayWillChange: function(content, idx, removedCnt, addedCnt) {
</span><del>- var keys = this._keys, key, array, lim;
</del><ins>+ var keys = this._keys, key, lim;
</ins><span class="cx">
</span><span class="cx"> lim = removedCnt>0 ? idx+removedCnt : -1;
</span><span class="cx"> Ember.beginPropertyChanges(this);
</span><span class="lines">@@ -11668,7 +19140,7 @@
</span><span class="cx"> for(key in keys) {
</span><span class="cx"> if (!keys.hasOwnProperty(key)) { continue; }
</span><span class="cx">
</span><del>- if (lim>0) removeObserverForContentKey(content, key, this, idx, lim);
</del><ins>+ if (lim>0) { removeObserverForContentKey(content, key, this, idx, lim); }
</ins><span class="cx">
</span><span class="cx"> Ember.propertyWillChange(this, key);
</span><span class="cx"> }
</span><span class="lines">@@ -11678,21 +19150,20 @@
</span><span class="cx"> },
</span><span class="cx">
</span><span class="cx"> arrayDidChange: function(content, idx, removedCnt, addedCnt) {
</span><del>- var keys = this._keys, key, array, lim;
</del><ins>+ var keys = this._keys, lim;
</ins><span class="cx">
</span><span class="cx"> lim = addedCnt>0 ? idx+addedCnt : -1;
</span><del>- Ember.beginPropertyChanges(this);
</del><ins>+ Ember.changeProperties(function() {
+ for(var key in keys) {
+ if (!keys.hasOwnProperty(key)) { continue; }
</ins><span class="cx">
</span><del>- for(key in keys) {
- if (!keys.hasOwnProperty(key)) { continue; }
</del><ins>+ if (lim>0) { addObserverForContentKey(content, key, this, idx, lim); }
</ins><span class="cx">
</span><del>- if (lim>0) addObserverForContentKey(content, key, this, idx, lim);
</del><ins>+ Ember.propertyDidChange(this, key);
+ }
</ins><span class="cx">
</span><del>- Ember.propertyDidChange(this, key);
- }
-
- Ember.propertyDidChange(this._content, '@each');
- Ember.endPropertyChanges(this);
</del><ins>+ Ember.propertyDidChange(this._content, '@each');
+ }, this);
</ins><span class="cx"> },
</span><span class="cx">
</span><span class="cx"> // ..........................................................
</span><span class="lines">@@ -11760,7 +19231,7 @@
</span><span class="cx"> */
</span><span class="cx">
</span><span class="cx">
</span><del>-var get = Ember.get, set = Ember.set;
</del><ins>+var get = Ember.get, set = Ember.set, replace = Ember.EnumerableUtils._replace;
</ins><span class="cx">
</span><span class="cx"> // Add Ember.Array to Array.prototype. Remove methods with native
</span><span class="cx"> // implementations and supply some more optimized versions of generic methods
</span><span class="lines">@@ -11782,7 +19253,7 @@
</span><span class="cx"> // primitive for array support.
</span><span class="cx"> replace: function(idx, amt, objects) {
</span><span class="cx">
</span><del>- if (this.isFrozen) throw Ember.FROZEN_ERROR ;
</del><ins>+ if (this.isFrozen) throw Ember.FROZEN_ERROR;
</ins><span class="cx">
</span><span class="cx"> // if we replaced exactly the same number of items, then pass only the
</span><span class="cx"> // replaced range. Otherwise, pass the full remaining array length
</span><span class="lines">@@ -11790,15 +19261,14 @@
</span><span class="cx"> var len = objects ? get(objects, 'length') : 0;
</span><span class="cx"> this.arrayContentWillChange(idx, amt, len);
</span><span class="cx">
</span><del>- if (!objects || objects.length === 0) {
- this.splice(idx, amt) ;
</del><ins>+ if (len === 0) {
+ this.splice(idx, amt);
</ins><span class="cx"> } else {
</span><del>- var args = [idx, amt].concat(objects) ;
- this.splice.apply(this,args) ;
</del><ins>+ replace(this, idx, amt, objects);
</ins><span class="cx"> }
</span><span class="cx">
</span><span class="cx"> this.arrayContentDidChange(idx, amt, len);
</span><del>- return this ;
</del><ins>+ return this;
</ins><span class="cx"> },
</span><span class="cx">
</span><span class="cx"> // If you ask for an unknown property, then try to collect the value
</span><span class="lines">@@ -11841,7 +19311,7 @@
</span><span class="cx">
</span><span class="cx"> copy: function(deep) {
</span><span class="cx"> if (deep) {
</span><del>- return this.map(function(item){ return Ember.copy(item, true); });
</del><ins>+ return this.map(function(item) { return Ember.copy(item, true); });
</ins><span class="cx"> }
</span><span class="cx">
</span><span class="cx"> return this.slice();
</span><span class="lines">@@ -11861,37 +19331,64 @@
</span><span class="cx"> /**
</span><span class="cx"> The NativeArray mixin contains the properties needed to to make the native
</span><span class="cx"> Array support Ember.MutableArray and all of its dependent APIs. Unless you
</span><del>- have `Ember.EXTEND_PROTOTYPES or `Ember.EXTEND_PROTOTYPES.Array` set to
</del><ins>+ have `Ember.EXTEND_PROTOTYPES` or `Ember.EXTEND_PROTOTYPES.Array` set to
</ins><span class="cx"> false, this will be applied automatically. Otherwise you can apply the mixin
</span><span class="cx"> at anytime by calling `Ember.NativeArray.activate`.
</span><span class="cx">
</span><span class="cx"> @class NativeArray
</span><span class="cx"> @namespace Ember
</span><del>- @extends Ember.Mixin
</del><span class="cx"> @uses Ember.MutableArray
</span><del>- @uses Ember.MutableEnumerable
</del><ins>+ @uses Ember.Observable
</ins><span class="cx"> @uses Ember.Copyable
</span><del>- @uses Ember.Freezable
</del><span class="cx"> */
</span><span class="cx"> Ember.NativeArray = NativeArray;
</span><span class="cx">
</span><span class="cx"> /**
</span><span class="cx"> Creates an `Ember.NativeArray` from an Array like object.
</span><del>- Does not modify the original object.
</del><ins>+ Does not modify the original object. Ember.A is not needed if
+ `Ember.EXTEND_PROTOTYPES` is `true` (the default value). However,
+ it is recommended that you use Ember.A when creating addons for
+ ember or when you can not guarantee that `Ember.EXTEND_PROTOTYPES`
+ will be `true`.
</ins><span class="cx">
</span><ins>+ Example
+
+ ```js
+ var Pagination = Ember.CollectionView.extend({
+ tagName: 'ul',
+ classNames: ['pagination'],
+ init: function() {
+ this._super();
+ if (!this.get('content')) {
+ this.set('content', Ember.A([]));
+ }
+ }
+ });
+ ```
+
</ins><span class="cx"> @method A
</span><span class="cx"> @for Ember
</span><span class="cx"> @return {Ember.NativeArray}
</span><span class="cx"> */
</span><del>-Ember.A = function(arr){
</del><ins>+Ember.A = function(arr) {
</ins><span class="cx"> if (arr === undefined) { arr = []; }
</span><span class="cx"> return Ember.Array.detect(arr) ? arr : Ember.NativeArray.apply(arr);
</span><span class="cx"> };
</span><span class="cx">
</span><span class="cx"> /**
</span><span class="cx"> Activates the mixin on the Array.prototype if not already applied. Calling
</span><del>- this method more than once is safe.
</del><ins>+ this method more than once is safe. This will be called when ember is loaded
+ unless you have `Ember.EXTEND_PROTOTYPES` or `Ember.EXTEND_PROTOTYPES.Array`
+ set to `false`.
</ins><span class="cx">
</span><ins>+ Example
+
+ ```js
+ if (Ember.EXTEND_PROTOTYPES === true || Ember.EXTEND_PROTOTYPES.Array) {
+ Ember.NativeArray.activate();
+ }
+ ```
+
</ins><span class="cx"> @method activate
</span><span class="cx"> @for Ember.NativeArray
</span><span class="cx"> @static
</span><span class="lines">@@ -11913,8 +19410,464 @@
</span><span class="cx">
</span><span class="cx">
</span><span class="cx"> (function() {
</span><ins>+/**
+@module ember
+@submodule ember-runtime
+*/
+
+var get = Ember.get, set = Ember.set, guidFor = Ember.guidFor, isNone = Ember.isNone, fmt = Ember.String.fmt;
+
+/**
+ An unordered collection of objects.
+
+ A Set works a bit like an array except that its items are not ordered. You
+ can create a set to efficiently test for membership for an object. You can
+ also iterate through a set just like an array, even accessing objects by
+ index, however there is no guarantee as to their order.
+
+ All Sets are observable via the Enumerable Observer API - which works
+ on any enumerable object including both Sets and Arrays.
+
+ ## Creating a Set
+
+ You can create a set like you would most objects using
+ `new Ember.Set()`. Most new sets you create will be empty, but you can
+ also initialize the set with some content by passing an array or other
+ enumerable of objects to the constructor.
+
+ Finally, you can pass in an existing set and the set will be copied. You
+ can also create a copy of a set by calling `Ember.Set#copy()`.
+
+ ```javascript
+ // creates a new empty set
+ var foundNames = new Ember.Set();
+
+ // creates a set with four names in it.
+ var names = new Ember.Set(["Charles", "Tom", "Juan", "Alex"]); // :P
+
+ // creates a copy of the names set.
+ var namesCopy = new Ember.Set(names);
+
+ // same as above.
+ var anotherNamesCopy = names.copy();
+ ```
+
+ ## Adding/Removing Objects
+
+ You generally add or remove objects from a set using `add()` or
+ `remove()`. You can add any type of object including primitives such as
+ numbers, strings, and booleans.
+
+ Unlike arrays, objects can only exist one time in a set. If you call `add()`
+ on a set with the same object multiple times, the object will only be added
+ once. Likewise, calling `remove()` with the same object multiple times will
+ remove the object the first time and have no effect on future calls until
+ you add the object to the set again.
+
+ NOTE: You cannot add/remove `null` or `undefined` to a set. Any attempt to do
+ so will be ignored.
+
+ In addition to add/remove you can also call `push()`/`pop()`. Push behaves
+ just like `add()` but `pop()`, unlike `remove()` will pick an arbitrary
+ object, remove it and return it. This is a good way to use a set as a job
+ queue when you don't care which order the jobs are executed in.
+
+ ## Testing for an Object
+
+ To test for an object's presence in a set you simply call
+ `Ember.Set#contains()`.
+
+ ## Observing changes
+
+ When using `Ember.Set`, you can observe the `"[]"` property to be
+ alerted whenever the content changes. You can also add an enumerable
+ observer to the set to be notified of specific objects that are added and
+ removed from the set. See [Ember.Enumerable](/api/classes/Ember.Enumerable.html)
+ for more information on enumerables.
+
+ This is often unhelpful. If you are filtering sets of objects, for instance,
+ it is very inefficient to re-filter all of the items each time the set
+ changes. It would be better if you could just adjust the filtered set based
+ on what was changed on the original set. The same issue applies to merging
+ sets, as well.
+
+ ## Other Methods
+
+ `Ember.Set` primary implements other mixin APIs. For a complete reference
+ on the methods you will use with `Ember.Set`, please consult these mixins.
+ The most useful ones will be `Ember.Enumerable` and
+ `Ember.MutableEnumerable` which implement most of the common iterator
+ methods you are used to on Array.
+
+ Note that you can also use the `Ember.Copyable` and `Ember.Freezable`
+ APIs on `Ember.Set` as well. Once a set is frozen it can no longer be
+ modified. The benefit of this is that when you call `frozenCopy()` on it,
+ Ember will avoid making copies of the set. This allows you to write
+ code that can know with certainty when the underlying set data will or
+ will not be modified.
+
+ @class Set
+ @namespace Ember
+ @extends Ember.CoreObject
+ @uses Ember.MutableEnumerable
+ @uses Ember.Copyable
+ @uses Ember.Freezable
+ @since Ember 0.9
+*/
+Ember.Set = Ember.CoreObject.extend(Ember.MutableEnumerable, Ember.Copyable, Ember.Freezable,
+ {
+
+ // ..........................................................
+ // IMPLEMENT ENUMERABLE APIS
+ //
+
+ /**
+ This property will change as the number of objects in the set changes.
+
+ @property length
+ @type number
+ @default 0
+ */
+ length: 0,
+
+ /**
+ Clears the set. This is useful if you want to reuse an existing set
+ without having to recreate it.
+
+ ```javascript
+ var colors = new Ember.Set(["red", "green", "blue"]);
+ colors.length; // 3
+ colors.clear();
+ colors.length; // 0
+ ```
+
+ @method clear
+ @return {Ember.Set} An empty Set
+ */
+ clear: function() {
+ if (this.isFrozen) { throw new Ember.Error(Ember.FROZEN_ERROR); }
+
+ var len = get(this, 'length');
+ if (len === 0) { return this; }
+
+ var guid;
+
+ this.enumerableContentWillChange(len, 0);
+ Ember.propertyWillChange(this, 'firstObject');
+ Ember.propertyWillChange(this, 'lastObject');
+
+ for (var i=0; i < len; i++) {
+ guid = guidFor(this[i]);
+ delete this[guid];
+ delete this[i];
+ }
+
+ set(this, 'length', 0);
+
+ Ember.propertyDidChange(this, 'firstObject');
+ Ember.propertyDidChange(this, 'lastObject');
+ this.enumerableContentDidChange(len, 0);
+
+ return this;
+ },
+
+ /**
+ Returns true if the passed object is also an enumerable that contains the
+ same objects as the receiver.
+
+ ```javascript
+ var colors = ["red", "green", "blue"],
+ same_colors = new Ember.Set(colors);
+
+ same_colors.isEqual(colors); // true
+ same_colors.isEqual(["purple", "brown"]); // false
+ ```
+
+ @method isEqual
+ @param {Ember.Set} obj the other object.
+ @return {Boolean}
+ */
+ isEqual: function(obj) {
+ // fail fast
+ if (!Ember.Enumerable.detect(obj)) return false;
+
+ var loc = get(this, 'length');
+ if (get(obj, 'length') !== loc) return false;
+
+ while(--loc >= 0) {
+ if (!obj.contains(this[loc])) return false;
+ }
+
+ return true;
+ },
+
+ /**
+ Adds an object to the set. Only non-`null` objects can be added to a set
+ and those can only be added once. If the object is already in the set or
+ the passed value is null this method will have no effect.
+
+ This is an alias for `Ember.MutableEnumerable.addObject()`.
+
+ ```javascript
+ var colors = new Ember.Set();
+ colors.add("blue"); // ["blue"]
+ colors.add("blue"); // ["blue"]
+ colors.add("red"); // ["blue", "red"]
+ colors.add(null); // ["blue", "red"]
+ colors.add(undefined); // ["blue", "red"]
+ ```
+
+ @method add
+ @param {Object} obj The object to add.
+ @return {Ember.Set} The set itself.
+ */
+ add: Ember.aliasMethod('addObject'),
+
+ /**
+ Removes the object from the set if it is found. If you pass a `null` value
+ or an object that is already not in the set, this method will have no
+ effect. This is an alias for `Ember.MutableEnumerable.removeObject()`.
+
+ ```javascript
+ var colors = new Ember.Set(["red", "green", "blue"]);
+ colors.remove("red"); // ["blue", "green"]
+ colors.remove("purple"); // ["blue", "green"]
+ colors.remove(null); // ["blue", "green"]
+ ```
+
+ @method remove
+ @param {Object} obj The object to remove
+ @return {Ember.Set} The set itself.
+ */
+ remove: Ember.aliasMethod('removeObject'),
+
+ /**
+ Removes the last element from the set and returns it, or `null` if it's empty.
+
+ ```javascript
+ var colors = new Ember.Set(["green", "blue"]);
+ colors.pop(); // "blue"
+ colors.pop(); // "green"
+ colors.pop(); // null
+ ```
+
+ @method pop
+ @return {Object} The removed object from the set or null.
+ */
+ pop: function() {
+ if (get(this, 'isFrozen')) throw new Ember.Error(Ember.FROZEN_ERROR);
+ var obj = this.length > 0 ? this[this.length-1] : null;
+ this.remove(obj);
+ return obj;
+ },
+
+ /**
+ Inserts the given object on to the end of the set. It returns
+ the set itself.
+
+ This is an alias for `Ember.MutableEnumerable.addObject()`.
+
+ ```javascript
+ var colors = new Ember.Set();
+ colors.push("red"); // ["red"]
+ colors.push("green"); // ["red", "green"]
+ colors.push("blue"); // ["red", "green", "blue"]
+ ```
+
+ @method push
+ @return {Ember.Set} The set itself.
+ */
+ push: Ember.aliasMethod('addObject'),
+
+ /**
+ Removes the last element from the set and returns it, or `null` if it's empty.
+
+ This is an alias for `Ember.Set.pop()`.
+
+ ```javascript
+ var colors = new Ember.Set(["green", "blue"]);
+ colors.shift(); // "blue"
+ colors.shift(); // "green"
+ colors.shift(); // null
+ ```
+
+ @method shift
+ @return {Object} The removed object from the set or null.
+ */
+ shift: Ember.aliasMethod('pop'),
+
+ /**
+ Inserts the given object on to the end of the set. It returns
+ the set itself.
+
+ This is an alias of `Ember.Set.push()`
+
+ ```javascript
+ var colors = new Ember.Set();
+ colors.unshift("red"); // ["red"]
+ colors.unshift("green"); // ["red", "green"]
+ colors.unshift("blue"); // ["red", "green", "blue"]
+ ```
+
+ @method unshift
+ @return {Ember.Set} The set itself.
+ */
+ unshift: Ember.aliasMethod('push'),
+
+ /**
+ Adds each object in the passed enumerable to the set.
+
+ This is an alias of `Ember.MutableEnumerable.addObjects()`
+
+ ```javascript
+ var colors = new Ember.Set();
+ colors.addEach(["red", "green", "blue"]); // ["red", "green", "blue"]
+ ```
+
+ @method addEach
+ @param {Ember.Enumerable} objects the objects to add.
+ @return {Ember.Set} The set itself.
+ */
+ addEach: Ember.aliasMethod('addObjects'),
+
+ /**
+ Removes each object in the passed enumerable to the set.
+
+ This is an alias of `Ember.MutableEnumerable.removeObjects()`
+
+ ```javascript
+ var colors = new Ember.Set(["red", "green", "blue"]);
+ colors.removeEach(["red", "blue"]); // ["green"]
+ ```
+
+ @method removeEach
+ @param {Ember.Enumerable} objects the objects to remove.
+ @return {Ember.Set} The set itself.
+ */
+ removeEach: Ember.aliasMethod('removeObjects'),
+
+ // ..........................................................
+ // PRIVATE ENUMERABLE SUPPORT
+ //
+
+ init: function(items) {
+ this._super();
+ if (items) this.addObjects(items);
+ },
+
+ // implement Ember.Enumerable
+ nextObject: function(idx) {
+ return this[idx];
+ },
+
+ // more optimized version
+ firstObject: Ember.computed(function() {
+ return this.length > 0 ? this[0] : undefined;
+ }),
+
+ // more optimized version
+ lastObject: Ember.computed(function() {
+ return this.length > 0 ? this[this.length-1] : undefined;
+ }),
+
+ // implements Ember.MutableEnumerable
+ addObject: function(obj) {
+ if (get(this, 'isFrozen')) throw new Ember.Error(Ember.FROZEN_ERROR);
+ if (isNone(obj)) return this; // nothing to do
+
+ var guid = guidFor(obj),
+ idx = this[guid],
+ len = get(this, 'length'),
+ added ;
+
+ if (idx>=0 && idx<len && (this[idx] === obj)) return this; // added
+
+ added = [obj];
+
+ this.enumerableContentWillChange(null, added);
+ Ember.propertyWillChange(this, 'lastObject');
+
+ len = get(this, 'length');
+ this[guid] = len;
+ this[len] = obj;
+ set(this, 'length', len+1);
+
+ Ember.propertyDidChange(this, 'lastObject');
+ this.enumerableContentDidChange(null, added);
+
+ return this;
+ },
+
+ // implements Ember.MutableEnumerable
+ removeObject: function(obj) {
+ if (get(this, 'isFrozen')) throw new Ember.Error(Ember.FROZEN_ERROR);
+ if (isNone(obj)) return this; // nothing to do
+
+ var guid = guidFor(obj),
+ idx = this[guid],
+ len = get(this, 'length'),
+ isFirst = idx === 0,
+ isLast = idx === len-1,
+ last, removed;
+
+
+ if (idx>=0 && idx<len && (this[idx] === obj)) {
+ removed = [obj];
+
+ this.enumerableContentWillChange(removed, null);
+ if (isFirst) { Ember.propertyWillChange(this, 'firstObject'); }
+ if (isLast) { Ember.propertyWillChange(this, 'lastObject'); }
+
+ // swap items - basically move the item to the end so it can be removed
+ if (idx < len-1) {
+ last = this[len-1];
+ this[idx] = last;
+ this[guidFor(last)] = idx;
+ }
+
+ delete this[guid];
+ delete this[len-1];
+ set(this, 'length', len-1);
+
+ if (isFirst) { Ember.propertyDidChange(this, 'firstObject'); }
+ if (isLast) { Ember.propertyDidChange(this, 'lastObject'); }
+ this.enumerableContentDidChange(removed, null);
+ }
+
+ return this;
+ },
+
+ // optimized version
+ contains: function(obj) {
+ return this[guidFor(obj)]>=0;
+ },
+
+ copy: function() {
+ var C = this.constructor, ret = new C(), loc = get(this, 'length');
+ set(ret, 'length', loc);
+ while(--loc>=0) {
+ ret[loc] = this[loc];
+ ret[guidFor(this[loc])] = loc;
+ }
+ return ret;
+ },
+
+ toString: function() {
+ var len = this.length, idx, array = [];
+ for(idx = 0; idx < len; idx++) {
+ array[idx] = this[idx];
+ }
+ return fmt("Ember.Set<%@>", [array.join(',')]);
+ }
+
+});
+
+})();
+
+
+
+(function() {
</ins><span class="cx"> var DeferredMixin = Ember.DeferredMixin, // mixins/deferred
</span><del>- EmberObject = Ember.Object, // system/object
</del><span class="cx"> get = Ember.get;
</span><span class="cx">
</span><span class="cx"> var Deferred = Ember.Object.extend(DeferredMixin);
</span><span class="lines">@@ -11923,7 +19876,7 @@
</span><span class="cx"> promise: function(callback, binding) {
</span><span class="cx"> var deferred = Deferred.create();
</span><span class="cx"> callback.call(binding, deferred);
</span><del>- return get(deferred, 'promise');
</del><ins>+ return deferred;
</ins><span class="cx"> }
</span><span class="cx"> });
</span><span class="cx">
</span><span class="lines">@@ -11934,19 +19887,33 @@
</span><span class="cx">
</span><span class="cx">
</span><span class="cx"> (function() {
</span><ins>+var forEach = Ember.ArrayPolyfills.forEach;
+
</ins><span class="cx"> /**
</span><del>-@module ember
-@submodule ember-runtime
</del><ins>+ @module ember
+ @submodule ember-runtime
</ins><span class="cx"> */
</span><span class="cx">
</span><span class="cx"> var loadHooks = Ember.ENV.EMBER_LOAD_HOOKS || {};
</span><span class="cx"> var loaded = {};
</span><span class="cx">
</span><span class="cx"> /**
</span><del>-@method onLoad
-@for Ember
-@param name {String} name of hook
-@param callback {Function} callback to be called
</del><ins>+ Detects when a specific package of Ember (e.g. 'Ember.Handlebars')
+ has fully loaded and is available for extension.
+
+ The provided `callback` will be called with the `name` passed
+ resolved from a string into the object:
+
+ ``` javascript
+ Ember.onLoad('Ember.Handlebars' function(hbars){
+ hbars.registerHelper(...);
+ });
+ ```
+
+ @method onLoad
+ @for Ember
+ @param name {String} name of hook
+ @param callback {Function} callback to be called
</ins><span class="cx"> */
</span><span class="cx"> Ember.onLoad = function(name, callback) {
</span><span class="cx"> var object;
</span><span class="lines">@@ -11960,18 +19927,19 @@
</span><span class="cx"> };
</span><span class="cx">
</span><span class="cx"> /**
</span><del>-@method runLoadHooks
-@for Ember
-@param name {String} name of hook
-@param object {Object} object to pass to callbacks
</del><ins>+ Called when an Ember.js package (e.g Ember.Handlebars) has finished
+ loading. Triggers any callbacks registered for this event.
+
+ @method runLoadHooks
+ @for Ember
+ @param name {String} name of hook
+ @param object {Object} object to pass to callbacks
</ins><span class="cx"> */
</span><span class="cx"> Ember.runLoadHooks = function(name, object) {
</span><del>- var hooks;
-
</del><span class="cx"> loaded[name] = object;
</span><span class="cx">
</span><del>- if (hooks = loadHooks[name]) {
- loadHooks[name].forEach(function(callback) {
</del><ins>+ if (loadHooks[name]) {
+ forEach.call(loadHooks[name], function(callback) {
</ins><span class="cx"> callback(object);
</span><span class="cx"> });
</span><span class="cx"> }
</span><span class="lines">@@ -12000,40 +19968,19 @@
</span><span class="cx"> compose Ember's controller layer: `Ember.Controller`,
</span><span class="cx"> `Ember.ArrayController`, and `Ember.ObjectController`.
</span><span class="cx">
</span><del>- Within an `Ember.Router`-managed application single shared instaces of every
- Controller object in your application's namespace will be added to the
- application's `Ember.Router` instance. See `Ember.Application#initialize`
- for additional information.
-
- ## Views
-
- By default a controller instance will be the rendering context
- for its associated `Ember.View.` This connection is made during calls to
- `Ember.ControllerMixin#connectOutlet`.
-
- Within the view's template, the `Ember.View` instance can be accessed
- through the controller with `{{view}}`.
-
- ## Target Forwarding
-
- By default a controller will target your application's `Ember.Router`
- instance. Calls to `{{action}}` within the template of a controller's view
- are forwarded to the router. See `Ember.Handlebars.helpers.action` for
- additional information.
-
</del><span class="cx"> @class ControllerMixin
</span><span class="cx"> @namespace Ember
</span><del>- @extends Ember.Mixin
</del><ins>+ @uses Ember.ActionHandler
</ins><span class="cx"> */
</span><del>-Ember.ControllerMixin = Ember.Mixin.create({
</del><ins>+Ember.ControllerMixin = Ember.Mixin.create(Ember.ActionHandler, {
</ins><span class="cx"> /* ducktype as a controller */
</span><span class="cx"> isController: true,
</span><span class="cx">
</span><span class="cx"> /**
</span><del>- The object to which events from the view should be sent.
</del><ins>+ The object to which actions from the view should be sent.
</ins><span class="cx">
</span><span class="cx"> For example, when a Handlebars template uses the `{{action}}` helper,
</span><del>- it will attempt to send the event to the view's controller's `target`.
</del><ins>+ it will attempt to send the action to the view's controller's `target`.
</ins><span class="cx">
</span><span class="cx"> By default, a controller's `target` is set to the router after it is
</span><span class="cx"> instantiated by `Ember.Application#initialize`.
</span><span class="lines">@@ -12045,20 +19992,22 @@
</span><span class="cx">
</span><span class="cx"> container: null,
</span><span class="cx">
</span><ins>+ parentController: null,
+
</ins><span class="cx"> store: null,
</span><span class="cx">
</span><span class="cx"> model: Ember.computed.alias('content'),
</span><span class="cx">
</span><del>- send: function(actionName) {
- var args = [].slice.call(arguments, 1), target;
</del><ins>+ deprecatedSendHandles: function(actionName) {
+ return !!this[actionName];
+ },
</ins><span class="cx">
</span><del>- if (this[actionName]) {
- Ember.assert("The controller " + this + " does not have the action " + actionName, typeof this[actionName] === 'function');
- this[actionName].apply(this, args);
- } else if(target = get(this, 'target')) {
- Ember.assert("The target for controller " + this + " (" + target + ") did not define a `send` method", typeof target.send === 'function');
- target.send.apply(target, arguments);
- }
</del><ins>+ deprecatedSend: function(actionName) {
+ var args = [].slice.call(arguments, 1);
+ Ember.assert('' + this + " has the action " + actionName + " but it is not a function", typeof this[actionName] === 'function');
+ Ember.deprecate('Action handlers implemented directly on controllers are deprecated in favor of action handlers on an `actions` object (' + actionName + ' on ' + this + ')', false);
+ this[actionName].apply(this, args);
+ return;
</ins><span class="cx"> }
</span><span class="cx"> });
</span><span class="cx">
</span><span class="lines">@@ -12107,9 +20056,31 @@
</span><span class="cx"> songsController.get('firstObject'); // {trackNumber: 1, title: 'Dear Prudence'}
</span><span class="cx"> ```
</span><span class="cx">
</span><ins>+ If you add or remove the properties to sort by or change the sort direction the content
+ sort order will be automatically updated.
+
+ ```javascript
+ songsController.set('sortProperties', ['title']);
+ songsController.get('firstObject'); // {trackNumber: 2, title: 'Back in the U.S.S.R.'}
+
+ songsController.toggleProperty('sortAscending');
+ songsController.get('firstObject'); // {trackNumber: 4, title: 'Ob-La-Di, Ob-La-Da'}
+ ```
+
+ SortableMixin works by sorting the arrangedContent array, which is the array that
+ arrayProxy displays. Due to the fact that the underlying 'content' array is not changed, that
+ array will not display the sorted list:
+
+ ```javascript
+ songsController.get('content').get('firstObject'); // Returns the unsorted original content
+ songsController.get('firstObject'); // Returns the sorted content.
+ ```
+
+ Although the sorted content can also be accessed through the arrangedContent property,
+ it is preferable to use the proxied class and not the arrangedContent array directly.
+
</ins><span class="cx"> @class SortableMixin
</span><span class="cx"> @namespace Ember
</span><del>- @extends Ember.Mixin
</del><span class="cx"> @uses Ember.MutableEnumerable
</span><span class="cx"> */
</span><span class="cx"> Ember.SortableMixin = Ember.Mixin.create(Ember.MutableEnumerable, {
</span><span class="lines">@@ -12117,6 +20088,9 @@
</span><span class="cx"> /**
</span><span class="cx"> Specifies which properties dictate the arrangedContent's sort order.
</span><span class="cx">
</span><ins>+ When specifying multiple properties the sorting will use properties
+ from the `sortProperties` array prioritized from first to last.
+
</ins><span class="cx"> @property {Array} sortProperties
</span><span class="cx"> */
</span><span class="cx"> sortProperties: null,
</span><span class="lines">@@ -12128,16 +20102,39 @@
</span><span class="cx"> */
</span><span class="cx"> sortAscending: true,
</span><span class="cx">
</span><ins>+ /**
+ The function used to compare two values. You can override this if you
+ want to do custom comparisons. Functions must be of the type expected by
+ Array#sort, i.e.
+ return 0 if the two parameters are equal,
+ return a negative value if the first parameter is smaller than the second or
+ return a positive value otherwise:
+
+ ```javascript
+ function(x,y) { // These are assumed to be integers
+ if (x === y)
+ return 0;
+ return x < y ? -1 : 1;
+ }
+ ```
+
+ @property sortFunction
+ @type {Function}
+ @default Ember.compare
+ */
+ sortFunction: Ember.compare,
+
</ins><span class="cx"> orderBy: function(item1, item2) {
</span><span class="cx"> var result = 0,
</span><span class="cx"> sortProperties = get(this, 'sortProperties'),
</span><del>- sortAscending = get(this, 'sortAscending');
</del><ins>+ sortAscending = get(this, 'sortAscending'),
+ sortFunction = get(this, 'sortFunction');
</ins><span class="cx">
</span><span class="cx"> Ember.assert("you need to define `sortProperties`", !!sortProperties);
</span><span class="cx">
</span><span class="cx"> forEach(sortProperties, function(propertyName) {
</span><span class="cx"> if (result === 0) {
</span><del>- result = Ember.compare(get(item1, propertyName), get(item2, propertyName));
</del><ins>+ result = sortFunction(get(item1, propertyName), get(item2, propertyName));
</ins><span class="cx"> if ((result !== 0) && !sortAscending) {
</span><span class="cx"> result = (-1) * result;
</span><span class="cx"> }
</span><span class="lines">@@ -12164,6 +20161,13 @@
</span><span class="cx">
</span><span class="cx"> isSorted: Ember.computed.bool('sortProperties'),
</span><span class="cx">
</span><ins>+ /**
+ Overrides the default arrangedContent from arrayProxy in order to sort by sortFunction.
+ Also sets up observers for each sortProperty on each item in the content Array.
+
+ @property arrangedContent
+ */
+
</ins><span class="cx"> arrangedContent: Ember.computed('content', 'sortProperties.@each', function(key, value) {
</span><span class="cx"> var content = get(this, 'content'),
</span><span class="cx"> isSorted = get(this, 'isSorted'),
</span><span class="lines">@@ -12186,7 +20190,7 @@
</span><span class="cx"> return content;
</span><span class="cx"> }),
</span><span class="cx">
</span><del>- _contentWillChange: Ember.beforeObserver(function() {
</del><ins>+ _contentWillChange: Ember.beforeObserver('content', function() {
</ins><span class="cx"> var content = get(this, 'content'),
</span><span class="cx"> sortProperties = get(this, 'sortProperties');
</span><span class="cx">
</span><span class="lines">@@ -12199,18 +20203,18 @@
</span><span class="cx"> }
</span><span class="cx">
</span><span class="cx"> this._super();
</span><del>- }, 'content'),
</del><ins>+ }),
</ins><span class="cx">
</span><del>- sortAscendingWillChange: Ember.beforeObserver(function() {
</del><ins>+ sortAscendingWillChange: Ember.beforeObserver('sortAscending', function() {
</ins><span class="cx"> this._lastSortAscending = get(this, 'sortAscending');
</span><del>- }, 'sortAscending'),
</del><ins>+ }),
</ins><span class="cx">
</span><del>- sortAscendingDidChange: Ember.observer(function() {
</del><ins>+ sortAscendingDidChange: Ember.observer('sortAscending', function() {
</ins><span class="cx"> if (get(this, 'sortAscending') !== this._lastSortAscending) {
</span><span class="cx"> var arrangedContent = get(this, 'arrangedContent');
</span><span class="cx"> arrangedContent.reverseObjects();
</span><span class="cx"> }
</span><del>- }, 'sortAscending'),
</del><ins>+ }),
</ins><span class="cx">
</span><span class="cx"> contentArrayWillChange: function(array, idx, removedCount, addedCount) {
</span><span class="cx"> var isSorted = get(this, 'isSorted');
</span><span class="lines">@@ -12238,7 +20242,6 @@
</span><span class="cx">
</span><span class="cx"> if (isSorted) {
</span><span class="cx"> var addedObjects = array.slice(idx, idx+addedCount);
</span><del>- var arrangedContent = get(this, 'arrangedContent');
</del><span class="cx">
</span><span class="cx"> forEach(addedObjects, function(item) {
</span><span class="cx"> this.insertItemSorted(item);
</span><span class="lines">@@ -12308,8 +20311,8 @@
</span><span class="cx"> @submodule ember-runtime
</span><span class="cx"> */
</span><span class="cx">
</span><del>-var get = Ember.get, set = Ember.set, isGlobalPath = Ember.isGlobalPath,
- forEach = Ember.EnumerableUtils.forEach, replace = Ember.EnumerableUtils.replace;
</del><ins>+var get = Ember.get, set = Ember.set, forEach = Ember.EnumerableUtils.forEach,
+ replace = Ember.EnumerableUtils.replace;
</ins><span class="cx">
</span><span class="cx"> /**
</span><span class="cx"> `Ember.ArrayController` provides a way for you to publish a collection of
</span><span class="lines">@@ -12388,6 +20391,10 @@
</span><span class="cx"> });
</span><span class="cx"> ```
</span><span class="cx">
</span><ins>+ The itemController instances will have a `parentController` property set to
+ either the the `parentController` property of the `ArrayController`
+ or to the `ArrayController` instance itself.
+
</ins><span class="cx"> @class ArrayController
</span><span class="cx"> @namespace Ember
</span><span class="cx"> @extends Ember.ArrayProxy
</span><span class="lines">@@ -12427,9 +20434,9 @@
</span><span class="cx"> });
</span><span class="cx"> ```
</span><span class="cx">
</span><del>- @method
- @type String
- @default null
</del><ins>+ @method lookupItemController
+ @param {Object} object
+ @return {String}
</ins><span class="cx"> */
</span><span class="cx"> lookupItemController: function(object) {
</span><span class="cx"> return get(this, 'itemController');
</span><span class="lines">@@ -12437,7 +20444,8 @@
</span><span class="cx">
</span><span class="cx"> objectAtContent: function(idx) {
</span><span class="cx"> var length = get(this, 'length'),
</span><del>- object = get(this,'arrangedContent').objectAt(idx);
</del><ins>+ arrangedContent = get(this,'arrangedContent'),
+ object = arrangedContent && arrangedContent.objectAt(idx);
</ins><span class="cx">
</span><span class="cx"> if (idx >= 0 && idx < length) {
</span><span class="cx"> var controllerClass = this.lookupItemController(object);
</span><span class="lines">@@ -12457,20 +20465,20 @@
</span><span class="cx">
</span><span class="cx"> arrangedContentDidChange: function() {
</span><span class="cx"> this._super();
</span><del>- this._resetSubContainers();
</del><ins>+ this._resetSubControllers();
</ins><span class="cx"> },
</span><span class="cx">
</span><span class="cx"> arrayContentDidChange: function(idx, removedCnt, addedCnt) {
</span><del>- var subContainers = get(this, 'subContainers'),
- subContainersToRemove = subContainers.slice(idx, idx+removedCnt);
</del><ins>+ var subControllers = get(this, '_subControllers'),
+ subControllersToRemove = subControllers.slice(idx, idx+removedCnt);
</ins><span class="cx">
</span><del>- forEach(subContainersToRemove, function(subContainer) {
- if (subContainer) { subContainer.destroy(); }
</del><ins>+ forEach(subControllersToRemove, function(subController) {
+ if (subController) { subController.destroy(); }
</ins><span class="cx"> });
</span><span class="cx">
</span><del>- replace(subContainers, idx, removedCnt, new Array(addedCnt));
</del><ins>+ replace(subControllers, idx, removedCnt, new Array(addedCnt));
</ins><span class="cx">
</span><del>- // The shadow array of subcontainers must be updated before we trigger
</del><ins>+ // The shadow array of subcontrollers must be updated before we trigger
</ins><span class="cx"> // observers, otherwise observers will get the wrong subcontainer when
</span><span class="cx"> // calling `objectAt`
</span><span class="cx"> this._super(idx, removedCnt, addedCnt);
</span><span class="lines">@@ -12478,43 +20486,50 @@
</span><span class="cx">
</span><span class="cx"> init: function() {
</span><span class="cx"> this._super();
</span><del>- if (!this.get('content')) { this.set('content', Ember.A()); }
- this._resetSubContainers();
</del><ins>+
+ this.set('_subControllers', Ember.A());
</ins><span class="cx"> },
</span><span class="cx">
</span><ins>+ content: Ember.computed(function () {
+ return Ember.A();
+ }),
+
</ins><span class="cx"> controllerAt: function(idx, object, controllerClass) {
</span><span class="cx"> var container = get(this, 'container'),
</span><del>- subContainers = get(this, 'subContainers'),
- subContainer = subContainers[idx],
- controller;
</del><ins>+ subControllers = get(this, '_subControllers'),
+ subController = subControllers[idx],
+ factory, fullName;
</ins><span class="cx">
</span><del>- if (!subContainer) {
- subContainer = subContainers[idx] = container.child();
- }
</del><ins>+ if (subController) { return subController; }
</ins><span class="cx">
</span><del>- controller = subContainer.lookup("controller:" + controllerClass);
- if (!controller) {
- throw new Error('Could not resolve itemController: "' + controllerClass + '"');
</del><ins>+ fullName = "controller:" + controllerClass;
+
+ if (!container.has(fullName)) {
+ throw new Ember.Error('Could not resolve itemController: "' + controllerClass + '"');
</ins><span class="cx"> }
</span><span class="cx">
</span><del>- controller.set('target', this);
- controller.set('content', object);
</del><ins>+ subController = container.lookupFactory(fullName).create({
+ target: this,
+ parentController: get(this, 'parentController') || this,
+ content: object
+ });
</ins><span class="cx">
</span><del>- return controller;
</del><ins>+ subControllers[idx] = subController;
+
+ return subController;
</ins><span class="cx"> },
</span><span class="cx">
</span><del>- subContainers: null,
</del><ins>+ _subControllers: null,
</ins><span class="cx">
</span><del>- _resetSubContainers: function() {
- var subContainers = get(this, 'subContainers');
-
- if (subContainers) {
- forEach(subContainers, function(subContainer) {
- if (subContainer) { subContainer.destroy(); }
</del><ins>+ _resetSubControllers: function() {
+ var subControllers = get(this, '_subControllers');
+ if (subControllers) {
+ forEach(subControllers, function(subController) {
+ if (subController) { subController.destroy(); }
</ins><span class="cx"> });
</span><span class="cx"> }
</span><span class="cx">
</span><del>- this.set('subContainers', Ember.A());
</del><ins>+ this.set('_subControllers', Ember.A());
</ins><span class="cx"> }
</span><span class="cx"> });
</span><span class="cx">
</span><span class="lines">@@ -12529,12 +20544,11 @@
</span><span class="cx"> */
</span><span class="cx">
</span><span class="cx"> /**
</span><del>- `Ember.ObjectController` is part of Ember's Controller layer. A single shared
- instance of each `Ember.ObjectController` subclass in your application's
- namespace will be created at application initialization and be stored on your
- application's `Ember.Router` instance.
</del><ins>+ `Ember.ObjectController` is part of Ember's Controller layer. It is intended
+ to wrap a single object, proxying unhandled attempts to `get` and `set` to the underlying
+ content object, and to forward unhandled action attempts to its `target`.
</ins><span class="cx">
</span><del>- `Ember.ObjectController` derives its functionality from its superclass
</del><ins>+ `Ember.ObjectController` derives this functionality from its superclass
</ins><span class="cx"> `Ember.ObjectProxy` and the `Ember.ControllerMixin` mixin.
</span><span class="cx">
</span><span class="cx"> @class ObjectController
</span><span class="lines">@@ -12571,9 +20585,13 @@
</span><span class="cx"> @submodule ember-views
</span><span class="cx"> */
</span><span class="cx">
</span><del>-var jQuery = Ember.imports.jQuery;
-Ember.assert("Ember Views require jQuery 1.8 or 1.9", jQuery && (jQuery().jquery.match(/^1\.(8|9)(\.\d+)?(pre|rc\d?)?/) || Ember.ENV.FORCE_JQUERY));
</del><ins>+var jQuery = this.jQuery || (Ember.imports && Ember.imports.jQuery);
+if (!jQuery && typeof require === 'function') {
+ jQuery = require('jquery');
+}
</ins><span class="cx">
</span><ins>+Ember.assert("Ember Views require jQuery 1.7, 1.8, 1.9, 1.10, or 2.0", jQuery && (jQuery().jquery.match(/^((1\.(7|8|9|10))|2.0)(\.\d+)?(pre|rc\d?)?/) || Ember.ENV.FORCE_JQUERY));
+
</ins><span class="cx"> /**
</span><span class="cx"> Alias for jQuery
</span><span class="cx">
</span><span class="lines">@@ -12591,16 +20609,17 @@
</span><span class="cx"> @module ember
</span><span class="cx"> @submodule ember-views
</span><span class="cx"> */
</span><ins>+if (Ember.$) {
+ // http://www.whatwg.org/specs/web-apps/current-work/multipage/dnd.html#dndevents
+ var dragEvents = Ember.String.w('dragstart drag dragenter dragleave dragover drop dragend');
</ins><span class="cx">
</span><del>-// http://www.whatwg.org/specs/web-apps/current-work/multipage/dnd.html#dndevents
-var dragEvents = Ember.String.w('dragstart drag dragenter dragleave dragover drop dragend');
</del><ins>+ // Copies the `dataTransfer` property from a browser event object onto the
+ // jQuery event object for the specified events
+ Ember.EnumerableUtils.forEach(dragEvents, function(eventName) {
+ Ember.$.event.fixHooks[eventName] = { props: ['dataTransfer'] };
+ });
+}
</ins><span class="cx">
</span><del>-// Copies the `dataTransfer` property from a browser event object onto the
-// jQuery event object for the specified events
-Ember.EnumerableUtils.forEach(dragEvents, function(eventName) {
- Ember.$.event.fixHooks[eventName] = { props: ['dataTransfer'] };
-});
-
</del><span class="cx"> })();
</span><span class="cx">
</span><span class="cx">
</span><span class="lines">@@ -12611,12 +20630,13 @@
</span><span class="cx"> @submodule ember-views
</span><span class="cx"> */
</span><span class="cx">
</span><del>-/*** BEGIN METAMORPH HELPERS ***/
</del><ins>+/* BEGIN METAMORPH HELPERS */
</ins><span class="cx">
</span><span class="cx"> // Internet Explorer prior to 9 does not allow setting innerHTML if the first element
</span><span class="cx"> // is a "zero-scope" element. This problem can be worked around by making
</span><span class="cx"> // the first node an invisible text node. We, like Modernizr, use &shy;
</span><del>-var needsShy = (function(){
</del><ins>+
+var needsShy = this.document && (function() {
</ins><span class="cx"> var testEl = document.createElement('div');
</span><span class="cx"> testEl.innerHTML = "<div></div>";
</span><span class="cx"> testEl.firstChild.innerHTML = "<script></script>";
</span><span class="lines">@@ -12626,7 +20646,7 @@
</span><span class="cx"> // IE 8 (and likely earlier) likes to move whitespace preceeding
</span><span class="cx"> // a script tag to appear after it. This means that we can
</span><span class="cx"> // accidentally remove whitespace when updating a morph.
</span><del>-var movesWhitespace = (function() {
</del><ins>+var movesWhitespace = this.document && (function() {
</ins><span class="cx"> var testEl = document.createElement('div');
</span><span class="cx"> testEl.innerHTML = "Test: <script type='text/x-placeholder'></script>Value";
</span><span class="cx"> return testEl.childNodes[0].nodeValue === 'Test:' &&
</span><span class="lines">@@ -12683,7 +20703,7 @@
</span><span class="cx"> }
</span><span class="cx"> };
</span><span class="cx">
</span><del>-/*** END METAMORPH HELPERS */
</del><ins>+/* END METAMORPH HELPERS */
</ins><span class="cx">
</span><span class="cx">
</span><span class="cx"> var innerHTMLTags = {};
</span><span class="lines">@@ -12712,9 +20732,11 @@
</span><span class="cx"> if (canSetInnerHTML(tagName)) {
</span><span class="cx"> setInnerHTMLWithoutFix(element, html);
</span><span class="cx"> } else {
</span><del>- Ember.assert("Can't set innerHTML on "+element.tagName+" in this browser", element.outerHTML);
</del><ins>+ // 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);
</ins><span class="cx">
</span><del>- var startTag = element.outerHTML.match(new RegExp("<"+tagName+"([^>]*)>", 'i'))[0],
</del><ins>+ var startTag = outerHTML.match(new RegExp("<"+tagName+"([^>]*)>", 'i'))[0],
</ins><span class="cx"> endTag = '</'+tagName+'>';
</span><span class="cx">
</span><span class="cx"> var wrapper = document.createElement('div');
</span><span class="lines">@@ -12751,12 +20773,7 @@
</span><span class="cx"> */
</span><span class="cx">
</span><span class="cx"> var get = Ember.get, set = Ember.set;
</span><del>-var indexOf = Ember.ArrayPolyfills.indexOf;
</del><span class="cx">
</span><del>-
-
-
-
</del><span class="cx"> var ClassSet = function() {
</span><span class="cx"> this.seen = {};
</span><span class="cx"> this.list = [];
</span><span class="lines">@@ -12775,14 +20792,71 @@
</span><span class="cx"> }
</span><span class="cx"> };
</span><span class="cx">
</span><ins>+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 = {
+ "<": "&lt;",
+ ">": "&gt;",
+ '"': "&quot;",
+ "'": "&#x27;",
+ "`": "&#x60;"
+ };
+
+ var escapeChar = function(chr) {
+ return escape[chr] || "&amp;";
+ };
+
+ var string = value.toString();
+
+ if(!POSSIBLE_CHARS_REGEXP.test(string)) { return string; }
+ return string.replace(BAD_CHARS_REGEXP, escapeChar);
+}
+
+// IE 6/7 have bugs arond 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');
+})();
+
</ins><span class="cx"> /**
</span><span class="cx"> `Ember.RenderBuffer` gathers information regarding the a view and generates the
</span><span class="cx"> final representation. `Ember.RenderBuffer` will generate HTML which can be pushed
</span><span class="cx"> to the DOM.
</span><span class="cx">
</span><ins>+ ```javascript
+ var buffer = Ember.RenderBuffer('div');
+ ```
+
</ins><span class="cx"> @class RenderBuffer
</span><span class="cx"> @namespace Ember
</span><span class="cx"> @constructor
</span><ins>+ @param {String} tagName tag name (such as 'div' or 'p') used for the buffer
</ins><span class="cx"> */
</span><span class="cx"> Ember.RenderBuffer = function(tagName) {
</span><span class="cx"> return new Ember._RenderBuffer(tagName);
</span><span class="lines">@@ -12790,22 +20864,22 @@
</span><span class="cx">
</span><span class="cx"> Ember._RenderBuffer = function(tagName) {
</span><span class="cx"> this.tagNames = [tagName || null];
</span><del>- this.buffer = [];
</del><ins>+ this.buffer = "";
</ins><span class="cx"> };
</span><span class="cx">
</span><del>-Ember._RenderBuffer.prototype =
-/** @scope Ember.RenderBuffer.prototype */ {
</del><ins>+Ember._RenderBuffer.prototype = {
</ins><span class="cx">
</span><span class="cx"> // The root view's element
</span><span class="cx"> _element: null,
</span><span class="cx">
</span><ins>+ _hasElement: true,
+
</ins><span class="cx"> /**
</span><del>- @private
-
</del><span class="cx"> An internal set used to de-dupe class names when `addClass()` is
</span><span class="cx"> used. After each call to `addClass()`, the `classes` property
</span><span class="cx"> will be updated.
</span><span class="cx">
</span><ins>+ @private
</ins><span class="cx"> @property elementClasses
</span><span class="cx"> @type Array
</span><span class="cx"> @default []
</span><span class="lines">@@ -12914,7 +20988,7 @@
</span><span class="cx"> @chainable
</span><span class="cx"> */
</span><span class="cx"> push: function(string) {
</span><del>- this.buffer.push(string);
</del><ins>+ this.buffer += string;
</ins><span class="cx"> return this;
</span><span class="cx"> },
</span><span class="cx">
</span><span class="lines">@@ -12927,7 +21001,7 @@
</span><span class="cx"> */
</span><span class="cx"> addClass: function(className) {
</span><span class="cx"> // lazily create elementClasses
</span><del>- var elementClasses = this.elementClasses = (this.elementClasses || new ClassSet());
</del><ins>+ this.elementClasses = (this.elementClasses || new ClassSet());
</ins><span class="cx"> this.elementClasses.add(className);
</span><span class="cx"> this.classes = this.elementClasses.list;
</span><span class="cx">
</span><span class="lines">@@ -13032,7 +21106,7 @@
</span><span class="cx"> @chainable
</span><span class="cx"> */
</span><span class="cx"> style: function(name, value) {
</span><del>- var style = this.elementStyle = (this.elementStyle || {});
</del><ins>+ this.elementStyle = (this.elementStyle || {});
</ins><span class="cx">
</span><span class="cx"> this.elementStyle[name] = value;
</span><span class="cx"> return this;
</span><span class="lines">@@ -13047,7 +21121,7 @@
</span><span class="cx"> var tagName = this.currentTagName();
</span><span class="cx"> if (!tagName) { return; }
</span><span class="cx">
</span><del>- if (!this._element && this.buffer.length === 0) {
</del><ins>+ if (this._hasElement && !this._element && this.buffer.length === 0) {
</ins><span class="cx"> this._element = this.generateElement();
</span><span class="cx"> return;
</span><span class="cx"> }
</span><span class="lines">@@ -13060,27 +21134,27 @@
</span><span class="cx"> style = this.elementStyle,
</span><span class="cx"> attr, prop;
</span><span class="cx">
</span><del>- buffer.push('<' + tagName);
</del><ins>+ buffer += '<' + stripTagName(tagName);
</ins><span class="cx">
</span><span class="cx"> if (id) {
</span><del>- buffer.push(' id="' + this._escapeAttribute(id) + '"');
</del><ins>+ buffer += ' id="' + escapeAttribute(id) + '"';
</ins><span class="cx"> this.elementId = null;
</span><span class="cx"> }
</span><span class="cx"> if (classes) {
</span><del>- buffer.push(' class="' + this._escapeAttribute(classes.join(' ')) + '"');
</del><ins>+ buffer += ' class="' + escapeAttribute(classes.join(' ')) + '"';
</ins><span class="cx"> this.classes = null;
</span><span class="cx"> }
</span><span class="cx">
</span><span class="cx"> if (style) {
</span><del>- buffer.push(' style="');
</del><ins>+ buffer += ' style="';
</ins><span class="cx">
</span><span class="cx"> for (prop in style) {
</span><span class="cx"> if (style.hasOwnProperty(prop)) {
</span><del>- buffer.push(prop + ':' + this._escapeAttribute(style[prop]) + ';');
</del><ins>+ buffer += prop + ':' + escapeAttribute(style[prop]) + ';';
</ins><span class="cx"> }
</span><span class="cx"> }
</span><span class="cx">
</span><del>- buffer.push('"');
</del><ins>+ buffer += '"';
</ins><span class="cx">
</span><span class="cx"> this.elementStyle = null;
</span><span class="cx"> }
</span><span class="lines">@@ -13088,7 +21162,7 @@
</span><span class="cx"> if (attrs) {
</span><span class="cx"> for (attr in attrs) {
</span><span class="cx"> if (attrs.hasOwnProperty(attr)) {
</span><del>- buffer.push(' ' + attr + '="' + this._escapeAttribute(attrs[attr]) + '"');
</del><ins>+ buffer += ' ' + attr + '="' + escapeAttribute(attrs[attr]) + '"';
</ins><span class="cx"> }
</span><span class="cx"> }
</span><span class="cx">
</span><span class="lines">@@ -13101,9 +21175,9 @@
</span><span class="cx"> var value = props[prop];
</span><span class="cx"> if (value || typeof(value) === 'number') {
</span><span class="cx"> if (value === true) {
</span><del>- buffer.push(' ' + prop + '="' + prop + '"');
</del><ins>+ buffer += ' ' + prop + '="' + prop + '"';
</ins><span class="cx"> } else {
</span><del>- buffer.push(' ' + prop + '="' + this._escapeAttribute(props[prop]) + '"');
</del><ins>+ buffer += ' ' + prop + '="' + escapeAttribute(props[prop]) + '"';
</ins><span class="cx"> }
</span><span class="cx"> }
</span><span class="cx"> }
</span><span class="lines">@@ -13112,12 +21186,13 @@
</span><span class="cx"> this.elementProperties = null;
</span><span class="cx"> }
</span><span class="cx">
</span><del>- buffer.push('>');
</del><ins>+ buffer += '>';
+ this.buffer = buffer;
</ins><span class="cx"> },
</span><span class="cx">
</span><span class="cx"> pushClosingTag: function() {
</span><span class="cx"> var tagName = this.tagNames.pop();
</span><del>- if (tagName) { this.buffer.push('</' + tagName + '>'); }
</del><ins>+ if (tagName) { this.buffer += '</' + stripTagName(tagName) + '>'; }
</ins><span class="cx"> },
</span><span class="cx">
</span><span class="cx"> currentTagName: function() {
</span><span class="lines">@@ -13126,15 +21201,23 @@
</span><span class="cx">
</span><span class="cx"> generateElement: function() {
</span><span class="cx"> var tagName = this.tagNames.pop(), // pop since we don't need to close
</span><del>- element = document.createElement(tagName),
- $element = Ember.$(element),
</del><span class="cx"> id = this.elementId,
</span><span class="cx"> classes = this.classes,
</span><span class="cx"> attrs = this.elementAttributes,
</span><span class="cx"> props = this.elementProperties,
</span><span class="cx"> style = this.elementStyle,
</span><del>- styleBuffer = '', attr, prop;
</del><ins>+ styleBuffer = '', attr, prop, tagString;
</ins><span class="cx">
</span><ins>+ 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);
+
</ins><span class="cx"> if (id) {
</span><span class="cx"> $element.attr('id', id);
</span><span class="cx"> this.elementId = null;
</span><span class="lines">@@ -13201,41 +21284,21 @@
</span><span class="cx"> @return {String} The generated HTML
</span><span class="cx"> */
</span><span class="cx"> string: function() {
</span><del>- if (this._element) {
- return this.element().outerHTML;
</del><ins>+ 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.$('<div/>').append(thisElement).html();
+ }
+ return outerHTML;
</ins><span class="cx"> } else {
</span><span class="cx"> return this.innerString();
</span><span class="cx"> }
</span><span class="cx"> },
</span><span class="cx">
</span><span class="cx"> innerString: function() {
</span><del>- return this.buffer.join('');
- },
-
- _escapeAttribute: function(value) {
- // Stolen shamelessly from Handlebars
-
- var escape = {
- "<": "&lt;",
- ">": "&gt;",
- '"': "&quot;",
- "'": "&#x27;",
- "`": "&#x60;"
- };
-
- var badChars = /&(?!\w+;)|[<>"'`]/g;
- var possible = /[&<>"'`]/;
-
- var escapeChar = function(chr) {
- return escape[chr] || "&amp;";
- };
-
- var string = value.toString();
-
- if(!possible.test(string)) { return string; }
- return string.replace(badChars, escapeChar);
</del><ins>+ return this.buffer;
</ins><span class="cx"> }
</span><del>-
</del><span class="cx"> };
</span><span class="cx">
</span><span class="cx"> })();
</span><span class="lines">@@ -13261,12 +21324,50 @@
</span><span class="cx"> @private
</span><span class="cx"> @extends Ember.Object
</span><span class="cx"> */
</span><del>-Ember.EventDispatcher = Ember.Object.extend(
-/** @scope Ember.EventDispatcher.prototype */{
</del><ins>+Ember.EventDispatcher = Ember.Object.extend({
</ins><span class="cx">
</span><span class="cx"> /**
</span><del>- @private
</del><ins>+ 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.
</ins><span class="cx">
</span><ins>+ 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'
+ },
+
+ /**
</ins><span class="cx"> The root DOM element to which event listeners should be attached. Event
</span><span class="cx"> listeners will be attached to the document unless this is overridden.
</span><span class="cx">
</span><span class="lines">@@ -13275,6 +21376,7 @@
</span><span class="cx"> The default body is a string since this may be evaluated before document.body
</span><span class="cx"> exists in the DOM.
</span><span class="cx">
</span><ins>+ @private
</ins><span class="cx"> @property rootElement
</span><span class="cx"> @type DOMElement
</span><span class="cx"> @default 'body'
</span><span class="lines">@@ -13282,8 +21384,6 @@
</span><span class="cx"> rootElement: 'body',
</span><span class="cx">
</span><span class="cx"> /**
</span><del>- @private
-
</del><span class="cx"> Sets up event listeners for standard browser events.
</span><span class="cx">
</span><span class="cx"> This will be called after the browser sends a `DOMContentReady` event. By
</span><span class="lines">@@ -13291,44 +21391,22 @@
</span><span class="cx"> would like to register the listeners on a different element, set the event
</span><span class="cx"> dispatcher's `root` property.
</span><span class="cx">
</span><ins>+ @private
</ins><span class="cx"> @method setup
</span><span class="cx"> @param addedEvents {Hash}
</span><span class="cx"> */
</span><del>- setup: function(addedEvents) {
- var event, 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'
- };
</del><ins>+ setup: function(addedEvents, rootElement) {
+ var event, events = get(this, 'events');
</ins><span class="cx">
</span><span class="cx"> Ember.$.extend(events, addedEvents || {});
</span><span class="cx">
</span><del>- var rootElement = Ember.$(get(this, 'rootElement'));
</del><span class="cx">
</span><ins>+ if (!Ember.isNone(rootElement)) {
+ set(this, 'rootElement', rootElement);
+ }
+
+ rootElement = Ember.$(get(this, 'rootElement'));
+
</ins><span class="cx"> 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'));
</span><span class="cx"> 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);
</span><span class="cx"> 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);
</span><span class="lines">@@ -13345,8 +21423,6 @@
</span><span class="cx"> },
</span><span class="cx">
</span><span class="cx"> /**
</span><del>- @private
-
</del><span class="cx"> Registers an event listener on the document. If the given event is
</span><span class="cx"> triggered, the provided event handler will be triggered on the target view.
</span><span class="cx">
</span><span class="lines">@@ -13361,6 +21437,7 @@
</span><span class="cx"> setupHandler('mousedown', 'mouseDown');
</span><span class="cx"> ```
</span><span class="cx">
</span><ins>+ @private
</ins><span class="cx"> @method setupHandler
</span><span class="cx"> @param {Element} rootElement
</span><span class="cx"> @param {String} event the browser-originated event to listen to
</span><span class="lines">@@ -13369,8 +21446,8 @@
</span><span class="cx"> setupHandler: function(rootElement, event, eventName) {
</span><span class="cx"> var self = this;
</span><span class="cx">
</span><del>- rootElement.delegate('.ember-view', event + '.ember', function(evt, triggeringManager) {
- return Ember.handleErrors(function() {
</del><ins>+ rootElement.on(event + '.ember', '.ember-view', function(evt, triggeringManager) {
+ return Ember.handleErrors(function handleViewEvent() {
</ins><span class="cx"> var view = Ember.View.views[this.id],
</span><span class="cx"> result = true, manager = null;
</span><span class="cx">
</span><span class="lines">@@ -13388,8 +21465,8 @@
</span><span class="cx"> }, this);
</span><span class="cx"> });
</span><span class="cx">
</span><del>- rootElement.delegate('[data-ember-action]', event + '.ember', function(evt) {
- return Ember.handleErrors(function() {
</del><ins>+ rootElement.on(event + '.ember', '[data-ember-action]', function(evt) {
+ return Ember.handleErrors(function handleActionEvent() {
</ins><span class="cx"> var actionId = Ember.$(evt.currentTarget).attr('data-ember-action'),
</span><span class="cx"> action = Ember.Handlebars.ActionHelper.registeredActions[actionId];
</span><span class="cx">
</span><span class="lines">@@ -13421,7 +21498,9 @@
</span><span class="cx">
</span><span class="cx"> var handler = object[eventName];
</span><span class="cx"> if (Ember.typeOf(handler) === 'function') {
</span><del>- result = handler.call(object, evt, view);
</del><ins>+ result = Ember.run(function() {
+ return handler.call(object, evt, view);
+ });
</ins><span class="cx"> // Do not preventDefault in eventManagers.
</span><span class="cx"> evt.stopPropagation();
</span><span class="cx"> }
</span><span class="lines">@@ -13433,14 +21512,14 @@
</span><span class="cx"> },
</span><span class="cx">
</span><span class="cx"> _bubbleEvent: function(view, evt, eventName) {
</span><del>- return Ember.run(function() {
</del><ins>+ return Ember.run(function bubbleEvent() {
</ins><span class="cx"> return view.handleEvent(eventName, evt);
</span><span class="cx"> });
</span><span class="cx"> },
</span><span class="cx">
</span><span class="cx"> destroy: function() {
</span><span class="cx"> var rootElement = get(this, 'rootElement');
</span><del>- Ember.$(rootElement).undelegate('.ember').removeClass('ember-application');
</del><ins>+ Ember.$(rootElement).off('.ember', '**').removeClass('ember-application');
</ins><span class="cx"> return this._super();
</span><span class="cx"> }
</span><span class="cx"> });
</span><span class="lines">@@ -13458,8 +21537,9 @@
</span><span class="cx"> // Add a new named queue for rendering views that happens
</span><span class="cx"> // after bindings have synced, and a queue for scheduling actions
</span><span class="cx"> // that that should occur after view rendering.
</span><del>-var queues = Ember.run.queues;
-queues.splice(Ember.$.inArray('actions', queues)+1, 0, 'render', 'afterRender');
</del><ins>+var queues = Ember.run.queues,
+ indexOf = Ember.ArrayPolyfills.indexOf;
+queues.splice(indexOf.call(queues, 'actions')+1, 0, 'render', 'afterRender');
</ins><span class="cx">
</span><span class="cx"> })();
</span><span class="cx">
</span><span class="lines">@@ -13494,9 +21574,8 @@
</span><span class="cx"> set(this, '_childContainers', {});
</span><span class="cx"> },
</span><span class="cx">
</span><del>- _modelDidChange: Ember.observer(function() {
- var containers = get(this, '_childContainers'),
- container;
</del><ins>+ _modelDidChange: Ember.observer('model', function() {
+ var containers = get(this, '_childContainers');
</ins><span class="cx">
</span><span class="cx"> for (var prop in containers) {
</span><span class="cx"> if (!containers.hasOwnProperty(prop)) { continue; }
</span><span class="lines">@@ -13504,7 +21583,7 @@
</span><span class="cx"> }
</span><span class="cx">
</span><span class="cx"> set(this, '_childContainers', {});
</span><del>- }, 'model')
</del><ins>+ })
</ins><span class="cx"> });
</span><span class="cx">
</span><span class="cx"> })();
</span><span class="lines">@@ -13525,18 +21604,21 @@
</span><span class="cx"> @submodule ember-views
</span><span class="cx"> */
</span><span class="cx">
</span><del>-var get = Ember.get, set = Ember.set, addObserver = Ember.addObserver, removeObserver = Ember.removeObserver;
-var meta = Ember.meta, guidFor = Ember.guidFor, fmt = Ember.String.fmt;
-var a_slice = [].slice;
</del><ins>+var get = Ember.get, set = Ember.set;
+var guidFor = Ember.guidFor;
</ins><span class="cx"> var a_forEach = Ember.EnumerableUtils.forEach;
</span><span class="cx"> var a_addObject = Ember.EnumerableUtils.addObject;
</span><ins>+var meta = Ember.meta;
</ins><span class="cx">
</span><span class="cx"> var childViewsProperty = Ember.computed(function() {
</span><span class="cx"> var childViews = this._childViews, ret = Ember.A(), view = this;
</span><span class="cx">
</span><span class="cx"> a_forEach(childViews, function(view) {
</span><ins>+ var currentChildViews;
</ins><span class="cx"> if (view.isVirtual) {
</span><del>- ret.pushObjects(get(view, 'childViews'));
</del><ins>+ if (currentChildViews = get(view, 'childViews')) {
+ ret.pushObjects(currentChildViews);
+ }
</ins><span class="cx"> } else {
</span><span class="cx"> ret.push(view);
</span><span class="cx"> }
</span><span class="lines">@@ -13544,10 +21626,10 @@
</span><span class="cx">
</span><span class="cx"> ret.replace = function (idx, removedCount, addedViews) {
</span><span class="cx"> if (view instanceof Ember.ContainerView) {
</span><del>- Ember.deprecate("Manipulating a Ember.ContainerView through its childViews property is deprecated. Please use the ContainerView instance itself as an Ember.MutableArray.");
</del><ins>+ Ember.deprecate("Manipulating an Ember.ContainerView through its childViews property is deprecated. Please use the ContainerView instance itself as an Ember.MutableArray.");
</ins><span class="cx"> return view.replace(idx, removedCount, addedViews);
</span><span class="cx"> }
</span><del>- throw new Error("childViews is immutable");
</del><ins>+ throw new Ember.Error("childViews is immutable");
</ins><span class="cx"> };
</span><span class="cx">
</span><span class="cx"> return ret;
</span><span class="lines">@@ -13566,25 +21648,29 @@
</span><span class="cx"> */
</span><span class="cx"> Ember.TEMPLATES = {};
</span><span class="cx">
</span><del>-Ember.CoreView = Ember.Object.extend(Ember.Evented, {
</del><ins>+/**
+ `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, {
</ins><span class="cx"> isView: true,
</span><span class="cx">
</span><span class="cx"> states: states,
</span><span class="cx">
</span><span class="cx"> init: function() {
</span><span class="cx"> this._super();
</span><del>-
- // Register the view for event handling. This hash is used by
- // Ember.EventDispatcher to dispatch incoming events.
- if (!this.isVirtual) {
- Ember.assert("Attempted to register a view with an id already in use: "+this.elementId, !Ember.View.views[this.elementId]);
- Ember.View.views[this.elementId] = this;
- }
-
- this.addBeforeObserver('elementId', function() {
- throw new Error("Changing a view's elementId after creation is not allowed");
- });
-
</del><span class="cx"> this.transitionTo('preRender');
</span><span class="cx"> },
</span><span class="cx">
</span><span class="lines">@@ -13614,7 +21700,7 @@
</span><span class="cx"> concreteView: Ember.computed(function() {
</span><span class="cx"> if (!this.isVirtual) { return this; }
</span><span class="cx"> else { return get(this, 'parentView'); }
</span><del>- }).property('parentView').volatile(),
</del><ins>+ }).property('parentView'),
</ins><span class="cx">
</span><span class="cx"> instrumentName: 'core_view',
</span><span class="cx">
</span><span class="lines">@@ -13623,8 +21709,6 @@
</span><span class="cx"> },
</span><span class="cx">
</span><span class="cx"> /**
</span><del>- @private
-
</del><span class="cx"> Invoked by the view system when this view needs to produce an HTML
</span><span class="cx"> representation. This method will create a new render buffer, if needed,
</span><span class="cx"> then apply any default attributes, such as class names and visibility.
</span><span class="lines">@@ -13639,6 +21723,7 @@
</span><span class="cx"> @param {Ember.RenderBuffer} buffer the render buffer. If no buffer is
</span><span class="cx"> passed, a default buffer, using the current view's `tagName`, will
</span><span class="cx"> be used.
</span><ins>+ @private
</ins><span class="cx"> */
</span><span class="cx"> renderToBuffer: function(parentBuffer, bufferOperation) {
</span><span class="cx"> var name = 'render.' + this.instrumentName,
</span><span class="lines">@@ -13646,14 +21731,12 @@
</span><span class="cx">
</span><span class="cx"> this.instrumentDetails(details);
</span><span class="cx">
</span><del>- return Ember.instrument(name, details, function() {
</del><ins>+ return Ember.instrument(name, details, function instrumentRenderToBuffer() {
</ins><span class="cx"> return this._renderToBuffer(parentBuffer, bufferOperation);
</span><span class="cx"> }, this);
</span><span class="cx"> },
</span><span class="cx">
</span><span class="cx"> _renderToBuffer: function(parentBuffer, bufferOperation) {
</span><del>- Ember.run.sync();
-
</del><span class="cx"> // If this is the top-most view, start a new buffer. Otherwise,
</span><span class="cx"> // create a new buffer relative to the original using the
</span><span class="cx"> // provided buffer operation (for example, `insertAfter` will
</span><span class="lines">@@ -13675,13 +21758,12 @@
</span><span class="cx"> },
</span><span class="cx">
</span><span class="cx"> /**
</span><del>- @private
-
</del><span class="cx"> Override the default event firing from `Ember.Evented` to
</span><span class="cx"> also call methods with the given name.
</span><span class="cx">
</span><span class="cx"> @method trigger
</span><span class="cx"> @param name {String}
</span><ins>+ @private
</ins><span class="cx"> */
</span><span class="cx"> trigger: function(name) {
</span><span class="cx"> this._super.apply(this, arguments);
</span><span class="lines">@@ -13695,13 +21777,27 @@
</span><span class="cx"> }
</span><span class="cx"> },
</span><span class="cx">
</span><ins>+ 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 (' + actionName + ' on ' + this + ')', false);
+ this[actionName].apply(this, args);
+ return;
+ },
+
</ins><span class="cx"> has: function(name) {
</span><span class="cx"> return Ember.typeOf(this[name]) === 'function' || this._super(name);
</span><span class="cx"> },
</span><span class="cx">
</span><del>- willDestroy: function() {
</del><ins>+ destroy: function() {
</ins><span class="cx"> var parent = this._parentView;
</span><span class="cx">
</span><ins>+ if (!this._super()) { return; }
+
</ins><span class="cx"> // destroy the element -- this will avoid each child view destroying
</span><span class="cx"> // the element over and over again...
</span><span class="cx"> if (!this.removedFromDOM) { this.destroyElement(); }
</span><span class="lines">@@ -13711,10 +21807,9 @@
</span><span class="cx"> // the DOM again.
</span><span class="cx"> if (parent) { parent.removeChild(this); }
</span><span class="cx">
</span><del>- this.transitionTo('destroyed');
</del><ins>+ this.transitionTo('destroying', false);
</ins><span class="cx">
</span><del>- // next remove view from global hash
- if (!this.isVirtual) delete Ember.View.views[this.elementId];
</del><ins>+ return this;
</ins><span class="cx"> },
</span><span class="cx">
</span><span class="cx"> clearRenderedChildren: Ember.K,
</span><span class="lines">@@ -13724,6 +21819,68 @@
</span><span class="cx"> destroyElement: Ember.K
</span><span class="cx"> });
</span><span class="cx">
</span><ins>+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 = [];
+
</ins><span class="cx"> /**
</span><span class="cx"> `Ember.View` is the class in Ember responsible for encapsulating templates of
</span><span class="cx"> HTML content, combining templates with data to render as sections of a page's
</span><span class="lines">@@ -13733,7 +21890,7 @@
</span><span class="cx">
</span><span class="cx"> The default HTML tag name used for a view's DOM representation is `div`. This
</span><span class="cx"> can be customized by setting the `tagName` property. The following view
</span><del>-class:
</del><ins>+ class:
</ins><span class="cx">
</span><span class="cx"> ```javascript
</span><span class="cx"> ParagraphView = Ember.View.extend({
</span><span class="lines">@@ -13773,8 +21930,8 @@
</span><span class="cx"> MyView = Ember.View.extend({
</span><span class="cx"> classNameBindings: ['propertyA', 'propertyB'],
</span><span class="cx"> propertyA: 'from-a',
</span><del>- propertyB: function(){
- if(someLogic){ return 'from-b'; }
</del><ins>+ propertyB: function() {
+ if (someLogic) { return 'from-b'; }
</ins><span class="cx"> }.property()
</span><span class="cx"> });
</span><span class="cx"> ```
</span><span class="lines">@@ -13859,7 +22016,7 @@
</span><span class="cx">
</span><span class="cx"> ```javascript
</span><span class="cx"> // Applies 'enabled' class when isEnabled is true and 'disabled' when isEnabled is false
</span><del>- Ember.View.create({
</del><ins>+ Ember.View.extend({
</ins><span class="cx"> classNameBindings: ['isEnabled:enabled:disabled']
</span><span class="cx"> isEnabled: true
</span><span class="cx"> });
</span><span class="lines">@@ -13882,7 +22039,7 @@
</span><span class="cx">
</span><span class="cx"> ```javascript
</span><span class="cx"> // Applies no class when isEnabled is true and class 'disabled' when isEnabled is false
</span><del>- Ember.View.create({
</del><ins>+ Ember.View.extend({
</ins><span class="cx"> classNameBindings: ['isEnabled::disabled']
</span><span class="cx"> isEnabled: true
</span><span class="cx"> });
</span><span class="lines">@@ -13907,8 +22064,8 @@
</span><span class="cx"> will be removed.
</span><span class="cx">
</span><span class="cx"> Both `classNames` and `classNameBindings` are concatenated properties. See
</span><del>- `Ember.Object` documentation for more information about concatenated
- properties.
</del><ins>+ [Ember.Object](/api/classes/Ember.Object.html) documentation for more
+ information about concatenated properties.
</ins><span class="cx">
</span><span class="cx"> ## HTML Attributes
</span><span class="cx">
</span><span class="lines">@@ -13955,7 +22112,7 @@
</span><span class="cx"> MyTextInput = Ember.View.extend({
</span><span class="cx"> tagName: 'input',
</span><span class="cx"> attributeBindings: ['disabled'],
</span><del>- disabled: function(){
</del><ins>+ disabled: function() {
</ins><span class="cx"> if (someLogic) {
</span><span class="cx"> return true;
</span><span class="cx"> } else {
</span><span class="lines">@@ -13968,7 +22125,7 @@
</span><span class="cx"> Updates to the the property of an attribute binding will result in automatic
</span><span class="cx"> update of the HTML attribute in the view's rendered HTML representation.
</span><span class="cx">
</span><del>- `attributeBindings` is a concatenated property. See `Ember.Object`
</del><ins>+ `attributeBindings` is a concatenated property. See [Ember.Object](/api/classes/Ember.Object.html)
</ins><span class="cx"> documentation for more information about concatenated properties.
</span><span class="cx">
</span><span class="cx"> ## Templates
</span><span class="lines">@@ -14008,12 +22165,25 @@
</span><span class="cx"> });
</span><span class="cx"> ```
</span><span class="cx">
</span><ins>+ If you have nested resources, your Handlebars template will look like this:
+
+ ```html
+ <script type='text/x-handlebars' data-template-name='posts/new'>
+ <h1>New Post</h1>
+ </script>
+ ```
+
+ And `templateName` property:
+
+ ```javascript
+ AView = Ember.View.extend({
+ templateName: 'posts/new'
+ });
+ ```
+
</ins><span class="cx"> Using a value for `templateName` that does not have a Handlebars template
</span><span class="cx"> with a matching `data-template-name` attribute will throw an error.
</span><span class="cx">
</span><del>- Assigning a value to both `template` and `templateName` properties will throw
- an error.
-
</del><span class="cx"> For views classes that may have a template later defined (e.g. as the block
</span><span class="cx"> portion of a `{{view}}` Handlebars helper call in another template or in
</span><span class="cx"> a subclass), you can provide a `defaultTemplate` property set to compiled
</span><span class="lines">@@ -14064,7 +22234,7 @@
</span><span class="cx">
</span><span class="cx"> aController = Ember.Object.create({
</span><span class="cx"> firstName: 'Barry',
</span><del>- excitedGreeting: function(){
</del><ins>+ excitedGreeting: function() {
</ins><span class="cx"> return this.get("content.firstName") + "!!!"
</span><span class="cx"> }.property()
</span><span class="cx"> });
</span><span class="lines">@@ -14119,7 +22289,8 @@
</span><span class="cx"> </div>
</span><span class="cx"> ```
</span><span class="cx">
</span><del>- See `Handlebars.helpers.yield` for more information.
</del><ins>+ See [Ember.Handlebars.helpers.yield](/api/classes/Ember.Handlebars.helpers.html#method_yield)
+ for more information.
</ins><span class="cx">
</span><span class="cx"> ## Responding to Browser Events
</span><span class="cx">
</span><span class="lines">@@ -14135,7 +22306,7 @@
</span><span class="cx">
</span><span class="cx"> ```javascript
</span><span class="cx"> AView = Ember.View.extend({
</span><del>- click: function(event){
</del><ins>+ click: function(event) {
</ins><span class="cx"> // will be called when when an instance's
</span><span class="cx"> // rendered element is clicked
</span><span class="cx"> }
</span><span class="lines">@@ -14156,7 +22327,7 @@
</span><span class="cx"> ```javascript
</span><span class="cx"> AView = Ember.View.extend({
</span><span class="cx"> eventManager: Ember.Object.create({
</span><del>- doubleClick: function(event, view){
</del><ins>+ doubleClick: function(event, view) {
</ins><span class="cx"> // will be called when when an instance's
</span><span class="cx"> // rendered element or any rendering
</span><span class="cx"> // of this views's descendent
</span><span class="lines">@@ -14171,12 +22342,12 @@
</span><span class="cx">
</span><span class="cx"> ```javascript
</span><span class="cx"> AView = Ember.View.extend({
</span><del>- mouseEnter: function(event){
</del><ins>+ mouseEnter: function(event) {
</ins><span class="cx"> // will never trigger.
</span><span class="cx"> },
</span><span class="cx"> eventManager: Ember.Object.create({
</span><del>- mouseEnter: function(event, view){
- // takes presedence over AView#mouseEnter
</del><ins>+ mouseEnter: function(event, view) {
+ // takes precedence over AView#mouseEnter
</ins><span class="cx"> }
</span><span class="cx"> })
</span><span class="cx"> });
</span><span class="lines">@@ -14193,21 +22364,21 @@
</span><span class="cx"> OuterView = Ember.View.extend({
</span><span class="cx"> template: Ember.Handlebars.compile("outer {{#view InnerView}}inner{{/view}} outer"),
</span><span class="cx"> eventManager: Ember.Object.create({
</span><del>- mouseEnter: function(event, view){
</del><ins>+ mouseEnter: function(event, view) {
</ins><span class="cx"> // view might be instance of either
</span><del>- // OutsideView or InnerView depending on
</del><ins>+ // OuterView or InnerView depending on
</ins><span class="cx"> // where on the page the user interaction occured
</span><span class="cx"> }
</span><span class="cx"> })
</span><span class="cx"> });
</span><span class="cx">
</span><span class="cx"> InnerView = Ember.View.extend({
</span><del>- click: function(event){
</del><ins>+ click: function(event) {
</ins><span class="cx"> // will be called if rendered inside
</span><span class="cx"> // an OuterView because OuterView's
</span><span class="cx"> // eventManager doesn't handle click events
</span><span class="cx"> },
</span><del>- mouseEnter: function(event){
</del><ins>+ mouseEnter: function(event) {
</ins><span class="cx"> // will never be called if rendered inside
</span><span class="cx"> // an OuterView.
</span><span class="cx"> }
</span><span class="lines">@@ -14216,12 +22387,14 @@
</span><span class="cx">
</span><span class="cx"> ### Handlebars `{{action}}` Helper
</span><span class="cx">
</span><del>- See `Handlebars.helpers.action`.
</del><ins>+ See [Handlebars.helpers.action](/api/classes/Ember.Handlebars.helpers.html#method_action).
</ins><span class="cx">
</span><span class="cx"> ### Event Names
</span><span class="cx">
</span><del>- Possible events names for any of the responding approaches described above
- are:
</del><ins>+ 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`.
</ins><span class="cx">
</span><span class="cx"> Touch events:
</span><span class="cx">
</span><span class="lines">@@ -14249,7 +22422,7 @@
</span><span class="cx"> * `mouseEnter`
</span><span class="cx"> * `mouseLeave`
</span><span class="cx">
</span><del>- Form events:
</del><ins>+ Form events:
</ins><span class="cx">
</span><span class="cx"> * `submit`
</span><span class="cx"> * `change`
</span><span class="lines">@@ -14257,7 +22430,7 @@
</span><span class="cx"> * `focusOut`
</span><span class="cx"> * `input`
</span><span class="cx">
</span><del>- HTML5 drag and drop events:
</del><ins>+ HTML5 drag and drop events:
</ins><span class="cx">
</span><span class="cx"> * `dragStart`
</span><span class="cx"> * `drag`
</span><span class="lines">@@ -14269,16 +22442,14 @@
</span><span class="cx"> ## Handlebars `{{view}}` Helper
</span><span class="cx">
</span><span class="cx"> Other `Ember.View` instances can be included as part of a view's template by
</span><del>- using the `{{view}}` Handlebars helper. See `Handlebars.helpers.view` for
- additional information.
</del><ins>+ using the `{{view}}` Handlebars helper. See [Ember.Handlebars.helpers.view](/api/classes/Ember.Handlebars.helpers.html#method_view)
+ for additional information.
</ins><span class="cx">
</span><span class="cx"> @class View
</span><span class="cx"> @namespace Ember
</span><del>- @extends Ember.Object
- @uses Ember.Evented
</del><ins>+ @extends Ember.CoreView
</ins><span class="cx"> */
</span><del>-Ember.View = Ember.CoreView.extend(
-/** @scope Ember.View.prototype */ {
</del><ins>+Ember.View = Ember.CoreView.extend({
</ins><span class="cx">
</span><span class="cx"> concatenatedProperties: ['classNames', 'classNameBindings', 'attributeBindings'],
</span><span class="cx">
</span><span class="lines">@@ -14286,7 +22457,7 @@
</span><span class="cx"> @property isView
</span><span class="cx"> @type Boolean
</span><span class="cx"> @default true
</span><del>- @final
</del><ins>+ @static
</ins><span class="cx"> */
</span><span class="cx"> isView: true,
</span><span class="cx">
</span><span class="lines">@@ -14297,9 +22468,8 @@
</span><span class="cx"> /**
</span><span class="cx"> The name of the template to lookup if no template is provided.
</span><span class="cx">
</span><del>- `Ember.View` will look for a template with this name in this view's
- `templates` object. By default, this will be a global object
- shared in `Ember.TEMPLATES`.
</del><ins>+ By default `Ember.View` will lookup a template with this name in
+ `Ember.TEMPLATES` (a shared global object).
</ins><span class="cx">
</span><span class="cx"> @property templateName
</span><span class="cx"> @type String
</span><span class="lines">@@ -14310,9 +22480,8 @@
</span><span class="cx"> /**
</span><span class="cx"> The name of the layout to lookup if no layout is provided.
</span><span class="cx">
</span><del>- `Ember.View` will look for a template with this name in this view's
- `templates` object. By default, this will be a global object
- shared in `Ember.TEMPLATES`.
</del><ins>+ By default `Ember.View` will lookup a template with this name in
+ `Ember.TEMPLATES` (a shared global object).
</ins><span class="cx">
</span><span class="cx"> @property layoutName
</span><span class="cx"> @type String
</span><span class="lines">@@ -14321,15 +22490,6 @@
</span><span class="cx"> layoutName: null,
</span><span class="cx">
</span><span class="cx"> /**
</span><del>- The hash in which to look for `templateName`.
-
- @property templates
- @type Ember.Object
- @default Ember.TEMPLATES
- */
- templates: Ember.TEMPLATES,
-
- /**
</del><span class="cx"> The template used to render the view. This should be a function that
</span><span class="cx"> accepts an optional context parameter and returns a string of HTML that
</span><span class="cx"> will be inserted into the DOM relative to its parent view.
</span><span class="lines">@@ -14351,14 +22511,6 @@
</span><span class="cx"> return template || get(this, 'defaultTemplate');
</span><span class="cx"> }).property('templateName'),
</span><span class="cx">
</span><del>- container: Ember.computed(function() {
- var parentView = get(this, '_parentView');
-
- if (parentView) { return get(parentView, 'container'); }
-
- return Ember.Container && Ember.Container.defaultContainer;
- }),
-
</del><span class="cx"> /**
</span><span class="cx"> The controller managing this view. If this property is set, it will be
</span><span class="cx"> made available for use by the template.
</span><span class="lines">@@ -14394,16 +22546,18 @@
</span><span class="cx"> return layout || get(this, 'defaultLayout');
</span><span class="cx"> }).property('layoutName'),
</span><span class="cx">
</span><ins>+ _yield: function(context, options) {
+ var template = get(this, 'template');
+ if (template) { template(context, options); }
+ },
+
</ins><span class="cx"> templateForName: function(name, type) {
</span><span class="cx"> if (!name) { return; }
</span><del>-
</del><span class="cx"> Ember.assert("templateNames are not allowed to contain periods: "+name, name.indexOf('.') === -1);
</span><span class="cx">
</span><del>- var container = get(this, 'container');
-
- if (container) {
- return container.lookup('template:' + name);
- }
</del><ins>+ // the defaultContainer is deprecated
+ var container = this.container || (Ember.Container && Ember.Container.defaultContainer);
+ return container && container.lookup('template:' + name);
</ins><span class="cx"> },
</span><span class="cx">
</span><span class="cx"> /**
</span><span class="lines">@@ -14428,8 +22582,6 @@
</span><span class="cx"> }).volatile(),
</span><span class="cx">
</span><span class="cx"> /**
</span><del>- @private
-
</del><span class="cx"> Private copy of the view's template context. This can be set directly
</span><span class="cx"> by Handlebars without triggering the observer that causes the view
</span><span class="cx"> to be re-rendered.
</span><span class="lines">@@ -14445,6 +22597,7 @@
</span><span class="cx"> something of a hack and should be revisited.
</span><span class="cx">
</span><span class="cx"> @property _context
</span><ins>+ @private
</ins><span class="cx"> */
</span><span class="cx"> _context: Ember.computed(function(key) {
</span><span class="cx"> var parentView, controller;
</span><span class="lines">@@ -14462,16 +22615,15 @@
</span><span class="cx"> }),
</span><span class="cx">
</span><span class="cx"> /**
</span><del>- @private
-
</del><span class="cx"> If a value that affects template rendering changes, the view should be
</span><span class="cx"> re-rendered to reflect the new value.
</span><span class="cx">
</span><del>- @method _displayPropertyDidChange
</del><ins>+ @method _contextDidChange
+ @private
</ins><span class="cx"> */
</span><del>- _contextDidChange: Ember.observer(function() {
</del><ins>+ _contextDidChange: Ember.observer('context', function() {
</ins><span class="cx"> this.rerender();
</span><del>- }, 'context'),
</del><ins>+ }),
</ins><span class="cx">
</span><span class="cx"> /**
</span><span class="cx"> If `false`, the view will appear hidden in DOM.
</span><span class="lines">@@ -14483,36 +22635,35 @@
</span><span class="cx"> isVisible: true,
</span><span class="cx">
</span><span class="cx"> /**
</span><del>- @private
-
</del><span class="cx"> Array of child views. You should never edit this array directly.
</span><span class="cx"> Instead, use `appendChild` and `removeFromParent`.
</span><span class="cx">
</span><span class="cx"> @property childViews
</span><span class="cx"> @type Array
</span><span class="cx"> @default []
</span><ins>+ @private
</ins><span class="cx"> */
</span><span class="cx"> childViews: childViewsProperty,
</span><span class="cx">
</span><del>- _childViews: [],
</del><ins>+ _childViews: EMPTY_ARRAY,
</ins><span class="cx">
</span><span class="cx"> // When it's a virtual view, we need to notify the parent that their
</span><span class="cx"> // childViews will change.
</span><del>- _childViewsWillChange: Ember.beforeObserver(function() {
</del><ins>+ _childViewsWillChange: Ember.beforeObserver('childViews', function() {
</ins><span class="cx"> if (this.isVirtual) {
</span><span class="cx"> var parentView = get(this, 'parentView');
</span><span class="cx"> if (parentView) { Ember.propertyWillChange(parentView, 'childViews'); }
</span><span class="cx"> }
</span><del>- }, 'childViews'),
</del><ins>+ }),
</ins><span class="cx">
</span><span class="cx"> // When it's a virtual view, we need to notify the parent that their
</span><span class="cx"> // childViews did change.
</span><del>- _childViewsDidChange: Ember.observer(function() {
</del><ins>+ _childViewsDidChange: Ember.observer('childViews', function() {
</ins><span class="cx"> if (this.isVirtual) {
</span><span class="cx"> var parentView = get(this, 'parentView');
</span><span class="cx"> if (parentView) { Ember.propertyDidChange(parentView, 'childViews'); }
</span><span class="cx"> }
</span><del>- }, 'childViews'),
</del><ins>+ }),
</ins><span class="cx">
</span><span class="cx"> /**
</span><span class="cx"> Return the nearest ancestor that is an instance of the provided
</span><span class="lines">@@ -14528,7 +22679,7 @@
</span><span class="cx"> var view = get(this, 'parentView');
</span><span class="cx">
</span><span class="cx"> while (view) {
</span><del>- if(view instanceof klass) { return view; }
</del><ins>+ if (view instanceof klass) { return view; }
</ins><span class="cx"> view = get(view, 'parentView');
</span><span class="cx"> }
</span><span class="cx"> },
</span><span class="lines">@@ -14549,7 +22700,7 @@
</span><span class="cx"> function(view) { return klass.detect(view.constructor); };
</span><span class="cx">
</span><span class="cx"> while (view) {
</span><del>- if( isOfType(view) ) { return view; }
</del><ins>+ if (isOfType(view)) { return view; }
</ins><span class="cx"> view = get(view, 'parentView');
</span><span class="cx"> }
</span><span class="cx"> },
</span><span class="lines">@@ -14557,7 +22708,7 @@
</span><span class="cx"> /**
</span><span class="cx"> Return the nearest ancestor that has a given property.
</span><span class="cx">
</span><del>- @property nearestWithProperty
</del><ins>+ @function nearestWithProperty
</ins><span class="cx"> @param {String} property A property name
</span><span class="cx"> @return Ember.View
</span><span class="cx"> */
</span><span class="lines">@@ -14574,7 +22725,7 @@
</span><span class="cx"> Return the nearest ancestor whose parent is an instance of
</span><span class="cx"> `klass`.
</span><span class="cx">
</span><del>- @property nearestChildOf
</del><ins>+ @method nearestChildOf
</ins><span class="cx"> @param {Class} klass Subclass of Ember.View (or Ember.View itself)
</span><span class="cx"> @return Ember.View
</span><span class="cx"> */
</span><span class="lines">@@ -14582,27 +22733,28 @@
</span><span class="cx"> var view = get(this, 'parentView');
</span><span class="cx">
</span><span class="cx"> while (view) {
</span><del>- if(get(view, 'parentView') instanceof klass) { return view; }
</del><ins>+ if (get(view, 'parentView') instanceof klass) { return view; }
</ins><span class="cx"> view = get(view, 'parentView');
</span><span class="cx"> }
</span><span class="cx"> },
</span><span class="cx">
</span><span class="cx"> /**
</span><del>- @private
-
</del><span class="cx"> When the parent view changes, recursively invalidate `controller`
</span><span class="cx">
</span><span class="cx"> @method _parentViewDidChange
</span><ins>+ @private
</ins><span class="cx"> */
</span><del>- _parentViewDidChange: Ember.observer(function() {
</del><ins>+ _parentViewDidChange: Ember.observer('_parentView', function() {
</ins><span class="cx"> if (this.isDestroying) { return; }
</span><span class="cx">
</span><ins>+ this.trigger('parentViewDidChange');
+
</ins><span class="cx"> if (get(this, 'parentView.controller') && !get(this, 'controller')) {
</span><span class="cx"> this.notifyPropertyChange('controller');
</span><span class="cx"> }
</span><del>- }, '_parentView'),
</del><ins>+ }),
</ins><span class="cx">
</span><del>- _controllerDidChange: Ember.observer(function() {
</del><ins>+ _controllerDidChange: Ember.observer('controller', function() {
</ins><span class="cx"> if (this.isDestroying) { return; }
</span><span class="cx">
</span><span class="cx"> this.rerender();
</span><span class="lines">@@ -14610,7 +22762,7 @@
</span><span class="cx"> this.forEachChildView(function(view) {
</span><span class="cx"> view.propertyDidChange('controller');
</span><span class="cx"> });
</span><del>- }, 'controller'),
</del><ins>+ }),
</ins><span class="cx">
</span><span class="cx"> cloneKeywords: function() {
</span><span class="cx"> var templateData = get(this, 'templateData');
</span><span class="lines">@@ -14705,14 +22857,13 @@
</span><span class="cx"> },
</span><span class="cx">
</span><span class="cx"> /**
</span><del>- @private
-
</del><span class="cx"> Iterates over the view's `classNameBindings` array, inserts the value
</span><span class="cx"> of the specified property into the `classNames` array, then creates an
</span><span class="cx"> observer to update the view's element if the bound property ever changes
</span><span class="cx"> in the future.
</span><span class="cx">
</span><span class="cx"> @method _applyClassNameBindings
</span><ins>+ @private
</ins><span class="cx"> */
</span><span class="cx"> _applyClassNameBindings: function(classBindings) {
</span><span class="cx"> var classNames = this.classNames,
</span><span class="lines">@@ -14783,16 +22934,15 @@
</span><span class="cx"> },
</span><span class="cx">
</span><span class="cx"> /**
</span><del>- @private
-
</del><span class="cx"> Iterates through the view's attribute bindings, sets up observers for each,
</span><span class="cx"> then applies the current value of the attributes to the passed render buffer.
</span><span class="cx">
</span><span class="cx"> @method _applyAttributeBindings
</span><span class="cx"> @param {Ember.RenderBuffer} buffer
</span><ins>+ @private
</ins><span class="cx"> */
</span><span class="cx"> _applyAttributeBindings: function(buffer, attributeBindings) {
</span><del>- var attributeValue, elem, type;
</del><ins>+ var attributeValue, elem;
</ins><span class="cx">
</span><span class="cx"> a_forEach(attributeBindings, function(binding) {
</span><span class="cx"> var split = binding.split(':'),
</span><span class="lines">@@ -14803,7 +22953,6 @@
</span><span class="cx"> // JavaScript property changes.
</span><span class="cx"> var observer = function() {
</span><span class="cx"> elem = this.$();
</span><del>- if (!elem) { return; }
</del><span class="cx">
</span><span class="cx"> attributeValue = get(this, property);
</span><span class="cx">
</span><span class="lines">@@ -14820,8 +22969,6 @@
</span><span class="cx"> },
</span><span class="cx">
</span><span class="cx"> /**
</span><del>- @private
-
</del><span class="cx"> Given a property name, returns a dasherized version of that
</span><span class="cx"> property name if the property evaluates to a non-falsy value.
</span><span class="cx">
</span><span class="lines">@@ -14830,6 +22977,7 @@
</span><span class="cx">
</span><span class="cx"> @method _classStringForProperty
</span><span class="cx"> @param property
</span><ins>+ @private
</ins><span class="cx"> */
</span><span class="cx"> _classStringForProperty: function(property) {
</span><span class="cx"> var parsedPath = Ember.View._parsePropertyPath(property);
</span><span class="lines">@@ -14869,9 +23017,9 @@
</span><span class="cx"> For example, calling `view.$('li')` will return a jQuery object containing
</span><span class="cx"> all of the `li` elements inside the DOM element of this view.
</span><span class="cx">
</span><del>- @property $
</del><ins>+ @method $
</ins><span class="cx"> @param {String} [selector] a jQuery-compatible selector string
</span><del>- @return {jQuery} the CoreQuery object for the DOM node
</del><ins>+ @return {jQuery} the jQuery object for the DOM node
</ins><span class="cx"> */
</span><span class="cx"> $: function(sel) {
</span><span class="cx"> return this.currentState.$(this, sel);
</span><span class="lines">@@ -14884,7 +23032,7 @@
</span><span class="cx">
</span><span class="cx"> while(--idx >= 0) {
</span><span class="cx"> view = childViews[idx];
</span><del>- callback.call(this, view, idx);
</del><ins>+ callback(this, view, idx);
</ins><span class="cx"> }
</span><span class="cx">
</span><span class="cx"> return this;
</span><span class="lines">@@ -14898,9 +23046,9 @@
</span><span class="cx"> var len = childViews.length,
</span><span class="cx"> view, idx;
</span><span class="cx">
</span><del>- for(idx = 0; idx < len; idx++) {
</del><ins>+ for (idx = 0; idx < len; idx++) {
</ins><span class="cx"> view = childViews[idx];
</span><del>- callback.call(this, view);
</del><ins>+ callback(view);
</ins><span class="cx"> }
</span><span class="cx">
</span><span class="cx"> return this;
</span><span class="lines">@@ -14930,6 +23078,7 @@
</span><span class="cx"> // Schedule the DOM element to be created and appended to the given
</span><span class="cx"> // element after bindings have synchronized.
</span><span class="cx"> this._insertElementLater(function() {
</span><ins>+ Ember.assert("You tried to append to (" + target + ") but that isn't in the DOM", Ember.$(target).length > 0);
</ins><span class="cx"> 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'));
</span><span class="cx"> this.$().appendTo(target);
</span><span class="cx"> });
</span><span class="lines">@@ -14947,10 +23096,11 @@
</span><span class="cx"> finished synchronizing
</span><span class="cx">
</span><span class="cx"> @method replaceIn
</span><del>- @param {String|DOMElement|jQuery} A selector, element, HTML string, or jQuery object
</del><ins>+ @param {String|DOMElement|jQuery} target A selector, element, HTML string, or jQuery object
</ins><span class="cx"> @return {Ember.View} received
</span><span class="cx"> */
</span><span class="cx"> replaceIn: function(target) {
</span><ins>+ Ember.assert("You tried to replace in (" + target + ") but that isn't in the DOM", Ember.$(target).length > 0);
</ins><span class="cx"> 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'));
</span><span class="cx">
</span><span class="cx"> this._insertElementLater(function() {
</span><span class="lines">@@ -14962,8 +23112,6 @@
</span><span class="cx"> },
</span><span class="cx">
</span><span class="cx"> /**
</span><del>- @private
-
</del><span class="cx"> Schedules a DOM operation to occur during the next render phase. This
</span><span class="cx"> ensures that all bindings have finished synchronizing before the view is
</span><span class="cx"> rendered.
</span><span class="lines">@@ -14983,6 +23131,7 @@
</span><span class="cx">
</span><span class="cx"> @method _insertElementLater
</span><span class="cx"> @param {Function} fn the function that inserts the element into the DOM
</span><ins>+ @private
</ins><span class="cx"> */
</span><span class="cx"> _insertElementLater: function(fn) {
</span><span class="cx"> this._scheduledInsert = Ember.run.scheduleOnce('render', this, '_insertElement', fn);
</span><span class="lines">@@ -14998,6 +23147,10 @@
</span><span class="cx"> not have an HTML representation yet, `createElement()` will be called
</span><span class="cx"> automatically.
</span><span class="cx">
</span><ins>+ If your application uses the `rootElement` property, you must append
+ the view within that element. Rendering views outside of the `rootElement`
+ is not supported.
+
</ins><span class="cx"> Note that this method just schedules the view to be appended; the DOM
</span><span class="cx"> element will not be appended to the document body until all bindings have
</span><span class="cx"> finished synchronizing.
</span><span class="lines">@@ -15072,9 +23225,9 @@
</span><span class="cx"> willInsertElement: Ember.K,
</span><span class="cx">
</span><span class="cx"> /**
</span><del>- Called when the element of the view has been inserted into the DOM.
- Override this function to do any set up that requires an element in the
- document body.
</del><ins>+ 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.
</ins><span class="cx">
</span><span class="cx"> @event didInsertElement
</span><span class="cx"> */
</span><span class="lines">@@ -15090,15 +23243,16 @@
</span><span class="cx"> willClearRender: Ember.K,
</span><span class="cx">
</span><span class="cx"> /**
</span><del>- @private
</del><ins>+ Run this callback on the current view (unless includeSelf is false) and recursively on child views.
</ins><span class="cx">
</span><del>- Run this callback on the current view and recursively on child views.
-
</del><span class="cx"> @method invokeRecursively
</span><span class="cx"> @param fn {Function}
</span><ins>+ @param includeSelf {Boolean} Includes itself if true.
+ @private
</ins><span class="cx"> */
</span><del>- invokeRecursively: function(fn) {
- var childViews = [this], currentViews, view;
</del><ins>+ invokeRecursively: function(fn, includeSelf) {
+ var childViews = (includeSelf === false) ? this._childViews : [this];
+ var currentViews, view, currentChildViews;
</ins><span class="cx">
</span><span class="cx"> while (childViews.length) {
</span><span class="cx"> currentViews = childViews.slice();
</span><span class="lines">@@ -15106,16 +23260,17 @@
</span><span class="cx">
</span><span class="cx"> for (var i=0, l=currentViews.length; i<l; i++) {
</span><span class="cx"> view = currentViews[i];
</span><del>- fn.call(view, view);
- if (view._childViews) {
- childViews.push.apply(childViews, view._childViews);
</del><ins>+ currentChildViews = view._childViews ? view._childViews.slice(0) : null;
+ fn(view);
+ if (currentChildViews) {
+ childViews.push.apply(childViews, currentChildViews);
</ins><span class="cx"> }
</span><span class="cx"> }
</span><span class="cx"> }
</span><span class="cx"> },
</span><span class="cx">
</span><span class="cx"> triggerRecursively: function(eventName) {
</span><del>- var childViews = [this], currentViews, view;
</del><ins>+ var childViews = [this], currentViews, view, currentChildViews;
</ins><span class="cx">
</span><span class="cx"> while (childViews.length) {
</span><span class="cx"> currentViews = childViews.slice();
</span><span class="lines">@@ -15123,14 +23278,29 @@
</span><span class="cx">
</span><span class="cx"> for (var i=0, l=currentViews.length; i<l; i++) {
</span><span class="cx"> view = currentViews[i];
</span><ins>+ currentChildViews = view._childViews ? view._childViews.slice(0) : null;
</ins><span class="cx"> if (view.trigger) { view.trigger(eventName); }
</span><del>- if (view._childViews) {
- childViews.push.apply(childViews, view._childViews);
</del><ins>+ if (currentChildViews) {
+ childViews.push.apply(childViews, currentChildViews);
</ins><span class="cx"> }
</span><ins>+
</ins><span class="cx"> }
</span><span class="cx"> }
</span><span class="cx"> },
</span><span class="cx">
</span><ins>+ viewHierarchyCollection: function() {
+ var currentView, viewCollection = new ViewCollection([this]);
+
+ for (var i = 0; i < viewCollection.length; i++) {
+ currentView = viewCollection.objectAt(i);
+ if (currentView._childViews) {
+ viewCollection.push.apply(viewCollection, currentView._childViews);
+ }
+ }
+
+ return viewCollection;
+ },
+
</ins><span class="cx"> /**
</span><span class="cx"> Destroys any existing element along with the element for any child views
</span><span class="cx"> as well. If the view does not currently have a element, then this method
</span><span class="lines">@@ -15143,8 +23313,8 @@
</span><span class="cx"> If you write a `willDestroyElement()` handler, you can assume that your
</span><span class="cx"> `didInsertElement()` handler was called earlier for the same element.
</span><span class="cx">
</span><del>- Normally you will not call or override this method yourself, but you may
- want to implement the above callbacks when it is run.
</del><ins>+ You should not call or override this method yourself, but you may
+ want to implement the above callbacks.
</ins><span class="cx">
</span><span class="cx"> @method destroyElement
</span><span class="cx"> @return {Ember.View} receiver
</span><span class="lines">@@ -15160,11 +23330,9 @@
</span><span class="cx">
</span><span class="cx"> @event willDestroyElement
</span><span class="cx"> */
</span><del>- willDestroyElement: function() {},
</del><ins>+ willDestroyElement: Ember.K,
</ins><span class="cx">
</span><span class="cx"> /**
</span><del>- @private
-
</del><span class="cx"> Triggers the `willDestroyElement` event (which invokes the
</span><span class="cx"> `willDestroyElement()` method if it exists) on this view and all child
</span><span class="cx"> views.
</span><span class="lines">@@ -15173,32 +23341,28 @@
</span><span class="cx"> `willClearRender` event recursively.
</span><span class="cx">
</span><span class="cx"> @method _notifyWillDestroyElement
</span><ins>+ @private
</ins><span class="cx"> */
</span><span class="cx"> _notifyWillDestroyElement: function() {
</span><del>- this.triggerRecursively('willClearRender');
- this.triggerRecursively('willDestroyElement');
</del><ins>+ var viewCollection = this.viewHierarchyCollection();
+ viewCollection.trigger('willClearRender');
+ viewCollection.trigger('willDestroyElement');
+ return viewCollection;
</ins><span class="cx"> },
</span><span class="cx">
</span><del>- _elementWillChange: Ember.beforeObserver(function() {
- this.forEachChildView(function(view) {
- Ember.propertyWillChange(view, 'element');
- });
- }, 'element'),
-
</del><span class="cx"> /**
</span><del>- @private
-
</del><span class="cx"> If this view's element changes, we need to invalidate the caches of our
</span><span class="cx"> child views so that we do not retain references to DOM elements that are
</span><span class="cx"> no longer needed.
</span><span class="cx">
</span><span class="cx"> @method _elementDidChange
</span><ins>+ @private
</ins><span class="cx"> */
</span><del>- _elementDidChange: Ember.observer(function() {
</del><ins>+ _elementDidChange: Ember.observer('element', function() {
</ins><span class="cx"> this.forEachChildView(function(view) {
</span><del>- Ember.propertyDidChange(view, 'element');
</del><ins>+ delete meta(view).cache.element;
</ins><span class="cx"> });
</span><del>- }, 'element'),
</del><ins>+ }),
</ins><span class="cx">
</span><span class="cx"> /**
</span><span class="cx"> Called when the parentView property has changed.
</span><span class="lines">@@ -15222,8 +23386,8 @@
</span><span class="cx"> return buffer;
</span><span class="cx"> },
</span><span class="cx">
</span><del>- renderToBufferIfNeeded: function () {
- return this.currentState.renderToBufferIfNeeded(this, this);
</del><ins>+ renderToBufferIfNeeded: function (buffer) {
+ return this.currentState.renderToBufferIfNeeded(this, buffer);
</ins><span class="cx"> },
</span><span class="cx">
</span><span class="cx"> beforeRender: function(buffer) {
</span><span class="lines">@@ -15291,7 +23455,7 @@
</span><span class="cx"> visually challenged users navigate rich web applications.
</span><span class="cx">
</span><span class="cx"> The full list of valid WAI-ARIA roles is available at:
</span><del>- http://www.w3.org/TR/wai-aria/roles#roles_categorization
</del><ins>+ [http://www.w3.org/TR/wai-aria/roles#roles_categorization](http://www.w3.org/TR/wai-aria/roles#roles_categorization)
</ins><span class="cx">
</span><span class="cx"> @property ariaRole
</span><span class="cx"> @type String
</span><span class="lines">@@ -15317,7 +23481,7 @@
</span><span class="cx">
</span><span class="cx"> ```javascript
</span><span class="cx"> // Applies the 'high' class to the view element
</span><del>- Ember.View.create({
</del><ins>+ Ember.View.extend({
</ins><span class="cx"> classNameBindings: ['priority']
</span><span class="cx"> priority: 'high'
</span><span class="cx"> });
</span><span class="lines">@@ -15328,7 +23492,7 @@
</span><span class="cx">
</span><span class="cx"> ```javascript
</span><span class="cx"> // Applies the 'is-urgent' class to the view element
</span><del>- Ember.View.create({
</del><ins>+ Ember.View.extend({
</ins><span class="cx"> classNameBindings: ['isUrgent']
</span><span class="cx"> isUrgent: true
</span><span class="cx"> });
</span><span class="lines">@@ -15339,7 +23503,7 @@
</span><span class="cx">
</span><span class="cx"> ```javascript
</span><span class="cx"> // Applies the 'urgent' class to the view element
</span><del>- Ember.View.create({
</del><ins>+ Ember.View.extend({
</ins><span class="cx"> classNameBindings: ['isUrgent:urgent']
</span><span class="cx"> isUrgent: true
</span><span class="cx"> });
</span><span class="lines">@@ -15351,7 +23515,7 @@
</span><span class="cx"> @type Array
</span><span class="cx"> @default []
</span><span class="cx"> */
</span><del>- classNameBindings: [],
</del><ins>+ classNameBindings: EMPTY_ARRAY,
</ins><span class="cx">
</span><span class="cx"> /**
</span><span class="cx"> A list of properties of the view to apply as attributes. If the property is
</span><span class="lines">@@ -15360,7 +23524,7 @@
</span><span class="cx"> ```javascript
</span><span class="cx"> // Applies the type attribute to the element
</span><span class="cx"> // with the value "button", like <div type="button">
</span><del>- Ember.View.create({
</del><ins>+ Ember.View.extend({
</ins><span class="cx"> attributeBindings: ['type'],
</span><span class="cx"> type: 'button'
</span><span class="cx"> });
</span><span class="lines">@@ -15371,7 +23535,7 @@
</span><span class="cx">
</span><span class="cx"> ```javascript
</span><span class="cx"> // Renders something like <div enabled="enabled">
</span><del>- Ember.View.create({
</del><ins>+ Ember.View.extend({
</ins><span class="cx"> attributeBindings: ['enabled'],
</span><span class="cx"> enabled: true
</span><span class="cx"> });
</span><span class="lines">@@ -15379,21 +23543,21 @@
</span><span class="cx">
</span><span class="cx"> @property attributeBindings
</span><span class="cx"> */
</span><del>- attributeBindings: [],
</del><ins>+ attributeBindings: EMPTY_ARRAY,
</ins><span class="cx">
</span><span class="cx"> // .......................................................
</span><span class="cx"> // CORE DISPLAY METHODS
</span><span class="cx"> //
</span><span class="cx">
</span><span class="cx"> /**
</span><del>- @private
</del><ins>+ Setup a view, but do not finish waking it up.
</ins><span class="cx">
</span><del>- 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
</del><ins>+ * configure `childViews`
+ * register the view with the global views hash, which is used for event
</ins><span class="cx"> dispatch
</span><span class="cx">
</span><span class="cx"> @method init
</span><ins>+ @private
</ins><span class="cx"> */
</span><span class="cx"> init: function() {
</span><span class="cx"> this.elementId = this.elementId || guidFor(this);
</span><span class="lines">@@ -15408,14 +23572,6 @@
</span><span class="cx">
</span><span class="cx"> Ember.assert("Only arrays are allowed for 'classNames'", Ember.typeOf(this.classNames) === 'array');
</span><span class="cx"> this.classNames = Ember.A(this.classNames.slice());
</span><del>-
- var viewController = get(this, 'viewController');
- if (viewController) {
- viewController = get(viewController);
- if (viewController) {
- set(viewController, 'view', this);
- }
- }
</del><span class="cx"> },
</span><span class="cx">
</span><span class="cx"> appendChild: function(view, options) {
</span><span class="lines">@@ -15455,13 +23611,13 @@
</span><span class="cx"> @return {Ember.View} receiver
</span><span class="cx"> */
</span><span class="cx"> removeAllChildren: function() {
</span><del>- return this.mutateChildViews(function(view) {
- this.removeChild(view);
</del><ins>+ return this.mutateChildViews(function(parentView, view) {
+ parentView.removeChild(view);
</ins><span class="cx"> });
</span><span class="cx"> },
</span><span class="cx">
</span><span class="cx"> destroyAllChildren: function() {
</span><del>- return this.mutateChildViews(function(view) {
</del><ins>+ return this.mutateChildViews(function(parentView, view) {
</ins><span class="cx"> view.destroy();
</span><span class="cx"> });
</span><span class="cx"> },
</span><span class="lines">@@ -15489,18 +23645,16 @@
</span><span class="cx"> sure that the DOM element managed by the view can be released by the
</span><span class="cx"> memory manager.
</span><span class="cx">
</span><del>- @method willDestroy
</del><ins>+ @method destroy
</ins><span class="cx"> */
</span><del>- willDestroy: function() {
- // calling this._super() will nuke computed properties and observers,
- // so collect any information we need before calling super.
</del><ins>+ destroy: function() {
</ins><span class="cx"> var childViews = this._childViews,
</span><del>- parent = this._parentView,
</del><ins>+ // get parentView before calling super because it'll be destroyed
+ nonVirtualParentView = get(this, 'parentView'),
+ viewName = this.viewName,
</ins><span class="cx"> childLen, i;
</span><span class="cx">
</span><del>- // destroy the element -- this will avoid each child view destroying
- // the element over and over again...
- if (!this.removedFromDOM) { this.destroyElement(); }
</del><ins>+ if (!this._super()) { return; }
</ins><span class="cx">
</span><span class="cx"> childLen = childViews.length;
</span><span class="cx"> for (i=childLen-1; i>=0; i--) {
</span><span class="lines">@@ -15508,27 +23662,16 @@
</span><span class="cx"> }
</span><span class="cx">
</span><span class="cx"> // remove from non-virtual parent view if viewName was specified
</span><del>- if (this.viewName) {
- var nonVirtualParentView = get(this, 'parentView');
- if (nonVirtualParentView) {
- set(nonVirtualParentView, this.viewName, null);
- }
</del><ins>+ if (viewName && nonVirtualParentView) {
+ nonVirtualParentView.set(viewName, null);
</ins><span class="cx"> }
</span><span class="cx">
</span><del>- // 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('destroyed');
-
</del><span class="cx"> childLen = childViews.length;
</span><span class="cx"> for (i=childLen-1; i>=0; i--) {
</span><span class="cx"> childViews[i].destroy();
</span><span class="cx"> }
</span><span class="cx">
</span><del>- // next remove view from global hash
- if (!this.isVirtual) delete Ember.View.views[get(this, 'elementId')];
</del><ins>+ return this;
</ins><span class="cx"> },
</span><span class="cx">
</span><span class="cx"> /**
</span><span class="lines">@@ -15539,35 +23682,51 @@
</span><span class="cx"> act as a child of the parent.
</span><span class="cx">
</span><span class="cx"> @method createChildView
</span><del>- @param {Class} viewClass
</del><ins>+ @param {Class|String} viewClass
</ins><span class="cx"> @param {Hash} [attrs] Attributes to add
</span><span class="cx"> @return {Ember.View} new instance
</span><span class="cx"> */
</span><span class="cx"> createChildView: function(view, attrs) {
</span><del>- if (view.isView && view._parentView === this) { return view; }
</del><ins>+ if (!view) {
+ throw new TypeError("createChildViews first argument must exist");
+ }
</ins><span class="cx">
</span><ins>+ if (view.isView && view._parentView === this && view.container === this.container) {
+ return view;
+ }
+
+ attrs = attrs || {};
+ attrs._parentView = this;
+
</ins><span class="cx"> if (Ember.CoreView.detect(view)) {
</span><del>- attrs = attrs || {};
- attrs._parentView = this;
</del><span class="cx"> attrs.templateData = attrs.templateData || get(this, 'templateData');
</span><span class="cx">
</span><ins>+ attrs.container = this.container;
</ins><span class="cx"> view = view.create(attrs);
</span><span class="cx">
</span><span class="cx"> // don't set the property on a virtual view, as they are invisible to
</span><span class="cx"> // consumers of the view API
</span><del>- if (view.viewName) { set(get(this, 'concreteView'), view.viewName, view); }
</del><ins>+ 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);
</ins><span class="cx"> } else {
</span><span class="cx"> Ember.assert('You must pass instance or subclass of View', view.isView);
</span><ins>+ attrs.container = this.container;
</ins><span class="cx">
</span><del>- if (attrs) {
- view.setProperties(attrs);
- }
-
</del><span class="cx"> if (!get(view, 'templateData')) {
</span><del>- set(view, 'templateData', get(this, 'templateData'));
</del><ins>+ attrs.templateData = get(this, 'templateData');
</ins><span class="cx"> }
</span><span class="cx">
</span><del>- set(view, '_parentView', this);
</del><ins>+ Ember.setProperties(view, attrs);
+
</ins><span class="cx"> }
</span><span class="cx">
</span><span class="cx"> return view;
</span><span class="lines">@@ -15577,14 +23736,13 @@
</span><span class="cx"> becameHidden: Ember.K,
</span><span class="cx">
</span><span class="cx"> /**
</span><del>- @private
-
</del><span class="cx"> When the view's `isVisible` property changes, toggle the visibility
</span><span class="cx"> element of the actual DOM element.
</span><span class="cx">
</span><span class="cx"> @method _isVisibleDidChange
</span><ins>+ @private
</ins><span class="cx"> */
</span><del>- _isVisibleDidChange: Ember.observer(function() {
</del><ins>+ _isVisibleDidChange: Ember.observer('isVisible', function() {
</ins><span class="cx"> var $el = this.$();
</span><span class="cx"> if (!$el) { return; }
</span><span class="cx">
</span><span class="lines">@@ -15599,7 +23757,7 @@
</span><span class="cx"> } else {
</span><span class="cx"> this._notifyBecameHidden();
</span><span class="cx"> }
</span><del>- }, 'isVisible'),
</del><ins>+ }),
</ins><span class="cx">
</span><span class="cx"> _notifyBecameVisible: function() {
</span><span class="cx"> this.trigger('becameVisible');
</span><span class="lines">@@ -15643,9 +23801,14 @@
</span><span class="cx"> },
</span><span class="cx">
</span><span class="cx"> transitionTo: function(state, children) {
</span><del>- this.currentState = this.states[state];
</del><ins>+ var priorState = this.currentState,
+ currentState = this.currentState = this.states[state];
</ins><span class="cx"> this.state = state;
</span><span class="cx">
</span><ins>+ if (priorState && priorState.exit) { priorState.exit(this); }
+ if (currentState.enter) { currentState.enter(this); }
+ if (state === 'inDOM') { delete Ember.meta(this).cache.element; }
+
</ins><span class="cx"> if (children !== false) {
</span><span class="cx"> this.forEachChildView(function(view) {
</span><span class="cx"> view.transitionTo(state);
</span><span class="lines">@@ -15658,23 +23821,39 @@
</span><span class="cx"> //
</span><span class="cx">
</span><span class="cx"> /**
</span><del>- @private
-
</del><span class="cx"> Handle events from `Ember.EventDispatcher`
</span><span class="cx">
</span><span class="cx"> @method handleEvent
</span><span class="cx"> @param eventName {String}
</span><span class="cx"> @param evt {Event}
</span><ins>+ @private
</ins><span class="cx"> */
</span><span class="cx"> handleEvent: function(eventName, evt) {
</span><span class="cx"> return this.currentState.handleEvent(this, eventName, evt);
</span><span class="cx"> },
</span><span class="cx">
</span><span class="cx"> registerObserver: function(root, path, target, observer) {
</span><del>- Ember.addObserver(root, path, target, observer);
</del><ins>+ if (!observer && 'function' === typeof target) {
+ observer = target;
+ target = null;
+ }
</ins><span class="cx">
</span><ins>+ 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);
+
</ins><span class="cx"> this.one('willClearRender', function() {
</span><del>- Ember.removeObserver(root, path, target, observer);
</del><ins>+ Ember.removeObserver(root, path, target, scheduledObserver);
</ins><span class="cx"> });
</span><span class="cx"> }
</span><span class="cx">
</span><span class="lines">@@ -15705,17 +23884,24 @@
</span><span class="cx"> // once the view has been inserted into the DOM, legal manipulations
</span><span class="cx"> // are done on the DOM element.
</span><span class="cx">
</span><ins>+function notifyMutationListeners() {
+ Ember.run.once(Ember.View, 'notifyMutationListeners');
+}
+
</ins><span class="cx"> var DOMManager = {
</span><span class="cx"> prepend: function(view, html) {
</span><span class="cx"> view.$().prepend(html);
</span><ins>+ notifyMutationListeners();
</ins><span class="cx"> },
</span><span class="cx">
</span><span class="cx"> after: function(view, html) {
</span><span class="cx"> view.$().after(html);
</span><ins>+ notifyMutationListeners();
</ins><span class="cx"> },
</span><span class="cx">
</span><span class="cx"> html: function(view, html) {
</span><span class="cx"> view.$().html(html);
</span><ins>+ notifyMutationListeners();
</ins><span class="cx"> },
</span><span class="cx">
</span><span class="cx"> replace: function(view) {
</span><span class="lines">@@ -15725,15 +23911,18 @@
</span><span class="cx">
</span><span class="cx"> view._insertElementLater(function() {
</span><span class="cx"> Ember.$(element).replaceWith(get(view, 'element'));
</span><ins>+ notifyMutationListeners();
</ins><span class="cx"> });
</span><span class="cx"> },
</span><span class="cx">
</span><span class="cx"> remove: function(view) {
</span><span class="cx"> view.$().remove();
</span><ins>+ notifyMutationListeners();
</ins><span class="cx"> },
</span><span class="cx">
</span><span class="cx"> empty: function(view) {
</span><span class="cx"> view.$().empty();
</span><ins>+ notifyMutationListeners();
</ins><span class="cx"> }
</span><span class="cx"> };
</span><span class="cx">
</span><span class="lines">@@ -15744,11 +23933,9 @@
</span><span class="cx"> Ember.View.reopenClass({
</span><span class="cx">
</span><span class="cx"> /**
</span><del>- @private
-
</del><span class="cx"> Parse a path and return an object which holds the parsed properties.
</span><span class="cx">
</span><del>- For example a path like "content.isEnabled:enabled:disabled" wil return the
</del><ins>+ For example a path like "content.isEnabled:enabled:disabled" will return the
</ins><span class="cx"> following object:
</span><span class="cx">
</span><span class="cx"> ```javascript
</span><span class="lines">@@ -15762,6 +23949,7 @@
</span><span class="cx">
</span><span class="cx"> @method _parsePropertyPath
</span><span class="cx"> @static
</span><ins>+ @private
</ins><span class="cx"> */
</span><span class="cx"> _parsePropertyPath: function(path) {
</span><span class="cx"> var split = path.split(':'),
</span><span class="lines">@@ -15788,20 +23976,18 @@
</span><span class="cx"> },
</span><span class="cx">
</span><span class="cx"> /**
</span><del>- @private
-
</del><span class="cx"> Get the class name for a given value, based on the path, optional
</span><span class="cx"> `className` and optional `falsyClassName`.
</span><span class="cx">
</span><span class="cx"> - if a `className` or `falsyClassName` has been specified:
</span><del>- - if the value is truthy and `className` has been specified,
</del><ins>+ - if the value is truthy and `className` has been specified,
</ins><span class="cx"> `className` is returned
</span><del>- - if the value is falsy and `falsyClassName` has been specified,
</del><ins>+ - if the value is falsy and `falsyClassName` has been specified,
</ins><span class="cx"> `falsyClassName` is returned
</span><span class="cx"> - otherwise `null` is returned
</span><del>- - if the value is `true`, the dasherized last part of the supplied path
</del><ins>+ - if the value is `true`, the dasherized last part of the supplied path
</ins><span class="cx"> is returned
</span><del>- - if the value is not `false`, `undefined` or `null`, the `value`
</del><ins>+ - if the value is not `false`, `undefined` or `null`, the `value`
</ins><span class="cx"> is returned
</span><span class="cx"> - if none of the above rules apply, `null` is returned
</span><span class="cx">
</span><span class="lines">@@ -15811,6 +23997,7 @@
</span><span class="cx"> @param className
</span><span class="cx"> @param falsyClassName
</span><span class="cx"> @static
</span><ins>+ @private
</ins><span class="cx"> */
</span><span class="cx"> _classStringForValue: function(path, val, className, falsyClassName) {
</span><span class="cx"> // When using the colon syntax, evaluate the truthiness or falsiness
</span><span class="lines">@@ -15837,7 +24024,7 @@
</span><span class="cx">
</span><span class="cx"> // If the value is not false, undefined, or null, return the current
</span><span class="cx"> // value of the property.
</span><del>- } else if (val !== false && val !== undefined && val !== null) {
</del><ins>+ } else if (val !== false && val != null) {
</ins><span class="cx"> return val;
</span><span class="cx">
</span><span class="cx"> // Nothing to display. Return null so that the old class is removed
</span><span class="lines">@@ -15848,6 +24035,20 @@
</span><span class="cx"> }
</span><span class="cx"> });
</span><span class="cx">
</span><ins>+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');
+};
+
</ins><span class="cx"> /**
</span><span class="cx"> Global views hash
</span><span class="cx">
</span><span class="lines">@@ -15873,6 +24074,13 @@
</span><span class="cx"> elem.attr(name, value);
</span><span class="cx"> }
</span><span class="cx"> } else if (name === 'value' || type === 'boolean') {
</span><ins>+ // We can't set properties to undefined or null
+ if (Ember.isNone(value)) { value = ''; }
+
+ if (!value) {
+ elem.removeAttr(name);
+ }
+
</ins><span class="cx"> if (value !== elem.prop(name)) {
</span><span class="cx"> // value and booleans should always be properties
</span><span class="cx"> elem.prop(name, value);
</span><span class="lines">@@ -15928,7 +24136,8 @@
</span><span class="cx"> return false;
</span><span class="cx"> },
</span><span class="cx">
</span><del>- rerender: Ember.K
</del><ins>+ rerender: Ember.K,
+ invokeObserver: Ember.K
</ins><span class="cx"> };
</span><span class="cx">
</span><span class="cx"> })();
</span><span class="lines">@@ -15948,15 +24157,26 @@
</span><span class="cx"> // created (createElement).
</span><span class="cx"> insertElement: function(view, fn) {
</span><span class="cx"> view.createElement();
</span><del>- view.triggerRecursively('willInsertElement');
- // after createElement, the view will be in the hasElement state.
</del><ins>+ var viewCollection = view.viewHierarchyCollection();
+
+ viewCollection.trigger('willInsertElement');
+
</ins><span class="cx"> fn.call(view);
</span><del>- view.transitionTo('inDOM');
- view.triggerRecursively('didInsertElement');
</del><ins>+
+ // We transition to `inDOM` if the element exists in the DOM
+ var element = view.get('element');
+ while (element = element.parentNode) {
+ if (element === document) {
+ viewCollection.transitionTo('inDOM', false);
+ viewCollection.trigger('didInsertElement');
+ }
+ }
+
</ins><span class="cx"> },
</span><span class="cx">
</span><del>- renderToBufferIfNeeded: function(view) {
- return view.renderToBuffer();
</del><ins>+ renderToBufferIfNeeded: function(view, buffer) {
+ view.renderToBuffer(buffer);
+ return true;
</ins><span class="cx"> },
</span><span class="cx">
</span><span class="cx"> empty: Ember.K,
</span><span class="lines">@@ -15979,7 +24199,7 @@
</span><span class="cx"> @submodule ember-views
</span><span class="cx"> */
</span><span class="cx">
</span><del>-var get = Ember.get, set = Ember.set, meta = Ember.meta;
</del><ins>+var get = Ember.get, set = Ember.set;
</ins><span class="cx">
</span><span class="cx"> var inBuffer = Ember.View.states.inBuffer = Ember.create(Ember.View.states._default);
</span><span class="cx">
</span><span class="lines">@@ -16003,10 +24223,11 @@
</span><span class="cx"> // view will render that view and append the resulting
</span><span class="cx"> // buffer into its buffer.
</span><span class="cx"> appendChild: function(view, childView, options) {
</span><del>- var buffer = view.buffer;
</del><ins>+ var buffer = view.buffer, _childViews = view._childViews;
</ins><span class="cx">
</span><span class="cx"> childView = view.createChildView(childView, options);
</span><del>- view._childViews.push(childView);
</del><ins>+ if (!_childViews.length) { _childViews = view._childViews = _childViews.slice(); }
+ _childViews.push(childView);
</ins><span class="cx">
</span><span class="cx"> childView.renderToBuffer(buffer);
</span><span class="cx">
</span><span class="lines">@@ -16020,18 +24241,21 @@
</span><span class="cx"> // state back into the preRender state.
</span><span class="cx"> destroyElement: function(view) {
</span><span class="cx"> view.clearBuffer();
</span><del>- view._notifyWillDestroyElement();
- view.transitionTo('preRender');
</del><ins>+ var viewCollection = view._notifyWillDestroyElement();
+ viewCollection.transitionTo('preRender', false);
</ins><span class="cx">
</span><span class="cx"> return view;
</span><span class="cx"> },
</span><span class="cx">
</span><span class="cx"> empty: function() {
</span><del>- 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.");
</del><ins>+ 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.");
</ins><span class="cx"> },
</span><span class="cx">
</span><del>- renderToBufferIfNeeded: function (view) {
- return view.buffer;
</del><ins>+ renderToBufferIfNeeded: function (view, buffer) {
+ return false;
</ins><span class="cx"> },
</span><span class="cx">
</span><span class="cx"> // It should be impossible for a rendered view to be scheduled for
</span><span class="lines">@@ -16049,6 +24273,10 @@
</span><span class="cx"> }
</span><span class="cx">
</span><span class="cx"> return value;
</span><ins>+ },
+
+ invokeObserver: function(target, observer) {
+ observer.call(target);
</ins><span class="cx"> }
</span><span class="cx"> });
</span><span class="cx">
</span><span class="lines">@@ -16063,7 +24291,7 @@
</span><span class="cx"> @submodule ember-views
</span><span class="cx"> */
</span><span class="cx">
</span><del>-var get = Ember.get, set = Ember.set, meta = Ember.meta;
</del><ins>+var get = Ember.get, set = Ember.set;
</ins><span class="cx">
</span><span class="cx"> var hasElement = Ember.View.states.hasElement = Ember.create(Ember.View.states._default);
</span><span class="cx">
</span><span class="lines">@@ -16136,12 +24364,33 @@
</span><span class="cx"> } else {
</span><span class="cx"> return true; // continue event propagation
</span><span class="cx"> }
</span><ins>+ },
+
+ invokeObserver: function(target, observer) {
+ observer.call(target);
</ins><span class="cx"> }
</span><span class="cx"> });
</span><span class="cx">
</span><span class="cx"> var inDOM = Ember.View.states.inDOM = Ember.create(hasElement);
</span><span class="cx">
</span><span class="cx"> Ember.merge(inDOM, {
</span><ins>+ 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];
+ },
+
</ins><span class="cx"> insertElement: function(view, fn) {
</span><span class="cx"> throw "You can't insert an element into the DOM that has already been inserted";
</span><span class="cx"> }
</span><span class="lines">@@ -16157,30 +24406,30 @@
</span><span class="cx"> @submodule ember-views
</span><span class="cx"> */
</span><span class="cx">
</span><del>-var destroyedError = "You can't call %@ on a destroyed view", fmt = Ember.String.fmt;
</del><ins>+var destroyingError = "You can't call %@ on a view being destroyed", fmt = Ember.String.fmt;
</ins><span class="cx">
</span><del>-var destroyed = Ember.View.states.destroyed = Ember.create(Ember.View.states._default);
</del><ins>+var destroying = Ember.View.states.destroying = Ember.create(Ember.View.states._default);
</ins><span class="cx">
</span><del>-Ember.merge(destroyed, {
</del><ins>+Ember.merge(destroying, {
</ins><span class="cx"> appendChild: function() {
</span><del>- throw fmt(destroyedError, ['appendChild']);
</del><ins>+ throw fmt(destroyingError, ['appendChild']);
</ins><span class="cx"> },
</span><span class="cx"> rerender: function() {
</span><del>- throw fmt(destroyedError, ['rerender']);
</del><ins>+ throw fmt(destroyingError, ['rerender']);
</ins><span class="cx"> },
</span><span class="cx"> destroyElement: function() {
</span><del>- throw fmt(destroyedError, ['destroyElement']);
</del><ins>+ throw fmt(destroyingError, ['destroyElement']);
</ins><span class="cx"> },
</span><span class="cx"> empty: function() {
</span><del>- throw fmt(destroyedError, ['empty']);
</del><ins>+ throw fmt(destroyingError, ['empty']);
</ins><span class="cx"> },
</span><span class="cx">
</span><span class="cx"> setElement: function() {
</span><del>- throw fmt(destroyedError, ["set('element', ...)"]);
</del><ins>+ throw fmt(destroyingError, ["set('element', ...)"]);
</ins><span class="cx"> },
</span><span class="cx">
</span><span class="cx"> renderToBufferIfNeeded: function() {
</span><del>- throw fmt(destroyedError, ["renderToBufferIfNeeded"]);
</del><ins>+ return false;
</ins><span class="cx"> },
</span><span class="cx">
</span><span class="cx"> // Since element insertion is scheduled, don't do anything if
</span><span class="lines">@@ -16199,13 +24448,11 @@
</span><span class="cx">
</span><span class="cx"> into._default = {};
</span><span class="cx"> into.preRender = Ember.create(into._default);
</span><del>- into.destroyed = Ember.create(into._default);
</del><ins>+ into.destroying = Ember.create(into._default);
</ins><span class="cx"> into.inBuffer = Ember.create(into._default);
</span><span class="cx"> into.hasElement = Ember.create(into._default);
</span><span class="cx"> into.inDOM = Ember.create(into.hasElement);
</span><span class="cx">
</span><del>- var viewState;
-
</del><span class="cx"> for (var stateName in from) {
</span><span class="cx"> if (!from.hasOwnProperty(stateName)) { continue; }
</span><span class="cx"> Ember.merge(into[stateName], from[stateName]);
</span><span class="lines">@@ -16226,12 +24473,13 @@
</span><span class="cx"> @submodule ember-views
</span><span class="cx"> */
</span><span class="cx">
</span><del>-var get = Ember.get, set = Ember.set, meta = Ember.meta;
</del><ins>+var get = Ember.get, set = Ember.set;
</ins><span class="cx"> var forEach = Ember.EnumerableUtils.forEach;
</span><ins>+var ViewCollection = Ember._ViewCollection;
</ins><span class="cx">
</span><span class="cx"> /**
</span><span class="cx"> A `ContainerView` is an `Ember.View` subclass that implements `Ember.MutableArray`
</span><del>- allowing programatic management of its child views.
</del><ins>+ allowing programmatic management of its child views.
</ins><span class="cx">
</span><span class="cx"> ## Setting Initial Child Views
</span><span class="cx">
</span><span class="lines">@@ -16271,7 +24519,7 @@
</span><span class="cx">
</span><span class="cx"> ## Adding and Removing Child Views
</span><span class="cx">
</span><del>- The container view implements `Ember.MutableArray` allowing programatic management of its child views.
</del><ins>+ The container view implements `Ember.MutableArray` allowing programmatic management of its child views.
</ins><span class="cx">
</span><span class="cx"> To remove a view, pass that view into a `removeObject` call on the container view.
</span><span class="cx">
</span><span class="lines">@@ -16375,30 +24623,6 @@
</span><span class="cx"> or layout being rendered. The HTML contents of a `Ember.ContainerView`'s DOM
</span><span class="cx"> representation will only be the rendered HTML of its child views.
</span><span class="cx">
</span><del>- ## Binding a View to Display
-
- If you would like to display a single view in your ContainerView, you can set
- its `currentView` property. When the `currentView` property is set to a view
- instance, it will be added to the ContainerView. If the `currentView` property
- is later changed to a different view, the new view will replace the old view.
- If `currentView` is set to `null`, the last `currentView` will be removed.
-
- This functionality is useful for cases where you want to bind the display of
- a ContainerView to a controller or state manager. For example, you can bind
- the `currentView` of a container to a controller like this:
-
- ```javascript
- App.appController = Ember.Object.create({
- view: Ember.View.create({
- templateName: 'person_template'
- })
- });
- ```
-
- ```handlebars
- {{view Ember.ContainerView currentViewBinding="App.appController.view"}}
- ```
-
</del><span class="cx"> @class ContainerView
</span><span class="cx"> @namespace Ember
</span><span class="cx"> @extends Ember.View
</span><span class="lines">@@ -16432,12 +24656,15 @@
</span><span class="cx">
</span><span class="cx"> var currentView = get(this, 'currentView');
</span><span class="cx"> if (currentView) {
</span><ins>+ if (!_childViews.length) { _childViews = this._childViews = this._childViews.slice(); }
</ins><span class="cx"> _childViews.push(this.createChildView(currentView));
</span><span class="cx"> }
</span><span class="cx"> },
</span><span class="cx">
</span><span class="cx"> replace: function(idx, removedCount, addedViews) {
</span><span class="cx"> var addedCount = addedViews ? get(addedViews, 'length') : 0;
</span><ins>+ 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; }));
</ins><span class="cx">
</span><span class="cx"> this.arrayContentWillChange(idx, removedCount, addedCount);
</span><span class="cx"> this.childViewsWillChange(this._childViews, idx, removedCount);
</span><span class="lines">@@ -16446,6 +24673,7 @@
</span><span class="cx"> this._childViews.splice(idx, removedCount) ;
</span><span class="cx"> } else {
</span><span class="cx"> var args = [idx, removedCount].concat(addedViews);
</span><ins>+ if (addedViews.length && !this._childViews.length) { this._childViews = this._childViews.slice(); }
</ins><span class="cx"> this._childViews.splice.apply(this._childViews, args);
</span><span class="cx"> }
</span><span class="cx">
</span><span class="lines">@@ -16461,13 +24689,12 @@
</span><span class="cx">
</span><span class="cx"> length: Ember.computed(function () {
</span><span class="cx"> return this._childViews.length;
</span><del>- }),
</del><ins>+ }).volatile(),
</ins><span class="cx">
</span><span class="cx"> /**
</span><del>- @private
-
</del><span class="cx"> Instructs each child view to render to the passed render buffer.
</span><span class="cx">
</span><ins>+ @private
</ins><span class="cx"> @method render
</span><span class="cx"> @param {Ember.RenderBuffer} buffer the buffer to render to
</span><span class="cx"> */
</span><span class="lines">@@ -16477,17 +24704,16 @@
</span><span class="cx"> });
</span><span class="cx"> },
</span><span class="cx">
</span><del>- instrumentName: 'render.container',
</del><ins>+ instrumentName: 'container',
</ins><span class="cx">
</span><span class="cx"> /**
</span><del>- @private
-
</del><span class="cx"> When a child view is removed, destroy its element so that
</span><span class="cx"> it is removed from the DOM.
</span><span class="cx">
</span><span class="cx"> The array observer that triggers this action is set up in the
</span><span class="cx"> `renderToBuffer` method.
</span><span class="cx">
</span><ins>+ @private
</ins><span class="cx"> @method childViewsWillChange
</span><span class="cx"> @param {Ember.Array} views the child views array before mutation
</span><span class="cx"> @param {Number} start the start position of the mutation
</span><span class="lines">@@ -16510,8 +24736,6 @@
</span><span class="cx"> },
</span><span class="cx">
</span><span class="cx"> /**
</span><del>- @private
-
</del><span class="cx"> When a child view is added, make sure the DOM gets updated appropriately.
</span><span class="cx">
</span><span class="cx"> If the view has already rendered an element, we tell the child view to
</span><span class="lines">@@ -16520,6 +24744,7 @@
</span><span class="cx"> into an element, we insert the string representation of the child into the
</span><span class="cx"> appropriate place in the buffer.
</span><span class="cx">
</span><ins>+ @private
</ins><span class="cx"> @method childViewsDidChange
</span><span class="cx"> @param {Ember.Array} views the array of child views afte the mutation has occurred
</span><span class="cx"> @param {Number} start the start position of the mutation
</span><span class="lines">@@ -16539,6 +24764,10 @@
</span><span class="cx"> forEach(views, function(view) {
</span><span class="cx"> set(view, '_parentView', parentView);
</span><span class="cx">
</span><ins>+ if (!view.container && parentView) {
+ set(view, 'container', parentView.container);
+ }
+
</ins><span class="cx"> if (!get(view, 'templateData')) {
</span><span class="cx"> set(view, 'templateData', templateData);
</span><span class="cx"> }
</span><span class="lines">@@ -16547,19 +24776,20 @@
</span><span class="cx">
</span><span class="cx"> currentView: null,
</span><span class="cx">
</span><del>- _currentViewWillChange: Ember.beforeObserver(function() {
</del><ins>+ _currentViewWillChange: Ember.beforeObserver('currentView', function() {
</ins><span class="cx"> var currentView = get(this, 'currentView');
</span><span class="cx"> if (currentView) {
</span><span class="cx"> currentView.destroy();
</span><span class="cx"> }
</span><del>- }, 'currentView'),
</del><ins>+ }),
</ins><span class="cx">
</span><del>- _currentViewDidChange: Ember.observer(function() {
</del><ins>+ _currentViewDidChange: Ember.observer('currentView', function() {
</ins><span class="cx"> var currentView = get(this, 'currentView');
</span><span class="cx"> if (currentView) {
</span><ins>+ 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'));
</ins><span class="cx"> this.pushObject(currentView);
</span><span class="cx"> }
</span><del>- }, 'currentView'),
</del><ins>+ }),
</ins><span class="cx">
</span><span class="cx"> _ensureChildrenAreInDOM: function () {
</span><span class="cx"> this.currentState.ensureChildrenAreInDOM(this);
</span><span class="lines">@@ -16574,7 +24804,7 @@
</span><span class="cx">
</span><span class="cx"> Ember.merge(states.inBuffer, {
</span><span class="cx"> childViewsDidChange: function(parentView, views, start, added) {
</span><del>- throw new Error('You cannot modify child views while in the inBuffer state');
</del><ins>+ throw new Ember.Error('You cannot modify child views while in the inBuffer state');
</ins><span class="cx"> }
</span><span class="cx"> });
</span><span class="cx">
</span><span class="lines">@@ -16590,26 +24820,48 @@
</span><span class="cx"> },
</span><span class="cx">
</span><span class="cx"> ensureChildrenAreInDOM: function(view) {
</span><del>- var childViews = view._childViews, i, len, childView, previous, buffer;
</del><ins>+ var childViews = view._childViews, i, len, childView, previous, buffer, viewCollection = new ViewCollection();
+
</ins><span class="cx"> for (i = 0, len = childViews.length; i < len; i++) {
</span><span class="cx"> childView = childViews[i];
</span><del>- buffer = childView.renderToBufferIfNeeded();
- if (buffer) {
- childView.triggerRecursively('willInsertElement');
- if (previous) {
- previous.domManager.after(previous, buffer.string());
- } else {
- view.domManager.prepend(view, buffer.string());
- }
- childView.transitionTo('inDOM');
- childView.propertyDidChange('element');
- childView.triggerRecursively('didInsertElement');
</del><ins>+
+ if (!buffer) { buffer = Ember.RenderBuffer(); buffer._hasElement = false; }
+
+ if (childView.renderToBufferIfNeeded(buffer)) {
+ viewCollection.push(childView);
+ } else if (viewCollection.length) {
+ insertViewCollection(view, viewCollection, previous, buffer);
+ buffer = null;
+ previous = childView;
+ viewCollection.clear();
+ } else {
+ previous = childView;
</ins><span class="cx"> }
</span><del>- previous = childView;
</del><span class="cx"> }
</span><ins>+
+ if (viewCollection.length) {
+ insertViewCollection(view, viewCollection, previous, buffer);
+ }
</ins><span class="cx"> }
</span><span class="cx"> });
</span><span class="cx">
</span><ins>+function insertViewCollection(view, viewCollection, previous, buffer) {
+ viewCollection.triggerRecursively('willInsertElement');
+
+ if (previous) {
+ previous.domManager.after(previous, buffer.string());
+ } else {
+ view.domManager.prepend(view, buffer.string());
+ }
+
+ viewCollection.forEach(function(v) {
+ v.transitionTo('inDOM');
+ v.propertyDidChange('element');
+ v.triggerRecursively('didInsertElement');
+ });
+}
+
+
</ins><span class="cx"> })();
</span><span class="cx">
</span><span class="cx">
</span><span class="lines">@@ -16624,7 +24876,7 @@
</span><span class="cx">
</span><span class="cx"> /**
</span><span class="cx"> `Ember.CollectionView` is an `Ember.View` descendent responsible for managing
</span><del>- a collection (an array or array-like object) by maintaing a child view object
</del><ins>+ a collection (an array or array-like object) by maintaining a child view object
</ins><span class="cx"> and associated DOM representation for each item in the array and ensuring
</span><span class="cx"> that child views and their associated rendered HTML are updated when items in
</span><span class="cx"> the array are added, removed, or replaced.
</span><span class="lines">@@ -16651,7 +24903,7 @@
</span><span class="cx">
</span><span class="cx"> Given an empty `<body>` and the following code:
</span><span class="cx">
</span><del>- ```javascript
</del><ins>+ ```javascript
</ins><span class="cx"> someItemsView = Ember.CollectionView.create({
</span><span class="cx"> classNames: ['a-collection'],
</span><span class="cx"> content: ['A','B','C'],
</span><span class="lines">@@ -16710,7 +24962,7 @@
</span><span class="cx"> Ember.CollectionView.CONTAINER_MAP['article'] = 'section'
</span><span class="cx"> ```
</span><span class="cx">
</span><del>- ## Programatic creation of child views
</del><ins>+ ## Programmatic creation of child views
</ins><span class="cx">
</span><span class="cx"> For cases where additional customization beyond the use of a single
</span><span class="cx"> `itemViewClass` or `tagName` matching is required CollectionView's
</span><span class="lines">@@ -16724,7 +24976,7 @@
</span><span class="cx"> } else {
</span><span class="cx"> viewClass = App.SongView;
</span><span class="cx"> }
</span><del>- this._super(viewClass, attrs);
</del><ins>+ return this._super(viewClass, attrs);
</ins><span class="cx"> }
</span><span class="cx"> });
</span><span class="cx"> ```
</span><span class="lines">@@ -16764,19 +25016,13 @@
</span><span class="cx"> manipulated. Instead, add, remove, replace items from its `content` property.
</span><span class="cx"> This will trigger appropriate changes to its rendered HTML.
</span><span class="cx">
</span><del>- ## Use in templates via the `{{collection}}` `Ember.Handlebars` helper
</del><span class="cx">
</span><del>- `Ember.Handlebars` provides a helper specifically for adding
- `CollectionView`s to templates. See `Ember.Handlebars.collection` for more
- details
-
</del><span class="cx"> @class CollectionView
</span><span class="cx"> @namespace Ember
</span><span class="cx"> @extends Ember.ContainerView
</span><span class="cx"> @since Ember 0.9
</span><span class="cx"> */
</span><del>-Ember.CollectionView = Ember.ContainerView.extend(
-/** @scope Ember.CollectionView.prototype */ {
</del><ins>+Ember.CollectionView = Ember.ContainerView.extend({
</ins><span class="cx">
</span><span class="cx"> /**
</span><span class="cx"> A list of items to be displayed by the `Ember.CollectionView`.
</span><span class="lines">@@ -16788,12 +25034,11 @@
</span><span class="cx"> content: null,
</span><span class="cx">
</span><span class="cx"> /**
</span><del>- @private
-
</del><span class="cx"> This provides metadata about what kind of empty view class this
</span><span class="cx"> collection would like if it is being instantiated from another
</span><span class="cx"> system (like Handlebars)
</span><span class="cx">
</span><ins>+ @private
</ins><span class="cx"> @property emptyViewClass
</span><span class="cx"> */
</span><span class="cx"> emptyViewClass: Ember.View,
</span><span class="lines">@@ -16814,53 +25059,94 @@
</span><span class="cx"> */
</span><span class="cx"> itemViewClass: Ember.View,
</span><span class="cx">
</span><ins>+ /**
+ Setup a CollectionView
+
+ @method init
+ */
</ins><span class="cx"> init: function() {
</span><span class="cx"> var ret = this._super();
</span><span class="cx"> this._contentDidChange();
</span><span class="cx"> return ret;
</span><span class="cx"> },
</span><span class="cx">
</span><del>- _contentWillChange: Ember.beforeObserver(function() {
</del><ins>+ /**
+ 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() {
</ins><span class="cx"> var content = this.get('content');
</span><span class="cx">
</span><span class="cx"> if (content) { content.removeArrayObserver(this); }
</span><span class="cx"> var len = content ? get(content, 'length') : 0;
</span><span class="cx"> this.arrayWillChange(content, 0, len);
</span><del>- }, 'content'),
</del><ins>+ }),
</ins><span class="cx">
</span><span class="cx"> /**
</span><del>- @private
-
</del><span class="cx"> Check to make sure that the content has changed, and if so,
</span><span class="cx"> update the children directly. This is always scheduled
</span><span class="cx"> asynchronously, to allow the element to be created before
</span><span class="cx"> bindings have synchronized and vice versa.
</span><span class="cx">
</span><ins>+ @private
</ins><span class="cx"> @method _contentDidChange
</span><span class="cx"> */
</span><del>- _contentDidChange: Ember.observer(function() {
</del><ins>+ _contentDidChange: Ember.observer('content', function() {
</ins><span class="cx"> var content = get(this, 'content');
</span><span class="cx">
</span><span class="cx"> if (content) {
</span><del>- Ember.assert(fmt("an Ember.CollectionView's content must implement Ember.Array. You passed %@", [content]), Ember.Array.detect(content));
</del><ins>+ this._assertArrayLike(content);
</ins><span class="cx"> content.addArrayObserver(this);
</span><span class="cx"> }
</span><span class="cx">
</span><span class="cx"> var len = content ? get(content, 'length') : 0;
</span><span class="cx"> this.arrayDidChange(content, 0, null, len);
</span><del>- }, 'content'),
</del><ins>+ }),
</ins><span class="cx">
</span><del>- willDestroy: function() {
</del><ins>+ /**
+ 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; }
+
</ins><span class="cx"> var content = get(this, 'content');
</span><span class="cx"> if (content) { content.removeArrayObserver(this); }
</span><span class="cx">
</span><del>- this._super();
-
</del><span class="cx"> if (this._createdEmptyView) {
</span><span class="cx"> this._createdEmptyView.destroy();
</span><span class="cx"> }
</span><ins>+
+ return this;
</ins><span class="cx"> },
</span><span class="cx">
</span><ins>+ /**
+ 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
+ */
</ins><span class="cx"> arrayWillChange: function(content, start, removedCount) {
</span><span class="cx"> // If the contents were empty before and this template collection has an
</span><span class="cx"> // empty view remove it now.
</span><span class="lines">@@ -16880,11 +25166,13 @@
</span><span class="cx">
</span><span class="cx"> if (removingAll) {
</span><span class="cx"> this.currentState.empty(this);
</span><ins>+ this.invokeRecursively(function(view) {
+ view.removedFromDOM = true;
+ }, false);
</ins><span class="cx"> }
</span><span class="cx">
</span><span class="cx"> for (idx = start + removedCount - 1; idx >= start; idx--) {
</span><span class="cx"> childView = childViews[idx];
</span><del>- if (removingAll) { childView.removedFromDOM = true; }
</del><span class="cx"> childView.destroy();
</span><span class="cx"> }
</span><span class="cx"> },
</span><span class="lines">@@ -16898,22 +25186,26 @@
</span><span class="cx"> This array observer is added in `contentDidChange`.
</span><span class="cx">
</span><span class="cx"> @method arrayDidChange
</span><del>- @param {Array} addedObjects the objects that were added to the content
- @param {Array} removedObjects the objects that were removed from the content
- @param {Number} changeIndex the index at which the changes occurred
</del><ins>+ @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
</ins><span class="cx"> */
</span><span class="cx"> arrayDidChange: function(content, start, removed, added) {
</span><del>- var itemViewClass = get(this, 'itemViewClass'),
- addedViews = [], view, item, idx, len, itemTagName;
</del><ins>+ var addedViews = [], view, item, idx, len, itemViewClass,
+ emptyView;
</ins><span class="cx">
</span><del>- if ('string' === typeof itemViewClass) {
- itemViewClass = get(itemViewClass);
- }
</del><ins>+ len = content ? get(content, 'length') : 0;
</ins><span class="cx">
</span><del>- Ember.assert(fmt("itemViewClass must be a subclass of Ember.View, not %@", [itemViewClass]), Ember.View.detect(itemViewClass));
</del><ins>+ if (len) {
+ itemViewClass = get(this, 'itemViewClass');
</ins><span class="cx">
</span><del>- len = content ? get(content, 'length') : 0;
- if (len) {
</del><ins>+ 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));
+
</ins><span class="cx"> for (idx = start; idx < start+added; idx++) {
</span><span class="cx"> item = content.objectAt(idx);
</span><span class="cx">
</span><span class="lines">@@ -16925,27 +25217,50 @@
</span><span class="cx"> addedViews.push(view);
</span><span class="cx"> }
</span><span class="cx"> } else {
</span><del>- var emptyView = get(this, 'emptyView');
</del><ins>+ emptyView = get(this, 'emptyView');
+
</ins><span class="cx"> if (!emptyView) { return; }
</span><span class="cx">
</span><del>- var isClass = Ember.CoreView.detect(emptyView);
</del><ins>+ if ('string' === typeof emptyView) {
+ emptyView = get(emptyView) || emptyView;
+ }
</ins><span class="cx">
</span><span class="cx"> emptyView = this.createChildView(emptyView);
</span><span class="cx"> addedViews.push(emptyView);
</span><span class="cx"> set(this, 'emptyView', emptyView);
</span><span class="cx">
</span><del>- if (isClass) { this._createdEmptyView = emptyView; }
</del><ins>+ if (Ember.CoreView.detect(emptyView)) {
+ this._createdEmptyView = emptyView;
+ }
</ins><span class="cx"> }
</span><ins>+
</ins><span class="cx"> this.replace(start, 0, addedViews);
</span><span class="cx"> },
</span><span class="cx">
</span><ins>+ /**
+ 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
+ */
</ins><span class="cx"> createChildView: function(view, attrs) {
</span><span class="cx"> view = this._super(view, attrs);
</span><span class="cx">
</span><span class="cx"> var itemTagName = get(view, 'tagName');
</span><del>- var tagName = (itemTagName === null || itemTagName === undefined) ? Ember.CollectionView.CONTAINER_MAP[get(this, 'tagName')] : itemTagName;
</del><span class="cx">
</span><del>- set(view, 'tagName', tagName);
</del><ins>+ if (itemTagName === null || itemTagName === undefined) {
+ itemTagName = Ember.CollectionView.CONTAINER_MAP[get(this, 'tagName')];
+ set(view, 'tagName', itemTagName);
+ }
</ins><span class="cx">
</span><span class="cx"> return view;
</span><span class="cx"> }
</span><span class="lines">@@ -16977,14 +25292,336 @@
</span><span class="cx">
</span><span class="cx">
</span><span class="cx"> (function() {
</span><ins>+var get = Ember.get, set = Ember.set, isNone = Ember.isNone,
+ a_slice = Array.prototype.slice;
</ins><span class="cx">
</span><ins>+
+/**
+@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.
+
+ ```html
+ {{app-profile person=currentUser}}
+ ```
+
+ ```html
+ <!-- app-profile template -->
+ <h1>{{person.title}}</h1>
+ <img {{bind-attr src=person.avatar}}>
+ <p class='signature'>{{person.signature}}</p>
+ ```
+
+ 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}}
+ <p>Admin mode</p>
+ {{! Executed in the controllers context. }}
+ {{/app-profile}}
+ ```
+
+ ```handlebars
+ <!-- app-profile template -->
+ <h1>{{person.title}}</h1>
+ {{! 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:
+
+ ```html
+ <!-- app-profile template -->
+
+ <h1>{{person.title}}</h1>
+ {{yield}} <!-- block contents -->
+
+ <button {{action 'hello' person.name}}>
+ Say Hello to {{person.name}}
+ </button>
+ ```
+
+ 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, {
+ init: function() {
+ this._super();
+ set(this, 'context', this);
+ set(this, 'controller', this);
+ },
+
+ defaultLayout: function(options){
+ options.data = {view: options._context};
+ Ember.Handlebars.helpers['yield'].apply(this, [options]);
+ },
+
+ // 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.triggerAction('play');
+ } else {
+ this.triggerAction('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
+ });
+ }
+});
+
</ins><span class="cx"> })();
</span><span class="cx">
</span><span class="cx">
</span><span class="cx">
</span><span class="cx"> (function() {
</span><del>-/*globals jQuery*/
</del><ins>+
+})();
+
+
+
+(function() {
</ins><span class="cx"> /**
</span><ins>+`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() {
+/**
</ins><span class="cx"> Ember Views
</span><span class="cx">
</span><span class="cx"> @module ember
</span><span class="lines">@@ -17002,20 +25639,28 @@
</span><span class="cx"> "use strict";
</span><span class="cx"> // ==========================================================================
</span><span class="cx"> // Project: metamorph
</span><del>- // Copyright: ©2011 My Company Inc. All rights reserved.
</del><ins>+ // Copyright: ©2014 Tilde, Inc. All rights reserved.
</ins><span class="cx"> // ==========================================================================
</span><span class="cx">
</span><del>- var K = function(){},
</del><ins>+ var K = function() {},
</ins><span class="cx"> guid = 0,
</span><del>- document = window.document,
</del><ins>+ disableRange = (function(){
+ if ('undefined' !== typeof MetamorphENV) {
+ return MetamorphENV.DISABLE_RANGE_API;
+ } else if ('undefined' !== ENV) {
+ return ENV.DISABLE_RANGE_API;
+ } else {
+ return false;
+ }
+ })(),
</ins><span class="cx">
</span><span class="cx"> // Feature-detect the W3C range API, the extended check is for IE9 which only partially supports ranges
</span><del>- supportsRange = ('createRange' in document) && (typeof Range !== 'undefined') && Range.prototype.createContextualFragment,
</del><ins>+ supportsRange = (!disableRange) && document && ('createRange' in document) && (typeof Range !== 'undefined') && Range.prototype.createContextualFragment,
</ins><span class="cx">
</span><span class="cx"> // Internet Explorer prior to 9 does not allow setting innerHTML if the first element
</span><span class="cx"> // is a "zero-scope" element. This problem can be worked around by making
</span><span class="cx"> // the first node an invisible text node. We, like Modernizr, use &shy;
</span><del>- needsShy = (function(){
</del><ins>+ needsShy = document && (function() {
</ins><span class="cx"> var testEl = document.createElement('div');
</span><span class="cx"> testEl.innerHTML = "<div></div>";
</span><span class="cx"> testEl.firstChild.innerHTML = "<script></script>";
</span><span class="lines">@@ -17026,7 +25671,7 @@
</span><span class="cx"> // IE 8 (and likely earlier) likes to move whitespace preceeding
</span><span class="cx"> // a script tag to appear after it. This means that we can
</span><span class="cx"> // accidentally remove whitespace when updating a morph.
</span><del>- movesWhitespace = (function() {
</del><ins>+ movesWhitespace = document && (function() {
</ins><span class="cx"> var testEl = document.createElement('div');
</span><span class="cx"> testEl.innerHTML = "Test: <script type='text/x-placeholder'></script>Value";
</span><span class="cx"> return testEl.childNodes[0].nodeValue === 'Test:' &&
</span><span class="lines">@@ -17117,6 +25762,14 @@
</span><span class="cx"> range.insertNode(fragment);
</span><span class="cx"> };
</span><span class="cx">
</span><ins>+ /**
+ * @public
+ *
+ * Remove this object (including starting and ending
+ * placeholders).
+ *
+ * @method remove
+ */
</ins><span class="cx"> removeFunc = function() {
</span><span class="cx"> // get a range for the current metamorph object including
</span><span class="cx"> // the starting and ending placeholders.
</span><span class="lines">@@ -17157,7 +25810,7 @@
</span><span class="cx"> };
</span><span class="cx">
</span><span class="cx"> } else {
</span><del>- /**
</del><ins>+ /*
</ins><span class="cx"> * This code is mostly taken from jQuery, with one exception. In jQuery's case, we
</span><span class="cx"> * have some HTML and we need to figure out how to convert it into some nodes.
</span><span class="cx"> *
</span><span class="lines">@@ -17211,12 +25864,12 @@
</span><span class="cx"> }
</span><span class="cx"> };
</span><span class="cx">
</span><del>- /**
</del><ins>+ /*
</ins><span class="cx"> * Given a parent node and some HTML, generate a set of nodes. Return the first
</span><span class="cx"> * node, which will allow us to traverse the rest using nextSibling.
</span><span class="cx"> *
</span><span class="cx"> * We need to do this because innerHTML in IE does not really parse the nodes.
</span><del>- **/
</del><ins>+ */
</ins><span class="cx"> var firstNodeFor = function(parentNode, html) {
</span><span class="cx"> var arr = wrapMap[parentNode.tagName.toLowerCase()] || wrapMap._default;
</span><span class="cx"> var depth = arr[0], start = arr[1], end = arr[2];
</span><span class="lines">@@ -17249,7 +25902,7 @@
</span><span class="cx"> return element;
</span><span class="cx"> };
</span><span class="cx">
</span><del>- /**
</del><ins>+ /*
</ins><span class="cx"> * In some cases, Internet Explorer can create an anonymous node in
</span><span class="cx"> * the hierarchy with no tagName. You can create this scenario via:
</span><span class="cx"> *
</span><span class="lines">@@ -17259,7 +25912,7 @@
</span><span class="cx"> *
</span><span class="cx"> * If our script markers are inside such a node, we need to find that
</span><span class="cx"> * node and use *it* as the marker.
</span><del>- **/
</del><ins>+ */
</ins><span class="cx"> var realNode = function(start) {
</span><span class="cx"> while (start.parentNode.tagName === "") {
</span><span class="cx"> start = start.parentNode;
</span><span class="lines">@@ -17268,7 +25921,7 @@
</span><span class="cx"> return start;
</span><span class="cx"> };
</span><span class="cx">
</span><del>- /**
</del><ins>+ /*
</ins><span class="cx"> * When automatically adding a tbody, Internet Explorer inserts the
</span><span class="cx"> * tbody immediately before the first <tr>. Other browsers create it
</span><span class="cx"> * before the first node, no matter what.
</span><span class="lines">@@ -17295,7 +25948,8 @@
</span><span class="cx"> *
</span><span class="cx"> * This code reparents the first script tag by making it the tbody's
</span><span class="cx"> * first child.
</span><del>- **/
</del><ins>+ *
+ */
</ins><span class="cx"> var fixParentage = function(start, end) {
</span><span class="cx"> if (start.parentNode !== end.parentNode) {
</span><span class="cx"> end.parentNode.insertBefore(start, end.parentNode.firstChild);
</span><span class="lines">@@ -17346,6 +26000,10 @@
</span><span class="cx"> // swallow some of the content.
</span><span class="cx"> node = firstNodeFor(start.parentNode, html);
</span><span class="cx">
</span><ins>+ if (outerToo) {
+ start.parentNode.removeChild(start);
+ }
+
</ins><span class="cx"> // copy the nodes for the HTML between the starting and ending
</span><span class="cx"> // placeholder.
</span><span class="cx"> while (node) {
</span><span class="lines">@@ -17460,7 +26118,7 @@
</span><span class="cx"> (function() {
</span><span class="cx"> /**
</span><span class="cx"> @module ember
</span><del>-@submodule ember-handlebars
</del><ins>+@submodule ember-handlebars-compiler
</ins><span class="cx"> */
</span><span class="cx">
</span><span class="cx"> // Eliminate dependency on any Ember to simplify precompilation workflow
</span><span class="lines">@@ -17470,9 +26128,20 @@
</span><span class="cx"> return new F();
</span><span class="cx"> };
</span><span class="cx">
</span><del>-var Handlebars = this.Handlebars || Ember.imports.Handlebars;
-Ember.assert("Ember Handlebars requires Handlebars 1.0.0-rc.3 or greater", Handlebars && Handlebars.VERSION.match(/^1\.0\.[0-9](\.rc\.[23456789]+)?/));
</del><ins>+var Handlebars = (Ember.imports && Ember.imports.Handlebars) || (this && this.Handlebars);
+if (!Handlebars && typeof require === 'function') {
+ Handlebars = require('handlebars');
+}
</ins><span class="cx">
</span><ins>+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);
+
</ins><span class="cx"> /**
</span><span class="cx"> Prepares the Handlebars templating library for use inside Ember's view
</span><span class="cx"> system.
</span><span class="lines">@@ -17490,6 +26159,87 @@
</span><span class="cx"> Ember.Handlebars = objectCreate(Handlebars);
</span><span class="cx">
</span><span class="cx"> /**
</span><ins>+ Register a bound helper or custom view helper.
+
+ ## Simple bound helper example
+
+ ```javascript
+ Ember.Handlebars.helper('capitalize', function(value) {
+ return value.toUpperCase();
+ });
+ ```
+
+ The above bound helper can be used inside of templates as follows:
+
+ ```handlebars
+ {{capitalize name}}
+ ```
+
+ In this case, when the `name` property of the template's context changes,
+ the rendered value of the helper will update to reflect this change.
+
+ For more examples of bound helpers, see documentation for
+ `Ember.Handlebars.registerBoundHelper`.
+
+ ## Custom view helper example
+
+ Assuming a view subclass named `App.CalendarView` were defined, a helper
+ for rendering instances of this view could be registered as follows:
+
+ ```javascript
+ Ember.Handlebars.helper('calendar', App.CalendarView):
+ ```
+
+ The above bound helper can be used inside of templates as follows:
+
+ ```handlebars
+ {{calendar}}
+ ```
+
+ Which is functionally equivalent to:
+
+ ```handlebars
+ {{view App.CalendarView}}
+ ```
+
+ Options in the helper will be passed to the view in exactly the same
+ manner as with the `view` helper.
+
+ @method helper
+ @for Ember.Handlebars
+ @param {String} name
+ @param {Function|Ember.View} function or view class constructor
+ @param {String} dependentKeys*
+*/
+Ember.Handlebars.helper = function(name, value) {
+ Ember.assert("You tried to register a component named '" + name + "', but component names must include a '-'", !Ember.Component.detect(value) || name.match(/-/));
+
+ if (Ember.View.detect(value)) {
+ Ember.Handlebars.registerHelper(name, Ember.Handlebars.makeViewHelper(value));
+ } else {
+ Ember.Handlebars.registerBoundHelper.apply(null, arguments);
+ }
+};
+
+/**
+ Returns a helper function that renders the provided ViewClass.
+
+ Used internally by Ember.Handlebars.helper and other methods
+ involving helper/component registration.
+
+ @private
+ @method helper
+ @for Ember.Handlebars
+ @param {Function} ViewClass view class constructor
+*/
+Ember.Handlebars.makeViewHelper = function(ViewClass) {
+ return function(options) {
+ Ember.assert("You can only pass attributes (such as name=value) not bare values to a helper for a View found in '" + ViewClass.toString() + "'", arguments.length < 2);
+ return Ember.Handlebars.helpers.view.call(this, ViewClass, options);
+ };
+};
+
+/**
</ins><span class="cx"> @class helpers
</span><span class="cx"> @namespace Ember.Handlebars
</span><span class="cx"> */
</span><span class="lines">@@ -17529,18 +26279,16 @@
</span><span class="cx">
</span><span class="cx"> Ember.Handlebars.JavaScriptCompiler.prototype.namespace = "Ember.Handlebars";
</span><span class="cx">
</span><del>-
</del><span class="cx"> Ember.Handlebars.JavaScriptCompiler.prototype.initializeBuffer = function() {
</span><span class="cx"> return "''";
</span><span class="cx"> };
</span><span class="cx">
</span><span class="cx"> /**
</span><del>- @private
-
</del><span class="cx"> Override the default buffer for Ember Handlebars. By default, Handlebars
</span><span class="cx"> creates an empty String at the beginning of each invocation and appends to
</span><span class="cx"> it. Ember's Handlebars overrides this to append to a single shared buffer.
</span><span class="cx">
</span><ins>+ @private
</ins><span class="cx"> @method appendToBuffer
</span><span class="cx"> @param string {String}
</span><span class="cx"> */
</span><span class="lines">@@ -17548,15 +26296,51 @@
</span><span class="cx"> return "data.buffer.push("+string+");";
</span><span class="cx"> };
</span><span class="cx">
</span><ins>+// 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);
+};
+
</ins><span class="cx"> var prefix = "ember" + (+new Date()), incr = 1;
</span><span class="cx">
</span><span class="cx"> /**
</span><del>- @private
-
</del><span class="cx"> Rewrite simple mustaches from `{{foo}}` to `{{bind "foo"}}`. This means that
</span><span class="cx"> all simple mustaches in Ember's Handlebars will also set up an observer to
</span><span class="cx"> keep the DOM up to date when the underlying property changes.
</span><span class="cx">
</span><ins>+ @private
</ins><span class="cx"> @method mustache
</span><span class="cx"> @for Ember.Handlebars.Compiler
</span><span class="cx"> @param mustache
</span><span class="lines">@@ -17568,12 +26352,12 @@
</span><span class="cx"> } else if (mustache.params.length || mustache.hash) {
</span><span class="cx"> // no changes required
</span><span class="cx"> } else {
</span><del>- var id = new Handlebars.AST.IdNode(['_triageMustache']);
</del><ins>+ var id = new Handlebars.AST.IdNode([{ part: '_triageMustache' }]);
</ins><span class="cx">
</span><span class="cx"> // Update the mustache node to include a hash value indicating whether the original node
</span><span class="cx"> // was escaped. This will allow us to properly escape values when the underlying value
</span><span class="cx"> // changes and we need to re-render the value.
</span><del>- if(!mustache.escaped) {
</del><ins>+ if (!mustache.escaped) {
</ins><span class="cx"> mustache.hash = mustache.hash || new Handlebars.AST.HashNode([]);
</span><span class="cx"> mustache.hash.pairs.push(["unescaped", new Handlebars.AST.StringNode("true")]);
</span><span class="cx"> }
</span><span class="lines">@@ -17599,7 +26383,7 @@
</span><span class="cx"> knownHelpers: {
</span><span class="cx"> action: true,
</span><span class="cx"> unbound: true,
</span><del>- bindAttr: true,
</del><ins>+ 'bind-attr': true,
</ins><span class="cx"> template: true,
</span><span class="cx"> view: true,
</span><span class="cx"> _triageMustache: true
</span><span class="lines">@@ -17631,7 +26415,10 @@
</span><span class="cx"> var environment = new Ember.Handlebars.Compiler().compile(ast, options);
</span><span class="cx"> var templateSpec = new Ember.Handlebars.JavaScriptCompiler().compile(environment, options, undefined, true);
</span><span class="cx">
</span><del>- return Ember.Handlebars.template(templateSpec);
</del><ins>+ var template = Ember.Handlebars.template(templateSpec);
+ template.isMethod = false; //Make sure we don't wrap templates with ._super
+
+ return template;
</ins><span class="cx"> };
</span><span class="cx"> }
</span><span class="cx">
</span><span class="lines">@@ -17639,14 +26426,14 @@
</span><span class="cx"> })();
</span><span class="cx">
</span><span class="cx"> (function() {
</span><del>-var slice = Array.prototype.slice;
</del><ins>+var slice = Array.prototype.slice,
+ originalTemplate = Ember.Handlebars.template;
</ins><span class="cx">
</span><span class="cx"> /**
</span><del>- @private
-
</del><span class="cx"> If a path starts with a reserved keyword, returns the root
</span><span class="cx"> that should be used.
</span><span class="cx">
</span><ins>+ @private
</ins><span class="cx"> @method normalizePath
</span><span class="cx"> @for Ember
</span><span class="cx"> @param root {Object}
</span><span class="lines">@@ -17700,22 +26487,19 @@
</span><span class="cx"> normalizedPath = normalizePath(root, path, data),
</span><span class="cx"> value;
</span><span class="cx">
</span><del>- // In cases where the path begins with a keyword, change the
- // root to the value represented by that keyword, and ensure
- // the path is relative to it.
- root = normalizedPath.root;
- path = normalizedPath.path;
</del><ins>+
+ root = normalizedPath.root;
+ path = normalizedPath.path;
</ins><span class="cx">
</span><del>- value = Ember.get(root, path);
</del><ins>+ value = Ember.get(root, path);
</ins><span class="cx">
</span><del>- // If the path starts with a capital letter, look it up on Ember.lookup,
- // which defaults to the `window` object in browsers.
- if (value === undefined && root !== Ember.lookup && Ember.isGlobalPath(path)) {
- value = Ember.get(Ember.lookup, path);
- }
</del><ins>+ if (value === undefined && root !== Ember.lookup && Ember.isGlobalPath(path)) {
+ value = Ember.get(Ember.lookup, path);
+ }
+
+
</ins><span class="cx"> return value;
</span><span class="cx"> };
</span><del>-Ember.Handlebars.getPath = Ember.deprecateFunc('`Ember.Handlebars.getPath` has been changed to `Ember.Handlebars.get` for consistency.', Ember.Handlebars.get);
</del><span class="cx">
</span><span class="cx"> Ember.Handlebars.resolveParams = function(context, params, options) {
</span><span class="cx"> var resolvedParams = [], types = options.types, param, type;
</span><span class="lines">@@ -17753,8 +26537,6 @@
</span><span class="cx"> };
</span><span class="cx">
</span><span class="cx"> /**
</span><del>- @private
-
</del><span class="cx"> Registers a helper in Handlebars that will be called if no property with the
</span><span class="cx"> given name can be found on the current context object, and no helper with
</span><span class="cx"> that name is registered.
</span><span class="lines">@@ -17762,22 +26544,66 @@
</span><span class="cx"> This throws an exception with a more helpful error message so the user can
</span><span class="cx"> track down where the problem is happening.
</span><span class="cx">
</span><ins>+ @private
</ins><span class="cx"> @method helperMissing
</span><span class="cx"> @for Ember.Handlebars.helpers
</span><span class="cx"> @param {String} path
</span><span class="cx"> @param {Hash} options
</span><span class="cx"> */
</span><del>-Ember.Handlebars.registerHelper('helperMissing', function(path, options) {
</del><ins>+Ember.Handlebars.registerHelper('helperMissing', function(path) {
</ins><span class="cx"> var error, view = "";
</span><span class="cx">
</span><ins>+ var options = arguments[arguments.length - 1];
+
+ var helper = Ember.Handlebars.resolveHelper(options.data.view.container, path);
+
+ if (helper) {
+ return helper.apply(this, slice.call(arguments, 1));
+ }
+
</ins><span class="cx"> error = "%@ Handlebars error: Could not find property '%@' on object %@.";
</span><del>- if (options.data){
</del><ins>+ if (options.data) {
</ins><span class="cx"> view = options.data.view;
</span><span class="cx"> }
</span><span class="cx"> throw new Ember.Error(Ember.String.fmt(error, [view, path, this]));
</span><span class="cx"> });
</span><span class="cx">
</span><span class="cx"> /**
</span><ins>+ Registers a helper in Handlebars that will be called if no property with the
+ given name can be found on the current context object, and no helper with
+ that name is registered.
+
+ This throws an exception with a more helpful error message so the user can
+ track down where the problem is happening.
+
+ @private
+ @method helperMissing
+ @for Ember.Handlebars.helpers
+ @param {String} path
+ @param {Hash} options
+*/
+Ember.Handlebars.registerHelper('blockHelperMissing', function(path) {
+
+ var options = arguments[arguments.length - 1];
+
+ Ember.assert("`blockHelperMissing` was invoked without a helper name, which " +
+ "is most likely due to a mismatch between the version of " +
+ "Ember.js you're running now and the one used to precompile your " +
+ "templates. Please make sure the version of " +
+ "`ember-handlebars-compiler` you're using is up to date.", path);
+
+ var helper = Ember.Handlebars.resolveHelper(options.data.view.container, path);
+
+ if (helper) {
+ return helper.apply(this, slice.call(arguments, 1));
+ } else {
+ return Handlebars.helpers.helperMissing.call(this, path);
+ }
+
+ return Handlebars.helpers.blockHelperMissing.apply(this, arguments);
+});
+
+/**
</ins><span class="cx"> Register a bound handlebars helper. Bound helpers behave similarly to regular
</span><span class="cx"> handlebars helpers, with the added ability to re-render when the underlying data
</span><span class="cx"> changes.
</span><span class="lines">@@ -17808,7 +26634,7 @@
</span><span class="cx"> Ember.Handlebars.registerBoundHelper('repeat', function(value, options) {
</span><span class="cx"> var count = options.hash.count;
</span><span class="cx"> var a = [];
</span><del>- while(a.length < count){
</del><ins>+ while(a.length < count) {
</ins><span class="cx"> a.push(value);
</span><span class="cx"> }
</span><span class="cx"> return a.join('');
</span><span class="lines">@@ -17823,7 +26649,7 @@
</span><span class="cx">
</span><span class="cx"> ## Example with bound options
</span><span class="cx">
</span><del>- Bound hash options are also supported. Example:
</del><ins>+ Bound hash options are also supported. Example:
</ins><span class="cx">
</span><span class="cx"> ```handlebars
</span><span class="cx"> {{repeat text countBinding="numRepeats"}}
</span><span class="lines">@@ -17852,30 +26678,34 @@
</span><span class="cx">
</span><span class="cx"> ```javascript
</span><span class="cx"> Ember.Handlebars.registerBoundHelper('concatenate', function() {
</span><del>- var values = arguments[arguments.length - 1];
</del><ins>+ var values = Array.prototype.slice.call(arguments, 0, -1);
</ins><span class="cx"> return values.join('||');
</span><span class="cx"> });
</span><span class="cx"> ```
</span><span class="cx">
</span><del>- Which allows for template syntax such as {{concatenate prop1 prop2}} or
- {{concatenate prop1 prop2 prop3}}. If any of the properties change,
</del><ins>+ Which allows for template syntax such as `{{concatenate prop1 prop2}}` or
+ `{{concatenate prop1 prop2 prop3}}`. If any of the properties change,
</ins><span class="cx"> the helpr will re-render. Note that dependency keys cannot be
</span><span class="cx"> using in conjunction with multi-property helpers, since it is ambiguous
</span><del>- which property the dependent keys would belong to.
-
</del><ins>+ which property the dependent keys would belong to.
+
</ins><span class="cx"> ## Use with unbound helper
</span><span class="cx">
</span><del>- The {{unbound}} helper can be used with bound helper invocations
</del><ins>+ The `{{unbound}}` helper can be used with bound helper invocations
</ins><span class="cx"> to render them in their unbound form, e.g.
</span><span class="cx">
</span><span class="cx"> ```handlebars
</span><del>- {{unbound capitalize name}}
</del><ins>+ {{unbound capitalize name}}
</ins><span class="cx"> ```
</span><span class="cx">
</span><span class="cx"> In this example, if the name property changes, the helper
</span><span class="cx"> will not re-render.
</span><span class="cx">
</span><ins>+ ## Use with blocks not supported
</ins><span class="cx">
</span><ins>+ Bound helpers do not support use with Handlebars blocks or
+ the addition of child views of any kind.
+
</ins><span class="cx"> @method registerBoundHelper
</span><span class="cx"> @for Ember.Handlebars
</span><span class="cx"> @param {String} name
</span><span class="lines">@@ -17883,161 +26713,177 @@
</span><span class="cx"> @param {String} dependentKeys*
</span><span class="cx"> */
</span><span class="cx"> Ember.Handlebars.registerBoundHelper = function(name, fn) {
</span><del>- var dependentKeys = slice.call(arguments, 2);
</del><ins>+ var boundHelperArgs = slice.call(arguments, 1),
+ boundFn = Ember.Handlebars.makeBoundHelper.apply(this, boundHelperArgs);
+ Ember.Handlebars.registerHelper(name, boundFn);
+};
</ins><span class="cx">
</span><ins>+/**
+ A (mostly) private helper function to `registerBoundHelper`. Takes the
+ provided Handlebars helper function fn and returns it in wrapped
+ bound helper form.
+
+ The main use case for using this outside of `registerBoundHelper`
+ is for registering helpers on the container:
+
+ ```js
+ var boundHelperFn = Ember.Handlebars.makeBoundHelper(function(word) {
+ return word.toUpperCase();
+ });
+
+ container.register('helper:my-bound-helper', boundHelperFn);
+ ```
+
+ In the above example, if the helper function hadn't been wrapped in
+ `makeBoundHelper`, the registered helper would be unbound.
+
+ @private
+ @method makeBoundHelper
+ @for Ember.Handlebars
+ @param {Function} function
+ @param {String} dependentKeys*
+*/
+Ember.Handlebars.makeBoundHelper = function(fn) {
+ var dependentKeys = slice.call(arguments, 1);
+
</ins><span class="cx"> function helper() {
</span><span class="cx"> var properties = slice.call(arguments, 0, -1),
</span><span class="cx"> numProperties = properties.length,
</span><span class="cx"> options = arguments[arguments.length - 1],
</span><span class="cx"> normalizedProperties = [],
</span><span class="cx"> data = options.data,
</span><ins>+ types = data.isUnbound ? slice.call(options.types, 1) : options.types,
</ins><span class="cx"> hash = options.hash,
</span><span class="cx"> view = data.view,
</span><del>- currentContext = (options.contexts && options.contexts[0]) || this,
- normalized,
- pathRoot, path,
- loc, hashOption;
</del><ins>+ contexts = options.contexts,
+ currentContext = (contexts && contexts.length) ? contexts[0] : this,
+ prefixPathForDependentKeys = '',
+ loc, len, hashOption,
+ boundOption, property,
+ normalizedValue = Ember._SimpleHandlebarsView.prototype.normalizedValue;
</ins><span class="cx">
</span><ins>+ Ember.assert("registerBoundHelper-generated helpers do not support use with Handlebars blocks.", !options.fn);
+
</ins><span class="cx"> // Detect bound options (e.g. countBinding="otherCount")
</span><del>- hash.boundOptions = {};
</del><ins>+ var boundOptions = hash.boundOptions = {};
</ins><span class="cx"> for (hashOption in hash) {
</span><del>- if (!hash.hasOwnProperty(hashOption)) { continue; }
-
- if (Ember.IS_BINDING.test(hashOption) && typeof hash[hashOption] === 'string') {
</del><ins>+ if (Ember.IS_BINDING.test(hashOption)) {
</ins><span class="cx"> // Lop off 'Binding' suffix.
</span><del>- hash.boundOptions[hashOption.slice(0, -7)] = hash[hashOption];
</del><ins>+ boundOptions[hashOption.slice(0, -7)] = hash[hashOption];
</ins><span class="cx"> }
</span><span class="cx"> }
</span><span class="cx">
</span><span class="cx"> // Expose property names on data.properties object.
</span><ins>+ var watchedProperties = [];
</ins><span class="cx"> data.properties = [];
</span><span class="cx"> for (loc = 0; loc < numProperties; ++loc) {
</span><span class="cx"> data.properties.push(properties[loc]);
</span><del>- normalizedProperties.push(normalizePath(currentContext, properties[loc], data));
</del><ins>+ if (types[loc] === 'ID') {
+ var normalizedProp = normalizePath(currentContext, properties[loc], data);
+ normalizedProperties.push(normalizedProp);
+ watchedProperties.push(normalizedProp);
+ } else {
+ if(data.isUnbound) {
+ normalizedProperties.push({path: properties[loc]});
+ }else {
+ normalizedProperties.push(null);
+ }
+ }
</ins><span class="cx"> }
</span><span class="cx">
</span><ins>+ // Handle case when helper invocation is preceded by `unbound`, e.g.
+ // {{unbound myHelper foo}}
</ins><span class="cx"> if (data.isUnbound) {
</span><span class="cx"> return evaluateUnboundHelper(this, fn, normalizedProperties, options);
</span><span class="cx"> }
</span><span class="cx">
</span><del>- if (dependentKeys.length === 0) {
- return evaluateMultiPropertyBoundHelper(currentContext, fn, normalizedProperties, options);
- }
</del><ins>+ var bindView = new Ember._SimpleHandlebarsView(null, null, !options.hash.unescaped, options.data);
</ins><span class="cx">
</span><del>- Ember.assert("Dependent keys can only be used with single-property helpers.", properties.length === 1);
</del><ins>+ // Override SimpleHandlebarsView's method for generating the view's content.
+ bindView.normalizedValue = function() {
+ var args = [], boundOption;
</ins><span class="cx">
</span><del>- normalized = normalizedProperties[0];
</del><ins>+ // Copy over bound hash options.
+ for (boundOption in boundOptions) {
+ if (!boundOptions.hasOwnProperty(boundOption)) { continue; }
+ property = normalizePath(currentContext, boundOptions[boundOption], data);
+ bindView.path = property.path;
+ bindView.pathRoot = property.root;
+ hash[boundOption] = normalizedValue.call(bindView);
+ }
</ins><span class="cx">
</span><del>- pathRoot = normalized.root;
- path = normalized.path;
</del><ins>+ for (loc = 0; loc < numProperties; ++loc) {
+ property = normalizedProperties[loc];
+ if (property) {
+ bindView.path = property.path;
+ bindView.pathRoot = property.root;
+ args.push(normalizedValue.call(bindView));
+ } else {
+ args.push(properties[loc]);
+ }
+ }
+ args.push(options);
</ins><span class="cx">
</span><del>- var bindView = new Ember._SimpleHandlebarsView(
- path, pathRoot, !options.hash.unescaped, options.data
- );
-
- bindView.normalizedValue = function() {
- var value = Ember._SimpleHandlebarsView.prototype.normalizedValue.call(bindView);
- return fn.call(view, value, options);
</del><ins>+ // Run the supplied helper function.
+ return fn.apply(currentContext, args);
</ins><span class="cx"> };
</span><span class="cx">
</span><span class="cx"> view.appendChild(bindView);
</span><span class="cx">
</span><del>- view.registerObserver(pathRoot, path, bindView, rerenderBoundHelperView);
-
- for (var i=0, l=dependentKeys.length; i<l; i++) {
- view.registerObserver(pathRoot, path + '.' + dependentKeys[i], bindView, rerenderBoundHelperView);
</del><ins>+ // Assemble list of watched properties that'll re-render this helper.
+ for (boundOption in boundOptions) {
+ if (boundOptions.hasOwnProperty(boundOption)) {
+ watchedProperties.push(normalizePath(currentContext, boundOptions[boundOption], data));
+ }
</ins><span class="cx"> }
</span><del>- }
</del><span class="cx">
</span><del>- helper._rawFunction = fn;
- Ember.Handlebars.registerHelper(name, helper);
-};
-
-/**
- @private
-
- Renders the unbound form of an otherwise bound helper function.
-
- @param {Function} fn
- @param {Object} context
- @param {Array} normalizedProperties
- @param {String} options
-*/
-function evaluateMultiPropertyBoundHelper(context, fn, normalizedProperties, options) {
- var numProperties = normalizedProperties.length,
- self = this,
- data = options.data,
- view = data.view,
- hash = options.hash,
- boundOptions = hash.boundOptions,
- watchedProperties,
- boundOption, bindView, loc, property, len;
-
- bindView = new Ember._SimpleHandlebarsView(null, null, !hash.unescaped, data);
- bindView.normalizedValue = function() {
- var args = [], value, boundOption;
-
- // Copy over bound options.
- for (boundOption in boundOptions) {
- if (!boundOptions.hasOwnProperty(boundOption)) { continue; }
- property = normalizePath(context, boundOptions[boundOption], data);
- bindView.path = property.path;
- bindView.pathRoot = property.root;
- hash[boundOption] = Ember._SimpleHandlebarsView.prototype.normalizedValue.call(bindView);
</del><ins>+ // Observe each property.
+ for (loc = 0, len = watchedProperties.length; loc < len; ++loc) {
+ property = watchedProperties[loc];
+ view.registerObserver(property.root, property.path, bindView, bindView.rerender);
</ins><span class="cx"> }
</span><span class="cx">
</span><del>- for (loc = 0; loc < numProperties; ++loc) {
- property = normalizedProperties[loc];
- bindView.path = property.path;
- bindView.pathRoot = property.root;
- args.push(Ember._SimpleHandlebarsView.prototype.normalizedValue.call(bindView));
</del><ins>+ if (types[0] !== 'ID' || normalizedProperties.length === 0) {
+ return;
</ins><span class="cx"> }
</span><del>- args.push(options);
- return fn.apply(context, args);
- };
</del><span class="cx">
</span><del>- view.appendChild(bindView);
</del><ins>+ // Add dependent key observers to the first param
+ var normalized = normalizedProperties[0],
+ pathRoot = normalized.root,
+ path = normalized.path;
</ins><span class="cx">
</span><del>- // Assemble liast of watched properties that'll re-render this helper.
- watchedProperties = [];
- for (boundOption in boundOptions) {
- if (boundOptions.hasOwnProperty(boundOption)) {
- watchedProperties.push(normalizePath(context, boundOptions[boundOption], data));
</del><ins>+ if(!Ember.isEmpty(path)) {
+ prefixPathForDependentKeys = path + '.';
</ins><span class="cx"> }
</span><ins>+ for (var i=0, l=dependentKeys.length; i<l; i++) {
+ view.registerObserver(pathRoot, prefixPathForDependentKeys + dependentKeys[i], bindView, bindView.rerender);
+ }
</ins><span class="cx"> }
</span><del>- watchedProperties = watchedProperties.concat(normalizedProperties);
</del><span class="cx">
</span><del>- // Observe each property.
- for (loc = 0, len = watchedProperties.length; loc < len; ++loc) {
- property = watchedProperties[loc];
- view.registerObserver(property.root, property.path, bindView, rerenderBoundHelperView);
- }
</del><ins>+ helper._rawFunction = fn;
+ return helper;
+};
</ins><span class="cx">
</span><del>-}
-
</del><span class="cx"> /**
</span><del>- @private
</del><ins>+ Renders the unbound form of an otherwise bound helper function.
</ins><span class="cx">
</span><del>- An observer function used with bound helpers which
- will schedule a re-render of the _SimpleHandlebarsView
- connected with the helper.
-*/
-function rerenderBoundHelperView() {
- Ember.run.scheduleOnce('render', this, 'rerender');
-}
-
-/**
</del><span class="cx"> @private
</span><del>-
- Renders the unbound form of an otherwise bound helper function.
-
</del><ins>+ @method evaluateUnboundHelper
</ins><span class="cx"> @param {Function} fn
</span><span class="cx"> @param {Object} context
</span><span class="cx"> @param {Array} normalizedProperties
</span><span class="cx"> @param {String} options
</span><span class="cx"> */
</span><span class="cx"> function evaluateUnboundHelper(context, fn, normalizedProperties, options) {
</span><del>- var args = [], hash = options.hash, boundOptions = hash.boundOptions, loc, len, property, boundOption;
</del><ins>+ var args = [],
+ hash = options.hash,
+ boundOptions = hash.boundOptions,
+ types = slice.call(options.types, 1),
+ loc,
+ len,
+ property,
+ propertyType,
+ boundOption;
</ins><span class="cx">
</span><span class="cx"> for (boundOption in boundOptions) {
</span><span class="cx"> if (!boundOptions.hasOwnProperty(boundOption)) { continue; }
</span><span class="lines">@@ -18046,38 +26892,50 @@
</span><span class="cx">
</span><span class="cx"> for(loc = 0, len = normalizedProperties.length; loc < len; ++loc) {
</span><span class="cx"> property = normalizedProperties[loc];
</span><del>- args.push(Ember.Handlebars.get(context, property.path, options));
</del><ins>+ propertyType = types[loc];
+ if(propertyType === "ID") {
+ args.push(Ember.Handlebars.get(property.root, property.path, options));
+ } else {
+ args.push(property.path);
+ }
</ins><span class="cx"> }
</span><span class="cx"> args.push(options);
</span><span class="cx"> return fn.apply(context, args);
</span><span class="cx"> }
</span><span class="cx">
</span><span class="cx"> /**
</span><del>- @private
-
</del><span class="cx"> Overrides Handlebars.template so that we can distinguish
</span><span class="cx"> user-created, top-level templates from inner contexts.
</span><span class="cx">
</span><ins>+ @private
</ins><span class="cx"> @method template
</span><span class="cx"> @for Ember.Handlebars
</span><del>- @param {String} template spec
</del><ins>+ @param {String} spec
</ins><span class="cx"> */
</span><del>-Ember.Handlebars.template = function(spec){
- var t = Handlebars.template(spec);
</del><ins>+Ember.Handlebars.template = function(spec) {
+ var t = originalTemplate(spec);
</ins><span class="cx"> t.isTop = true;
</span><span class="cx"> return t;
</span><span class="cx"> };
</span><span class="cx">
</span><del>-
</del><span class="cx"> })();
</span><span class="cx">
</span><span class="cx">
</span><span class="cx">
</span><span class="cx"> (function() {
</span><span class="cx"> /**
</span><ins>+ Mark a string as safe for unescaped output with Handlebars. If you
+ return HTML from a Handlebars helper, use this function to
+ ensure Handlebars does not escape the HTML.
+
+ ```javascript
+ Ember.String.htmlSafe('<div>someString</div>')
+ ```
+
</ins><span class="cx"> @method htmlSafe
</span><span class="cx"> @for Ember.String
</span><span class="cx"> @static
</span><ins>+ @return {Handlebars.SafeString} a string that will not be html escaped by Handlebars
</ins><span class="cx"> */
</span><span class="cx"> Ember.String.htmlSafe = function(str) {
</span><span class="cx"> return new Handlebars.SafeString(str);
</span><span class="lines">@@ -18088,10 +26946,17 @@
</span><span class="cx"> if (Ember.EXTEND_PROTOTYPES === true || Ember.EXTEND_PROTOTYPES.String) {
</span><span class="cx">
</span><span class="cx"> /**
</span><del>- See {{#crossLink "Ember.String/htmlSafe"}}{{/crossLink}}
</del><ins>+ Mark a string as being safe for unescaped output with Handlebars.
</ins><span class="cx">
</span><ins>+ ```javascript
+ '<div>someString</div>'.htmlSafe()
+ ```
+
+ See [Ember.String.htmlSafe](/api/classes/Ember.String.html#method_htmlSafe).
+
</ins><span class="cx"> @method htmlSafe
</span><span class="cx"> @for String
</span><ins>+ @return {Handlebars.SafeString} a string that will not be html escaped by Handlebars
</ins><span class="cx"> */
</span><span class="cx"> String.prototype.htmlSafe = function() {
</span><span class="cx"> return htmlSafe(this);
</span><span class="lines">@@ -18130,22 +26995,30 @@
</span><span class="cx"> var set = Ember.set, get = Ember.get;
</span><span class="cx"> var Metamorph = requireModule('metamorph');
</span><span class="cx">
</span><ins>+function notifyMutationListeners() {
+ Ember.run.once(Ember.View, 'notifyMutationListeners');
+}
+
</ins><span class="cx"> // DOMManager should just abstract dom manipulation between jquery and metamorph
</span><span class="cx"> var DOMManager = {
</span><span class="cx"> remove: function(view) {
</span><span class="cx"> view.morph.remove();
</span><ins>+ notifyMutationListeners();
</ins><span class="cx"> },
</span><span class="cx">
</span><span class="cx"> prepend: function(view, html) {
</span><span class="cx"> view.morph.prepend(html);
</span><ins>+ notifyMutationListeners();
</ins><span class="cx"> },
</span><span class="cx">
</span><span class="cx"> after: function(view, html) {
</span><span class="cx"> view.morph.after(html);
</span><ins>+ notifyMutationListeners();
</ins><span class="cx"> },
</span><span class="cx">
</span><span class="cx"> html: function(view, html) {
</span><span class="cx"> view.morph.html(html);
</span><ins>+ notifyMutationListeners();
</ins><span class="cx"> },
</span><span class="cx">
</span><span class="cx"> // This is messed up.
</span><span class="lines">@@ -18154,25 +27027,32 @@
</span><span class="cx">
</span><span class="cx"> view.transitionTo('preRender');
</span><span class="cx">
</span><del>- Ember.run.schedule('render', this, function() {
</del><ins>+ Ember.run.schedule('render', this, function renderMetamorphView() {
</ins><span class="cx"> if (view.isDestroying) { return; }
</span><span class="cx">
</span><span class="cx"> view.clearRenderedChildren();
</span><span class="cx"> var buffer = view.renderToBuffer();
</span><span class="cx">
</span><span class="cx"> view.invokeRecursively(function(view) {
</span><del>- view.propertyDidChange('element');
</del><ins>+ view.propertyWillChange('element');
</ins><span class="cx"> });
</span><ins>+ view.triggerRecursively('willInsertElement');
</ins><span class="cx">
</span><del>- view.triggerRecursively('willInsertElement');
</del><span class="cx"> morph.replaceWith(buffer.string());
</span><span class="cx"> view.transitionTo('inDOM');
</span><ins>+
+ view.invokeRecursively(function(view) {
+ view.propertyDidChange('element');
+ });
</ins><span class="cx"> view.triggerRecursively('didInsertElement');
</span><ins>+
+ notifyMutationListeners();
</ins><span class="cx"> });
</span><span class="cx"> },
</span><span class="cx">
</span><span class="cx"> empty: function(view) {
</span><span class="cx"> view.morph.html("");
</span><ins>+ notifyMutationListeners();
</ins><span class="cx"> }
</span><span class="cx"> };
</span><span class="cx">
</span><span class="lines">@@ -18182,18 +27062,18 @@
</span><span class="cx"> /**
</span><span class="cx"> @class _Metamorph
</span><span class="cx"> @namespace Ember
</span><del>- @extends Ember.Mixin
</del><span class="cx"> @private
</span><span class="cx"> */
</span><span class="cx"> Ember._Metamorph = Ember.Mixin.create({
</span><span class="cx"> isVirtual: true,
</span><span class="cx"> tagName: '',
</span><span class="cx">
</span><del>- instrumentName: 'render.metamorph',
</del><ins>+ instrumentName: 'metamorph',
</ins><span class="cx">
</span><span class="cx"> init: function() {
</span><span class="cx"> this._super();
</span><span class="cx"> this.morph = Metamorph();
</span><ins>+ Ember.deprecate('Supplying a tagName to Metamorph views is unreliable and is deprecated. You may be setting the tagName on a Handlebars helper that creates a Metamorph.', !this.tagName);
</ins><span class="cx"> },
</span><span class="cx">
</span><span class="cx"> beforeRender: function(buffer) {
</span><span class="lines">@@ -18227,7 +27107,7 @@
</span><span class="cx"> /**
</span><span class="cx"> @class _SimpleMetamorphView
</span><span class="cx"> @namespace Ember
</span><del>- @extends Ember.View
</del><ins>+ @extends Ember.CoreView
</ins><span class="cx"> @uses Ember._Metamorph
</span><span class="cx"> @private
</span><span class="cx"> */
</span><span class="lines">@@ -18257,6 +27137,8 @@
</span><span class="cx"> this.morph = Metamorph();
</span><span class="cx"> this.state = 'preRender';
</span><span class="cx"> this.updateId = null;
</span><ins>+ this._parentView = null;
+ this.buffer = null;
</ins><span class="cx"> }
</span><span class="cx">
</span><span class="cx"> Ember._SimpleHandlebarsView = SimpleHandlebarsView;
</span><span class="lines">@@ -18270,9 +27152,15 @@
</span><span class="cx"> Ember.run.cancel(this.updateId);
</span><span class="cx"> this.updateId = null;
</span><span class="cx"> }
</span><ins>+ if (this._parentView) {
+ this._parentView.removeChild(this);
+ }
</ins><span class="cx"> this.morph = null;
</span><ins>+ this.state = 'destroyed';
</ins><span class="cx"> },
</span><span class="cx">
</span><ins>+ propertyWillChange: Ember.K,
+
</ins><span class="cx"> propertyDidChange: Ember.K,
</span><span class="cx">
</span><span class="cx"> normalizedValue: function() {
</span><span class="lines">@@ -18354,7 +27242,7 @@
</span><span class="cx">
</span><span class="cx"> merge(states.inDOM, {
</span><span class="cx"> rerenderIfNeeded: function(view) {
</span><del>- if (get(view, 'normalizedValue') !== view._lastNormalizedValue) {
</del><ins>+ if (view.normalizedValue() !== view._lastNormalizedValue) {
</ins><span class="cx"> view.rerender();
</span><span class="cx"> }
</span><span class="cx"> }
</span><span class="lines">@@ -18375,7 +27263,7 @@
</span><span class="cx"> @private
</span><span class="cx"> */
</span><span class="cx"> Ember._HandlebarsBoundView = Ember._MetamorphView.extend({
</span><del>- instrumentName: 'render.boundHandlebars',
</del><ins>+ instrumentName: 'boundHandlebars',
</ins><span class="cx"> states: states,
</span><span class="cx">
</span><span class="cx"> /**
</span><span class="lines">@@ -18458,7 +27346,7 @@
</span><span class="cx"> */
</span><span class="cx"> pathRoot: null,
</span><span class="cx">
</span><del>- normalizedValue: Ember.computed(function() {
</del><ins>+ normalizedValue: function() {
</ins><span class="cx"> var path = get(this, 'path'),
</span><span class="cx"> pathRoot = get(this, 'pathRoot'),
</span><span class="cx"> valueNormalizer = get(this, 'valueNormalizerFunc'),
</span><span class="lines">@@ -18476,7 +27364,7 @@
</span><span class="cx"> }
</span><span class="cx">
</span><span class="cx"> return valueNormalizer ? valueNormalizer(result) : result;
</span><del>- }).property('path', 'pathRoot', 'valueNormalizerFunc').volatile(),
</del><ins>+ },
</ins><span class="cx">
</span><span class="cx"> rerenderIfNeeded: function() {
</span><span class="cx"> this.currentState.rerenderIfNeeded(this);
</span><span class="lines">@@ -18511,7 +27399,7 @@
</span><span class="cx"> var inverseTemplate = get(this, 'inverseTemplate'),
</span><span class="cx"> displayTemplate = get(this, 'displayTemplate');
</span><span class="cx">
</span><del>- var result = get(this, 'normalizedValue');
</del><ins>+ var result = this.normalizedValue();
</ins><span class="cx"> this._lastNormalizedValue = result;
</span><span class="cx">
</span><span class="cx"> // First, test the conditional to see if we should
</span><span class="lines">@@ -18571,9 +27459,28 @@
</span><span class="cx"> var get = Ember.get, set = Ember.set, fmt = Ember.String.fmt;
</span><span class="cx"> var handlebarsGet = Ember.Handlebars.get, normalizePath = Ember.Handlebars.normalizePath;
</span><span class="cx"> var forEach = Ember.ArrayPolyfills.forEach;
</span><ins>+var o_create = Ember.create;
</ins><span class="cx">
</span><span class="cx"> var EmberHandlebars = Ember.Handlebars, helpers = EmberHandlebars.helpers;
</span><span class="cx">
</span><ins>+function exists(value) {
+ return !Ember.isNone(value);
+}
+
+function sanitizedHandlebarsGet(currentContext, property, options) {
+ var result = handlebarsGet(currentContext, property, 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;
+}
+
</ins><span class="cx"> // Binds a property into the DOM. This will create a hook in DOM that the
</span><span class="cx"> // KVO system will look for and update if the property changes.
</span><span class="cx"> function bind(property, options, preserveContext, shouldDisplay, valueNormalizer, childProperties) {
</span><span class="lines">@@ -18595,7 +27502,7 @@
</span><span class="cx">
</span><span class="cx"> var template, context, result = handlebarsGet(currentContext, property, options);
</span><span class="cx">
</span><del>- result = valueNormalizer(result);
</del><ins>+ result = valueNormalizer ? valueNormalizer(result) : result;
</ins><span class="cx">
</span><span class="cx"> context = preserveContext ? currentContext : result;
</span><span class="cx"> if (shouldDisplay(result)) {
</span><span class="lines">@@ -18648,24 +27555,26 @@
</span><span class="cx"> }
</span><span class="cx"> }
</span><span class="cx">
</span><del>-function simpleBind(property, options) {
</del><ins>+EmberHandlebars.bind = bind;
+
+function simpleBind(currentContext, property, options) {
</ins><span class="cx"> var data = options.data,
</span><span class="cx"> view = data.view,
</span><del>- currentContext = this,
- normalized, observer;
</del><ins>+ normalized, observer, pathRoot, output;
</ins><span class="cx">
</span><span class="cx"> normalized = normalizePath(currentContext, property, data);
</span><ins>+ pathRoot = normalized.root;
</ins><span class="cx">
</span><span class="cx"> // Set up observers for observable objects
</span><del>- if ('object' === typeof this) {
</del><ins>+ if (pathRoot && ('object' === typeof pathRoot)) {
</ins><span class="cx"> if (data.insideGroup) {
</span><span class="cx"> observer = function() {
</span><span class="cx"> Ember.run.once(view, 'rerender');
</span><span class="cx"> };
</span><span class="cx">
</span><del>- var result = handlebarsGet(currentContext, property, options);
- if (result === null || result === undefined) { result = ""; }
- data.buffer.push(result);
</del><ins>+ output = sanitizedHandlebarsGet(currentContext, property, options);
+
+ data.buffer.push(output);
</ins><span class="cx"> } else {
</span><span class="cx"> var bindView = new Ember._SimpleHandlebarsView(
</span><span class="cx"> property, currentContext, !options.hash.unescaped, options.data
</span><span class="lines">@@ -18689,39 +27598,63 @@
</span><span class="cx"> } else {
</span><span class="cx"> // The object is not observable, so just render it out and
</span><span class="cx"> // be done with it.
</span><del>- data.buffer.push(handlebarsGet(currentContext, property, options));
</del><ins>+ output = sanitizedHandlebarsGet(currentContext, property, options);
+
+ data.buffer.push(output);
</ins><span class="cx"> }
</span><span class="cx"> }
</span><span class="cx">
</span><span class="cx"> /**
</span><del>- @private
-
- '_triageMustache' is used internally select between a binding and helper for
</del><ins>+ '_triageMustache' is used internally select between a binding, helper, or component for
</ins><span class="cx"> the given context. Until this point, it would be hard to determine if the
</span><span class="cx"> mustache is a property reference or a regular helper reference. This triage
</span><span class="cx"> helper resolves that.
</span><span class="cx">
</span><span class="cx"> This would not be typically invoked by directly.
</span><span class="cx">
</span><ins>+ @private
</ins><span class="cx"> @method _triageMustache
</span><span class="cx"> @for Ember.Handlebars.helpers
</span><span class="cx"> @param {String} property Property/helperID to triage
</span><del>- @param {Function} fn Context to provide for rendering
</del><ins>+ @param {Object} options hash of template/rendering options
</ins><span class="cx"> @return {String} HTML string
</span><span class="cx"> */
</span><del>-EmberHandlebars.registerHelper('_triageMustache', function(property, fn) {
</del><ins>+EmberHandlebars.registerHelper('_triageMustache', function(property, options) {
</ins><span class="cx"> Ember.assert("You cannot pass more than one argument to the _triageMustache helper", arguments.length <= 2);
</span><ins>+
</ins><span class="cx"> if (helpers[property]) {
</span><del>- return helpers[property].call(this, fn);
</del><ins>+ return helpers[property].call(this, options);
</ins><span class="cx"> }
</span><del>- else {
- return helpers.bind.apply(this, arguments);
</del><ins>+
+ var helper = Ember.Handlebars.resolveHelper(options.data.view.container, property);
+ if (helper) {
+ return helper.call(this, options);
</ins><span class="cx"> }
</span><ins>+
+ return helpers.bind.call(this, property, options);
</ins><span class="cx"> });
</span><span class="cx">
</span><ins>+Ember.Handlebars.resolveHelper = function(container, name) {
+
+ if (!container || name.indexOf('-') === -1) {
+ return;
+ }
+
+ var helper = container.lookup('helper:' + name);
+ if (!helper) {
+ var componentLookup = container.lookup('component-lookup:main');
+ Ember.assert("Could not find 'component-lookup:main' on the provided container, which is necessary for performing component lookups", componentLookup);
+
+ var Component = componentLookup.lookupFactory(name, container);
+ if (Component) {
+ helper = EmberHandlebars.makeViewHelper(Component);
+ container.register('helper:' + name, helper);
+ }
+ }
+ return helper;
+};
+
</ins><span class="cx"> /**
</span><del>- @private
-
</del><span class="cx"> `bind` can be used to display a value, then update that value if it
</span><span class="cx"> changes. For example, if you wanted to print the `title` property of
</span><span class="cx"> `content`:
</span><span class="lines">@@ -18737,29 +27670,26 @@
</span><span class="cx"> relies on Ember's KVO system. For all other browsers this will be handled for
</span><span class="cx"> you automatically.
</span><span class="cx">
</span><ins>+ @private
</ins><span class="cx"> @method bind
</span><span class="cx"> @for Ember.Handlebars.helpers
</span><span class="cx"> @param {String} property Property to bind
</span><span class="cx"> @param {Function} fn Context to provide for rendering
</span><span class="cx"> @return {String} HTML string
</span><span class="cx"> */
</span><del>-EmberHandlebars.registerHelper('bind', function(property, options) {
</del><ins>+EmberHandlebars.registerHelper('bind', function bindHelper(property, options) {
</ins><span class="cx"> Ember.assert("You cannot pass more than one argument to the bind helper", arguments.length <= 2);
</span><span class="cx">
</span><del>- var context = (options.contexts && options.contexts[0]) || this;
</del><ins>+ var context = (options.contexts && options.contexts.length) ? options.contexts[0] : this;
</ins><span class="cx">
</span><span class="cx"> if (!options.fn) {
</span><del>- return simpleBind.call(context, property, options);
</del><ins>+ return simpleBind(context, property, options);
</ins><span class="cx"> }
</span><span class="cx">
</span><del>- return bind.call(context, property, options, false, function(result) {
- return !Ember.isNone(result);
- });
</del><ins>+ return bind.call(context, property, options, false, exists);
</ins><span class="cx"> });
</span><span class="cx">
</span><span class="cx"> /**
</span><del>- @private
-
</del><span class="cx"> Use the `boundIf` helper to create a conditional that re-evaluates
</span><span class="cx"> whenever the truthiness of the bound value changes.
</span><span class="cx">
</span><span class="lines">@@ -18769,14 +27699,15 @@
</span><span class="cx"> {{/boundIf}}
</span><span class="cx"> ```
</span><span class="cx">
</span><ins>+ @private
</ins><span class="cx"> @method boundIf
</span><span class="cx"> @for Ember.Handlebars.helpers
</span><span class="cx"> @param {String} property Property to bind
</span><span class="cx"> @param {Function} fn Context to provide for rendering
</span><span class="cx"> @return {String} HTML string
</span><span class="cx"> */
</span><del>-EmberHandlebars.registerHelper('boundIf', function(property, fn) {
- var context = (fn.contexts && fn.contexts[0]) || this;
</del><ins>+EmberHandlebars.registerHelper('boundIf', function boundIfHelper(property, fn) {
+ var context = (fn.contexts && fn.contexts.length) ? fn.contexts[0] : this;
</ins><span class="cx"> var func = function(result) {
</span><span class="cx"> var truthy = result && get(result, 'isTruthy');
</span><span class="cx"> if (typeof truthy === 'boolean') { return truthy; }
</span><span class="lines">@@ -18792,15 +27723,65 @@
</span><span class="cx"> });
</span><span class="cx">
</span><span class="cx"> /**
</span><ins>+ Use the `{{with}}` helper when you want to scope context. Take the following code as an example:
+
+ ```handlebars
+ <h5>{{user.name}}</h5>
+
+ <div class="role">
+ <h6>{{user.role.label}}</h6>
+ <span class="role-id">{{user.role.id}}</span>
+
+ <p class="role-desc">{{user.role.description}}</p>
+ </div>
+ ```
+
+ `{{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
+ <h5>{{user.name}}</h5>
+
+ <div class="role">
+ {{#with user.role}}
+ <h6>{{label}}</h6>
+ <span class="role-id">{{id}}</span>
+
+ <p class="role-desc">{{description}}</p>
+ {{/with}}
+ </div>
+ ```
+
+ ### `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}}
+ <div class="notice">
+ There are {{blogPosts.length}} blog posts written by {{user.name}}.
+ </div>
+
+ {{#each post in blogPosts}}
+ <li>{{post.title}}</li>
+ {{/each}}
+ {{/with}}
+ ```
+
+ Without the `as` operator, it would be impossible to reference `user.name` in the example above.
+
</ins><span class="cx"> @method with
</span><span class="cx"> @for Ember.Handlebars.helpers
</span><span class="cx"> @param {Function} context
</span><span class="cx"> @param {Hash} options
</span><span class="cx"> @return {String} HTML string
</span><span class="cx"> */
</span><del>-EmberHandlebars.registerHelper('with', function(context, options) {
</del><ins>+EmberHandlebars.registerHelper('with', function withHelper(context, options) {
</ins><span class="cx"> if (arguments.length === 4) {
</span><del>- var keywordName, path, rootPath, normalized;
</del><ins>+ var keywordName, path, rootPath, normalized, contextPath;
</ins><span class="cx">
</span><span class="cx"> 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");
</span><span class="cx"> options = arguments[3];
</span><span class="lines">@@ -18809,8 +27790,12 @@
</span><span class="cx">
</span><span class="cx"> Ember.assert("You must pass a block to the with helper", options.fn && options.fn !== Handlebars.VM.noop);
</span><span class="cx">
</span><ins>+ var localizedOptions = o_create(options);
+ localizedOptions.data = o_create(options.data);
+ localizedOptions.data.keywords = o_create(options.data.keywords || {});
+
</ins><span class="cx"> if (Ember.isGlobalPath(path)) {
</span><del>- Ember.bind(options.data.keywords, keywordName, path);
</del><ins>+ contextPath = path;
</ins><span class="cx"> } else {
</span><span class="cx"> normalized = normalizePath(this, path, options.data);
</span><span class="cx"> path = normalized.path;
</span><span class="lines">@@ -18819,16 +27804,14 @@
</span><span class="cx"> // This is a workaround for the fact that you cannot bind separate objects
</span><span class="cx"> // together. When we implement that functionality, we should use it here.
</span><span class="cx"> var contextKey = Ember.$.expando + Ember.guidFor(rootPath);
</span><del>- options.data.keywords[contextKey] = rootPath;
-
</del><ins>+ localizedOptions.data.keywords[contextKey] = rootPath;
</ins><span class="cx"> // if the path is '' ("this"), just bind directly to the current context
</span><del>- var contextPath = path ? contextKey + '.' + path : contextKey;
- Ember.bind(options.data.keywords, keywordName, contextPath);
</del><ins>+ contextPath = path ? contextKey + '.' + path : contextKey;
</ins><span class="cx"> }
</span><span class="cx">
</span><del>- return bind.call(this, path, options, true, function(result) {
- return !Ember.isNone(result);
- });
</del><ins>+ Ember.bind(localizedOptions.data.keywords, keywordName, contextPath);
+
+ return bind.call(this, path, localizedOptions, true, exists);
</ins><span class="cx"> } else {
</span><span class="cx"> Ember.assert("You must pass exactly one argument to the with helper", arguments.length === 2);
</span><span class="cx"> Ember.assert("You must pass a block to the with helper", options.fn && options.fn !== Handlebars.VM.noop);
</span><span class="lines">@@ -18838,7 +27821,7 @@
</span><span class="cx">
</span><span class="cx">
</span><span class="cx"> /**
</span><del>- See `boundIf`
</del><ins>+ See [boundIf](/api/classes/Ember.Handlebars.helpers.html#method_boundIf)
</ins><span class="cx">
</span><span class="cx"> @method if
</span><span class="cx"> @for Ember.Handlebars.helpers
</span><span class="lines">@@ -18846,7 +27829,7 @@
</span><span class="cx"> @param {Hash} options
</span><span class="cx"> @return {String} HTML string
</span><span class="cx"> */
</span><del>-EmberHandlebars.registerHelper('if', function(context, options) {
</del><ins>+EmberHandlebars.registerHelper('if', function ifHelper(context, options) {
</ins><span class="cx"> Ember.assert("You must pass exactly one argument to the if helper", arguments.length === 2);
</span><span class="cx"> Ember.assert("You must pass a block to the if helper", options.fn && options.fn !== Handlebars.VM.noop);
</span><span class="cx">
</span><span class="lines">@@ -18860,7 +27843,7 @@
</span><span class="cx"> @param {Hash} options
</span><span class="cx"> @return {String} HTML string
</span><span class="cx"> */
</span><del>-EmberHandlebars.registerHelper('unless', function(context, options) {
</del><ins>+EmberHandlebars.registerHelper('unless', function unlessHelper(context, options) {
</ins><span class="cx"> Ember.assert("You must pass exactly one argument to the unless helper", arguments.length === 2);
</span><span class="cx"> Ember.assert("You must pass a block to the unless helper", options.fn && options.fn !== Handlebars.VM.noop);
</span><span class="cx">
</span><span class="lines">@@ -18873,11 +27856,11 @@
</span><span class="cx"> });
</span><span class="cx">
</span><span class="cx"> /**
</span><del>- `bindAttr` allows you to create a binding between DOM element attributes and
</del><ins>+ `bind-attr` allows you to create a binding between DOM element attributes and
</ins><span class="cx"> Ember objects. For example:
</span><span class="cx">
</span><span class="cx"> ```handlebars
</span><del>- <img {{bindAttr src="imageUrl" alt="imageTitle"}}>
</del><ins>+ <img {{bind-attr src="imageUrl" alt="imageTitle"}}>
</ins><span class="cx"> ```
</span><span class="cx">
</span><span class="cx"> The above handlebars template will fill the `<img>`'s `src` attribute will
</span><span class="lines">@@ -18899,51 +27882,51 @@
</span><span class="cx"> <img src="http://lolcats.info/haz-a-funny" alt="A humorous image of a cat">
</span><span class="cx"> ```
</span><span class="cx">
</span><del>- `bindAttr` cannot redeclare existing DOM element attributes. The use of `src`
- in the following `bindAttr` example will be ignored and the hard coded value
</del><ins>+ `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
</ins><span class="cx"> of `src="/failwhale.gif"` will take precedence:
</span><span class="cx">
</span><span class="cx"> ```handlebars
</span><del>- <img src="/failwhale.gif" {{bindAttr src="imageUrl" alt="imageTitle"}}>
</del><ins>+ <img src="/failwhale.gif" {{bind-attr src="imageUrl" alt="imageTitle"}}>
</ins><span class="cx"> ```
</span><span class="cx">
</span><del>- ### `bindAttr` and the `class` attribute
</del><ins>+ ### `bind-attr` and the `class` attribute
</ins><span class="cx">
</span><del>- `bindAttr` supports a special syntax for handling a number of cases unique
</del><ins>+ `bind-attr` supports a special syntax for handling a number of cases unique
</ins><span class="cx"> to the `class` DOM element attribute. The `class` attribute combines
</span><del>- multiple discreet values into a single attribute as a space-delimited
</del><ins>+ multiple discrete values into a single attribute as a space-delimited
</ins><span class="cx"> list of strings. Each string can be:
</span><span class="cx">
</span><span class="cx"> * a string return value of an object's property.
</span><span class="cx"> * a boolean return value of an object's property
</span><span class="cx"> * a hard-coded value
</span><span class="cx">
</span><del>- A string return value works identically to other uses of `bindAttr`. The
</del><ins>+ A string return value works identically to other uses of `bind-attr`. The
</ins><span class="cx"> return value of the property will become the value of the attribute. For
</span><span class="cx"> example, the following view and template:
</span><span class="cx">
</span><span class="cx"> ```javascript
</span><span class="cx"> AView = Ember.View.extend({
</span><del>- someProperty: function(){
</del><ins>+ someProperty: function() {
</ins><span class="cx"> return "aValue";
</span><span class="cx"> }.property()
</span><span class="cx"> })
</span><span class="cx"> ```
</span><span class="cx">
</span><span class="cx"> ```handlebars
</span><del>- <img {{bindAttr class="view.someProperty}}>
</del><ins>+ <img {{bind-attr class="view.someProperty}}>
</ins><span class="cx"> ```
</span><span class="cx">
</span><span class="cx"> Result in the following rendered output:
</span><span class="cx">
</span><del>- ```html
</del><ins>+ ```html
</ins><span class="cx"> <img class="aValue">
</span><span class="cx"> ```
</span><span class="cx">
</span><span class="cx"> A boolean return value will insert a specified class name if the property
</span><span class="cx"> returns `true` and remove the class name if the property returns `false`.
</span><span class="cx">
</span><del>- A class name is provided via the syntax
</del><ins>+ A class name is provided via the syntax
</ins><span class="cx"> `somePropertyName:class-name-if-true`.
</span><span class="cx">
</span><span class="cx"> ```javascript
</span><span class="lines">@@ -18953,7 +27936,7 @@
</span><span class="cx"> ```
</span><span class="cx">
</span><span class="cx"> ```handlebars
</span><del>- <img {{bindAttr class="view.someBool:class-name-if-true"}}>
</del><ins>+ <img {{bind-attr class="view.someBool:class-name-if-true"}}>
</ins><span class="cx"> ```
</span><span class="cx">
</span><span class="cx"> Result in the following rendered output:
</span><span class="lines">@@ -18967,39 +27950,39 @@
</span><span class="cx"> value changes:
</span><span class="cx">
</span><span class="cx"> ```handlebars
</span><del>- <img {{bindAttr class="view.someBool:class-name-if-true:class-name-if-false"}}>
</del><ins>+ <img {{bind-attr class="view.someBool:class-name-if-true:class-name-if-false"}}>
</ins><span class="cx"> ```
</span><span class="cx">
</span><span class="cx"> A hard-coded value can be used by prepending `:` to the desired
</span><span class="cx"> class name: `:class-name-to-always-apply`.
</span><span class="cx">
</span><span class="cx"> ```handlebars
</span><del>- <img {{bindAttr class=":class-name-to-always-apply"}}>
</del><ins>+ <img {{bind-attr class=":class-name-to-always-apply"}}>
</ins><span class="cx"> ```
</span><span class="cx">
</span><span class="cx"> Results in the following rendered output:
</span><span class="cx">
</span><span class="cx"> ```html
</span><del>- <img class=":class-name-to-always-apply">
</del><ins>+ <img class="class-name-to-always-apply">
</ins><span class="cx"> ```
</span><span class="cx">
</span><span class="cx"> All three strategies - string return value, boolean return value, and
</span><span class="cx"> hard-coded value – can be combined in a single declaration:
</span><span class="cx">
</span><span class="cx"> ```handlebars
</span><del>- <img {{bindAttr class=":class-name-to-always-apply view.someBool:class-name-if-true view.someProperty"}}>
</del><ins>+ <img {{bind-attr class=":class-name-to-always-apply view.someBool:class-name-if-true view.someProperty"}}>
</ins><span class="cx"> ```
</span><span class="cx">
</span><del>- @method bindAttr
</del><ins>+ @method bind-attr
</ins><span class="cx"> @for Ember.Handlebars.helpers
</span><span class="cx"> @param {Hash} options
</span><span class="cx"> @return {String} HTML string
</span><span class="cx"> */
</span><del>-EmberHandlebars.registerHelper('bindAttr', function(options) {
</del><ins>+EmberHandlebars.registerHelper('bind-attr', function bindAttrHelper(options) {
</ins><span class="cx">
</span><span class="cx"> var attrs = options.hash;
</span><span class="cx">
</span><del>- Ember.assert("You must specify at least one hash argument to bindAttr", !!Ember.keys(attrs).length);
</del><ins>+ Ember.assert("You must specify at least one hash argument to bind-attr", !!Ember.keys(attrs).length);
</ins><span class="cx">
</span><span class="cx"> var view = options.data.view;
</span><span class="cx"> var ret = [];
</span><span class="lines">@@ -19012,7 +27995,7 @@
</span><span class="cx">
</span><span class="cx"> // Handle classes differently, as we can bind multiple classes
</span><span class="cx"> var classBindings = attrs['class'];
</span><del>- if (classBindings !== null && classBindings !== undefined) {
</del><ins>+ if (classBindings != null) {
</ins><span class="cx"> var classResults = EmberHandlebars.bindClasses(this, classBindings, view, dataId, options);
</span><span class="cx">
</span><span class="cx"> ret.push('class="' + Handlebars.Utils.escapeExpression(classResults.join(' ')) + '"');
</span><span class="lines">@@ -19027,7 +28010,7 @@
</span><span class="cx"> var path = attrs[attr],
</span><span class="cx"> normalized;
</span><span class="cx">
</span><del>- Ember.assert(fmt("You must provide a String for a bound attribute, not %@", [path]), typeof path === 'string');
</del><ins>+ Ember.assert(fmt("You must provide an expression as the value of bound attribute. You specified: %@=%@", [attr, path]), typeof path === 'string');
</ins><span class="cx">
</span><span class="cx"> normalized = normalizePath(ctx, path, options.data);
</span><span class="cx">
</span><span class="lines">@@ -19041,7 +28024,9 @@
</span><span class="cx"> observer = function observer() {
</span><span class="cx"> var result = handlebarsGet(ctx, path, options);
</span><span class="cx">
</span><del>- 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');
</del><ins>+ 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');
</ins><span class="cx">
</span><span class="cx"> var elem = view.$("[data-bindattr-" + dataId + "='" + dataId + "']");
</span><span class="cx">
</span><span class="lines">@@ -19057,15 +28042,13 @@
</span><span class="cx"> Ember.View.applyAttributeBindings(elem, attr, result);
</span><span class="cx"> };
</span><span class="cx">
</span><del>- invoker = function() {
- Ember.run.scheduleOnce('render', observer);
- };
-
</del><span class="cx"> // Add an observer to the view for when the property changes.
</span><span class="cx"> // When the observer fires, find the element using the
</span><span class="cx"> // unique data id and update the attribute to the new value.
</span><del>- if (path !== 'this') {
- view.registerObserver(normalized.root, normalized.path, invoker);
</del><ins>+ // 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);
</ins><span class="cx"> }
</span><span class="cx">
</span><span class="cx"> // if this changes, also change the logic in ember-views/lib/views/view.js
</span><span class="lines">@@ -19084,8 +28067,21 @@
</span><span class="cx"> });
</span><span class="cx">
</span><span class="cx"> /**
</span><del>- @private
</del><ins>+ See `bind-attr`
</ins><span class="cx">
</span><ins>+ @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);
+});
+
+/**
</ins><span class="cx"> Helper that, given a space-separated string of property paths and a context,
</span><span class="cx"> returns an array of class names. Calling this method also has the side
</span><span class="cx"> effect of setting up observers at those property paths, such that if they
</span><span class="lines">@@ -19097,12 +28093,13 @@
</span><span class="cx"> "fooBar"). If the value is a string, it will add that string as the class.
</span><span class="cx"> Otherwise, it will not add any new class name.
</span><span class="cx">
</span><ins>+ @private
</ins><span class="cx"> @method bindClasses
</span><span class="cx"> @for Ember.Handlebars
</span><span class="cx"> @param {Ember.Object} context The context from which to lookup properties
</span><del>- @param {String} classBindings A string, space-separated, of class bindings
</del><ins>+ @param {String} classBindings A string, space-separated, of class bindings
</ins><span class="cx"> to use
</span><del>- @param {Ember.View} view The view in which observers should look for the
</del><ins>+ @param {Ember.View} view The view in which observers should look for the
</ins><span class="cx"> element to update
</span><span class="cx"> @param {Srting} bindAttrId Optional bindAttr id used to lookup elements
</span><span class="cx"> @return {Array} An array of class names to add
</span><span class="lines">@@ -19179,12 +28176,8 @@
</span><span class="cx"> }
</span><span class="cx"> };
</span><span class="cx">
</span><del>- invoker = function() {
- Ember.run.scheduleOnce('render', observer);
- };
-
</del><span class="cx"> if (path !== '' && path !== 'this') {
</span><del>- view.registerObserver(pathRoot, path, invoker);
</del><ins>+ view.registerObserver(pathRoot, path, observer);
</ins><span class="cx"> }
</span><span class="cx">
</span><span class="cx"> // We've already setup the observer; now we just need to figure out the
</span><span class="lines">@@ -19218,12 +28211,42 @@
</span><span class="cx"> */
</span><span class="cx">
</span><span class="cx"> var get = Ember.get, set = Ember.set;
</span><del>-var PARENT_VIEW_PATH = /^parentView\./;
</del><span class="cx"> var EmberHandlebars = Ember.Handlebars;
</span><ins>+var LOWERCASE_A_Z = /^[a-z]/;
+var VIEW_PREFIX = /^view\./;
</ins><span class="cx">
</span><ins>+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;
+ }
+}
+
</ins><span class="cx"> EmberHandlebars.ViewHelper = Ember.Object.create({
</span><span class="cx">
</span><del>- propertiesFromHTMLOptions: function(options, thisContext) {
</del><ins>+ propertiesFromHTMLOptions: function(options) {
</ins><span class="cx"> var hash = options.hash, data = options.data;
</span><span class="cx"> var extensions = {},
</span><span class="cx"> classes = hash['class'],
</span><span class="lines">@@ -19234,6 +28257,11 @@
</span><span class="cx"> dup = true;
</span><span class="cx"> }
</span><span class="cx">
</span><ins>+ if (hash.tag) {
+ extensions.tagName = hash.tag;
+ dup = true;
+ }
+
</ins><span class="cx"> if (classes) {
</span><span class="cx"> classes = classes.split(' ');
</span><span class="cx"> extensions.classNames = classes;
</span><span class="lines">@@ -19260,6 +28288,7 @@
</span><span class="cx"> if (dup) {
</span><span class="cx"> hash = Ember.$.extend({}, hash);
</span><span class="cx"> delete hash.id;
</span><ins>+ delete hash.tag;
</ins><span class="cx"> delete hash['class'];
</span><span class="cx"> delete hash.classBinding;
</span><span class="cx"> }
</span><span class="lines">@@ -19312,7 +28341,7 @@
</span><span class="cx"> return 'templateData.keywords.' + path;
</span><span class="cx"> } else if (Ember.isGlobalPath(path)) {
</span><span class="cx"> return null;
</span><del>- } else if (path === 'this') {
</del><ins>+ } else if (path === 'this' || path === '') {
</ins><span class="cx"> return '_parentView.context';
</span><span class="cx"> } else {
</span><span class="cx"> return '_parentView.context.' + path;
</span><span class="lines">@@ -19320,15 +28349,25 @@
</span><span class="cx"> },
</span><span class="cx">
</span><span class="cx"> helper: function(thisContext, path, options) {
</span><del>- var inverse = options.inverse,
- data = options.data,
- view = data.view,
</del><ins>+ var data = options.data,
</ins><span class="cx"> fn = options.fn,
</span><del>- hash = options.hash,
</del><span class="cx"> newView;
</span><span class="cx">
</span><ins>+ makeBindings(thisContext, options);
+
</ins><span class="cx"> if ('string' === typeof path) {
</span><del>- newView = EmberHandlebars.get(thisContext, path, options);
</del><ins>+
+ // 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);
+ }
+
</ins><span class="cx"> Ember.assert("Unable to find view at path '" + path + "'", !!newView);
</span><span class="cx"> } else {
</span><span class="cx"> newView = path;
</span><span class="lines">@@ -19338,7 +28377,7 @@
</span><span class="cx">
</span><span class="cx"> var viewOptions = this.propertiesFromHTMLOptions(options, thisContext);
</span><span class="cx"> var currentView = data.view;
</span><del>- viewOptions.templateData = options.data;
</del><ins>+ viewOptions.templateData = data;
</ins><span class="cx"> var newViewProto = newView.proto ? newView.proto() : newView;
</span><span class="cx">
</span><span class="cx"> if (fn) {
</span><span class="lines">@@ -19465,9 +28504,8 @@
</span><span class="cx"> {{/view}}
</span><span class="cx"> ```
</span><span class="cx">
</span><del>- The first argument can also be a relative path. Ember will search for the
- view class starting at the `Ember.View` of the template where `{{view}}` was
- used as the root object:
</del><ins>+ The first argument can also be a relative path accessible from the current
+ context.
</ins><span class="cx">
</span><span class="cx"> ```javascript
</span><span class="cx"> MyApp = Ember.Application.create({});
</span><span class="lines">@@ -19475,7 +28513,7 @@
</span><span class="cx"> innerViewClass: Ember.View.extend({
</span><span class="cx"> classNames: ['a-custom-view-class-as-property']
</span><span class="cx"> }),
</span><del>- template: Ember.Handlebars.compile('{{#view "innerViewClass"}} hi {{/view}}')
</del><ins>+ template: Ember.Handlebars.compile('{{#view "view.innerViewClass"}} hi {{/view}}')
</ins><span class="cx"> });
</span><span class="cx">
</span><span class="cx"> MyApp.OuterView.create().appendTo('body');
</span><span class="lines">@@ -19522,7 +28560,7 @@
</span><span class="cx"> @param {Hash} options
</span><span class="cx"> @return {String} HTML string
</span><span class="cx"> */
</span><del>-EmberHandlebars.registerHelper('view', function(path, options) {
</del><ins>+EmberHandlebars.registerHelper('view', function viewHelper(path, options) {
</ins><span class="cx"> Ember.assert("The view helper only takes a single argument", arguments.length <= 2);
</span><span class="cx">
</span><span class="cx"> // If no path is provided, treat path param as options.
</span><span class="lines">@@ -19540,8 +28578,6 @@
</span><span class="cx">
</span><span class="cx">
</span><span class="cx"> (function() {
</span><del>-/*globals Handlebars */
-
</del><span class="cx"> // TODO: Don't require all of this module
</span><span class="cx"> /**
</span><span class="cx"> @module ember
</span><span class="lines">@@ -19552,8 +28588,8 @@
</span><span class="cx">
</span><span class="cx"> /**
</span><span class="cx"> `{{collection}}` is a `Ember.Handlebars` helper for adding instances of
</span><del>- `Ember.CollectionView` to a template. See `Ember.CollectionView` for
- additional information on how a `CollectionView` functions.
</del><ins>+ `Ember.CollectionView` to a template. See [Ember.CollectionView](/api/classes/Ember.CollectionView.html)
+ for additional information on how a `CollectionView` functions.
</ins><span class="cx">
</span><span class="cx"> `{{collection}}`'s primary use is as a block helper with a `contentBinding`
</span><span class="cx"> option pointing towards an `Ember.Array`-compatible object. An `Ember.View`
</span><span class="lines">@@ -19672,7 +28708,7 @@
</span><span class="cx"> @return {String} HTML string
</span><span class="cx"> @deprecated Use `{{each}}` helper instead.
</span><span class="cx"> */
</span><del>-Ember.Handlebars.registerHelper('collection', function(path, options) {
</del><ins>+Ember.Handlebars.registerHelper('collection', function collectionHelper(path, options) {
</ins><span class="cx"> Ember.deprecate("Using the {{collection}} helper without specifying a class has been deprecated as the {{each}} helper now supports the same functionality.", path !== 'collection');
</span><span class="cx">
</span><span class="cx"> // If no path is provided, treat path param as options.
</span><span class="lines">@@ -19698,11 +28734,32 @@
</span><span class="cx"> var hash = options.hash, itemHash = {}, match;
</span><span class="cx">
</span><span class="cx"> // Extract item view class if provided else default to the standard class
</span><del>- var itemViewClass, itemViewPath = hash.itemViewClass;
- var collectionPrototype = collectionClass.proto();
</del><ins>+ var collectionPrototype = collectionClass.proto(),
+ itemViewClass;
+
+ if (hash.itemView) {
+ var 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);
+ var container = controller.container;
+ itemViewClass = container.resolve('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);
+
</ins><span class="cx"> delete hash.itemViewClass;
</span><del>- itemViewClass = itemViewPath ? handlebarsGet(collectionPrototype, itemViewPath, options) : collectionPrototype.itemViewClass;
- Ember.assert(fmt("%@ #collection: Could not find itemViewClass %@", [data.view, itemViewPath]), !!itemViewClass);
</del><ins>+ delete hash.itemView;
</ins><span class="cx">
</span><span class="cx"> // Go through options passed to the {{collection}} helper and extract options
</span><span class="cx"> // that configure item views instead of the collection itself.
</span><span class="lines">@@ -19710,7 +28767,7 @@
</span><span class="cx"> if (hash.hasOwnProperty(prop)) {
</span><span class="cx"> match = prop.match(/^item(.)(.*)$/);
</span><span class="cx">
</span><del>- if(match && prop !== 'itemController') {
</del><ins>+ if (match && prop !== 'itemController') {
</ins><span class="cx"> // Convert itemShouldFoo -> shouldFoo
</span><span class="cx"> itemHash[match[1].toLowerCase() + match[2]] = hash[prop];
</span><span class="cx"> // Delete from hash as this will end up getting passed to the
</span><span class="lines">@@ -19720,15 +28777,13 @@
</span><span class="cx"> }
</span><span class="cx"> }
</span><span class="cx">
</span><del>- var tagName = hash.tagName || collectionPrototype.tagName;
-
</del><span class="cx"> if (fn) {
</span><span class="cx"> itemHash.template = fn;
</span><span class="cx"> delete options.fn;
</span><span class="cx"> }
</span><span class="cx">
</span><span class="cx"> var emptyViewClass;
</span><del>- if (inverse && inverse !== Handlebars.VM.noop) {
</del><ins>+ if (inverse && inverse !== Ember.Handlebars.VM.noop) {
</ins><span class="cx"> emptyViewClass = get(collectionPrototype, 'emptyViewClass');
</span><span class="cx"> emptyViewClass = emptyViewClass.extend({
</span><span class="cx"> template: inverse,
</span><span class="lines">@@ -19739,12 +28794,10 @@
</span><span class="cx"> }
</span><span class="cx"> if (emptyViewClass) { hash.emptyView = emptyViewClass; }
</span><span class="cx">
</span><del>- if(!hash.keyword){
</del><ins>+ if (!hash.keyword) {
</ins><span class="cx"> itemHash._context = Ember.computed.alias('content');
</span><span class="cx"> }
</span><span class="cx">
</span><del>- var viewString = view.toString();
-
</del><span class="cx"> var viewOptions = Ember.Handlebars.ViewHelper.propertiesFromHTMLOptions({ data: data, hash: itemHash }, this);
</span><span class="cx"> hash.itemViewClass = itemViewClass.extend(viewOptions);
</span><span class="cx">
</span><span class="lines">@@ -19785,19 +28838,19 @@
</span><span class="cx"> @param {String} property
</span><span class="cx"> @return {String} HTML string
</span><span class="cx"> */
</span><del>-Ember.Handlebars.registerHelper('unbound', function(property, fn) {
</del><ins>+Ember.Handlebars.registerHelper('unbound', function unboundHelper(property, fn) {
</ins><span class="cx"> var options = arguments[arguments.length - 1], helper, context, out;
</span><span class="cx">
</span><del>- if(arguments.length > 2) {
</del><ins>+ if (arguments.length > 2) {
</ins><span class="cx"> // Unbound helper call.
</span><span class="cx"> options.data.isUnbound = true;
</span><del>- helper = Ember.Handlebars.helpers[arguments[0]] || Ember.Handlebars.helperMissing;
- out = helper.apply(this, Array.prototype.slice.call(arguments, 1));
</del><ins>+ helper = Ember.Handlebars.helpers[arguments[0]] || Ember.Handlebars.helpers.helperMissing;
+ out = helper.apply(this, Array.prototype.slice.call(arguments, 1));
</ins><span class="cx"> delete options.data.isUnbound;
</span><span class="cx"> return out;
</span><span class="cx"> }
</span><span class="cx">
</span><del>- context = (fn.contexts && fn.contexts[0]) || this;
</del><ins>+ context = (fn.contexts && fn.contexts.length) ? fn.contexts[0] : this;
</ins><span class="cx"> return handlebarsGet(context, property, fn);
</span><span class="cx"> });
</span><span class="cx">
</span><span class="lines">@@ -19812,10 +28865,10 @@
</span><span class="cx"> @submodule ember-handlebars
</span><span class="cx"> */
</span><span class="cx">
</span><del>-var handlebarsGet = Ember.Handlebars.get, normalizePath = Ember.Handlebars.normalizePath;
</del><ins>+var get = Ember.get, handlebarsGet = Ember.Handlebars.get, normalizePath = Ember.Handlebars.normalizePath;
</ins><span class="cx">
</span><span class="cx"> /**
</span><del>- `log` allows you to output the value of a value in the current rendering
</del><ins>+ `log` allows you to output the value of a variable in the current rendering
</ins><span class="cx"> context.
</span><span class="cx">
</span><span class="cx"> ```handlebars
</span><span class="lines">@@ -19826,8 +28879,8 @@
</span><span class="cx"> @for Ember.Handlebars.helpers
</span><span class="cx"> @param {String} property
</span><span class="cx"> */
</span><del>-Ember.Handlebars.registerHelper('log', function(property, options) {
- var context = (options.contexts && options.contexts[0]) || this,
</del><ins>+Ember.Handlebars.registerHelper('log', function logHelper(property, options) {
+ var context = (options.contexts && options.contexts.length) ? options.contexts[0] : this,
</ins><span class="cx"> normalized = normalizePath(context, property, options.data),
</span><span class="cx"> pathRoot = normalized.root,
</span><span class="cx"> path = normalized.path,
</span><span class="lines">@@ -19842,14 +28895,44 @@
</span><span class="cx"> {{debugger}}
</span><span class="cx"> ```
</span><span class="cx">
</span><ins>+ 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') // -> "<value of {{foo}}>"
+ ```
+
</ins><span class="cx"> @method debugger
</span><span class="cx"> @for Ember.Handlebars.helpers
</span><span class="cx"> @param {String} property
</span><span class="cx"> */
</span><del>-Ember.Handlebars.registerHelper('debugger', function() {
</del><ins>+Ember.Handlebars.registerHelper('debugger', function debuggerHelper(options) {
+
+ // These are helpful values you can inspect while debugging.
+ var templateContext = this;
+ var typeOfTemplateContext = Ember.inspect(templateContext);
+
</ins><span class="cx"> debugger;
</span><span class="cx"> });
</span><span class="cx">
</span><ins>+
+
</ins><span class="cx"> })();
</span><span class="cx">
</span><span class="cx">
</span><span class="lines">@@ -19868,11 +28951,12 @@
</span><span class="cx"> var binding;
</span><span class="cx">
</span><span class="cx"> if (itemController) {
</span><del>- var controller = Ember.ArrayController.create();
- set(controller, 'itemController', itemController);
- set(controller, 'container', get(this, 'controller.container'));
- set(controller, '_eachView', this);
- set(controller, 'target', get(this, 'controller'));
</del><ins>+ var controller = get(this, 'controller.container').lookupFactory('controller:array').create({
+ parentController: get(this, 'controller'),
+ itemController: itemController,
+ target: get(this, 'controller'),
+ _eachView: this
+ });
</ins><span class="cx">
</span><span class="cx"> this.disableContentObservers(function() {
</span><span class="cx"> set(this, 'content', controller);
</span><span class="lines">@@ -19891,11 +28975,16 @@
</span><span class="cx"> return this._super();
</span><span class="cx"> },
</span><span class="cx">
</span><ins>+ _assertArrayLike: function(content) {
+ Ember.assert("The value that #each loops over must be an Array. You passed " + content.constructor + ", but it should have been an ArrayController", !Ember.ControllerMixin.detect(content) || (content && content.isGenerated) || content instanceof Ember.ArrayController);
+ Ember.assert("The value that #each loops over must be an Array. You passed " + ((Ember.ControllerMixin.detect(content) && content.get('model') !== undefined) ? ("" + content.get('model') + " (wrapped in " + content + ")") : ("" + content)), Ember.Array.detect(content));
+ },
+
</ins><span class="cx"> disableContentObservers: function(callback) {
</span><span class="cx"> Ember.removeBeforeObserver(this, 'content', null, '_contentWillChange');
</span><span class="cx"> Ember.removeObserver(this, 'content', null, '_contentDidChange');
</span><span class="cx">
</span><del>- callback.apply(this);
</del><ins>+ callback.call(this);
</ins><span class="cx">
</span><span class="cx"> Ember.addBeforeObserver(this, 'content', null, '_contentWillChange');
</span><span class="cx"> Ember.addObserver(this, 'content', null, '_contentDidChange');
</span><span class="lines">@@ -19934,14 +29023,16 @@
</span><span class="cx"> return view;
</span><span class="cx"> },
</span><span class="cx">
</span><del>- willDestroy: function() {
</del><ins>+ destroy: function() {
+ if (!this._super()) { return; }
+
</ins><span class="cx"> var arrayController = get(this, '_arrayController');
</span><span class="cx">
</span><span class="cx"> if (arrayController) {
</span><span class="cx"> arrayController.destroy();
</span><span class="cx"> }
</span><span class="cx">
</span><del>- return this._super();
</del><ins>+ return this;
</ins><span class="cx"> }
</span><span class="cx"> });
</span><span class="cx">
</span><span class="lines">@@ -19988,6 +29079,8 @@
</span><span class="cx"> },
</span><span class="cx">
</span><span class="cx"> addArrayObservers: function() {
</span><ins>+ if (!this.content) { return; }
+
</ins><span class="cx"> this.content.addArrayObserver(this, {
</span><span class="cx"> willChange: 'contentArrayWillChange',
</span><span class="cx"> didChange: 'contentArrayDidChange'
</span><span class="lines">@@ -19995,6 +29088,8 @@
</span><span class="cx"> },
</span><span class="cx">
</span><span class="cx"> removeArrayObservers: function() {
</span><ins>+ if (!this.content) { return; }
+
</ins><span class="cx"> this.content.removeArrayObserver(this, {
</span><span class="cx"> willChange: 'contentArrayWillChange',
</span><span class="cx"> didChange: 'contentArrayDidChange'
</span><span class="lines">@@ -20012,6 +29107,8 @@
</span><span class="cx"> },
</span><span class="cx">
</span><span class="cx"> render: function() {
</span><ins>+ if (!this.content) { return; }
+
</ins><span class="cx"> var content = this.content,
</span><span class="cx"> contentLength = get(content, 'length'),
</span><span class="cx"> data = this.options.data,
</span><span class="lines">@@ -20024,12 +29121,21 @@
</span><span class="cx"> },
</span><span class="cx">
</span><span class="cx"> rerenderContainingView: function() {
</span><del>- Ember.run.scheduleOnce('render', this.containingView, 'rerender');
</del><ins>+ 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();
+ }
+ });
</ins><span class="cx"> },
</span><span class="cx">
</span><span class="cx"> destroy: function() {
</span><span class="cx"> this.removeContentObservers();
</span><del>- this.removeArrayObservers();
</del><ins>+ if (this.content) {
+ this.removeArrayObservers();
+ }
+ this.destroyed = true;
</ins><span class="cx"> }
</span><span class="cx"> };
</span><span class="cx">
</span><span class="lines">@@ -20087,7 +29193,7 @@
</span><span class="cx">
</span><span class="cx"> ```handlebars
</span><span class="cx"> {{#view App.MyView }}
</span><del>- {{each view.items itemViewClass="App.AnItemView"}}
</del><ins>+ {{each view.items itemViewClass="App.AnItemView"}}
</ins><span class="cx"> {{/view}}
</span><span class="cx"> ```
</span><span class="cx">
</span><span class="lines">@@ -20118,7 +29224,13 @@
</span><span class="cx"> <div class="ember-view">Greetings Sara</div>
</span><span class="cx"> </div>
</span><span class="cx"> ```
</span><del>-
</del><ins>+
+ 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.
+
</ins><span class="cx"> ### Representing each item with a Controller.
</span><span class="cx"> By default the controller lookup within an `{{#each}}` block will be
</span><span class="cx"> the controller of the template where the `{{#each}}` was used. If each
</span><span class="lines">@@ -20126,33 +29238,80 @@
</span><span class="cx"> `itemController` option which references a controller by lookup name.
</span><span class="cx"> Each item in the loop will be wrapped in an instance of this controller
</span><span class="cx"> and the item itself will be set to the `content` property of that controller.
</span><del>-
</del><ins>+
</ins><span class="cx"> This is useful in cases where properties of model objects need transformation
</span><span class="cx"> or synthesis for display:
</span><del>-
</del><ins>+
</ins><span class="cx"> ```javascript
</span><span class="cx"> App.DeveloperController = Ember.ObjectController.extend({
</span><del>- isAvailableForHire: function(){
</del><ins>+ isAvailableForHire: function() {
</ins><span class="cx"> return !this.get('content.isEmployed') && this.get('content.isSeekingWork');
</span><span class="cx"> }.property('isEmployed', 'isSeekingWork')
</span><span class="cx"> })
</span><span class="cx"> ```
</span><del>-
</del><ins>+
</ins><span class="cx"> ```handlebars
</span><del>- {{#each person in Developers itemController="developer"}}
</del><ins>+ {{#each person in developers itemController="developer"}}
</ins><span class="cx"> {{person.name}} {{#if person.isAvailableForHire}}Hire me!{{/if}}
</span><span class="cx"> {{/each}}
</span><span class="cx"> ```
</span><del>-
</del><ins>+
+ 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`.
+
</ins><span class="cx"> @method each
</span><span class="cx"> @for Ember.Handlebars.helpers
</span><span class="cx"> @param [name] {String} name for item (used with `in`)
</span><del>- @param path {String} path
</del><ins>+ @param [path] {String} path
</ins><span class="cx"> @param [options] {Object} Handlebars key/value pairs of options
</span><span class="cx"> @param [options.itemViewClass] {String} a path to a view class used for each item
</span><span class="cx"> @param [options.itemController] {String} name of a controller to be created for each item
</span><ins>+ @param [options.groupedRows] {boolean} enable normal item-by-item rendering when inside a `#group` helper
</ins><span class="cx"> */
</span><del>-Ember.Handlebars.registerHelper('each', function(path, options) {
</del><ins>+Ember.Handlebars.registerHelper('each', function eachHelper(path, options) {
</ins><span class="cx"> if (arguments.length === 4) {
</span><span class="cx"> 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");
</span><span class="cx">
</span><span class="lines">@@ -20165,6 +29324,11 @@
</span><span class="cx"> options.hash.keyword = keywordName;
</span><span class="cx"> }
</span><span class="cx">
</span><ins>+ if (arguments.length === 1) {
+ options = path;
+ path = 'this';
+ }
+
</ins><span class="cx"> options.hash.dataSourceBinding = path;
</span><span class="cx"> // Set up emptyView as a metamorph with no tag
</span><span class="cx"> //options.hash.emptyViewClass = Ember._MetamorphView;
</span><span class="lines">@@ -20206,6 +29370,14 @@
</span><span class="cx"> </script>
</span><span class="cx"> ```
</span><span class="cx">
</span><ins>+ ```handlebars
+ {{#if isUser}}
+ {{template "user_info"}}
+ {{else}}
+ {{template "unlogged_user_info"}}
+ {{/if}}
+ ```
+
</ins><span class="cx"> This helper looks for templates in the global `Ember.TEMPLATES` hash. If you
</span><span class="cx"> add `<script>` tags to your page with the `data-template-name` attribute set,
</span><span class="cx"> they will be compiled and placed in this hash automatically.
</span><span class="lines">@@ -20216,37 +29388,118 @@
</span><span class="cx"> Ember.TEMPLATES["my_cool_template"] = Ember.Handlebars.compile('<b>{{user}}</b>');
</span><span class="cx"> ```
</span><span class="cx">
</span><ins>+ @deprecated
</ins><span class="cx"> @method template
</span><span class="cx"> @for Ember.Handlebars.helpers
</span><span class="cx"> @param {String} templateName the template to render
</span><span class="cx"> */
</span><span class="cx">
</span><span class="cx"> Ember.Handlebars.registerHelper('template', function(name, options) {
</span><del>- var template = Ember.TEMPLATES[name];
</del><ins>+ Ember.deprecate("The `template` helper has been deprecated in favor of the `partial` helper. Please use `partial` instead, which will work the same way.");
+ return Ember.Handlebars.helpers.partial.apply(this, arguments);
+});
</ins><span class="cx">
</span><del>- Ember.assert("Unable to find template with name '"+name+"'.", !!template);
</del><ins>+})();
</ins><span class="cx">
</span><del>- Ember.TEMPLATES[name](this, { data: options.data });
</del><ins>+
+
+(function() {
+/**
+@module ember
+@submodule ember-handlebars
+*/
+
+/**
+ The `partial` helper renders another template without
+ changing the template context:
+
+ ```handlebars
+ {{foo}}
+ {{partial "nav"}}
+ ```
+
+ The above example template will render a template named
+ "_nav", which has the same context as the parent template
+ it's rendered into, so if the "_nav" template also referenced
+ `{{foo}}`, it would print the same thing as the `{{foo}}`
+ in the above example.
+
+ If a "_nav" template isn't found, the `partial` helper will
+ fall back to a template named "nav".
+
+ ## Bound template names
+
+ The parameter supplied to `partial` can also be a path
+ to a property containing a template name, e.g.:
+
+ ```handlebars
+ {{partial someTemplateName}}
+ ```
+
+ The above example will look up the value of `someTemplateName`
+ on the template context (e.g. a controller) and use that
+ value as the name of the template to render. If the resolved
+ value is falsy, nothing will be rendered. If `someTemplateName`
+ changes, the partial will be re-rendered using the new template
+ name.
+
+ ## Setting the partial's context with `with`
+
+ The `partial` helper can be used in conjunction with the `with`
+ helper to set a context that will be used by the partial:
+
+ ```handlebars
+ {{#with currentUser}}
+ {{partial "user_info"}}
+ {{/with}}
+ ```
+
+ @method partial
+ @for Ember.Handlebars.helpers
+ @param {String} partialName the name of the template to render minus the leading underscore
+*/
+
+Ember.Handlebars.registerHelper('partial', function partialHelper(name, options) {
+
+ var context = (options.contexts && options.contexts.length) ? options.contexts[0] : this;
+
+ if (options.types[0] === "ID") {
+ // Helper was passed a property path; we need to
+ // create a binding that will re-render whenever
+ // this property changes.
+ options.fn = function(context, fnOptions) {
+ var partialName = Ember.Handlebars.get(context, name, fnOptions);
+ renderPartial(context, partialName, fnOptions);
+ };
+
+ return Ember.Handlebars.bind.call(context, name, options, true, exists);
+ } else {
+ // Render the partial right into parent template.
+ renderPartial(context, name, options);
+ }
</ins><span class="cx"> });
</span><span class="cx">
</span><del>-Ember.Handlebars.registerHelper('partial', function(name, options) {
</del><ins>+function exists(value) {
+ return !Ember.isNone(value);
+}
+
+function renderPartial(context, name, options) {
</ins><span class="cx"> var nameParts = name.split("/"),
</span><span class="cx"> lastPart = nameParts[nameParts.length - 1];
</span><span class="cx">
</span><span class="cx"> nameParts[nameParts.length - 1] = "_" + lastPart;
</span><span class="cx">
</span><del>- var underscoredName = nameParts.join("/");
</del><ins>+ var view = options.data.view,
+ underscoredName = nameParts.join("/"),
+ template = view.templateForName(underscoredName),
+ deprecatedTemplate = !template && view.templateForName(name);
</ins><span class="cx">
</span><del>- var template = Ember.TEMPLATES[underscoredName],
- deprecatedTemplate = Ember.TEMPLATES[name];
-
- Ember.deprecate("You tried to render the partial " + name + ", which should be at '" + underscoredName + "', but Ember found '" + name + "'. Please use a leading underscore in your partials", template);
</del><span class="cx"> Ember.assert("Unable to find partial with name '"+name+"'.", template || deprecatedTemplate);
</span><span class="cx">
</span><span class="cx"> template = template || deprecatedTemplate;
</span><span class="cx">
</span><del>- template(this, { data: options.data });
-});
</del><ins>+ template(context, { data: options.data });
+}
</ins><span class="cx">
</span><span class="cx"> })();
</span><span class="cx">
</span><span class="lines">@@ -20261,6 +29514,10 @@
</span><span class="cx"> var get = Ember.get, set = Ember.set;
</span><span class="cx">
</span><span class="cx"> /**
</span><ins>+ `{{yield}}` denotes an area of a template that will be rendered inside
+ of another template. It has two main uses:
+
+ ### Use with `layout`
</ins><span class="cx"> When used in a Handlebars template that is assigned to an `Ember.View`
</span><span class="cx"> instance's `layout` property Ember will render the layout template first,
</span><span class="cx"> inserting the view's own rendered output at the `{{yield}}` location.
</span><span class="lines">@@ -20303,26 +29560,87 @@
</span><span class="cx"> bView.appendTo('body');
</span><span class="cx">
</span><span class="cx"> // throws
</span><del>- // Uncaught Error: assertion failed: You called yield in a template that was not a layout
</del><ins>+ // Uncaught Error: assertion failed:
+ // You called yield in a template that was not a layout
</ins><span class="cx"> ```
</span><span class="cx">
</span><ins>+ ### Use with Ember.Component
+ When designing components `{{yield}}` is used to denote where, inside the component's
+ template, an optional block passed to the component should render:
+
+ ```handlebars
+ <!-- application.hbs -->
+ {{#labeled-textfield value=someProperty}}
+ First name:
+ {{/labeled-textfield}}
+ ```
+
+ ```handlebars
+ <!-- components/labeled-textfield.hbs -->
+ <label>
+ {{yield}} {{input value=value}}
+ </label>
+ ```
+
+ Result:
+
+ ```html
+ <label>
+ First name: <input type="text" />
+ <label>
+ ```
+
</ins><span class="cx"> @method yield
</span><span class="cx"> @for Ember.Handlebars.helpers
</span><span class="cx"> @param {Hash} options
</span><span class="cx"> @return {String} HTML string
</span><span class="cx"> */
</span><del>-Ember.Handlebars.registerHelper('yield', function(options) {
- var view = options.data.view, template;
</del><ins>+Ember.Handlebars.registerHelper('yield', function yieldHelper(options) {
+ var view = options.data.view;
</ins><span class="cx">
</span><span class="cx"> while (view && !get(view, 'layout')) {
</span><del>- view = get(view, 'parentView');
</del><ins>+ if (view._contextView) {
+ view = view._contextView;
+ } else {
+ view = get(view, 'parentView');
+ }
</ins><span class="cx"> }
</span><span class="cx">
</span><span class="cx"> Ember.assert("You called yield in a template that was not a layout", !!view);
</span><span class="cx">
</span><del>- template = get(view, 'template');
</del><ins>+ view._yield(this, options);
+});
</ins><span class="cx">
</span><del>- if (template) { template(this, options); }
</del><ins>+})();
+
+
+
+(function() {
+/**
+@module ember
+@submodule ember-handlebars
+*/
+
+/**
+ `loc` looks up the string in the localized strings hash.
+ This is a convenient way to localize text. For example:
+
+ ```html
+ <script type="text/x-handlebars" data-template-name="home">
+ {{loc "welcome"}}
+ </script>
+ ```
+
+ 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);
</ins><span class="cx"> });
</span><span class="cx">
</span><span class="cx"> })();
</span><span class="lines">@@ -20350,27 +29668,13 @@
</span><span class="cx"> var set = Ember.set, get = Ember.get;
</span><span class="cx">
</span><span class="cx"> /**
</span><del>- The `Ember.Checkbox` view class renders a checkbox
- [input](https://developer.mozilla.org/en/HTML/Element/Input) element. It
- allows for binding an Ember property (`checked`) to the status of the
- checkbox.
</del><ins>+ The internal class used to create text inputs when the `{{input}}`
+ helper is used with `type` of `checkbox`.
</ins><span class="cx">
</span><del>- Example:
</del><ins>+ See [handlebars.helpers.input](/api/classes/Ember.Handlebars.helpers.html#method_input) for usage details.
</ins><span class="cx">
</span><del>- ```handlebars
- {{view Ember.Checkbox checkedBinding="receiveEmail"}}
- ```
</del><ins>+ ## Direct manipulation of `checked`
</ins><span class="cx">
</span><del>- You can add a `label` tag yourself in the template where the `Ember.Checkbox`
- is being used.
-
- ```html
- <label>
- {{view Ember.Checkbox classNames="applicaton-specific-checkbox"}}
- Some Title
- </label>
- ```
-
</del><span class="cx"> The `checked` attribute of an `Ember.Checkbox` object should always be set
</span><span class="cx"> through the Ember object or by interacting with its rendered element
</span><span class="cx"> representation via the mouse, keyboard, or touch. Updating the value of the
</span><span class="lines">@@ -20380,8 +29684,8 @@
</span><span class="cx"> ## Layout and LayoutName properties
</span><span class="cx">
</span><span class="cx"> Because HTML `input` elements are self closing `layout` and `layoutName`
</span><del>- properties will not be applied. See `Ember.View`'s layout section for more
- information.
</del><ins>+ properties will not be applied. See [Ember.View](/api/classes/Ember.View.html)'s
+ layout section for more information.
</ins><span class="cx">
</span><span class="cx"> @class Checkbox
</span><span class="cx"> @namespace Ember
</span><span class="lines">@@ -20392,17 +29696,23 @@
</span><span class="cx">
</span><span class="cx"> tagName: 'input',
</span><span class="cx">
</span><del>- attributeBindings: ['type', 'checked', 'disabled', 'tabindex'],
</del><ins>+ attributeBindings: ['type', 'checked', 'indeterminate', 'disabled', 'tabindex', 'name'],
</ins><span class="cx">
</span><span class="cx"> type: "checkbox",
</span><span class="cx"> checked: false,
</span><span class="cx"> disabled: false,
</span><ins>+ indeterminate: false,
</ins><span class="cx">
</span><span class="cx"> init: function() {
</span><span class="cx"> this._super();
</span><span class="cx"> this.on("change", this, this._updateElementValue);
</span><span class="cx"> },
</span><span class="cx">
</span><ins>+ didInsertElement: function() {
+ this._super();
+ this.get('element').indeterminate = !!this.get('indeterminate');
+ },
+
</ins><span class="cx"> _updateElementValue: function() {
</span><span class="cx"> set(this, 'checked', this.$().prop('checked'));
</span><span class="cx"> }
</span><span class="lines">@@ -20425,20 +29735,16 @@
</span><span class="cx">
</span><span class="cx"> @class TextSupport
</span><span class="cx"> @namespace Ember
</span><del>- @extends Ember.Mixin
</del><span class="cx"> @private
</span><span class="cx"> */
</span><span class="cx"> Ember.TextSupport = Ember.Mixin.create({
</span><span class="cx"> value: "",
</span><span class="cx">
</span><del>- attributeBindings: ['placeholder', 'disabled', 'maxlength', 'tabindex'],
</del><ins>+ attributeBindings: ['placeholder', 'disabled', 'maxlength', 'tabindex', 'readonly'],
</ins><span class="cx"> placeholder: null,
</span><span class="cx"> disabled: false,
</span><span class="cx"> maxlength: null,
</span><span class="cx">
</span><del>- insertNewline: Ember.K,
- cancel: Ember.K,
-
</del><span class="cx"> init: function() {
</span><span class="cx"> this._super();
</span><span class="cx"> this.on("focusOut", this, this._elementValueDidChange);
</span><span class="lines">@@ -20449,6 +29755,50 @@
</span><span class="cx"> this.on("keyUp", this, this.interpretKeyEvents);
</span><span class="cx"> },
</span><span class="cx">
</span><ins>+ /**
+ 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,
+
</ins><span class="cx"> interpretKeyEvents: function(event) {
</span><span class="cx"> var map = Ember.TextSupport.KEY_EVENTS;
</span><span class="cx"> var method = map[event.keyCode];
</span><span class="lines">@@ -20459,6 +29809,66 @@
</span><span class="cx">
</span><span class="cx"> _elementValueDidChange: function() {
</span><span class="cx"> set(this, 'value', this.$().val());
</span><ins>+ },
+
+ /**
+ 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);
</ins><span class="cx"> }
</span><span class="cx">
</span><span class="cx"> });
</span><span class="lines">@@ -20468,6 +29878,30 @@
</span><span class="cx"> 27: 'cancel'
</span><span class="cx"> };
</span><span class="cx">
</span><ins>+// 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();
+ }
+ }
+}
+
</ins><span class="cx"> })();
</span><span class="cx">
</span><span class="cx">
</span><span class="lines">@@ -20481,50 +29915,28 @@
</span><span class="cx"> var get = Ember.get, set = Ember.set;
</span><span class="cx">
</span><span class="cx"> /**
</span><del>- The `Ember.TextField` view class renders a text
- [input](https://developer.mozilla.org/en/HTML/Element/Input) element. It
- allows for binding Ember properties to the text field contents (`value`),
- live-updating as the user inputs text.
</del><span class="cx">
</span><del>- Example:
</del><ins>+ The internal class used to create text inputs when the `{{input}}`
+ helper is used with `type` of `text`.
</ins><span class="cx">
</span><del>- ```handlebars
- {{view Ember.TextField valueBinding="firstName"}}
- ```
</del><ins>+ See [Handlebars.helpers.input](/api/classes/Ember.Handlebars.helpers.html#method_input) for usage details.
</ins><span class="cx">
</span><span class="cx"> ## Layout and LayoutName properties
</span><span class="cx">
</span><span class="cx"> Because HTML `input` elements are self closing `layout` and `layoutName`
</span><del>- properties will not be applied. See `Ember.View`'s layout section for more
- information.
</del><ins>+ properties will not be applied. See [Ember.View](/api/classes/Ember.View.html)'s
+ layout section for more information.
</ins><span class="cx">
</span><del>- ## HTML Attributes
-
- By default `Ember.TextField` provides support for `type`, `value`, `size`,
- `placeholder`, `disabled`, `maxlength` and `tabindex` attributes on a
- test field. If you need to support more attributes have a look at the
- `attributeBindings` property in `Ember.View`'s HTML Attributes section.
-
- To globally add support for additional attributes you can reopen
- `Ember.TextField` or `Ember.TextSupport`.
-
- ```javascript
- Ember.TextSupport.reopen({
- attributeBindings: ["required"]
- })
- ```
-
</del><span class="cx"> @class TextField
</span><span class="cx"> @namespace Ember
</span><del>- @extends Ember.View
</del><ins>+ @extends Ember.Component
</ins><span class="cx"> @uses Ember.TextSupport
</span><span class="cx"> */
</span><del>-Ember.TextField = Ember.View.extend(Ember.TextSupport,
- /** @scope Ember.TextField.prototype */ {
</del><ins>+Ember.TextField = Ember.Component.extend(Ember.TextSupport, {
</ins><span class="cx">
</span><span class="cx"> classNames: ['ember-text-field'],
</span><span class="cx"> tagName: "input",
</span><del>- attributeBindings: ['type', 'value', 'size', 'pattern'],
</del><ins>+ attributeBindings: ['type', 'value', 'size', 'pattern', 'name'],
</ins><span class="cx">
</span><span class="cx"> /**
</span><span class="cx"> The `value` attribute of the input element. As the user inputs text, this
</span><span class="lines">@@ -20561,50 +29973,7 @@
</span><span class="cx"> @type String
</span><span class="cx"> @default null
</span><span class="cx"> */
</span><del>- pattern: null,
-
- /**
- 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,
-
- /**
- 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,
-
- insertNewline: function(event) {
- var controller = get(this, 'controller'),
- action = get(this, 'action');
-
- if (action) {
- controller.send(action, get(this, 'value'), this);
-
- if (!get(this, 'bubbles')) {
- event.stopPropagation();
- }
- }
- }
</del><ins>+ pattern: null
</ins><span class="cx"> });
</span><span class="cx">
</span><span class="cx"> })();
</span><span class="lines">@@ -20612,14 +29981,14 @@
</span><span class="cx">
</span><span class="cx">
</span><span class="cx"> (function() {
</span><del>-/**
</del><ins>+/*
</ins><span class="cx"> @module ember
</span><span class="cx"> @submodule ember-handlebars
</span><span class="cx"> */
</span><span class="cx">
</span><span class="cx"> var get = Ember.get, set = Ember.set;
</span><span class="cx">
</span><del>-/**
</del><ins>+/*
</ins><span class="cx"> @class Button
</span><span class="cx"> @namespace Ember
</span><span class="cx"> @extends Ember.View
</span><span class="lines">@@ -20636,7 +30005,7 @@
</span><span class="cx">
</span><span class="cx"> attributeBindings: ['type', 'disabled', 'href', 'tabindex'],
</span><span class="cx">
</span><del>- /**
</del><ins>+ /*
</ins><span class="cx"> @private
</span><span class="cx">
</span><span class="cx"> Overrides `TargetActionSupport`'s `targetObject` computed
</span><span class="lines">@@ -20748,54 +30117,38 @@
</span><span class="cx"> var get = Ember.get, set = Ember.set;
</span><span class="cx">
</span><span class="cx"> /**
</span><del>- The `Ember.TextArea` view class renders a
- [textarea](https://developer.mozilla.org/en/HTML/Element/textarea) element.
- It allows for binding Ember properties to the text area contents (`value`),
- live-updating as the user inputs text.
</del><ins>+ The internal class used to create textarea element when the `{{textarea}}`
+ helper is used.
</ins><span class="cx">
</span><ins>+ See [handlebars.helpers.textarea](/api/classes/Ember.Handlebars.helpers.html#method_textarea) for usage details.
+
</ins><span class="cx"> ## Layout and LayoutName properties
</span><span class="cx">
</span><span class="cx"> Because HTML `textarea` elements do not contain inner HTML the `layout` and
</span><del>- `layoutName` properties will not be applied. See `Ember.View`'s layout
- section for more information.
</del><ins>+ `layoutName` properties will not be applied. See [Ember.View](/api/classes/Ember.View.html)'s
+ layout section for more information.
</ins><span class="cx">
</span><del>- ## HTML Attributes
-
- By default `Ember.TextArea` provides support for `rows`, `cols`,
- `placeholder`, `disabled`, `maxlength` and `tabindex` attributes on a
- textarea. If you need to support more attributes have a look at the
- `attributeBindings` property in `Ember.View`'s HTML Attributes section.
-
- To globally add support for additional attributes you can reopen
- `Ember.TextArea` or `Ember.TextSupport`.
-
- ```javascript
- Ember.TextSupport.reopen({
- attributeBindings: ["required"]
- })
- ```
-
</del><span class="cx"> @class TextArea
</span><span class="cx"> @namespace Ember
</span><del>- @extends Ember.View
</del><ins>+ @extends Ember.Component
</ins><span class="cx"> @uses Ember.TextSupport
</span><span class="cx"> */
</span><del>-Ember.TextArea = Ember.View.extend(Ember.TextSupport, {
</del><ins>+Ember.TextArea = Ember.Component.extend(Ember.TextSupport, {
</ins><span class="cx"> classNames: ['ember-text-area'],
</span><span class="cx">
</span><span class="cx"> tagName: "textarea",
</span><del>- attributeBindings: ['rows', 'cols'],
</del><ins>+ attributeBindings: ['rows', 'cols', 'name'],
</ins><span class="cx"> rows: null,
</span><span class="cx"> cols: null,
</span><span class="cx">
</span><del>- _updateElementValue: Ember.observer(function() {
</del><ins>+ _updateElementValue: Ember.observer('value', function() {
</ins><span class="cx"> // We do this check so cursor position doesn't get affected in IE
</span><span class="cx"> var value = get(this, 'value'),
</span><span class="cx"> $el = this.$();
</span><span class="cx"> if ($el && value !== $el.val()) {
</span><span class="cx"> $el.val(value);
</span><span class="cx"> }
</span><del>- }, 'value'),
</del><ins>+ }),
</ins><span class="cx">
</span><span class="cx"> init: function() {
</span><span class="cx"> this._super();
</span><span class="lines">@@ -20820,10 +30173,72 @@
</span><span class="cx"> get = Ember.get,
</span><span class="cx"> indexOf = Ember.EnumerableUtils.indexOf,
</span><span class="cx"> indexesOf = Ember.EnumerableUtils.indexesOf,
</span><ins>+ forEach = Ember.EnumerableUtils.forEach,
</ins><span class="cx"> replace = Ember.EnumerableUtils.replace,
</span><span class="cx"> isArray = Ember.isArray,
</span><span class="cx"> precompileTemplate = Ember.Handlebars.compile;
</span><span class="cx">
</span><ins>+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'
+});
+
</ins><span class="cx"> /**
</span><span class="cx"> The `Ember.Select` view class renders a
</span><span class="cx"> [select](https://developer.mozilla.org/en/HTML/Element/select) HTML element,
</span><span class="lines">@@ -20834,7 +30249,7 @@
</span><span class="cx"> `content` property. The underlying data object of the selected `<option>` is
</span><span class="cx"> stored in the `Element.Select`'s `value` property.
</span><span class="cx">
</span><del>- ### `content` as an array of Strings
</del><ins>+ ## The Content Property (array of strings)
</ins><span class="cx">
</span><span class="cx"> The simplest version of an `Ember.Select` takes an array of strings as its
</span><span class="cx"> `content` property. The string will be used as both the `value` property and
</span><span class="lines">@@ -20843,11 +30258,13 @@
</span><span class="cx"> Example:
</span><span class="cx">
</span><span class="cx"> ```javascript
</span><del>- App.names = ["Yehuda", "Tom"];
</del><ins>+ App.ApplicationController = Ember.Controller.extend({
+ names: ["Yehuda", "Tom"]
+ });
</ins><span class="cx"> ```
</span><span class="cx">
</span><span class="cx"> ```handlebars
</span><del>- {{view Ember.Select contentBinding="App.names"}}
</del><ins>+ {{view Ember.Select content=names}}
</ins><span class="cx"> ```
</span><span class="cx">
</span><span class="cx"> Would result in the following HTML:
</span><span class="lines">@@ -20860,19 +30277,19 @@
</span><span class="cx"> ```
</span><span class="cx">
</span><span class="cx"> You can control which `<option>` is selected through the `Ember.Select`'s
</span><del>- `value` property directly or as a binding:
</del><ins>+ `value` property:
</ins><span class="cx">
</span><span class="cx"> ```javascript
</span><del>- App.names = Ember.Object.create({
- selected: 'Tom',
- content: ["Yehuda", "Tom"]
</del><ins>+ App.ApplicationController = Ember.Controller.extend({
+ selectedName: 'Tom',
+ names: ["Yehuda", "Tom"]
</ins><span class="cx"> });
</span><span class="cx"> ```
</span><span class="cx">
</span><span class="cx"> ```handlebars
</span><span class="cx"> {{view Ember.Select
</span><del>- contentBinding="App.names.content"
- valueBinding="App.names.selected"
</del><ins>+ content=names
+ value=selectedName
</ins><span class="cx"> }}
</span><span class="cx"> ```
</span><span class="cx">
</span><span class="lines">@@ -20886,9 +30303,9 @@
</span><span class="cx"> ```
</span><span class="cx">
</span><span class="cx"> A user interacting with the rendered `<select>` to choose "Yehuda" would
</span><del>- update the value of `App.names.selected` to "Yehuda".
</del><ins>+ update the value of `selectedName` to "Yehuda".
</ins><span class="cx">
</span><del>- ### `content` as an Array of Objects
</del><ins>+ ## The Content Property (array of Objects)
</ins><span class="cx">
</span><span class="cx"> An `Ember.Select` can also take an array of JavaScript or Ember objects as
</span><span class="cx"> its `content` property.
</span><span class="lines">@@ -20903,15 +30320,17 @@
</span><span class="cx"> element's text. Both paths must reference each object itself as `content`:
</span><span class="cx">
</span><span class="cx"> ```javascript
</span><del>- App.programmers = [
- Ember.Object.create({firstName: "Yehuda", id: 1}),
- Ember.Object.create({firstName: "Tom", id: 2})
- ];
</del><ins>+ App.ApplicationController = Ember.Controller.extend({
+ programmers: [
+ {firstName: "Yehuda", id: 1},
+ {firstName: "Tom", id: 2}
+ ]
+ });
</ins><span class="cx"> ```
</span><span class="cx">
</span><span class="cx"> ```handlebars
</span><span class="cx"> {{view Ember.Select
</span><del>- contentBinding="App.programmers"
</del><ins>+ content=programmers
</ins><span class="cx"> optionValuePath="content.id"
</span><span class="cx"> optionLabelPath="content.firstName"}}
</span><span class="cx"> ```
</span><span class="lines">@@ -20920,97 +30339,94 @@
</span><span class="cx">
</span><span class="cx"> ```html
</span><span class="cx"> <select class="ember-select">
</span><del>- <option value>Please Select</option>
</del><span class="cx"> <option value="1">Yehuda</option>
</span><span class="cx"> <option value="2">Tom</option>
</span><span class="cx"> </select>
</span><span class="cx"> ```
</span><span class="cx">
</span><span class="cx"> The `value` attribute of the selected `<option>` within an `Ember.Select`
</span><del>- can be bound to a property on another object by providing a
- `valueBinding` option:
</del><ins>+ can be bound to a property on another object:
</ins><span class="cx">
</span><span class="cx"> ```javascript
</span><del>- App.programmers = [
- Ember.Object.create({firstName: "Yehuda", id: 1}),
- Ember.Object.create({firstName: "Tom", id: 2})
- ];
-
- App.currentProgrammer = Ember.Object.create({
- id: 2
</del><ins>+ App.ApplicationController = Ember.Controller.extend({
+ programmers: [
+ {firstName: "Yehuda", id: 1},
+ {firstName: "Tom", id: 2}
+ ],
+ currentProgrammer: {
+ id: 2
+ }
</ins><span class="cx"> });
</span><span class="cx"> ```
</span><span class="cx">
</span><span class="cx"> ```handlebars
</span><span class="cx"> {{view Ember.Select
</span><del>- contentBinding="App.programmers"
</del><ins>+ content=programmers
</ins><span class="cx"> optionValuePath="content.id"
</span><span class="cx"> optionLabelPath="content.firstName"
</span><del>- valueBinding="App.currentProgrammer.id"}}
</del><ins>+ value=currentProgrammer.id}}
</ins><span class="cx"> ```
</span><span class="cx">
</span><span class="cx"> Would result in the following HTML with a selected option:
</span><span class="cx">
</span><span class="cx"> ```html
</span><span class="cx"> <select class="ember-select">
</span><del>- <option value>Please Select</option>
</del><span class="cx"> <option value="1">Yehuda</option>
</span><span class="cx"> <option value="2" selected="selected">Tom</option>
</span><span class="cx"> </select>
</span><span class="cx"> ```
</span><span class="cx">
</span><span class="cx"> Interacting with the rendered element by selecting the first option
</span><del>- ('Yehuda') will update the `id` value of `App.currentProgrammer`
</del><ins>+ ('Yehuda') will update the `id` of `currentProgrammer`
</ins><span class="cx"> to match the `value` property of the newly selected `<option>`.
</span><span class="cx">
</span><span class="cx"> Alternatively, you can control selection through the underlying objects
</span><del>- used to render each object providing a `selectionBinding`. When the selected
- `<option>` is changed, the property path provided to `selectionBinding`
</del><ins>+ used to render each object by binding the `selection` option. When the selected
+ `<option>` is changed, the property path provided to `selection`
</ins><span class="cx"> will be updated to match the content object of the rendered `<option>`
</span><span class="cx"> element:
</span><span class="cx">
</span><span class="cx"> ```javascript
</span><del>- App.controller = Ember.Object.create({
</del><ins>+ App.ApplicationController = Ember.Controller.extend({
</ins><span class="cx"> selectedPerson: null,
</span><del>- content: [
- Ember.Object.create({firstName: "Yehuda", id: 1}),
- Ember.Object.create({firstName: "Tom", id: 2})
</del><ins>+ programmers: [
+ {firstName: "Yehuda", id: 1},
+ {firstName: "Tom", id: 2}
</ins><span class="cx"> ]
</span><span class="cx"> });
</span><span class="cx"> ```
</span><span class="cx">
</span><span class="cx"> ```handlebars
</span><span class="cx"> {{view Ember.Select
</span><del>- contentBinding="App.controller.content"
</del><ins>+ content=programmers
</ins><span class="cx"> optionValuePath="content.id"
</span><span class="cx"> optionLabelPath="content.firstName"
</span><del>- selectionBinding="App.controller.selectedPerson"}}
</del><ins>+ selection=selectedPerson}}
</ins><span class="cx"> ```
</span><span class="cx">
</span><span class="cx"> Would result in the following HTML with a selected option:
</span><span class="cx">
</span><span class="cx"> ```html
</span><span class="cx"> <select class="ember-select">
</span><del>- <option value>Please Select</option>
</del><span class="cx"> <option value="1">Yehuda</option>
</span><span class="cx"> <option value="2" selected="selected">Tom</option>
</span><span class="cx"> </select>
</span><span class="cx"> ```
</span><span class="cx">
</span><span class="cx"> Interacting with the rendered element by selecting the first option
</span><del>- ('Yehuda') will update the `selectedPerson` value of `App.controller`
- to match the content object of the newly selected `<option>`. In this
- case it is the first object in the `App.content.content`
</del><ins>+ ('Yehuda') will update the `selectedPerson` to match the object of
+ the newly selected `<option>`. In this case it is the first object
+ in the `programmers`
</ins><span class="cx">
</span><del>- ### Supplying a Prompt
</del><ins>+ ## Supplying a Prompt
</ins><span class="cx">
</span><span class="cx"> A `null` value for the `Ember.Select`'s `value` or `selection` property
</span><span class="cx"> results in there being no `<option>` with a `selected` attribute:
</span><span class="cx">
</span><span class="cx"> ```javascript
</span><del>- App.controller = Ember.Object.create({
- selected: null,
- content: [
</del><ins>+ App.ApplicationController = Ember.Controller.extend({
+ selectedProgrammer: null,
+ programmers: [
</ins><span class="cx"> "Yehuda",
</span><span class="cx"> "Tom"
</span><span class="cx"> ]
</span><span class="lines">@@ -21019,8 +30435,8 @@
</span><span class="cx">
</span><span class="cx"> ``` handlebars
</span><span class="cx"> {{view Ember.Select
</span><del>- contentBinding="App.controller.content"
- valueBinding="App.controller.selected"
</del><ins>+ content=programmers
+ value=selectedProgrammer
</ins><span class="cx"> }}
</span><span class="cx"> ```
</span><span class="cx">
</span><span class="lines">@@ -21033,16 +30449,16 @@
</span><span class="cx"> </select>
</span><span class="cx"> ```
</span><span class="cx">
</span><del>- Although `App.controller.selected` is `null` and no `<option>`
</del><ins>+ Although `selectedProgrammer` is `null` and no `<option>`
</ins><span class="cx"> has a `selected` attribute the rendered HTML will display the
</span><span class="cx"> first item as though it were selected. You can supply a string
</span><span class="cx"> value for the `Ember.Select` to display when there is no selection
</span><span class="cx"> with the `prompt` option:
</span><span class="cx">
</span><span class="cx"> ```javascript
</span><del>- App.controller = Ember.Object.create({
- selected: null,
- content: [
</del><ins>+ App.ApplicationController = Ember.Controller.extend({
+ selectedProgrammer: null,
+ programmers: [
</ins><span class="cx"> "Yehuda",
</span><span class="cx"> "Tom"
</span><span class="cx"> ]
</span><span class="lines">@@ -21051,8 +30467,8 @@
</span><span class="cx">
</span><span class="cx"> ```handlebars
</span><span class="cx"> {{view Ember.Select
</span><del>- contentBinding="App.controller.content"
- valueBinding="App.controller.selected"
</del><ins>+ content=programmers
+ value=selectedProgrammer
</ins><span class="cx"> prompt="Please select a name"
</span><span class="cx"> }}
</span><span class="cx"> ```
</span><span class="lines">@@ -21071,45 +30487,77 @@
</span><span class="cx"> @namespace Ember
</span><span class="cx"> @extends Ember.View
</span><span class="cx"> */
</span><del>-Ember.Select = Ember.View.extend(
- /** @scope Ember.Select.prototype */ {
-
</del><ins>+Ember.Select = Ember.View.extend({
</ins><span class="cx"> tagName: 'select',
</span><span class="cx"> classNames: ['ember-select'],
</span><span class="cx"> defaultTemplate: Ember.Handlebars.template(function anonymous(Handlebars,depth0,helpers,partials,data) {
</span><del>-this.compilerInfo = [2,'>= 1.0.0-rc.3'];
-helpers = helpers || Ember.Handlebars.helpers; data = data || {};
- var buffer = '', stack1, hashTypes, escapeExpression=this.escapeExpression, self=this;
</del><ins>+this.compilerInfo = [4,'>= 1.0.0'];
+helpers = this.merge(helpers, Ember.Handlebars.helpers); data = data || {};
+ var buffer = '', stack1, hashTypes, hashContexts, escapeExpression=this.escapeExpression, self=this;
</ins><span class="cx">
</span><span class="cx"> function program1(depth0,data) {
</span><span class="cx">
</span><del>- var buffer = '', hashTypes;
</del><ins>+ var buffer = '', stack1, hashTypes, hashContexts;
</ins><span class="cx"> data.buffer.push("<option value=\"\">");
</span><span class="cx"> hashTypes = {};
</span><del>- data.buffer.push(escapeExpression(helpers._triageMustache.call(depth0, "view.prompt", {hash:{},contexts:[depth0],types:["ID"],hashTypes:hashTypes,data:data})));
</del><ins>+ hashContexts = {};
+ stack1 = helpers._triageMustache.call(depth0, "view.prompt", {hash:{},contexts:[depth0],types:["ID"],hashContexts:hashContexts,hashTypes:hashTypes,data:data});
+ if(stack1 || stack1 === 0) { data.buffer.push(stack1); }
</ins><span class="cx"> data.buffer.push("</option>");
</span><span class="cx"> return buffer;
</span><span class="cx"> }
</span><span class="cx">
</span><span class="cx"> function program3(depth0,data) {
</span><span class="cx">
</span><del>- var hashTypes;
- hashTypes = {'contentBinding': "STRING"};
- data.buffer.push(escapeExpression(helpers.view.call(depth0, "Ember.SelectOption", {hash:{
- 'contentBinding': ("this")
- },contexts:[depth0],types:["ID"],hashTypes:hashTypes,data:data})));
</del><ins>+ var stack1, hashTypes, hashContexts;
+ hashTypes = {};
+ hashContexts = {};
+ stack1 = helpers.each.call(depth0, "view.groupedContent", {hash:{},inverse:self.noop,fn:self.program(4, program4, data),contexts:[depth0],types:["ID"],hashContexts:hashContexts,hashTypes:hashTypes,data:data});
+ if(stack1 || stack1 === 0) { data.buffer.push(stack1); }
+ else { data.buffer.push(''); }
</ins><span class="cx"> }
</span><ins>+function program4(depth0,data) {
+
+ var hashContexts, hashTypes;
+ hashContexts = {'content': depth0,'label': depth0};
+ hashTypes = {'content': "ID",'label': "ID"};
+ data.buffer.push(escapeExpression(helpers.view.call(depth0, "view.groupView", {hash:{
+ 'content': ("content"),
+ 'label': ("label")
+ },contexts:[depth0],types:["ID"],hashContexts:hashContexts,hashTypes:hashTypes,data:data})));
+ }
</ins><span class="cx">
</span><ins>+function program6(depth0,data) {
+
+ var stack1, hashTypes, hashContexts;
</ins><span class="cx"> hashTypes = {};
</span><del>- stack1 = helpers['if'].call(depth0, "view.prompt", {hash:{},inverse:self.noop,fn:self.program(1, program1, data),contexts:[depth0],types:["ID"],hashTypes:hashTypes,data:data});
</del><ins>+ hashContexts = {};
+ stack1 = helpers.each.call(depth0, "view.content", {hash:{},inverse:self.noop,fn:self.program(7, program7, data),contexts:[depth0],types:["ID"],hashContexts:hashContexts,hashTypes:hashTypes,data:data});
</ins><span class="cx"> if(stack1 || stack1 === 0) { data.buffer.push(stack1); }
</span><ins>+ else { data.buffer.push(''); }
+ }
+function program7(depth0,data) {
+
+ var hashContexts, hashTypes;
+ hashContexts = {'content': depth0};
+ hashTypes = {'content': "ID"};
+ data.buffer.push(escapeExpression(helpers.view.call(depth0, "view.optionView", {hash:{
+ 'content': ("")
+ },contexts:[depth0],types:["ID"],hashContexts:hashContexts,hashTypes:hashTypes,data:data})));
+ }
+
</ins><span class="cx"> hashTypes = {};
</span><del>- stack1 = helpers.each.call(depth0, "view.content", {hash:{},inverse:self.noop,fn:self.program(3, program3, data),contexts:[depth0],types:["ID"],hashTypes:hashTypes,data:data});
</del><ins>+ hashContexts = {};
+ stack1 = helpers['if'].call(depth0, "view.prompt", {hash:{},inverse:self.noop,fn:self.program(1, program1, data),contexts:[depth0],types:["ID"],hashContexts:hashContexts,hashTypes:hashTypes,data:data});
</ins><span class="cx"> if(stack1 || stack1 === 0) { data.buffer.push(stack1); }
</span><ins>+ hashTypes = {};
+ hashContexts = {};
+ stack1 = helpers['if'].call(depth0, "view.optionGroupPath", {hash:{},inverse:self.program(6, program6, data),fn:self.program(3, program3, data),contexts:[depth0],types:["ID"],hashContexts:hashContexts,hashTypes:hashTypes,data:data});
+ if(stack1 || stack1 === 0) { data.buffer.push(stack1); }
</ins><span class="cx"> return buffer;
</span><span class="cx">
</span><span class="cx"> }),
</span><del>- attributeBindings: ['multiple', 'disabled', 'tabindex'],
</del><ins>+ attributeBindings: ['multiple', 'disabled', 'tabindex', 'name'],
</ins><span class="cx">
</span><span class="cx"> /**
</span><span class="cx"> The `multiple` attribute of the select element. Indicates whether multiple
</span><span class="lines">@@ -21121,6 +30569,14 @@
</span><span class="cx"> */
</span><span class="cx"> multiple: false,
</span><span class="cx">
</span><ins>+ /**
+ The `disabled` attribute of the select element. Indicates whether
+ the element is disabled from interactions.
+
+ @property disabled
+ @type Boolean
+ @default false
+ */
</ins><span class="cx"> disabled: false,
</span><span class="cx">
</span><span class="cx"> /**
</span><span class="lines">@@ -21187,7 +30643,7 @@
</span><span class="cx"> prompt: null,
</span><span class="cx">
</span><span class="cx"> /**
</span><del>- The path of the option labels. See `content`.
</del><ins>+ The path of the option labels. See [content](/api/classes/Ember.Select.html#property_content).
</ins><span class="cx">
</span><span class="cx"> @property optionLabelPath
</span><span class="cx"> @type String
</span><span class="lines">@@ -21196,7 +30652,7 @@
</span><span class="cx"> optionLabelPath: 'content',
</span><span class="cx">
</span><span class="cx"> /**
</span><del>- The path of the option values. See `content`.
</del><ins>+ The path of the option values. See [content](/api/classes/Ember.Select.html#property_content).
</ins><span class="cx">
</span><span class="cx"> @property optionValuePath
</span><span class="cx"> @type String
</span><span class="lines">@@ -21204,6 +30660,55 @@
</span><span class="cx"> */
</span><span class="cx"> optionValuePath: 'content',
</span><span class="cx">
</span><ins>+ /**
+ 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,
+
</ins><span class="cx"> _change: function() {
</span><span class="cx"> if (get(this, 'multiple')) {
</span><span class="cx"> this._changeMultiple();
</span><span class="lines">@@ -21212,7 +30717,7 @@
</span><span class="cx"> }
</span><span class="cx"> },
</span><span class="cx">
</span><del>- selectionDidChange: Ember.observer(function() {
</del><ins>+ selectionDidChange: Ember.observer('selection.@each', function() {
</ins><span class="cx"> var selection = get(this, 'selection');
</span><span class="cx"> if (get(this, 'multiple')) {
</span><span class="cx"> if (!isArray(selection)) {
</span><span class="lines">@@ -21223,9 +30728,9 @@
</span><span class="cx"> } else {
</span><span class="cx"> this._selectionDidChangeSingle();
</span><span class="cx"> }
</span><del>- }, 'selection.@each'),
</del><ins>+ }),
</ins><span class="cx">
</span><del>- valueDidChange: Ember.observer(function() {
</del><ins>+ valueDidChange: Ember.observer('value', function() {
</ins><span class="cx"> var content = get(this, 'content'),
</span><span class="cx"> value = get(this, 'value'),
</span><span class="cx"> valuePath = get(this, 'optionValuePath').replace(/^content\.?/, ''),
</span><span class="lines">@@ -21233,21 +30738,21 @@
</span><span class="cx"> selection;
</span><span class="cx">
</span><span class="cx"> if (value !== selectedValue) {
</span><del>- selection = content.find(function(obj) {
</del><ins>+ selection = content ? content.find(function(obj) {
</ins><span class="cx"> return value === (valuePath ? get(obj, valuePath) : obj);
</span><del>- });
</del><ins>+ }) : null;
</ins><span class="cx">
</span><span class="cx"> this.set('selection', selection);
</span><span class="cx"> }
</span><del>- }, 'value'),
</del><ins>+ }),
</ins><span class="cx">
</span><span class="cx">
</span><span class="cx"> _triggerChange: function() {
</span><span class="cx"> var selection = get(this, 'selection');
</span><span class="cx"> var value = get(this, 'value');
</span><span class="cx">
</span><del>- if (selection) { this.selectionDidChange(); }
- if (value) { this.valueDidChange(); }
</del><ins>+ if (!Ember.isNone(selection)) { this.selectionDidChange(); }
+ if (!Ember.isNone(value)) { this.valueDidChange(); }
</ins><span class="cx">
</span><span class="cx"> this._change();
</span><span class="cx"> },
</span><span class="lines">@@ -21257,7 +30762,7 @@
</span><span class="cx"> content = get(this, 'content'),
</span><span class="cx"> prompt = get(this, 'prompt');
</span><span class="cx">
</span><del>- if (!get(content, 'length')) { return; }
</del><ins>+ if (!content || !get(content, 'length')) { return; }
</ins><span class="cx"> if (prompt && selectedIndex === 0) { set(this, 'selection', null); return; }
</span><span class="cx">
</span><span class="cx"> if (prompt) { selectedIndex -= 1; }
</span><span class="lines">@@ -21272,9 +30777,9 @@
</span><span class="cx"> content = get(this, 'content'),
</span><span class="cx"> selection = get(this, 'selection');
</span><span class="cx">
</span><del>- if (!content){ return; }
</del><ins>+ if (!content) { return; }
</ins><span class="cx"> if (options) {
</span><del>- var selectedIndexes = options.map(function(){
</del><ins>+ var selectedIndexes = options.map(function() {
</ins><span class="cx"> return this.index - offset;
</span><span class="cx"> }).toArray();
</span><span class="cx"> var newSelection = content.objectsAt(selectedIndexes);
</span><span class="lines">@@ -21324,61 +30829,364 @@
</span><span class="cx"> }
</span><span class="cx"> });
</span><span class="cx">
</span><del>-Ember.SelectOption = Ember.View.extend({
- tagName: 'option',
- attributeBindings: ['value', 'selected'],
</del><ins>+})();
</ins><span class="cx">
</span><del>- defaultTemplate: function(context, options) {
- options = { data: options.data, hash: {} };
- Ember.Handlebars.helpers.bind.call(context, "view.label", options);
- },
</del><span class="cx">
</span><del>- init: function() {
- this.labelPathDidChange();
- this.valuePathDidChange();
</del><span class="cx">
</span><del>- this._super();
- },
</del><ins>+(function() {
+/**
+@module ember
+@submodule ember-handlebars-compiler
+*/
</ins><span class="cx">
</span><del>- 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').volatile(),
</del><ins>+/**
</ins><span class="cx">
</span><del>- labelPathDidChange: Ember.observer(function() {
- var labelPath = get(this, 'parentView.optionLabelPath');
</del><ins>+ The `{{input}}` helper inserts an HTML `<input>` 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.
</ins><span class="cx">
</span><del>- if (!labelPath) { return; }
</del><ins>+ ## 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:
</ins><span class="cx">
</span><del>- Ember.defineProperty(this, 'label', Ember.computed(function() {
- return get(this, labelPath);
- }).property(labelPath));
- }, 'parentView.optionLabelPath'),
</del><ins>+* `value`
+* `size`
+* `name`
+* `pattern`
+* `placeholder`
+* `disabled`
+* `maxlength`
+* `tabindex`
</ins><span class="cx">
</span><del>- valuePathDidChange: Ember.observer(function() {
- var valuePath = get(this, 'parentView.optionValuePath');
</del><span class="cx">
</span><del>- if (!valuePath) { return; }
</del><ins>+ 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).
</ins><span class="cx">
</span><del>- Ember.defineProperty(this, 'value', Ember.computed(function() {
- return get(this, valuePath);
- }).property(valuePath));
- }, 'parentView.optionValuePath')
</del><ins>+ ## Unbound:
+
+ ```handlebars
+ {{input value="http://www.facebook.com"}}
+ ```
+
+
+ ```html
+ <input type="text" value="http://www.facebook.com"/>
+ ```
+
+ ## Bound:
+
+ ```javascript
+ App.ApplicationController = Ember.Controller.extend({
+ firstName: "Stanley",
+ entryNotAllowed: true
+ });
+ ```
+
+
+ ```handlebars
+ {{input type="text" value=firstName disabled=entryNotAllowed size="50"}}
+ ```
+
+
+ ```html
+ <input type="text" value="Stanley" disabled="disabled" size="50"/>
+ ```
+
+ ## 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`
+
+
+ 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
+ <input type="checkbox" name="isAdmin" />
+ ```
+
+ ## Bound:
+
+ ```javascript
+ App.ApplicationController = Ember.Controller.extend({
+ isAdmin: true
+ });
+ ```
+
+
+ ```handlebars
+ {{input type="checkbox" checked=isAdmin }}
+ ```
+
+
+ ```html
+ <input type="checkbox" checked="checked" />
+ ```
+
+ ## 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') {
+ 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);
+ }
</ins><span class="cx"> });
</span><span class="cx">
</span><ins>+/**
+ `{{textarea}}` inserts a new instance of `<textarea>` tag into the template.
+ The attributes of `{{textarea}}` match those of the native HTML tags as
+ closely as possible.
+
+ The following HTML attributes can be set:
+
+ * `value`
+ * `name`
+ * `rows`
+ * `cols`
+ * `placeholder`
+ * `disabled`
+ * `maxlength`
+ * `tabindex`
+
+ When set to a quoted string, these value 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
+ {{textarea value="Lots of static text that ISN'T bound"}}
+ ```
+
+ Would result in the following HTML:
+
+ ```html
+ <textarea class="ember-text-area">
+ Lots of static text that ISN'T bound
+ </textarea>
+ ```
+
+ 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
+ <textarea class="ember-text-area">
+ Lots of text that IS bound
+ </textarea>
+ ```
+
+ 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}}
+
+ <div>
+ {{outputWrittenWords}}
+ </div>
+ ```
+
+ Would result in the following HTML:
+
+ ```html
+ <textarea class="ember-text-area">
+ Lots of text that IS bound
+ </textarea>
+
+ <-- the following div will be updated in real time as you type -->
+
+ <div>
+ Lots of text that IS bound
+ </div>
+ ```
+
+ 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
+ <textarea id="ember1" class="ember-text-area">
+ Lots of text that IS bound
+ </textarea>
+
+ <-- both updated in real time -->
+
+ <textarea id="ember2" class="ember-text-area">
+ Lots of text that IS bound
+ </textarea>
+ ```
+
+ ## 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);
+});
+
</ins><span class="cx"> })();
</span><span class="cx">
</span><span class="cx">
</span><span class="cx">
</span><span class="cx"> (function() {
</span><ins>+Ember.ComponentLookup = Ember.Object.extend({
+ lookupFactory: function(name, container) {
</ins><span class="cx">
</span><ins>+ 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;
+ }
+ }
+});
+
</ins><span class="cx"> })();
</span><span class="cx">
</span><span class="cx">
</span><span class="lines">@@ -21391,8 +31199,6 @@
</span><span class="cx"> */
</span><span class="cx">
</span><span class="cx"> /**
</span><del>- @private
-
</del><span class="cx"> Find templates stored in the head tag as script tags and make them available
</span><span class="cx"> to `Ember.CoreView` in the global `Ember.TEMPLATES` object. This will be run
</span><span class="cx"> as as jQuery DOM-ready callback.
</span><span class="lines">@@ -21402,6 +31208,7 @@
</span><span class="cx"> Those with type `text/x-raw-handlebars` will be compiled with regular
</span><span class="cx"> Handlebars and are suitable for use in views' computed properties.
</span><span class="cx">
</span><ins>+ @private
</ins><span class="cx"> @method bootstrap
</span><span class="cx"> @for Ember.Handlebars
</span><span class="cx"> @static
</span><span class="lines">@@ -21413,8 +31220,7 @@
</span><span class="cx"> Ember.$(selectors, ctx)
</span><span class="cx"> .each(function() {
</span><span class="cx"> // Get a reference to the script tag
</span><del>- var script = Ember.$(this),
- type = script.attr('type');
</del><ins>+ var script = Ember.$(this);
</ins><span class="cx">
</span><span class="cx"> var compile = (script.attr('type') === 'text/x-raw-handlebars') ?
</span><span class="cx"> Ember.$.proxy(Handlebars.compile, Handlebars) :
</span><span class="lines">@@ -21425,6 +31231,11 @@
</span><span class="cx"> templateName = script.attr('data-template-name') || script.attr('id') || 'application',
</span><span class="cx"> template = compile(script.html());
</span><span class="cx">
</span><ins>+ // Check if template of same name already exists
+ if (Ember.TEMPLATES[templateName] !== undefined) {
+ throw new Ember.Error('Template named "' + templateName + '" already exists.');
+ }
+
</ins><span class="cx"> // For templates which have a name, we save them and then remove them from the DOM
</span><span class="cx"> Ember.TEMPLATES[templateName] = template;
</span><span class="cx">
</span><span class="lines">@@ -21437,6 +31248,10 @@
</span><span class="cx"> Ember.Handlebars.bootstrap( Ember.$(document) );
</span><span class="cx"> }
</span><span class="cx">
</span><ins>+function registerComponentLookup(container) {
+ container.register('component-lookup:main', Ember.ComponentLookup);
+}
+
</ins><span class="cx"> /*
</span><span class="cx"> We tie this to application.load to ensure that we've at least
</span><span class="cx"> attempted to bootstrap at the point that the application is loaded.
</span><span class="lines">@@ -21448,8 +31263,19 @@
</span><span class="cx"> from the DOM after processing.
</span><span class="cx"> */
</span><span class="cx">
</span><del>-Ember.onLoad('application', bootstrap);
</del><ins>+Ember.onLoad('Ember.Application', function(Application) {
+ Application.initializer({
+ name: 'domTemplates',
+ initialize: bootstrap
+ });
</ins><span class="cx">
</span><ins>+ Application.initializer({
+ name: 'registerComponentLookup',
+ after: 'domTemplates',
+ initialize: registerComponentLookup
+ });
+});
+
</ins><span class="cx"> })();
</span><span class="cx">
</span><span class="cx">
</span><span class="lines">@@ -21702,7 +31528,7 @@
</span><span class="cx"> return states.sort(function(a, b) {
</span><span class="cx"> if (a.types.stars !== b.types.stars) { return a.types.stars - b.types.stars; }
</span><span class="cx"> if (a.types.dynamics !== b.types.dynamics) { return a.types.dynamics - b.types.dynamics; }
</span><del>- if (a.types.statics !== b.types.statics) { return a.types.statics - b.types.statics; }
</del><ins>+ if (a.types.statics !== b.types.statics) { return b.types.statics - a.types.statics; }
</ins><span class="cx">
</span><span class="cx"> return 0;
</span><span class="cx"> });
</span><span class="lines">@@ -21720,19 +31546,31 @@
</span><span class="cx"> return nextStates;
</span><span class="cx"> }
</span><span class="cx">
</span><del>- function findHandler(state, path) {
</del><ins>+ function findHandler(state, path, queryParams) {
</ins><span class="cx"> var handlers = state.handlers, regex = state.regex;
</span><span class="cx"> var captures = path.match(regex), currentCapture = 1;
</span><span class="cx"> var result = [];
</span><span class="cx">
</span><span class="cx"> for (var i=0, l=handlers.length; i<l; i++) {
</span><del>- var handler = handlers[i], names = handler.names, params = {};
</del><ins>+ var handler = handlers[i], names = handler.names, params = {},
+ watchedQueryParams = handler.queryParams || [],
+ activeQueryParams = {},
+ j, m;
</ins><span class="cx">
</span><del>- for (var j=0, m=names.length; j<m; j++) {
</del><ins>+ for (j=0, m=names.length; j<m; j++) {
</ins><span class="cx"> params[names[j]] = captures[currentCapture++];
</span><span class="cx"> }
</span><del>-
- result.push({ handler: handler.handler, params: params, isDynamic: !!names.length });
</del><ins>+ for (j=0, m=watchedQueryParams.length; j < m; j++) {
+ var key = watchedQueryParams[j];
+ if(queryParams[key]){
+ activeQueryParams[key] = queryParams[key];
+ }
+ }
+ var currentResult = { handler: handler.handler, params: params, isDynamic: !!names.length };
+ if(watchedQueryParams && watchedQueryParams.length > 0) {
+ currentResult.queryParams = activeQueryParams;
+ }
+ result.push(currentResult);
</ins><span class="cx"> }
</span><span class="cx">
</span><span class="cx"> return result;
</span><span class="lines">@@ -21787,7 +31625,11 @@
</span><span class="cx"> regex += segment.regex();
</span><span class="cx"> }
</span><span class="cx">
</span><del>- handlers.push({ handler: route.handler, names: names });
</del><ins>+ var handler = { handler: route.handler, names: names };
+ if(route.queryParams) {
+ handler.queryParams = route.queryParams;
+ }
+ handlers.push(handler);
</ins><span class="cx"> }
</span><span class="cx">
</span><span class="cx"> if (isEmpty) {
</span><span class="lines">@@ -21839,18 +31681,67 @@
</span><span class="cx">
</span><span class="cx"> if (output.charAt(0) !== '/') { output = '/' + output; }
</span><span class="cx">
</span><ins>+ if (params && params.queryParams) {
+ output += this.generateQueryString(params.queryParams, route.handlers);
+ }
+
</ins><span class="cx"> return output;
</span><span class="cx"> },
</span><span class="cx">
</span><ins>+ generateQueryString: function(params, handlers) {
+ var pairs = [], allowedParams = [];
+ for(var i=0; i < handlers.length; i++) {
+ var currentParamList = handlers[i].queryParams;
+ if(currentParamList) {
+ allowedParams.push.apply(allowedParams, currentParamList);
+ }
+ }
+ for(var key in params) {
+ if (params.hasOwnProperty(key)) {
+ if(allowedParams.indexOf(key) === -1) {
+ throw 'Query param "' + key + '" is not specified as a valid param for this route';
+ }
+ var value = params[key];
+ var pair = encodeURIComponent(key);
+ if(value !== true) {
+ pair += "=" + encodeURIComponent(value);
+ }
+ pairs.push(pair);
+ }
+ }
+
+ if (pairs.length === 0) { return ''; }
+
+ return "?" + pairs.join("&");
+ },
+
+ parseQueryString: function(queryString) {
+ var pairs = queryString.split("&"), queryParams = {};
+ for(var i=0; i < pairs.length; i++) {
+ var pair = pairs[i].split('='),
+ key = decodeURIComponent(pair[0]),
+ value = pair[1] ? decodeURIComponent(pair[1]) : true;
+ queryParams[key] = value;
+ }
+ return queryParams;
+ },
+
</ins><span class="cx"> recognize: function(path) {
</span><del>- var states = [ this.rootState ], i, l;
</del><ins>+ var states = [ this.rootState ],
+ pathLen, i, l, queryStart, queryParams = {};
</ins><span class="cx">
</span><ins>+ queryStart = path.indexOf('?');
+ if (queryStart !== -1) {
+ var queryString = path.substr(queryStart + 1, path.length);
+ path = path.substr(0, queryStart);
+ queryParams = this.parseQueryString(queryString);
+ }
+
</ins><span class="cx"> // DEBUG GROUP path
</span><span class="cx">
</span><del>- var pathLen = path.length;
-
</del><span class="cx"> if (path.charAt(0) !== "/") { path = "/" + path; }
</span><span class="cx">
</span><ins>+ pathLen = path.length;
</ins><span class="cx"> if (pathLen > 1 && path.charAt(pathLen - 1) === "/") {
</span><span class="cx"> path = path.substr(0, pathLen - 1);
</span><span class="cx"> }
</span><span class="lines">@@ -21872,7 +31763,7 @@
</span><span class="cx"> var state = solutions[0];
</span><span class="cx">
</span><span class="cx"> if (state && state.handlers) {
</span><del>- return findHandler(state, path);
</del><ins>+ return findHandler(state, path, queryParams);
</ins><span class="cx"> }
</span><span class="cx"> }
</span><span class="cx"> };
</span><span class="lines">@@ -21897,12 +31788,25 @@
</span><span class="cx"> if (callback.length === 0) { throw new Error("You must have an argument in the function passed to `to`"); }
</span><span class="cx"> this.matcher.addChild(this.path, target, callback, this.delegate);
</span><span class="cx"> }
</span><ins>+ return this;
+ },
+
+ withQueryParams: function() {
+ if (arguments.length === 0) { throw new Error("you must provide arguments to the withQueryParams method"); }
+ for (var i = 0; i < arguments.length; i++) {
+ if (typeof arguments[i] !== "string") {
+ throw new Error('you should call withQueryParams with a list of strings, e.g. withQueryParams("foo", "bar")');
+ }
+ }
+ var queryParams = [].slice.call(arguments);
+ this.matcher.addQueryParams(this.path, queryParams);
</ins><span class="cx"> }
</span><span class="cx"> };
</span><span class="cx">
</span><span class="cx"> function Matcher(target) {
</span><span class="cx"> this.routes = {};
</span><span class="cx"> this.children = {};
</span><ins>+ this.queryParams = {};
</ins><span class="cx"> this.target = target;
</span><span class="cx"> }
</span><span class="cx">
</span><span class="lines">@@ -21911,6 +31815,10 @@
</span><span class="cx"> this.routes[path] = handler;
</span><span class="cx"> },
</span><span class="cx">
</span><ins>+ addQueryParams: function(path, params) {
+ this.queryParams[path] = params;
+ },
+
</ins><span class="cx"> addChild: function(path, target, callback, delegate) {
</span><span class="cx"> var matcher = new Matcher(target);
</span><span class="cx"> this.children[path] = matcher;
</span><span class="lines">@@ -21937,23 +31845,26 @@
</span><span class="cx"> };
</span><span class="cx"> }
</span><span class="cx">
</span><del>- function addRoute(routeArray, path, handler) {
</del><ins>+ function addRoute(routeArray, path, handler, queryParams) {
</ins><span class="cx"> var len = 0;
</span><span class="cx"> for (var i=0, l=routeArray.length; i<l; i++) {
</span><span class="cx"> len += routeArray[i].path.length;
</span><span class="cx"> }
</span><span class="cx">
</span><span class="cx"> path = path.substr(len);
</span><del>- routeArray.push({ path: path, handler: handler });
</del><ins>+ var route = { path: path, handler: handler };
+ if(queryParams) { route.queryParams = queryParams; }
+ routeArray.push(route);
</ins><span class="cx"> }
</span><span class="cx">
</span><span class="cx"> function eachRoute(baseRoute, matcher, callback, binding) {
</span><span class="cx"> var routes = matcher.routes;
</span><ins>+ var queryParams = matcher.queryParams;
</ins><span class="cx">
</span><span class="cx"> for (var path in routes) {
</span><span class="cx"> if (routes.hasOwnProperty(path)) {
</span><span class="cx"> var routeArray = baseRoute.slice();
</span><del>- addRoute(routeArray, path, routes[path]);
</del><ins>+ addRoute(routeArray, path, routes[path], queryParams[path]);
</ins><span class="cx">
</span><span class="cx"> if (matcher.children[path]) {
</span><span class="cx"> eachRoute(routeArray, matcher.children[path], callback, binding);
</span><span class="lines">@@ -21983,8 +31894,8 @@
</span><span class="cx">
</span><span class="cx"> (function() {
</span><span class="cx"> define("router",
</span><del>- ["route-recognizer"],
- function(RouteRecognizer) {
</del><ins>+ ["route-recognizer","rsvp","exports"],
+ function(__dependency1__, __dependency2__, __exports__) {
</ins><span class="cx"> "use strict";
</span><span class="cx"> /**
</span><span class="cx"> @private
</span><span class="lines">@@ -21996,26 +31907,192 @@
</span><span class="cx"> * `{String} handler`: A handler name
</span><span class="cx"> * `{Object} params`: A hash of recognized parameters
</span><span class="cx">
</span><del>- ## `UnresolvedHandlerInfo`
</del><ins>+ ## `HandlerInfo`
</ins><span class="cx">
</span><span class="cx"> * `{Boolean} isDynamic`: whether a handler has any dynamic segments
</span><span class="cx"> * `{String} name`: the name of a handler
</span><del>- * `{Object} context`: the active context for the handler
-
- ## `HandlerInfo`
-
- * `{Boolean} isDynamic`: whether a handler has any dynamic segments
- * `{String} name`: the original unresolved handler name
</del><span class="cx"> * `{Object} handler`: a handler object
</span><span class="cx"> * `{Object} context`: the active context for the handler
</span><span class="cx"> */
</span><span class="cx">
</span><ins>+ var RouteRecognizer = __dependency1__;
+ var RSVP = __dependency2__;
</ins><span class="cx">
</span><ins>+ var slice = Array.prototype.slice;
+
+
+
+ /**
+ @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, promise) {
+ this.router = router;
+ this.promise = promise;
+ this.data = {};
+ this.resolvedModels = {};
+ this.providedModels = {};
+ this.providedModelsArray = [];
+ this.sequence = ++Transition.currentSequence;
+ this.params = {};
+ }
+
+ Transition.currentSequence = 0;
+
+ Transition.prototype = {
+ targetName: null,
+ urlMethod: 'update',
+ providedModels: null,
+ resolvedModels: null,
+ params: null,
+ pivotHandler: null,
+ resolveIndex: 0,
+ handlerInfos: null,
+
+ isActive: true,
+
+ /**
+ 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,
+
+ /**
+ 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,
+
+ /**
+ 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);
+ },
+
+ /**
+ 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;
+ },
+
+ /**
+ 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() {
+ this.abort();
+ var recogHandlers = this.router.recognizer.handlersFor(this.targetName),
+ handlerInfos = generateHandlerInfosWithQueryParams(this.router, recogHandlers, this.queryParams),
+ newTransition = performTransition(this.router, handlerInfos, this.providedModelsArray, this.params, this.queryParams, this.data);
+
+ return newTransition;
+ },
+
+ /**
+ 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;
+ },
+
+ /**
+ 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.
+
+ @param {Boolean} ignoreFailure the name of the event to fire
+ @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.handlerInfos.slice(0, this.resolveIndex + 1), ignoreFailure, args);
+ },
+
+ toString: function() {
+ return "Transition (sequence " + this.sequence + ")";
+ }
+ };
+
</ins><span class="cx"> function Router() {
</span><span class="cx"> this.recognizer = new RouteRecognizer();
</span><span class="cx"> }
</span><span class="cx">
</span><ins>+ // TODO: separate into module?
+ Router.Transition = Transition;
</ins><span class="cx">
</span><ins>+ __exports__["default"] = Router;
+
+
+ /**
+ Promise reject reasons passed to promise rejection
+ handlers for failed transitions.
+ */
+ Router.UnrecognizedURLError = function(message) {
+ this.message = (message || "UnrecognizedURLError");
+ this.name = "UnrecognizedURLError";
+ };
+
+ Router.TransitionAborted = function(message) {
+ this.message = (message || "TransitionAborted");
+ this.name = "TransitionAborted";
+ };
+
+ function errorTransition(router, reason) {
+ return new Transition(router, RSVP.reject(reason));
+ }
+
+
</ins><span class="cx"> Router.prototype = {
</span><span class="cx"> /**
</span><span class="cx"> The main entry point into the router. The API is essentially
</span><span class="lines">@@ -22041,6 +32118,25 @@
</span><span class="cx"> },
</span><span class="cx">
</span><span class="cx"> /**
</span><ins>+ 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() {
+ eachHandler(this.currentHandlerInfos || [], function(handlerInfo) {
+ var handler = handlerInfo.handler;
+ if (handler.exit) {
+ handler.exit();
+ }
+ });
+ this.currentHandlerInfos = null;
+ this.targetHandlerInfos = null;
+ },
+
+ activeTransition: null,
+
+ /**
+ var handler = handlerInfo.handler;
</ins><span class="cx"> The entry point for handling a change to the URL (usually
</span><span class="cx"> via the back and forward button).
</span><span class="cx">
</span><span class="lines">@@ -22052,14 +32148,11 @@
</span><span class="cx"> @return {Array} an Array of `[handler, parameter]` tuples
</span><span class="cx"> */
</span><span class="cx"> handleURL: function(url) {
</span><del>- var results = this.recognizer.recognize(url),
- objects = [];
-
- if (!results) {
- throw new Error("No route matched the URL '" + url + "'");
- }
-
- collectObjects(this, results, 0, []);
</del><ins>+ // 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(null);
</ins><span class="cx"> },
</span><span class="cx">
</span><span class="cx"> /**
</span><span class="lines">@@ -22068,7 +32161,7 @@
</span><span class="cx"> @param {String} url a URL to update to
</span><span class="cx"> */
</span><span class="cx"> updateURL: function() {
</span><del>- throw "updateURL is not implemented";
</del><ins>+ throw new Error("updateURL is not implemented");
</ins><span class="cx"> },
</span><span class="cx">
</span><span class="cx"> /**
</span><span class="lines">@@ -22091,10 +32184,13 @@
</span><span class="cx"> @param {String} name the name of the route
</span><span class="cx"> */
</span><span class="cx"> transitionTo: function(name) {
</span><del>- var args = Array.prototype.slice.call(arguments, 1);
- doTransition(this, name, this.updateURL, args);
</del><ins>+ return doTransition(this, arguments);
</ins><span class="cx"> },
</span><span class="cx">
</span><ins>+ intermediateTransitionTo: function(name) {
+ doTransition(this, arguments, true);
+ },
+
</ins><span class="cx"> /**
</span><span class="cx"> Identical to `transitionTo` except that the current URL will be replaced
</span><span class="cx"> if possible.
</span><span class="lines">@@ -22104,8 +32200,7 @@
</span><span class="cx"> @param {String} name the name of the route
</span><span class="cx"> */
</span><span class="cx"> replaceWith: function(name) {
</span><del>- var args = Array.prototype.slice.call(arguments, 1);
- doTransition(this, name, this.replaceURL, args);
</del><ins>+ return doTransition(this, arguments).method('replace');
</ins><span class="cx"> },
</span><span class="cx">
</span><span class="cx"> /**
</span><span class="lines">@@ -22118,12 +32213,24 @@
</span><span class="cx"> @param {Array[Object]} contexts
</span><span class="cx"> @return {Object} a serialized parameter hash
</span><span class="cx"> */
</span><del>- paramsForHandler: function(handlerName, callback) {
- var output = this._paramsForHandler(handlerName, [].slice.call(arguments, 1));
- return output.params;
</del><ins>+
+ paramsForHandler: function(handlerName, contexts) {
+ var partitionedArgs = extractQueryParams(slice.call(arguments, 1));
+ return paramsForHandler(this, handlerName, partitionedArgs[0], partitionedArgs[1]);
</ins><span class="cx"> },
</span><span class="cx">
</span><span class="cx"> /**
</span><ins>+ This method takes a handler name and returns a list of query params
+ that are valid to pass to the handler or its parents
+
+ @param {String} handlerName
+ @return {Array[String]} a list of query parameters
+ */
+ queryParamsForHandler: function (handlerName) {
+ return queryParamsForHandler(this, handlerName);
+ },
+
+ /**
</ins><span class="cx"> Take a named route and context objects and generate a
</span><span class="cx"> URL.
</span><span class="cx">
</span><span class="lines">@@ -22134,262 +32241,390 @@
</span><span class="cx"> @return {String} a URL
</span><span class="cx"> */
</span><span class="cx"> generate: function(handlerName) {
</span><del>- var params = this.paramsForHandler.apply(this, arguments);
- return this.recognizer.generate(handlerName, params);
- },
</del><ins>+ var partitionedArgs = extractQueryParams(slice.call(arguments, 1)),
+ suppliedParams = partitionedArgs[0],
+ queryParams = partitionedArgs[1];
</ins><span class="cx">
</span><del>- /**
- @private
</del><ins>+ var params = paramsForHandler(this, handlerName, suppliedParams, queryParams),
+ validQueryParams = queryParamsForHandler(this, handlerName);
</ins><span class="cx">
</span><del>- Used internally by `generate` and `transitionTo`.
- */
- _paramsForHandler: function(handlerName, objects, doUpdate) {
- var handlers = this.recognizer.handlersFor(handlerName),
- params = {},
- toSetup = [],
- startIdx = handlers.length,
- objectsToMatch = objects.length,
- object, objectChanged, handlerObj, handler, names, i, len;
</del><ins>+ var missingParams = [];
</ins><span class="cx">
</span><del>- // Find out which handler to start matching at
- for (i=handlers.length-1; i>=0 && objectsToMatch>0; i--) {
- if (handlers[i].names.length) {
- objectsToMatch--;
- startIdx = i;
</del><ins>+ for (var key in queryParams) {
+ if (queryParams.hasOwnProperty(key) && !~validQueryParams.indexOf(key)) {
+ missingParams.push(key);
</ins><span class="cx"> }
</span><span class="cx"> }
</span><span class="cx">
</span><del>- if (objectsToMatch > 0) {
- throw "More objects were passed than dynamic segments";
- }
</del><ins>+ if (missingParams.length > 0) {
+ var err = 'You supplied the params ';
+ err += missingParams.map(function(param) {
+ return '"' + param + "=" + queryParams[param] + '"';
+ }).join(' and ');
</ins><span class="cx">
</span><del>- // Connect the objects to the routes
- for (i=0, len=handlers.length; i<len; i++) {
- handlerObj = handlers[i];
- handler = this.getHandler(handlerObj.handler);
- names = handlerObj.names;
- objectChanged = false;
</del><ins>+ err += ' which are not valid for the "' + handlerName + '" handler or its parents';
</ins><span class="cx">
</span><del>- // If it's a dynamic segment
- if (names.length) {
- // If we have objects, use them
- if (i >= startIdx) {
- object = objects.shift();
- objectChanged = true;
- // Otherwise use existing context
- } else {
- object = handler.context;
- }
-
- // Serialize to generate params
- if (handler.serialize) {
- merge(params, handler.serialize(object, names));
- }
- // If it's not a dynamic segment and we're updating
- } else if (doUpdate) {
- // If we've passed the match point we need to deserialize again
- // or if we never had a context
- if (i > startIdx || !handler.hasOwnProperty('context')) {
- if (handler.deserialize) {
- object = handler.deserialize({});
- objectChanged = true;
- }
- // Otherwise use existing context
- } else {
- object = handler.context;
- }
- }
-
- // Make sure that we update the context here so it's available to
- // subsequent deserialize calls
- if (doUpdate && objectChanged) {
- // TODO: It's a bit awkward to set the context twice, see if we can DRY things up
- setContext(handler, object);
- }
-
- toSetup.push({
- isDynamic: !!handlerObj.names.length,
- handler: handlerObj.handler,
- name: handlerObj.name,
- context: object
- });
</del><ins>+ throw new Error(err);
</ins><span class="cx"> }
</span><span class="cx">
</span><del>- return { params: params, toSetup: toSetup };
</del><ins>+ return this.recognizer.generate(handlerName, params);
</ins><span class="cx"> },
</span><span class="cx">
</span><span class="cx"> isActive: function(handlerName) {
</span><del>- var contexts = [].slice.call(arguments, 1);
</del><ins>+ var partitionedArgs = extractQueryParams(slice.call(arguments, 1)),
+ contexts = partitionedArgs[0],
+ queryParams = partitionedArgs[1],
+ activeQueryParams = {},
+ effectiveQueryParams = {};
</ins><span class="cx">
</span><del>- var currentHandlerInfos = this.currentHandlerInfos,
</del><ins>+ var targetHandlerInfos = this.targetHandlerInfos,
</ins><span class="cx"> found = false, names, object, handlerInfo, handlerObj;
</span><span class="cx">
</span><del>- for (var i=currentHandlerInfos.length-1; i>=0; i--) {
- handlerInfo = currentHandlerInfos[i];
</del><ins>+ if (!targetHandlerInfos) { return false; }
+
+ var recogHandlers = this.recognizer.handlersFor(targetHandlerInfos[targetHandlerInfos.length - 1].name);
+ for (var i=targetHandlerInfos.length-1; i>=0; i--) {
+ handlerInfo = targetHandlerInfos[i];
</ins><span class="cx"> if (handlerInfo.name === handlerName) { found = true; }
</span><span class="cx">
</span><span class="cx"> if (found) {
</span><del>- if (contexts.length === 0) { break; }
</del><ins>+ var recogHandler = recogHandlers[i];
</ins><span class="cx">
</span><del>- if (handlerInfo.isDynamic) {
</del><ins>+ merge(activeQueryParams, handlerInfo.queryParams);
+ if (queryParams !== false) {
+ merge(effectiveQueryParams, handlerInfo.queryParams);
+ mergeSomeKeys(effectiveQueryParams, queryParams, recogHandler.queryParams);
+ }
+
+ if (handlerInfo.isDynamic && contexts.length > 0) {
</ins><span class="cx"> object = contexts.pop();
</span><del>- if (handlerInfo.context !== object) { return false; }
</del><ins>+
+ if (isParam(object)) {
+ var name = recogHandler.names[0];
+ if (!this.currentParams || "" + object !== this.currentParams[name]) { return false; }
+ } else if (handlerInfo.context !== object) {
+ return false;
+ }
</ins><span class="cx"> }
</span><span class="cx"> }
</span><span class="cx"> }
</span><span class="cx">
</span><del>- return contexts.length === 0 && found;
</del><ins>+
+ return contexts.length === 0 && found && queryParamsEqual(activeQueryParams, effectiveQueryParams);
</ins><span class="cx"> },
</span><span class="cx">
</span><span class="cx"> trigger: function(name) {
</span><del>- var args = [].slice.call(arguments);
- trigger(this, args);
- }
</del><ins>+ var args = slice.call(arguments);
+ trigger(this, this.currentHandlerInfos, false, args);
+ },
+
+ /**
+ Hook point for logging transition status updates.
+
+ @param {String} message The message to log.
+ */
+ log: null
</ins><span class="cx"> };
</span><span class="cx">
</span><del>- function merge(hash, other) {
- for (var prop in other) {
- if (other.hasOwnProperty(prop)) { hash[prop] = other[prop]; }
</del><ins>+ /**
+ @private
+
+ Used internally for both URL and named transition to determine
+ a shared pivot parent route and other data necessary to perform
+ a transition.
+ */
+ function getMatchPoint(router, handlers, objects, inputParams, queryParams) {
+
+ var matchPoint = handlers.length,
+ providedModels = {}, i,
+ currentHandlerInfos = router.currentHandlerInfos || [],
+ params = {},
+ oldParams = router.currentParams || {},
+ activeTransition = router.activeTransition,
+ handlerParams = {},
+ obj;
+
+ objects = slice.call(objects);
+ merge(params, inputParams);
+
+ for (i = handlers.length - 1; i >= 0; i--) {
+ var handlerObj = handlers[i],
+ handlerName = handlerObj.handler,
+ oldHandlerInfo = currentHandlerInfos[i],
+ hasChanged = false;
+
+ // Check if handler names have changed.
+ if (!oldHandlerInfo || oldHandlerInfo.name !== handlerObj.handler) { hasChanged = true; }
+
+ if (handlerObj.isDynamic) {
+ // URL transition.
+
+ if (obj = getMatchPointObject(objects, handlerName, activeTransition, true, params)) {
+ hasChanged = true;
+ providedModels[handlerName] = obj;
+ } else {
+ handlerParams[handlerName] = {};
+ for (var prop in handlerObj.params) {
+ if (!handlerObj.params.hasOwnProperty(prop)) { continue; }
+ var newParam = handlerObj.params[prop];
+ if (oldParams[prop] !== newParam) { hasChanged = true; }
+ handlerParams[handlerName][prop] = params[prop] = newParam;
+ }
+ }
+ } else if (handlerObj.hasOwnProperty('names')) {
+ // Named transition.
+
+ if (objects.length) { hasChanged = true; }
+
+ if (obj = getMatchPointObject(objects, handlerName, activeTransition, handlerObj.names[0], params)) {
+ providedModels[handlerName] = obj;
+ } else {
+ var names = handlerObj.names;
+ handlerParams[handlerName] = {};
+ for (var j = 0, len = names.length; j < len; ++j) {
+ var name = names[j];
+ handlerParams[handlerName][name] = params[name] = params[name] || oldParams[name];
+ }
+ }
+ }
+
+ // If there is an old handler, see if query params are the same. If there isn't an old handler,
+ // hasChanged will already be true here
+ if(oldHandlerInfo && !queryParamsEqual(oldHandlerInfo.queryParams, handlerObj.queryParams)) {
+ hasChanged = true;
+ }
+
+ if (hasChanged) { matchPoint = i; }
</ins><span class="cx"> }
</span><ins>+
+ if (objects.length > 0) {
+ throw new Error("More context objects were passed than there are dynamic segments for the route: " + handlers[handlers.length - 1].handler);
+ }
+
+ var pivotHandlerInfo = currentHandlerInfos[matchPoint - 1],
+ pivotHandler = pivotHandlerInfo && pivotHandlerInfo.handler;
+
+ return { matchPoint: matchPoint, providedModels: providedModels, params: params, handlerParams: handlerParams, pivotHandler: pivotHandler };
</ins><span class="cx"> }
</span><span class="cx">
</span><del>- function isCurrent(currentHandlerInfos, handlerName) {
- return currentHandlerInfos[currentHandlerInfos.length - 1].name === handlerName;
</del><ins>+ function getMatchPointObject(objects, handlerName, activeTransition, paramName, params) {
+
+ if (objects.length && paramName) {
+
+ var object = objects.pop();
+
+ // If provided object is string or number, treat as param.
+ if (isParam(object)) {
+ params[paramName] = object.toString();
+ } else {
+ return object;
+ }
+ } else if (activeTransition) {
+ // Use model from previous transition attempt, preferably the resolved one.
+ return activeTransition.resolvedModels[handlerName] ||
+ (paramName && activeTransition.providedModels[handlerName]);
+ }
</ins><span class="cx"> }
</span><span class="cx">
</span><ins>+ function isParam(object) {
+ return (typeof object === "string" || object instanceof String || typeof object === "number" || object instanceof Number);
+ }
+
+
+
</ins><span class="cx"> /**
</span><span class="cx"> @private
</span><span class="cx">
</span><del>- This function is called the first time the `collectObjects`
- function encounters a promise while converting URL parameters
- into objects.
</del><ins>+ This method takes a handler name and returns a list of query params
+ that are valid to pass to the handler or its parents
</ins><span class="cx">
</span><del>- It triggers the `enter` and `setup` methods on the `loading`
- handler.
-
</del><span class="cx"> @param {Router} router
</span><ins>+ @param {String} handlerName
+ @return {Array[String]} a list of query parameters
</ins><span class="cx"> */
</span><del>- function loading(router) {
- if (!router.isLoading) {
- router.isLoading = true;
- var handler = router.getHandler('loading');
</del><ins>+ function queryParamsForHandler(router, handlerName) {
+ var handlers = router.recognizer.handlersFor(handlerName),
+ queryParams = [];
</ins><span class="cx">
</span><del>- if (handler) {
- if (handler.enter) { handler.enter(); }
- if (handler.setup) { handler.setup(); }
- }
</del><ins>+ for (var i = 0; i < handlers.length; i++) {
+ queryParams.push.apply(queryParams, handlers[i].queryParams || []);
</ins><span class="cx"> }
</span><ins>+
+ return queryParams;
</ins><span class="cx"> }
</span><del>-
</del><span class="cx"> /**
</span><span class="cx"> @private
</span><span class="cx">
</span><del>- This function is called if a promise was previously
- encountered once all promises are resolved.
</del><ins>+ This method takes a handler name and a list of contexts and returns
+ a serialized parameter hash suitable to pass to `recognizer.generate()`.
</ins><span class="cx">
</span><del>- It triggers the `exit` method on the `loading` handler.
-
</del><span class="cx"> @param {Router} router
</span><ins>+ @param {String} handlerName
+ @param {Array[Object]} objects
+ @return {Object} a serialized parameter hash
</ins><span class="cx"> */
</span><del>- function loaded(router) {
- router.isLoading = false;
- var handler = router.getHandler('loading');
- if (handler && handler.exit) { handler.exit(); }
</del><ins>+ function paramsForHandler(router, handlerName, objects, queryParams) {
+
+ var handlers = router.recognizer.handlersFor(handlerName),
+ params = {},
+ handlerInfos = generateHandlerInfosWithQueryParams(router, handlers, queryParams),
+ matchPoint = getMatchPoint(router, handlerInfos, objects).matchPoint,
+ mergedQueryParams = {},
+ object, handlerObj, handler, names, i;
+
+ params.queryParams = {};
+
+ for (i=0; i<handlers.length; i++) {
+ handlerObj = handlers[i];
+ handler = router.getHandler(handlerObj.handler);
+ names = handlerObj.names;
+
+ // If it's a dynamic segment
+ if (names.length) {
+ // If we have objects, use them
+ if (i >= matchPoint) {
+ object = objects.shift();
+ // Otherwise use existing context
+ } else {
+ object = handler.context;
+ }
+
+ // Serialize to generate params
+ merge(params, serialize(handler, object, names));
+ }
+ if (queryParams !== false) {
+ mergeSomeKeys(params.queryParams, router.currentQueryParams, handlerObj.queryParams);
+ mergeSomeKeys(params.queryParams, queryParams, handlerObj.queryParams);
+ }
+ }
+
+ if (queryParamsEqual(params.queryParams, {})) { delete params.queryParams; }
+ return params;
</ins><span class="cx"> }
</span><span class="cx">
</span><ins>+ function merge(hash, other) {
+ for (var prop in other) {
+ if (other.hasOwnProperty(prop)) { hash[prop] = other[prop]; }
+ }
+ }
+
+ function mergeSomeKeys(hash, other, keys) {
+ if (!other || !keys) { return; }
+ for(var i = 0; i < keys.length; i++) {
+ var key = keys[i], value;
+ if(other.hasOwnProperty(key)) {
+ value = other[key];
+ if(value === null || value === false || typeof value === "undefined") {
+ delete hash[key];
+ } else {
+ hash[key] = other[key];
+ }
+ }
+ }
+ }
+
</ins><span class="cx"> /**
</span><span class="cx"> @private
</span><ins>+ */
</ins><span class="cx">
</span><del>- This function is called if any encountered promise
- is rejected.
</del><ins>+ function generateHandlerInfosWithQueryParams(router, handlers, queryParams) {
+ var handlerInfos = [];
</ins><span class="cx">
</span><del>- It triggers the `exit` method on the `loading` handler,
- the `enter` method on the `failure` handler, and the
- `setup` method on the `failure` handler with the
- `error`.
</del><ins>+ for (var i = 0; i < handlers.length; i++) {
+ var handler = handlers[i],
+ handlerInfo = { handler: handler.handler, names: handler.names, context: handler.context, isDynamic: handler.isDynamic },
+ activeQueryParams = {};
</ins><span class="cx">
</span><del>- @param {Router} router
- @param {Object} error the reason for the promise
- rejection, to pass into the failure handler's
- `setup` method.
- */
- function failure(router, error) {
- loaded(router);
- var handler = router.getHandler('failure');
- if (handler && handler.setup) { handler.setup(error); }
</del><ins>+ if (queryParams !== false) {
+ mergeSomeKeys(activeQueryParams, router.currentQueryParams, handler.queryParams);
+ mergeSomeKeys(activeQueryParams, queryParams, handler.queryParams);
+ }
+
+ if (handler.queryParams && handler.queryParams.length > 0) {
+ handlerInfo.queryParams = activeQueryParams;
+ }
+
+ handlerInfos.push(handlerInfo);
+ }
+
+ return handlerInfos;
</ins><span class="cx"> }
</span><span class="cx">
</span><span class="cx"> /**
</span><span class="cx"> @private
</span><span class="cx"> */
</span><del>- function doTransition(router, name, method, args) {
- var output = router._paramsForHandler(name, args, true);
- var params = output.params, toSetup = output.toSetup;
</del><ins>+ function createQueryParamTransition(router, queryParams, isIntermediate) {
+ var currentHandlers = router.currentHandlerInfos,
+ currentHandler = currentHandlers[currentHandlers.length - 1],
+ name = currentHandler.name;
</ins><span class="cx">
</span><del>- var url = router.recognizer.generate(name, params);
- method.call(router, url);
</del><ins>+ log(router, "Attempting query param transition");
</ins><span class="cx">
</span><del>- setupContexts(router, toSetup);
</del><ins>+ return createNamedTransition(router, [name, queryParams], isIntermediate);
</ins><span class="cx"> }
</span><span class="cx">
</span><span class="cx"> /**
</span><span class="cx"> @private
</span><ins>+ */
+ function createNamedTransition(router, args, isIntermediate) {
+ var partitionedArgs = extractQueryParams(args),
+ pureArgs = partitionedArgs[0],
+ queryParams = partitionedArgs[1],
+ handlers = router.recognizer.handlersFor(pureArgs[0]),
+ handlerInfos = generateHandlerInfosWithQueryParams(router, handlers, queryParams);
</ins><span class="cx">
</span><del>- This function is called after a URL change has been handled
- by `router.handleURL`.
</del><span class="cx">
</span><del>- Takes an Array of `RecognizedHandler`s, and converts the raw
- params hashes into deserialized objects by calling deserialize
- on the handlers. This process builds up an Array of
- `HandlerInfo`s. It then calls `setupContexts` with the Array.
</del><ins>+ log(router, "Attempting transition to " + pureArgs[0]);
</ins><span class="cx">
</span><del>- If the `deserialize` method on a handler returns a promise
- (i.e. has a method called `then`), this function will pause
- building up the `HandlerInfo` Array until the promise is
- resolved. It will use the resolved value as the context of
- `HandlerInfo`.
</del><ins>+ return performTransition(router,
+ handlerInfos,
+ slice.call(pureArgs, 1),
+ router.currentParams,
+ queryParams,
+ null,
+ isIntermediate);
+ }
+
+ /**
+ @private
</ins><span class="cx"> */
</span><del>- function collectObjects(router, results, index, objects) {
- if (results.length === index) {
- loaded(router);
- setupContexts(router, objects);
- return;
- }
</del><ins>+ function createURLTransition(router, url, isIntermediate) {
+ var results = router.recognizer.recognize(url),
+ currentHandlerInfos = router.currentHandlerInfos,
+ queryParams = {},
+ i, len;
</ins><span class="cx">
</span><del>- var result = results[index];
- var handler = router.getHandler(result.handler);
- var object = handler.deserialize && handler.deserialize(result.params);
</del><ins>+ log(router, "Attempting URL transition to " + url);
</ins><span class="cx">
</span><del>- if (object && typeof object.then === 'function') {
- loading(router);
</del><ins>+ if (results) {
+ // Make sure this route is actually accessible by URL.
+ for (i = 0, len = results.length; i < len; ++i) {
</ins><span class="cx">
</span><del>- // The chained `then` means that we can also catch errors that happen in `proceed`
- object.then(proceed).then(null, function(error) {
- failure(router, error);
- });
- } else {
- proceed(object);
</del><ins>+ if (router.getHandler(results[i].handler).inaccessibleByURL) {
+ results = null;
+ break;
+ }
+ }
</ins><span class="cx"> }
</span><span class="cx">
</span><del>- function proceed(value) {
- if (handler.context !== object) {
- setContext(handler, object);
- }
</del><ins>+ if (!results) {
+ return errorTransition(router, new Router.UnrecognizedURLError(url));
+ }
</ins><span class="cx">
</span><del>- var updatedObjects = objects.concat([{
- context: value,
- handler: result.handler,
- isDynamic: result.isDynamic
- }]);
- collectObjects(router, results, index + 1, updatedObjects);
</del><ins>+ for(i = 0, len = results.length; i < len; i++) {
+ merge(queryParams, results[i].queryParams);
</ins><span class="cx"> }
</span><ins>+
+ return performTransition(router, results, [], {}, queryParams, null, isIntermediate);
</ins><span class="cx"> }
</span><span class="cx">
</span><ins>+
</ins><span class="cx"> /**
</span><span class="cx"> @private
</span><span class="cx">
</span><del>- Takes an Array of `UnresolvedHandlerInfo`s, resolves the handler names
- into handlers, and then figures out what to do with each of the handlers.
</del><ins>+ Takes an Array of `HandlerInfo`s, figures out which ones are
+ exiting, entering, or changing contexts, and calls the
+ proper handler hooks.
</ins><span class="cx">
</span><span class="cx"> For example, consider the following tree of handlers. Each handler is
</span><span class="cx"> followed by the URL segment it handles.
</span><span class="lines">@@ -22406,7 +32641,7 @@
</span><span class="cx"> Consider the following transitions:
</span><span class="cx">
</span><span class="cx"> 1. A URL transition to `/posts/1`.
</span><del>- 1. Triggers the `deserialize` callback on the
</del><ins>+ 1. Triggers the `*model` callbacks on the
</ins><span class="cx"> `index`, `posts`, and `showPost` handlers
</span><span class="cx"> 2. Triggers the `enter` callback on the same
</span><span class="cx"> 3. Triggers the `setup` callback on the same
</span><span class="lines">@@ -22422,44 +32657,66 @@
</span><span class="cx"> 3. Triggers the `enter` callback on `about`
</span><span class="cx"> 4. Triggers the `setup` callback on `about`
</span><span class="cx">
</span><del>- @param {Router} router
- @param {Array[UnresolvedHandlerInfo]} handlerInfos
</del><ins>+ @param {Transition} transition
+ @param {Array[HandlerInfo]} handlerInfos
</ins><span class="cx"> */
</span><del>- function setupContexts(router, handlerInfos) {
- resolveHandlers(router, handlerInfos);
</del><ins>+ function setupContexts(transition, handlerInfos) {
+ var router = transition.router,
+ partition = partitionHandlers(router.currentHandlerInfos || [], handlerInfos);
</ins><span class="cx">
</span><del>- var partition =
- partitionHandlers(router.currentHandlerInfos || [], handlerInfos);
</del><ins>+ router.targetHandlerInfos = handlerInfos;
</ins><span class="cx">
</span><del>- router.currentHandlerInfos = handlerInfos;
-
- eachHandler(partition.exited, function(handler, context) {
</del><ins>+ eachHandler(partition.exited, function(handlerInfo) {
+ var handler = handlerInfo.handler;
</ins><span class="cx"> delete handler.context;
</span><span class="cx"> if (handler.exit) { handler.exit(); }
</span><span class="cx"> });
</span><span class="cx">
</span><del>- eachHandler(partition.updatedContext, function(handler, context) {
- setContext(handler, context);
- if (handler.setup) { handler.setup(context); }
</del><ins>+ var currentHandlerInfos = partition.unchanged.slice();
+ router.currentHandlerInfos = currentHandlerInfos;
+
+ eachHandler(partition.updatedContext, function(handlerInfo) {
+ handlerEnteredOrUpdated(transition, currentHandlerInfos, handlerInfo, false);
</ins><span class="cx"> });
</span><span class="cx">
</span><del>- var aborted = false;
- eachHandler(partition.entered, function(handler, context) {
- if (aborted) { return; }
- if (handler.enter) { handler.enter(); }
</del><ins>+ eachHandler(partition.entered, function(handlerInfo) {
+ handlerEnteredOrUpdated(transition, currentHandlerInfos, handlerInfo, true);
+ });
+ }
+
+ /**
+ @private
+
+ Helper method used by setupContexts. Handles errors or redirects
+ that may happen in enter/setup.
+ */
+ function handlerEnteredOrUpdated(transition, currentHandlerInfos, handlerInfo, enter) {
+ var handler = handlerInfo.handler,
+ context = handlerInfo.context;
+
+ try {
+ if (enter && handler.enter) { handler.enter(); }
+ checkAbort(transition);
+
</ins><span class="cx"> setContext(handler, context);
</span><del>- if (handler.setup) {
- if (false === handler.setup(context)) {
- aborted = true;
- }
</del><ins>+ setQueryParams(handler, handlerInfo.queryParams);
+
+ if (handler.setup) { handler.setup(context, handlerInfo.queryParams); }
+ checkAbort(transition);
+ } catch(e) {
+ if (!(e instanceof Router.TransitionAborted)) {
+ // Trigger the `error` event starting from this failed handler.
+ transition.trigger(true, 'error', e, transition, handler);
</ins><span class="cx"> }
</span><del>- });
</del><span class="cx">
</span><del>- if (router.didTransition) {
- router.didTransition(handlerInfos);
</del><ins>+ // Propagate the error so that the transition promise will reject.
+ throw e;
</ins><span class="cx"> }
</span><ins>+
+ currentHandlerInfos.push(handlerInfo);
</ins><span class="cx"> }
</span><span class="cx">
</span><ins>+
</ins><span class="cx"> /**
</span><span class="cx"> @private
</span><span class="cx">
</span><span class="lines">@@ -22471,41 +32728,38 @@
</span><span class="cx"> */
</span><span class="cx"> function eachHandler(handlerInfos, callback) {
</span><span class="cx"> for (var i=0, l=handlerInfos.length; i<l; i++) {
</span><del>- var handlerInfo = handlerInfos[i],
- handler = handlerInfo.handler,
- context = handlerInfo.context;
-
- callback(handler, context);
</del><ins>+ callback(handlerInfos[i]);
</ins><span class="cx"> }
</span><span class="cx"> }
</span><span class="cx">
</span><span class="cx"> /**
</span><span class="cx"> @private
</span><span class="cx">
</span><del>- Updates the `handler` field in each element in an Array of
- `UnresolvedHandlerInfo`s from a handler name to a resolved handler.
-
- When done, the Array will contain `HandlerInfo` structures.
-
- @param {Router} router
- @param {Array[UnresolvedHandlerInfo]} handlerInfos
- */
- function resolveHandlers(router, handlerInfos) {
- var handlerInfo;
-
- for (var i=0, l=handlerInfos.length; i<l; i++) {
- handlerInfo = handlerInfos[i];
-
- handlerInfo.name = handlerInfo.handler;
- handlerInfo.handler = router.getHandler(handlerInfo.handler);
</del><ins>+ determines if two queryparam objects are the same or not
+ **/
+ function queryParamsEqual(a, b) {
+ a = a || {};
+ b = b || {};
+ var checkedKeys = [], key;
+ for(key in a) {
+ if (!a.hasOwnProperty(key)) { continue; }
+ if(b[key] !== a[key]) { return false; }
+ checkedKeys.push(key);
</ins><span class="cx"> }
</span><ins>+ for(key in b) {
+ if (!b.hasOwnProperty(key)) { continue; }
+ if (~checkedKeys.indexOf(key)) { continue; }
+ // b has a key not in a
+ return false;
+ }
+ return true;
</ins><span class="cx"> }
</span><span class="cx">
</span><span class="cx"> /**
</span><span class="cx"> @private
</span><span class="cx">
</span><span class="cx"> This function is called when transitioning from one URL to
</span><del>- another to determine which handlers are not longer active,
</del><ins>+ another to determine which handlers are no longer active,
</ins><span class="cx"> which handlers are newly active, and which handlers remain
</span><span class="cx"> active but have their context changed.
</span><span class="cx">
</span><span class="lines">@@ -22523,7 +32777,7 @@
</span><span class="cx"> * entered: the handler was not active in the old URL, but
</span><span class="cx"> is now active.
</span><span class="cx">
</span><del>- The PartitionedHandlers structure has three fields:
</del><ins>+ The PartitionedHandlers structure has four fields:
</ins><span class="cx">
</span><span class="cx"> * `updatedContext`: a list of `HandlerInfo` objects that
</span><span class="cx"> represent handlers that remain active but have a changed
</span><span class="lines">@@ -22532,6 +32786,7 @@
</span><span class="cx"> handlers that are newly active
</span><span class="cx"> * `exited`: a list of `HandlerInfo` objects that are no
</span><span class="cx"> longer active.
</span><ins>+ * `unchanged`: a list of `HanderInfo` objects that remain active.
</ins><span class="cx">
</span><span class="cx"> @param {Array[HandlerInfo]} oldHandlers a list of the handler
</span><span class="cx"> information for the previous URL (or `[]` if this is the
</span><span class="lines">@@ -22545,24 +32800,29 @@
</span><span class="cx"> var handlers = {
</span><span class="cx"> updatedContext: [],
</span><span class="cx"> exited: [],
</span><del>- entered: []
</del><ins>+ entered: [],
+ unchanged: []
</ins><span class="cx"> };
</span><span class="cx">
</span><del>- var handlerChanged, contextChanged, i, l;
</del><ins>+ var handlerChanged, contextChanged, queryParamsChanged, i, l;
</ins><span class="cx">
</span><span class="cx"> for (i=0, l=newHandlers.length; i<l; i++) {
</span><span class="cx"> var oldHandler = oldHandlers[i], newHandler = newHandlers[i];
</span><span class="cx">
</span><span class="cx"> if (!oldHandler || oldHandler.handler !== newHandler.handler) {
</span><span class="cx"> handlerChanged = true;
</span><ins>+ } else if (!queryParamsEqual(oldHandler.queryParams, newHandler.queryParams)) {
+ queryParamsChanged = true;
</ins><span class="cx"> }
</span><span class="cx">
</span><span class="cx"> if (handlerChanged) {
</span><span class="cx"> handlers.entered.push(newHandler);
</span><span class="cx"> if (oldHandler) { handlers.exited.unshift(oldHandler); }
</span><del>- } else if (contextChanged || oldHandler.context !== newHandler.context) {
</del><ins>+ } else if (contextChanged || oldHandler.context !== newHandler.context || queryParamsChanged) {
</ins><span class="cx"> contextChanged = true;
</span><span class="cx"> handlers.updatedContext.push(newHandler);
</span><ins>+ } else {
+ handlers.unchanged.push(oldHandler);
</ins><span class="cx"> }
</span><span class="cx"> }
</span><span class="cx">
</span><span class="lines">@@ -22573,33 +32833,516 @@
</span><span class="cx"> return handlers;
</span><span class="cx"> }
</span><span class="cx">
</span><del>- function trigger(router, args) {
- var currentHandlerInfos = router.currentHandlerInfos;
</del><ins>+ function trigger(router, handlerInfos, ignoreFailure, args) {
+ if (router.triggerEvent) {
+ router.triggerEvent(handlerInfos, ignoreFailure, args);
+ return;
+ }
</ins><span class="cx">
</span><span class="cx"> var name = args.shift();
</span><span class="cx">
</span><del>- if (!currentHandlerInfos) {
</del><ins>+ if (!handlerInfos) {
+ if (ignoreFailure) { return; }
</ins><span class="cx"> throw new Error("Could not trigger event '" + name + "'. There are no active handlers");
</span><span class="cx"> }
</span><span class="cx">
</span><del>- for (var i=currentHandlerInfos.length-1; i>=0; i--) {
- var handlerInfo = currentHandlerInfos[i],
</del><ins>+ var eventWasHandled = false;
+
+ for (var i=handlerInfos.length-1; i>=0; i--) {
+ var handlerInfo = handlerInfos[i],
</ins><span class="cx"> handler = handlerInfo.handler;
</span><span class="cx">
</span><span class="cx"> if (handler.events && handler.events[name]) {
</span><del>- handler.events[name].apply(handler, args);
- return;
</del><ins>+ if (handler.events[name].apply(handler, args) === true) {
+ eventWasHandled = true;
+ } else {
+ return;
+ }
</ins><span class="cx"> }
</span><span class="cx"> }
</span><span class="cx">
</span><del>- throw new Error("Nothing handled the event '" + name + "'.");
</del><ins>+ if (!eventWasHandled && !ignoreFailure) {
+ throw new Error("Nothing handled the event '" + name + "'.");
+ }
</ins><span class="cx"> }
</span><span class="cx">
</span><span class="cx"> function setContext(handler, context) {
</span><span class="cx"> handler.context = context;
</span><span class="cx"> if (handler.contextDidChange) { handler.contextDidChange(); }
</span><span class="cx"> }
</span><del>- return Router;
</del><ins>+
+ function setQueryParams(handler, queryParams) {
+ handler.queryParams = queryParams;
+ if (handler.queryParamsDidChange) { handler.queryParamsDidChange(); }
+ }
+
+
+ /**
+ @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];
+ }
+ }
+
+ function performIntermediateTransition(router, recogHandlers, matchPointResults) {
+
+ var handlerInfos = generateHandlerInfos(router, recogHandlers);
+ for (var i = 0; i < handlerInfos.length; ++i) {
+ var handlerInfo = handlerInfos[i];
+ handlerInfo.context = matchPointResults.providedModels[handlerInfo.name];
+ }
+
+ var stubbedTransition = {
+ router: router,
+ isAborted: false
+ };
+
+ setupContexts(stubbedTransition, handlerInfos);
+ }
+
+ /**
+ @private
+
+ Creates, begins, and returns a Transition.
+ */
+ function performTransition(router, recogHandlers, providedModelsArray, params, queryParams, data, isIntermediate) {
+
+ var matchPointResults = getMatchPoint(router, recogHandlers, providedModelsArray, params, queryParams),
+ targetName = recogHandlers[recogHandlers.length - 1].handler,
+ wasTransitioning = false,
+ currentHandlerInfos = router.currentHandlerInfos;
+
+ if (isIntermediate) {
+ return performIntermediateTransition(router, recogHandlers, matchPointResults);
+ }
+
+ // Check if there's already a transition underway.
+ if (router.activeTransition) {
+ if (transitionsIdentical(router.activeTransition, targetName, providedModelsArray, queryParams)) {
+ return router.activeTransition;
+ }
+ router.activeTransition.abort();
+ wasTransitioning = true;
+ }
+
+ var deferred = RSVP.defer(),
+ transition = new Transition(router, deferred.promise);
+
+ transition.targetName = targetName;
+ transition.providedModels = matchPointResults.providedModels;
+ transition.providedModelsArray = providedModelsArray;
+ transition.params = matchPointResults.params;
+ transition.data = data || {};
+ transition.queryParams = queryParams;
+ transition.pivotHandler = matchPointResults.pivotHandler;
+ router.activeTransition = transition;
+
+ var handlerInfos = generateHandlerInfos(router, recogHandlers);
+ transition.handlerInfos = handlerInfos;
+
+ // Fire 'willTransition' event on current handlers, but don't fire it
+ // if a transition was already underway.
+ if (!wasTransitioning) {
+ trigger(router, currentHandlerInfos, true, ['willTransition', transition]);
+ }
+
+ log(router, transition.sequence, "Beginning validation for transition to " + transition.targetName);
+ validateEntry(transition, matchPointResults.matchPoint, matchPointResults.handlerParams)
+ .then(transitionSuccess, transitionFailure);
+
+ return transition;
+
+ function transitionSuccess() {
+ checkAbort(transition);
+
+ try {
+ finalizeTransition(transition, handlerInfos);
+
+ // currentHandlerInfos was updated in finalizeTransition
+ trigger(router, router.currentHandlerInfos, true, ['didTransition']);
+
+ if (router.didTransition) {
+ router.didTransition(handlerInfos);
+ }
+
+ log(router, transition.sequence, "TRANSITION COMPLETE.");
+
+ // Resolve with the final handler.
+ transition.isActive = false;
+ deferred.resolve(handlerInfos[handlerInfos.length - 1].handler);
+ } catch(e) {
+ deferred.reject(e);
+ }
+
+ // Don't nullify if another transition is underway (meaning
+ // there was a transition initiated with enter/setup).
+ if (!transition.isAborted) {
+ router.activeTransition = null;
+ }
+ }
+
+ function transitionFailure(reason) {
+ deferred.reject(reason);
+ }
+ }
+
+ /**
+ @private
+
+ Accepts handlers in Recognizer format, either returned from
+ recognize() or handlersFor(), and returns unified
+ `HandlerInfo`s.
+ */
+ function generateHandlerInfos(router, recogHandlers) {
+ var handlerInfos = [];
+ for (var i = 0, len = recogHandlers.length; i < len; ++i) {
+ var handlerObj = recogHandlers[i],
+ isDynamic = handlerObj.isDynamic || (handlerObj.names && handlerObj.names.length);
+
+ var handlerInfo = {
+ isDynamic: !!isDynamic,
+ name: handlerObj.handler,
+ handler: router.getHandler(handlerObj.handler)
+ };
+ if(handlerObj.queryParams) {
+ handlerInfo.queryParams = handlerObj.queryParams;
+ }
+ handlerInfos.push(handlerInfo);
+ }
+ return handlerInfos;
+ }
+
+ /**
+ @private
+ */
+ function transitionsIdentical(oldTransition, targetName, providedModelsArray, queryParams) {
+
+ if (oldTransition.targetName !== targetName) { return false; }
+
+ var oldModels = oldTransition.providedModelsArray;
+ if (oldModels.length !== providedModelsArray.length) { return false; }
+
+ for (var i = 0, len = oldModels.length; i < len; ++i) {
+ if (oldModels[i] !== providedModelsArray[i]) { return false; }
+ }
+
+ if(!queryParamsEqual(oldTransition.queryParams, queryParams)) {
+ return false;
+ }
+
+ return true;
+ }
+
+ /**
+ @private
+
+ Updates the URL (if necessary) and calls `setupContexts`
+ to update the router's array of `currentHandlerInfos`.
+ */
+ function finalizeTransition(transition, handlerInfos) {
+
+ log(transition.router, transition.sequence, "Validation succeeded, finalizing transition;");
+
+ var router = transition.router,
+ seq = transition.sequence,
+ handlerName = handlerInfos[handlerInfos.length - 1].name,
+ urlMethod = transition.urlMethod,
+ i;
+
+ // Collect params for URL.
+ var objects = [], providedModels = transition.providedModelsArray.slice();
+ for (i = handlerInfos.length - 1; i>=0; --i) {
+ var handlerInfo = handlerInfos[i];
+ if (handlerInfo.isDynamic) {
+ var providedModel = providedModels.pop();
+ objects.unshift(isParam(providedModel) ? providedModel.toString() : handlerInfo.context);
+ }
+
+ if (handlerInfo.handler.inaccessibleByURL) {
+ urlMethod = null;
+ }
+ }
+
+ var newQueryParams = {};
+ for (i = handlerInfos.length - 1; i>=0; --i) {
+ merge(newQueryParams, handlerInfos[i].queryParams);
+ }
+ router.currentQueryParams = newQueryParams;
+
+
+ var params = paramsForHandler(router, handlerName, objects, transition.queryParams);
+
+ router.currentParams = params;
+
+ if (urlMethod) {
+ var url = router.recognizer.generate(handlerName, params);
+
+ if (urlMethod === 'replace') {
+ router.replaceURL(url);
+ } else {
+ // Assume everything else is just a URL update for now.
+ router.updateURL(url);
+ }
+ }
+
+ setupContexts(transition, handlerInfos);
+ }
+
+ /**
+ @private
+
+ Internal function used to construct the chain of promises used
+ to validate a transition. Wraps calls to `beforeModel`, `model`,
+ and `afterModel` in promises, and checks for redirects/aborts
+ between each.
+ */
+ function validateEntry(transition, matchPoint, handlerParams) {
+
+ var handlerInfos = transition.handlerInfos,
+ index = transition.resolveIndex;
+
+ if (index === handlerInfos.length) {
+ // No more contexts to resolve.
+ return RSVP.resolve(transition.resolvedModels);
+ }
+
+ var router = transition.router,
+ handlerInfo = handlerInfos[index],
+ handler = handlerInfo.handler,
+ handlerName = handlerInfo.name,
+ seq = transition.sequence;
+
+ if (index < matchPoint) {
+ log(router, seq, handlerName + ": using context from already-active handler");
+
+ // We're before the match point, so don't run any hooks,
+ // just use the already resolved context from the handler.
+ transition.resolvedModels[handlerInfo.name] =
+ transition.providedModels[handlerInfo.name] ||
+ handlerInfo.handler.context;
+ return proceed();
+ }
+
+ transition.trigger(true, 'willResolveModel', transition, handler);
+
+ return RSVP.resolve().then(handleAbort)
+ .then(beforeModel)
+ .then(handleAbort)
+ .then(model)
+ .then(handleAbort)
+ .then(afterModel)
+ .then(handleAbort)
+ .then(null, handleError)
+ .then(proceed);
+
+ function handleAbort(result) {
+ if (transition.isAborted) {
+ log(transition.router, transition.sequence, "detected abort.");
+ return RSVP.reject(new Router.TransitionAborted());
+ }
+
+ return result;
+ }
+
+ function handleError(reason) {
+ if (reason instanceof Router.TransitionAborted || transition.isAborted) {
+ // if the transition was aborted and *no additional* error was thrown,
+ // reject with the Router.TransitionAborted instance
+ return RSVP.reject(reason);
+ }
+
+ // otherwise, we're here because of a different error
+ transition.abort();
+
+ log(router, seq, handlerName + ": handling error: " + reason);
+
+ // An error was thrown / promise rejected, so fire an
+ // `error` event from this handler info up to root.
+ transition.trigger(true, 'error', reason, transition, handlerInfo.handler);
+
+ // Propagate the original error.
+ return RSVP.reject(reason);
+ }
+
+ function beforeModel() {
+
+ log(router, seq, handlerName + ": calling beforeModel hook");
+
+ var args;
+
+ if (handlerInfo.queryParams) {
+ args = [handlerInfo.queryParams, transition];
+ } else {
+ args = [transition];
+ }
+
+ var p = handler.beforeModel && handler.beforeModel.apply(handler, args);
+ return (p instanceof Transition) ? null : p;
+ }
+
+ function model() {
+ log(router, seq, handlerName + ": resolving model");
+ var p = getModel(handlerInfo, transition, handlerParams[handlerName], index >= matchPoint);
+ return (p instanceof Transition) ? null : p;
+ }
+
+ function afterModel(context) {
+
+ log(router, seq, handlerName + ": calling afterModel hook");
+
+ // Pass the context and resolved parent contexts to afterModel, but we don't
+ // want to use the value returned from `afterModel` in any way, but rather
+ // always resolve with the original `context` object.
+
+ transition.resolvedModels[handlerInfo.name] = context;
+
+ var args;
+
+ if (handlerInfo.queryParams) {
+ args = [context, handlerInfo.queryParams, transition];
+ } else {
+ args = [context, transition];
+ }
+
+ var p = handler.afterModel && handler.afterModel.apply(handler, args);
+ return (p instanceof Transition) ? null : p;
+ }
+
+ function proceed() {
+ log(router, seq, handlerName + ": validation succeeded, proceeding");
+
+ handlerInfo.context = transition.resolvedModels[handlerInfo.name];
+ transition.resolveIndex++;
+ return validateEntry(transition, matchPoint, handlerParams);
+ }
+ }
+
+ /**
+ @private
+
+ Throws a TransitionAborted if the provided transition has been aborted.
+ */
+ function checkAbort(transition) {
+ if (transition.isAborted) {
+ log(transition.router, transition.sequence, "detected abort.");
+ throw new Router.TransitionAborted();
+ }
+ }
+
+ /**
+ @private
+
+ Encapsulates the logic for whether to call `model` on a route,
+ or use one of the models provided to `transitionTo`.
+ */
+ function getModel(handlerInfo, transition, handlerParams, needsUpdate) {
+ var handler = handlerInfo.handler,
+ handlerName = handlerInfo.name, args;
+
+ if (!needsUpdate && handler.hasOwnProperty('context')) {
+ return handler.context;
+ }
+
+ if (transition.providedModels.hasOwnProperty(handlerName)) {
+ var providedModel = transition.providedModels[handlerName];
+ return typeof providedModel === 'function' ? providedModel() : providedModel;
+ }
+
+ if (handlerInfo.queryParams) {
+ args = [handlerParams || {}, handlerInfo.queryParams, transition];
+ } else {
+ args = [handlerParams || {}, transition, handlerInfo.queryParams];
+ }
+
+ return handler.model && handler.model.apply(handler, args);
+ }
+
+ /**
+ @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);
+ }
+ }
+
+ /**
+ @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] || '/';
+
+ if(args.length === 1 && args[0].hasOwnProperty('queryParams')) {
+ return createQueryParamTransition(router, args[0], isIntermediate);
+ } else if (name.charAt(0) === '/') {
+ return createURLTransition(router, name, isIntermediate);
+ } else {
+ return createNamedTransition(router, slice.call(args), isIntermediate);
+ }
+ }
+
+ /**
+ @private
+
+ Serializes a handler using its custom `serialize` method or
+ by a default that looks up the expected property name from
+ the dynamic segment.
+
+ @param {Object} handler a router handler
+ @param {Object} model the model to be serialized for this handler
+ @param {Array[Object]} names the names array attached to an
+ handler object returned from router.recognizer.handlersFor()
+ */
+ function serialize(handler, model, names) {
+
+ var object = {};
+ if (isParam(model)) {
+ object[names[0]] = model;
+ return object;
+ }
+
+ // Use custom serialize if it exists.
+ if (handler.serialize) {
+ return handler.serialize(model, names);
+ }
+
+ if (names.length !== 1) { return; }
+
+ var name = names[0];
+
+ if (/_id$/.test(name)) {
+ object[name] = model.id;
+ } else {
+ object[name] = model;
+ }
+ return object;
+ }
</ins><span class="cx"> });
</span><span class="cx">
</span><span class="cx"> })();
</span><span class="lines">@@ -22634,35 +33377,28 @@
</span><span class="cx">
</span><span class="cx"> if (callback) {
</span><span class="cx"> var dsl = new DSL(name);
</span><ins>+ route(dsl, 'loading');
+ route(dsl, 'error', { path: "/_unused_dummy_error_path_route_" + name + "/:error" });
</ins><span class="cx"> callback.call(dsl);
</span><del>- this.push(options.path, name, dsl.generate());
</del><ins>+ this.push(options.path, name, dsl.generate(), options.queryParams);
</ins><span class="cx"> } else {
</span><del>- this.push(options.path, name);
</del><ins>+ this.push(options.path, name, null, options.queryParams);
</ins><span class="cx"> }
</span><del>- },
</del><span class="cx">
</span><del>- push: function(url, name, callback) {
- if (url === "" || url === "/") { this.explicitIndex = true; }
</del><span class="cx">
</span><del>- this.matches.push([url, name, callback]);
</del><ins>+ },
+
+ push: function(url, name, callback, queryParams) {
+ var parts = name.split('.');
+ if (url === "" || url === "/" || parts[parts.length-1] === "index") { this.explicitIndex = true; }
+
+ this.matches.push([url, name, callback, queryParams]);
</ins><span class="cx"> },
</span><span class="cx">
</span><span class="cx"> route: function(name, options) {
</span><del>- Ember.assert("You must use `this.resource` to nest", typeof options !== 'function');
</del><ins>+ route(this, name, options);
+ },
</ins><span class="cx">
</span><del>- options = options || {};
-
- if (typeof options.path !== 'string') {
- options.path = "/" + name;
- }
-
- if (this.parent && this.parent !== 'application') {
- name = this.parent + "." + name;
- }
-
- this.push(options.path, name);
- },
-
</del><span class="cx"> generate: function() {
</span><span class="cx"> var dslMatches = this.matches;
</span><span class="cx">
</span><span class="lines">@@ -22673,12 +33409,28 @@
</span><span class="cx"> return function(match) {
</span><span class="cx"> for (var i=0, l=dslMatches.length; i<l; i++) {
</span><span class="cx"> var dslMatch = dslMatches[i];
</span><del>- match(dslMatch[0]).to(dslMatch[1], dslMatch[2]);
- }
</del><ins>+ var matchObj = match(dslMatch[0]).to(dslMatch[1], dslMatch[2]);
+ }
</ins><span class="cx"> };
</span><span class="cx"> }
</span><span class="cx"> };
</span><span class="cx">
</span><ins>+function route(dsl, name, options) {
+ Ember.assert("You must use `this.resource` to nest", typeof options !== 'function');
+
+ options = options || {};
+
+ if (typeof options.path !== 'string') {
+ options.path = "/" + name;
+ }
+
+ if (dsl.parent && dsl.parent !== 'application') {
+ name = dsl.parent + "." + name;
+ }
+
+ dsl.push(options.path, name, null, options.queryParams);
+}
+
</ins><span class="cx"> DSL.map = function(callback) {
</span><span class="cx"> var dsl = new DSL();
</span><span class="cx"> callback.call(dsl);
</span><span class="lines">@@ -22692,39 +33444,91 @@
</span><span class="cx">
</span><span class="cx">
</span><span class="cx"> (function() {
</span><ins>+var get = Ember.get;
+
</ins><span class="cx"> /**
</span><span class="cx"> @module ember
</span><span class="cx"> @submodule ember-routing
</span><span class="cx"> */
</span><span class="cx">
</span><del>-Ember.controllerFor = function(container, controllerName, context) {
- return container.lookup('controller:' + controllerName) ||
- Ember.generateController(container, controllerName, context);
</del><ins>+/**
+
+ Finds a controller instance.
+
+ @for Ember
+ @method controllerFor
+ @private
+*/
+Ember.controllerFor = function(container, controllerName, lookupOptions) {
+ return container.lookup('controller:' + controllerName, lookupOptions);
</ins><span class="cx"> };
</span><span class="cx">
</span><del>-Ember.generateController = function(container, controllerName, context) {
- var controller;
</del><ins>+/**
+ Generates a controller factory
</ins><span class="cx">
</span><ins>+ The type of the generated controller factory is derived
+ from the context. If the context is an array an array controller
+ is generated, if an object, an object controller otherwise, a basic
+ controller is generated.
+
+ You can customize your generated controllers by defining
+ `App.ObjectController` or `App.ArrayController`.
+
+ @for Ember
+ @method generateControllerFactory
+ @private
+*/
+Ember.generateControllerFactory = function(container, controllerName, context) {
+ var Factory, fullName, instance, name, factoryName, controllerType;
+
</ins><span class="cx"> if (context && Ember.isArray(context)) {
</span><del>- controller = Ember.ArrayController.extend({
- content: context
- });
</del><ins>+ controllerType = 'array';
</ins><span class="cx"> } else if (context) {
</span><del>- controller = Ember.ObjectController.extend({
- content: context
- });
</del><ins>+ controllerType = 'object';
</ins><span class="cx"> } else {
</span><del>- controller = Ember.Controller.extend();
</del><ins>+ controllerType = 'basic';
</ins><span class="cx"> }
</span><span class="cx">
</span><del>- controller.toString = function() {
- return "(generated " + controllerName + " controller)";
- };
</del><ins>+ factoryName = 'controller:' + controllerType;
</ins><span class="cx">
</span><del>- container.register('controller', controllerName, controller);
- return container.lookup('controller:' + controllerName);
</del><ins>+ Factory = container.lookupFactory(factoryName).extend({
+ isGenerated: true,
+ toString: function() {
+ return "(generated " + controllerName + " controller)";
+ }
+ });
+
+ fullName = 'controller:' + controllerName;
+
+ container.register(fullName, Factory);
+
+ return Factory;
</ins><span class="cx"> };
</span><span class="cx">
</span><ins>+/**
+ Generates and instantiates a controller.
+
+ The type of the generated controller factory is derived
+ from the context. If the context is an array an array controller
+ is generated, if an object, an object controller otherwise, a basic
+ controller is generated.
+
+ @for Ember
+ @method generateController
+ @private
+*/
+Ember.generateController = function(container, controllerName, context) {
+ Ember.generateControllerFactory(container, controllerName, context);
+ var fullName = 'controller:' + controllerName;
+ var instance = container.lookup(fullName);
+
+ if (get(instance, 'namespace.LOG_ACTIVE_GENERATION')) {
+ Ember.Logger.info("generated -> " + fullName, { fullName: fullName });
+ }
+
+ return instance;
+};
+
</ins><span class="cx"> })();
</span><span class="cx">
</span><span class="cx">
</span><span class="lines">@@ -22735,25 +33539,12 @@
</span><span class="cx"> @submodule ember-routing
</span><span class="cx"> */
</span><span class="cx">
</span><del>-var Router = requireModule("router");
-var get = Ember.get, set = Ember.set, classify = Ember.String.classify;
</del><ins>+var Router = requireModule("router")['default'];
+var get = Ember.get, set = Ember.set;
+var defineProperty = Ember.defineProperty;
+var slice = Array.prototype.slice;
</ins><span class="cx">
</span><span class="cx"> var DefaultView = Ember._MetamorphView;
</span><del>-function setupLocation(router) {
- var location = get(router, 'location'),
- rootURL = get(router, 'rootURL');
-
- if ('string' === typeof location) {
- location = set(router, 'location', Ember.Location.create({
- implementation: location
- }));
-
- if (typeof rootURL === 'string') {
- set(location, 'rootURL', rootURL);
- }
- }
-}
-
</del><span class="cx"> /**
</span><span class="cx"> The `Ember.Router` class manages the application state and URLs. Refer to
</span><span class="cx"> the [routing guide](http://emberjs.com/guides/routing/) for documentation.
</span><span class="lines">@@ -22762,19 +33553,50 @@
</span><span class="cx"> @namespace Ember
</span><span class="cx"> @extends Ember.Object
</span><span class="cx"> */
</span><del>-Ember.Router = Ember.Object.extend({
</del><ins>+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}
+ */
</ins><span class="cx"> location: 'hash',
</span><span class="cx">
</span><span class="cx"> init: function() {
</span><del>- this.router = this.constructor.router;
</del><ins>+ this.router = this.constructor.router || this.constructor.map(Ember.K);
</ins><span class="cx"> this._activeViews = {};
</span><del>- setupLocation(this);
</del><ins>+ this._setupLocation();
+
+ if (get(this, 'namespace.LOG_TRANSITIONS_INTERNAL')) {
+ this.router.log = Ember.Logger.debug;
+ }
</ins><span class="cx"> },
</span><span class="cx">
</span><ins>+ /**
+ Represents the current URL.
+
+ @method url
+ @returns {String} The current URL.
+ */
</ins><span class="cx"> url: Ember.computed(function() {
</span><span class="cx"> return get(this, 'location').getURL();
</span><span class="cx"> }),
</span><span class="cx">
</span><ins>+ /**
+ Initializes the current router instance and sets up the change handling
+ event listeners used by the instances `location` implementation.
+
+ @method startRouting
+ @private
+ */
</ins><span class="cx"> startRouting: function() {
</span><span class="cx"> this.router = this.router || this.constructor.map(Ember.K);
</span><span class="cx">
</span><span class="lines">@@ -22783,10 +33605,10 @@
</span><span class="cx"> container = this.container,
</span><span class="cx"> self = this;
</span><span class="cx">
</span><del>- setupRouter(this, router, location);
</del><ins>+ this._setupRouter(router, location);
</ins><span class="cx">
</span><del>- container.register('view', 'default', DefaultView);
- container.register('view', 'toplevel', Ember.View.extend());
</del><ins>+ container.register('view:default', DefaultView);
+ container.register('view:toplevel', Ember.View.extend());
</ins><span class="cx">
</span><span class="cx"> location.onUpdateURL(function(url) {
</span><span class="cx"> self.handleURL(url);
</span><span class="lines">@@ -22795,36 +33617,52 @@
</span><span class="cx"> this.handleURL(location.getURL());
</span><span class="cx"> },
</span><span class="cx">
</span><ins>+ /**
+ Handles updating the paths and notifying any listeners of the URL
+ change.
+
+ Triggers the router level `didTransition` hook.
+
+ @method didTransition
+ @private
+ */
</ins><span class="cx"> didTransition: function(infos) {
</span><del>- // Don't do any further action here if we redirected
- for (var i=0, l=infos.length; i<l; i++) {
- if (infos[i].handler.redirected) { return; }
- }
</del><ins>+ updatePaths(this);
</ins><span class="cx">
</span><del>- var appController = this.container.lookup('controller:application'),
- path = routePath(infos);
</del><ins>+ this._cancelLoadingEvent();
</ins><span class="cx">
</span><del>- set(appController, 'currentPath', path);
</del><span class="cx"> this.notifyPropertyChange('url');
</span><span class="cx">
</span><ins>+ // 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');
+
</ins><span class="cx"> if (get(this, 'namespace').LOG_TRANSITIONS) {
</span><del>- Ember.Logger.log("Transitioned into '" + path + "'");
</del><ins>+ Ember.Logger.log("Transitioned into '" + Ember.Router._routePath(infos) + "'");
</ins><span class="cx"> }
</span><span class="cx"> },
</span><span class="cx">
</span><span class="cx"> handleURL: function(url) {
</span><del>- this.router.handleURL(url);
- this.notifyPropertyChange('url');
</del><ins>+ return this._doTransition('handleURL', [url]);
</ins><span class="cx"> },
</span><span class="cx">
</span><del>- transitionTo: function(name) {
- var args = [].slice.call(arguments);
- doTransition(this, 'transitionTo', args);
</del><ins>+ transitionTo: function() {
+ return this._doTransition('transitionTo', arguments);
</ins><span class="cx"> },
</span><span class="cx">
</span><ins>+ 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) + "'");
+ }
+ },
+
</ins><span class="cx"> replaceWith: function() {
</span><del>- var args = [].slice.call(arguments);
- doTransition(this, 'replaceWith', args);
</del><ins>+ return this._doTransition('replaceWith', arguments);
</ins><span class="cx"> },
</span><span class="cx">
</span><span class="cx"> generate: function() {
</span><span class="lines">@@ -22832,6 +33670,14 @@
</span><span class="cx"> return this.location.formatURL(url);
</span><span class="cx"> },
</span><span class="cx">
</span><ins>+ /**
+ Determines if the supplied route is currently active.
+
+ @method isActive
+ @param routeName
+ @returns {Boolean}
+ @private
+ */
</ins><span class="cx"> isActive: function(routeName) {
</span><span class="cx"> var router = this.router;
</span><span class="cx"> return router.isActive.apply(router, arguments);
</span><span class="lines">@@ -22841,10 +33687,35 @@
</span><span class="cx"> this.router.trigger.apply(this.router, arguments);
</span><span class="cx"> },
</span><span class="cx">
</span><ins>+ /**
+ Does this router instance have the given route.
+
+ @method hasRoute
+ @returns {Boolean}
+ @private
+ */
</ins><span class="cx"> hasRoute: function(route) {
</span><span class="cx"> return this.router.hasRoute(route);
</span><span class="cx"> },
</span><span class="cx">
</span><ins>+ /**
+ Resets the state of the router by clearing the current route
+ handlers and deactivating them.
+
+ @private
+ @method reset
+ */
+ reset: function() {
+ this.router.reset();
+ },
+
+ willDestroy: function(){
+ var location = get(this, 'location');
+ location.destroy();
+
+ this._super.apply(this, arguments);
+ },
+
</ins><span class="cx"> _lookupActiveView: function(templateName) {
</span><span class="cx"> var active = this._activeViews[templateName];
</span><span class="cx"> return active && active[0];
</span><span class="lines">@@ -22863,128 +33734,379 @@
</span><span class="cx">
</span><span class="cx"> this._activeViews[templateName] = [view, disconnect];
</span><span class="cx"> view.one('willDestroyElement', this, disconnect);
</span><del>- }
-});
</del><ins>+ },
</ins><span class="cx">
</span><del>-Ember.Router.reopenClass({
- defaultFailureHandler: {
- setup: function(error) {
- Ember.Logger.error('Error while loading route:', error);
</del><ins>+ _setupLocation: function() {
+ var location = get(this, 'location'),
+ rootURL = get(this, 'rootURL'),
+ options = {};
</ins><span class="cx">
</span><del>- // Using setTimeout allows us to escape from the Promise's try/catch block
- setTimeout(function() { throw error; });
</del><ins>+ if (typeof rootURL === 'string') {
+ options.rootURL = rootURL;
</ins><span class="cx"> }
</span><ins>+
+ if ('string' === typeof location) {
+ options.implementation = location;
+ location = set(this, 'location', Ember.Location.create(options));
+ }
+
+ // 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 passedName = args[0], name, self = this,
+ isQueryParamsOnly = false;
+
+
+ if (!isQueryParamsOnly && passedName.charAt(0) === '/') {
+ name = passedName;
+ } else if (!isQueryParamsOnly) {
+ if (!this.router.hasRoute(passedName)) {
+ name = args[0] = passedName + '.index';
+ } else {
+ name = passedName;
+ }
+
+ Ember.assert("The route " + passedName + " was not found", this.router.hasRoute(name));
+ }
+
+ var transitionPromise = this.router[method].apply(this.router, args);
+
+ transitionPromise.then(null, function(error) {
+ if (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;
</ins><span class="cx"> }
</span><span class="cx"> });
</span><span class="cx">
</span><del>-function getHandlerFunction(router) {
- var seen = {}, container = router.container;
</del><ins>+/**
+ Helper function for iterating root-ward, starting
+ from (but not including) the provided `originRoute`.
</ins><span class="cx">
</span><del>- return function(name) {
- var handler = container.lookup('route:' + name);
- if (seen[name]) { return handler; }
</del><ins>+ Returns true if the last callback fired requested
+ to bubble upward.
</ins><span class="cx">
</span><del>- seen[name] = true;
</del><ins>+ @private
+ */
+function forEachRouteAbove(originRoute, transition, callback) {
+ var handlerInfos = transition.handlerInfos,
+ originRouteFound = false;
</ins><span class="cx">
</span><del>- if (!handler) {
- if (name === 'loading') { return {}; }
- if (name === 'failure') { return router.constructor.defaultFailureHandler; }
</del><ins>+ for (var i = handlerInfos.length - 1; i >= 0; --i) {
+ var handlerInfo = handlerInfos[i],
+ route = handlerInfo.handler;
</ins><span class="cx">
</span><del>- container.register('route', name, Ember.Route.extend());
- handler = container.lookup('route:' + name);
</del><ins>+ if (!originRouteFound) {
+ if (originRoute === route) {
+ originRouteFound = true;
+ }
+ continue;
</ins><span class="cx"> }
</span><span class="cx">
</span><del>- handler.routeName = name;
- return handler;
- };
</del><ins>+ if (callback(route, handlerInfos[i + 1].handler) !== true) {
+ return false;
+ }
+ }
+ return true;
</ins><span class="cx"> }
</span><span class="cx">
</span><del>-function handlerIsActive(router, handlerName) {
- var handler = router.container.lookup('route:' + handlerName),
- currentHandlerInfos = router.router.currentHandlerInfos,
- handlerInfo;
</del><ins>+// These get invoked when an action bubbles above ApplicationRoute
+// and are not meant to be overridable.
+var defaultActionHandlers = {
</ins><span class="cx">
</span><del>- for (var i=0, l=currentHandlerInfos.length; i<l; i++) {
- handlerInfo = currentHandlerInfos[i];
- if (handlerInfo.handler === handler) { return true; }
- }
</del><ins>+ willResolveModel: function(transition, originRoute) {
+ originRoute.router._scheduleLoadingEvent(transition, originRoute);
+ },
</ins><span class="cx">
</span><del>- return false;
-}
</del><ins>+ error: function(error, transition, originRoute) {
+ // Attempt to find an appropriate error substate to enter.
+ var router = originRoute.router;
</ins><span class="cx">
</span><del>-function routePath(handlerInfos) {
- var path = [];
</del><ins>+ var tryTopLevel = forEachRouteAbove(originRoute, transition, function(route, childRoute) {
+ var childErrorRouteName = findChildRouteName(route, childRoute, 'error');
+ if (childErrorRouteName) {
+ router.intermediateTransitionTo(childErrorRouteName, error);
+ return;
+ }
+ return true;
+ });
</ins><span class="cx">
</span><del>- for (var i=1, l=handlerInfos.length; i<l; i++) {
- var name = handlerInfos[i].name,
- nameParts = name.split(".");
</del><ins>+ 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;
+ }
</ins><span class="cx">
</span><del>- path.push(nameParts[nameParts.length - 1]);
</del><ins>+ Ember.Logger.error('Error while loading route: ' + 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;
+ }
+ }
</ins><span class="cx"> }
</span><ins>+};
</ins><span class="cx">
</span><del>- return path.join(".");
</del><ins>+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;
+ }
</ins><span class="cx"> }
</span><span class="cx">
</span><del>-function setupRouter(emberRouter, router, location) {
- var lastURL;
</del><ins>+function routeHasBeenDefined(router, name) {
+ var container = router.container;
+ return router.hasRoute(name) &&
+ (container.has('template:' + name) || container.has('route:' + name));
+}
</ins><span class="cx">
</span><del>- router.getHandler = getHandlerFunction(emberRouter);
</del><ins>+function triggerEvent(handlerInfos, ignoreFailure, args) {
+ var name = args.shift();
</ins><span class="cx">
</span><del>- var doUpdateURL = function() {
- location.setURL(lastURL);
- };
</del><ins>+ 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.");
+ }
</ins><span class="cx">
</span><del>- router.updateURL = function(path) {
- lastURL = path;
- Ember.run.once(doUpdateURL);
- };
</del><ins>+ var eventWasHandled = false;
</ins><span class="cx">
</span><del>- if (location.replaceURL) {
- var doReplaceURL = function() {
- location.replaceURL(lastURL);
- };
</del><ins>+ for (var i = handlerInfos.length - 1; i >= 0; i--) {
+ var handlerInfo = handlerInfos[i],
+ handler = handlerInfo.handler;
</ins><span class="cx">
</span><del>- router.replaceURL = function(path) {
- lastURL = path;
- Ember.run.once(doReplaceURL);
- };
</del><ins>+ if (handler._actions && handler._actions[name]) {
+ if (handler._actions[name].apply(handler, args) === true) {
+ eventWasHandled = true;
+ } else {
+ return;
+ }
+ }
</ins><span class="cx"> }
</span><span class="cx">
</span><del>- router.didTransition = function(infos) {
- emberRouter.didTransition(infos);
- };
</del><ins>+ 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.");
+ }
</ins><span class="cx"> }
</span><span class="cx">
</span><del>-function doTransition(router, method, args) {
- var passedName = args[0], name;
</del><ins>+function updatePaths(router) {
+ var appController = router.container.lookup('controller:application');
</ins><span class="cx">
</span><del>- if (!router.router.hasRoute(args[0])) {
- name = args[0] = passedName + '.index';
- } else {
- name = passedName;
</del><ins>+ 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;
</ins><span class="cx"> }
</span><span class="cx">
</span><del>- Ember.assert("The route " + passedName + " was not found", router.router.hasRoute(name));
</del><ins>+ var infos = router.router.currentHandlerInfos,
+ path = Ember.Router._routePath(infos);
</ins><span class="cx">
</span><del>- router.router[method].apply(router.router, args);
- router.notifyPropertyChange('url');
</del><ins>+ 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);
</ins><span class="cx"> }
</span><span class="cx">
</span><span class="cx"> Ember.Router.reopenClass({
</span><ins>+ router: null,
</ins><span class="cx"> map: function(callback) {
</span><del>- var router = this.router = new Router();
</del><ins>+ var router = this.router;
+ if (!router) {
+ router = new Router();
+ router.callbacks = [];
+ router.triggerEvent = triggerEvent;
+ this.reopenClass({ router: router });
+ }
</ins><span class="cx">
</span><span class="cx"> var dsl = Ember.RouterDSL.map(function() {
</span><span class="cx"> this.resource('application', { path: "/" }, function() {
</span><ins>+ for (var i=0; i < router.callbacks.length; i++) {
+ router.callbacks[i].call(this);
+ }
</ins><span class="cx"> callback.call(this);
</span><span class="cx"> });
</span><span class="cx"> });
</span><span class="cx">
</span><ins>+ router.callbacks.push(callback);
</ins><span class="cx"> router.map(dsl.generate());
</span><span class="cx"> return router;
</span><ins>+ },
+
+ _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<l; i++) {
+ var name = handlerInfos[i].name,
+ nameParts = name.split("."),
+ oldNameParts = slice.call(path);
+
+ while (oldNameParts.length) {
+ if (intersectionMatches(oldNameParts, nameParts)) {
+ break;
+ }
+ oldNameParts.shift();
+ }
+
+ path.push.apply(path, nameParts.slice(oldNameParts.length));
+ }
+
+ return path.join(".");
</ins><span class="cx"> }
</span><span class="cx"> });
</span><span class="cx">
</span><ins>+Router.Transition.prototype.send = Router.Transition.prototype.trigger;
+
+
+
</ins><span class="cx"> })();
</span><span class="cx">
</span><span class="cx">
</span><span class="lines">@@ -22996,9 +34118,13 @@
</span><span class="cx"> */
</span><span class="cx">
</span><span class="cx"> var get = Ember.get, set = Ember.set,
</span><ins>+ getProperties = Ember.getProperties,
</ins><span class="cx"> classify = Ember.String.classify,
</span><del>- decamelize = Ember.String.decamelize;
</del><ins>+ fmt = Ember.String.fmt,
+ a_forEach = Ember.EnumerableUtils.forEach,
+ a_replace = Ember.EnumerableUtils.replace;
</ins><span class="cx">
</span><ins>+
</ins><span class="cx"> /**
</span><span class="cx"> The `Ember.Route` class is used to define individual routes. Refer to
</span><span class="cx"> the [routing guide](http://emberjs.com/guides/routing/) for documentation.
</span><span class="lines">@@ -23006,8 +34132,10 @@
</span><span class="cx"> @class Route
</span><span class="cx"> @namespace Ember
</span><span class="cx"> @extends Ember.Object
</span><ins>+ @uses Ember.ActionHandler
</ins><span class="cx"> */
</span><del>-Ember.Route = Ember.Object.extend({
</del><ins>+Ember.Route = Ember.Object.extend(Ember.ActionHandler, {
+
</ins><span class="cx"> /**
</span><span class="cx"> @private
</span><span class="cx">
</span><span class="lines">@@ -23015,7 +34143,7 @@
</span><span class="cx"> */
</span><span class="cx"> exit: function() {
</span><span class="cx"> this.deactivate();
</span><del>- teardownView(this);
</del><ins>+ this.teardownViews();
</ins><span class="cx"> },
</span><span class="cx">
</span><span class="cx"> /**
</span><span class="lines">@@ -23028,25 +34156,227 @@
</span><span class="cx"> },
</span><span class="cx">
</span><span class="cx"> /**
</span><del>- The collection of functions keyed by name available on this route as
</del><ins>+ The collection of functions, keyed by name, available on this route as
</ins><span class="cx"> action targets.
</span><span class="cx">
</span><span class="cx"> These functions will be invoked when a matching `{{action}}` is triggered
</span><span class="cx"> from within a template and the application's current route is this route.
</span><span class="cx">
</span><del>- Events can also be invoked from other parts of your application via `Route#send`.
</del><ins>+ Actions can also be invoked from other parts of your application via `Route#send`
+ or `Controller#send`.
</ins><span class="cx">
</span><del>- The context of event will be the this route.
</del><ins>+ The `actions` hash will inherit action handlers from
+ the `actions` hash defined on extended Route parent classes
+ or mixins rather than just replace the entire hash, e.g.:
</ins><span class="cx">
</span><del>- @see {Ember.Route#send}
- @see {Handlebars.helpers.action}
</del><ins>+ ```js
+ App.CanDisplayBanner = Ember.Mixin.create({
+ actions: {
+ displayBanner: function(msg) {
+ // ...
+ }
+ }
+ });
</ins><span class="cx">
</span><del>- @property events
</del><ins>+ App.WelcomeRoute = Ember.Route.extend(App.CanDisplayBanner, {
+ actions: {
+ playMusic: function() {
+ // ...
+ }
+ }
+ });
+
+ // `WelcomeRoute`, when active, will be able to respond
+ // to both actions, since the actions hash is merged rather
+ // then replaced when extending mixins / parent classes.
+ this.send('displayBanner');
+ this.send('playMusic');
+ ```
+
+ Within a route's action handler, the value of the `this` context
+ is the Route object:
+
+ ```js
+ App.SongRoute = Ember.Route.extend({
+ actions: {
+ myAction: function() {
+ this.controllerFor("song");
+ this.transitionTo("other.route");
+ ...
+ }
+ }
+ });
+ ```
+
+ It is also possible to call `this._super()` from within an
+ action handler if it overrides a handler defined on a parent
+ class or mixin:
+
+ Take for example the following routes:
+
+ ```js
+ App.DebugRoute = Ember.Mixin.create({
+ actions: {
+ debugRouteInformation: function() {
+ console.debug("trololo");
+ }
+ }
+ });
+
+ App.AnnoyingDebugRoute = Ember.Route.extend(App.DebugRoute, {
+ actions: {
+ debugRouteInformation: function() {
+ // also call the debugRouteInformation of mixed in App.DebugRoute
+ this._super();
+
+ // show additional annoyance
+ window.alert(...);
+ }
+ }
+ });
+ ```
+
+ ## Bubbling
+
+ By default, an action will stop bubbling once a handler defined
+ on the `actions` hash handles it. To continue bubbling the action,
+ you must return `true` from the handler:
+
+ ```js
+ App.Router.map(function() {
+ this.resource("album", function() {
+ this.route("song");
+ });
+ });
+
+ App.AlbumRoute = Ember.Route.extend({
+ actions: {
+ startPlaying: function() {
+ }
+ }
+ });
+
+ App.AlbumSongRoute = Ember.Route.extend({
+ actions: {
+ startPlaying: function() {
+ // ...
+
+ if (actionShouldAlsoBeTriggeredOnParentRoute) {
+ return true;
+ }
+ }
+ }
+ });
+ ```
+
+ ## Built-in actions
+
+ There are a few built-in actions pertaining to transitions that you
+ can use to customize transition behavior: `willTransition` and
+ `error`.
+
+ ### `willTransition`
+
+ The `willTransition` action is fired at the beginning of any
+ attempted transition with a `Transition` object as the sole
+ argument. This action can be used for aborting, redirecting,
+ or decorating the transition from the currently active routes.
+
+ A good example is preventing navigation when a form is
+ half-filled out:
+
+ ```js
+ App.ContactFormRoute = Ember.Route.extend({
+ actions: {
+ willTransition: function(transition) {
+ if (this.controller.get('userHasEnteredData')) {
+ this.controller.displayNavigationConfirm();
+ transition.abort();
+ }
+ }
+ }
+ });
+ ```
+
+ You can also redirect elsewhere by calling
+ `this.transitionTo('elsewhere')` from within `willTransition`.
+ Note that `willTransition` will not be fired for the
+ redirecting `transitionTo`, since `willTransition` doesn't
+ fire when there is already a transition underway. If you want
+ subsequent `willTransition` actions to fire for the redirecting
+ transition, you must first explicitly call
+ `transition.abort()`.
+
+ ### `error`
+
+ When attempting to transition into a route, any of the hooks
+ may return a promise that rejects, at which point an `error`
+ action will be fired on the partially-entered routes, allowing
+ for per-route error handling logic, or shared error handling
+ logic defined on a parent route.
+
+ Here is an example of an error handler that will be invoked
+ for rejected promises from the various hooks on the route,
+ as well as any unhandled errors from child routes:
+
+ ```js
+ App.AdminRoute = Ember.Route.extend({
+ beforeModel: function() {
+ return Ember.RSVP.reject("bad things!");
+ },
+
+ actions: {
+ error: function(error, transition) {
+ // Assuming we got here due to the error in `beforeModel`,
+ // we can expect that error === "bad things!",
+ // but a promise model rejecting would also
+ // call this hook, as would any errors encountered
+ // in `afterModel`.
+
+ // The `error` hook is also provided the failed
+ // `transition`, which can be stored and later
+ // `.retry()`d if desired.
+
+ this.transitionTo('login');
+ }
+ }
+ });
+ ```
+
+ `error` actions that bubble up all the way to `ApplicationRoute`
+ will fire a default error handler that logs the error. You can
+ specify your own global default error handler by overriding the
+ `error` handler on `ApplicationRoute`:
+
+ ```js
+ App.ApplicationRoute = Ember.Route.extend({
+ actions: {
+ error: function(error, transition) {
+ this.controllerFor('banner').displayError(error.message);
+ }
+ }
+ });
+ ```
+
+ @property actions
</ins><span class="cx"> @type Hash
</span><span class="cx"> @default null
</span><span class="cx"> */
</span><ins>+ _actions: {
+ finalizeQueryParamChange: function(params, finalParams) {
+ }
+ },
+
+ /**
+ @deprecated
+
+ Please use `actions` instead.
+ @method events
+ */
</ins><span class="cx"> events: null,
</span><span class="cx">
</span><ins>+ mergedProperties: ['events'],
+
</ins><span class="cx"> /**
</span><span class="cx"> This hook is executed when the router completely exits this route. It is
</span><span class="cx"> not executed when the model for the route changes.
</span><span class="lines">@@ -23056,79 +34386,213 @@
</span><span class="cx"> deactivate: Ember.K,
</span><span class="cx">
</span><span class="cx"> /**
</span><del>- This hook is executed when the router enters the route for the first time.
- It is not executed when the model for the route changes.
</del><ins>+ This hook is executed when the router enters the route. It is not executed
+ when the model for the route changes.
</ins><span class="cx">
</span><span class="cx"> @method activate
</span><span class="cx"> */
</span><span class="cx"> activate: Ember.K,
</span><span class="cx">
</span><span class="cx"> /**
</span><del>- Transition into another route. Optionally supply a model for the
- route in question. The model will be serialized into the URL
- using the `serialize` hook.
</del><ins>+ Transition into another route. Optionally supply model(s) for the
+ route in question. If multiple models are supplied they will be applied
+ last to first recursively up the resource tree (see Multiple Models Example
+ below). The model(s) will be serialized into the URL using the appropriate
+ route's `serialize` hook. See also 'replaceWith'.
</ins><span class="cx">
</span><ins>+ Simple Transition Example
+
+ ```javascript
+ App.Router.map(function() {
+ this.route("index");
+ this.route("secret");
+ this.route("fourOhFour", { path: "*:"});
+ });
+
+ App.IndexRoute = Ember.Route.extend({
+ actions: {
+ moveToSecret: function(context){
+ if (authorized()){
+ this.transitionTo('secret', context);
+ }
+ this.transitionTo('fourOhFour');
+ }
+ }
+ });
+ ```
+
+ Transition to a nested route
+
+ ```javascript
+ App.Router.map(function() {
+ this.resource('articles', { path: '/articles' }, function() {
+ this.route('new');
+ });
+ });
+
+ App.IndexRoute = Ember.Route.extend({
+ actions: {
+ transitionToNewArticle: function() {
+ this.transitionTo('articles.new');
+ }
+ }
+ });
+ ```
+
+ Multiple Models Example
+
+ ```javascript
+ App.Router.map(function() {
+ this.route("index");
+ this.resource('breakfast', {path:':breakfastId'}, function(){
+ this.resource('cereal', {path: ':cerealId'});
+ });
+ });
+
+ App.IndexRoute = Ember.Route.extend({
+ actions: {
+ moveToChocolateCereal: function(){
+ var cereal = { cerealId: "ChocolateYumminess"},
+ breakfast = {breakfastId: "CerealAndMilk"};
+
+ this.transitionTo('cereal', breakfast, cereal);
+ }
+ }
+ });
+
</ins><span class="cx"> @method transitionTo
</span><span class="cx"> @param {String} name the name of the route
</span><del>- @param {...Object} models the
</del><ins>+ @param {...Object} models the model(s) to be used while transitioning
+ to the route.
</ins><span class="cx"> */
</span><del>- transitionTo: function() {
- if (this._checkingRedirect) { this.redirected = true; }
- return this.router.transitionTo.apply(this.router, arguments);
</del><ins>+ transitionTo: function(name, context) {
+ var router = this.router;
+ return router.transitionTo.apply(router, arguments);
</ins><span class="cx"> },
</span><span class="cx">
</span><span class="cx"> /**
</span><del>- Transition into another route while replacing the current URL if
- possible. Identical to `transitionTo` in all other respects.
</del><ins>+ Perform a synchronous transition into another route with out attempting
+ to resolve promises, update the URL, or abort any currently active
+ asynchronous transitions (i.e. regular transitions caused by
+ `transitionTo` or URL changes).
</ins><span class="cx">
</span><ins>+ This method is handy for performing intermediate transitions on the
+ way to a final destination route, and is called internally by the
+ default implementations of the `error` and `loading` handlers.
+
+ @method intermediateTransitionTo
+ @param {String} name the name of the route
+ @param {...Object} models the model(s) to be used while transitioning
+ to the route.
+ */
+ intermediateTransitionTo: function() {
+ var router = this.router;
+ router.intermediateTransitionTo.apply(router, 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 `transitionTo` in all other respects. See
+ 'transitionTo' for additional information regarding multiple models.
+
+ Example
+
+ ```javascript
+ App.Router.map(function() {
+ this.route("index");
+ this.route("secret");
+ });
+
+ App.SecretRoute = Ember.Route.extend({
+ afterModel: function() {
+ if (!authorized()){
+ this.replaceWith('index');
+ }
+ }
+ });
+ ```
+
</ins><span class="cx"> @method replaceWith
</span><span class="cx"> @param {String} name the name of the route
</span><del>- @param {...Object} models the
</del><ins>+ @param {...Object} models the model(s) to be used while transitioning
+ to the route.
</ins><span class="cx"> */
</span><span class="cx"> replaceWith: function() {
</span><del>- if (this._checkingRedirect) { this.redirected = true; }
- return this.router.replaceWith.apply(this.router, arguments);
</del><ins>+ var router = this.router;
+ return router.replaceWith.apply(router, arguments);
</ins><span class="cx"> },
</span><span class="cx">
</span><ins>+ /**
+ Sends an action to the router, which will delegate it to the currently
+ active route hierarchy per the bubbling rules explained under `actions`.
+
+ Example
+
+ ```javascript
+ App.Router.map(function() {
+ this.route("index");
+ });
+
+ App.ApplicationRoute = Ember.Route.extend({
+ actions: {
+ track: function(arg) {
+ console.log(arg, 'was clicked');
+ }
+ }
+ });
+
+ App.IndexRoute = Ember.Route.extend({
+ actions: {
+ trackIfDebug: function(arg) {
+ if (debug) {
+ this.send('track', arg);
+ }
+ }
+ }
+ });
+ ```
+
+ @method send
+ @param {String} name the name of the action to trigger
+ @param {...*} args
+ */
</ins><span class="cx"> send: function() {
</span><span class="cx"> return this.router.send.apply(this.router, arguments);
</span><span class="cx"> },
</span><span class="cx">
</span><span class="cx"> /**
</span><del>- @private
-
</del><span class="cx"> This hook is the entry point for router.js
</span><span class="cx">
</span><ins>+ @private
</ins><span class="cx"> @method setup
</span><span class="cx"> */
</span><del>- setup: function(context) {
- this.redirected = false;
- this._checkingRedirect = true;
</del><ins>+ setup: function(context, queryParams) {
+ var controllerName = this.controllerName || this.routeName,
+ controller = this.controllerFor(controllerName, true);
+ if (!controller) {
+ controller = this.generateController(controllerName, context);
+ }
</ins><span class="cx">
</span><del>- this.redirect(context);
</del><ins>+ // Assign the route's controller so that it can more easily be
+ // referenced in action handlers
+ this.controller = controller;
</ins><span class="cx">
</span><del>- this._checkingRedirect = false;
- if (this.redirected) { return false; }
</del><ins>+ var args = [controller, context];
</ins><span class="cx">
</span><del>- var controller = this.controllerFor(this.routeName, context);
-
- if (controller) {
- this.controller = controller;
- set(controller, 'model', context);
- }
-
</del><ins>+
</ins><span class="cx"> if (this.setupControllers) {
</span><span class="cx"> Ember.deprecate("Ember.Route.setupControllers is deprecated. Please use Ember.Route.setupController(controller, model) instead.");
</span><span class="cx"> this.setupControllers(controller, context);
</span><span class="cx"> } else {
</span><del>- this.setupController(controller, context);
</del><ins>+ this.setupController.apply(this, args);
</ins><span class="cx"> }
</span><span class="cx">
</span><span class="cx"> if (this.renderTemplates) {
</span><span class="cx"> Ember.deprecate("Ember.Route.renderTemplates is deprecated. Please use Ember.Route.renderTemplate(controller, model) instead.");
</span><span class="cx"> this.renderTemplates(context);
</span><span class="cx"> } else {
</span><del>- this.renderTemplate(controller, context);
</del><ins>+ this.renderTemplate.apply(this, args);
</ins><span class="cx"> }
</span><span class="cx"> },
</span><span class="cx">
</span><span class="lines">@@ -23138,28 +34602,137 @@
</span><span class="cx"> If you call `this.transitionTo` from inside of this hook, this route
</span><span class="cx"> will not be entered in favor of the other hook.
</span><span class="cx">
</span><ins>+ Note that this hook is called by the default implementation of
+ `afterModel`, so if you override `afterModel`, you must either
+ explicitly call `redirect` or just put your redirecting
+ `this.transitionTo()` call within `afterModel`.
+
</ins><span class="cx"> @method redirect
</span><span class="cx"> @param {Object} model the model for this route
</span><span class="cx"> */
</span><span class="cx"> redirect: Ember.K,
</span><span class="cx">
</span><span class="cx"> /**
</span><del>- @private
</del><ins>+ This hook is the first of the route entry validation hooks
+ called when an attempt is made to transition into a route
+ or one of its children. It is called before `model` and
+ `afterModel`, and is appropriate for cases when:
</ins><span class="cx">
</span><del>- The hook called by `router.js` to convert parameters into the context
- for this handler. The public Ember hook is `model`.
</del><ins>+ 1) A decision can be made to redirect elsewhere without
+ needing to resolve the model first.
+ 2) Any async operations need to occur first before the
+ model is attempted to be resolved.
</ins><span class="cx">
</span><del>- @method deserialize
</del><ins>+ This hook is provided the current `transition` attempt
+ as a parameter, which can be used to `.abort()` the transition,
+ save it for a later `.retry()`, or retrieve values set
+ on it from a previous hook. You can also just call
+ `this.transitionTo` to another route to implicitly
+ abort the `transition`.
+
+ You can return a promise from this hook to pause the
+ transition until the promise resolves (or rejects). This could
+ be useful, for instance, for retrieving async code from
+ the server that is required to enter a route.
+
+ ```js
+ App.PostRoute = Ember.Route.extend({
+ beforeModel: function(transition) {
+ if (!App.Post) {
+ return Ember.$.getScript('/models/post.js');
+ }
+ }
+ });
+ ```
+
+ If `App.Post` doesn't exist in the above example,
+ `beforeModel` will use jQuery's `getScript`, which
+ returns a promise that resolves after the server has
+ successfully retrieved and executed the code from the
+ server. Note that if an error were to occur, it would
+ be passed to the `error` hook on `Ember.Route`, but
+ it's also possible to handle errors specific to
+ `beforeModel` right from within the hook (to distinguish
+ from the shared error handling behavior of the `error`
+ hook):
+
+ ```js
+ App.PostRoute = Ember.Route.extend({
+ beforeModel: function(transition) {
+ if (!App.Post) {
+ var self = this;
+ return Ember.$.getScript('post.js').then(null, function(e) {
+ self.transitionTo('help');
+
+ // Note that the above transitionTo will implicitly
+ // halt the transition. If you were to return
+ // nothing from this promise reject handler,
+ // according to promise semantics, that would
+ // convert the reject into a resolve and the
+ // transition would continue. To propagate the
+ // error so that it'd be handled by the `error`
+ // hook, you would have to either
+ return Ember.RSVP.reject(e);
+ // or
+ throw e;
+ });
+ }
+ }
+ });
+ ```
+
+ @method beforeModel
+ @param {Transition} transition
+ @param {Object} queryParams the active query params for this route
+ @return {Promise} if the value returned from this hook is
+ a promise, the transition will pause until the transition
+ resolves. Otherwise, non-promise return values are not
+ utilized in any way.
</ins><span class="cx"> */
</span><del>- deserialize: function(params) {
- var model = this.model(params);
- return this.currentModel = model;
</del><ins>+ beforeModel: Ember.K,
+
+ /**
+ This hook is called after this route's model has resolved.
+ It follows identical async/promise semantics to `beforeModel`
+ but is provided the route's resolved model in addition to
+ the `transition`, and is therefore suited to performing
+ logic that can only take place after the model has already
+ resolved.
+
+ ```js
+ App.PostsRoute = Ember.Route.extend({
+ afterModel: function(posts, transition) {
+ if (posts.length === 1) {
+ this.transitionTo('post.show', posts[0]);
+ }
+ }
+ });
+ ```
+
+ Refer to documentation for `beforeModel` for a description
+ of transition-pausing semantics when a promise is returned
+ from this hook.
+
+ @method afterModel
+ @param {Object} resolvedModel the value returned from `model`,
+ or its resolved value if it was a promise
+ @param {Transition} transition
+ @param {Object} queryParams the active query params for this handler
+ @return {Promise} if the value returned from this hook is
+ a promise, the transition will pause until the transition
+ resolves. Otherwise, non-promise return values are not
+ utilized in any way.
+ */
+ afterModel: function(resolvedModel, transition, queryParams) {
+ this.redirect(resolvedModel, transition);
</ins><span class="cx"> },
</span><span class="cx">
</span><ins>+
</ins><span class="cx"> /**
</span><ins>+ Called when the context is changed by router.js.
+
</ins><span class="cx"> @private
</span><del>-
- Called when the context is changed by router.js.
</del><ins>+ @method contextDidChange
</ins><span class="cx"> */
</span><span class="cx"> contextDidChange: function() {
</span><span class="cx"> this.currentModel = this.context;
</span><span class="lines">@@ -23170,8 +34743,8 @@
</span><span class="cx"> this route.
</span><span class="cx">
</span><span class="cx"> ```js
</span><del>- App.Route.map(function(match) {
- match("/posts/:post_id").to("post");
</del><ins>+ App.Router.map(function() {
+ this.resource('post', {path: '/posts/:post_id'});
</ins><span class="cx"> });
</span><span class="cx"> ```
</span><span class="cx">
</span><span class="lines">@@ -23184,10 +34757,38 @@
</span><span class="cx"> * The find method is called on the model class with the value of
</span><span class="cx"> the dynamic segment.
</span><span class="cx">
</span><ins>+ Note that for routes with dynamic segments, this hook is only
+ executed when entered via the URL. If the route is entered
+ through a transition (e.g. when using the `link-to` Handlebars
+ helper), then a model context is already provided and this hook
+ is not called. Routes without dynamic segments will always
+ execute the model hook.
+
+ This hook follows the asynchronous/promise semantics
+ described in the documentation for `beforeModel`. In particular,
+ if a promise returned from `model` fails, the error will be
+ handled by the `error` hook on `Ember.Route`.
+
+ Example
+
+ ```js
+ App.PostRoute = Ember.Route.extend({
+ model: function(params) {
+ return App.Post.find(params.post_id);
+ }
+ });
+ ```
+
</ins><span class="cx"> @method model
</span><span class="cx"> @param {Object} params the parameters extracted from the URL
</span><ins>+ @param {Transition} transition
+ @param {Object} queryParams the query params for this route
+ @return {Object|Promise} the model for this route. If
+ a promise is returned, the transition will pause until
+ the promise resolves, and the resolved value of the promise
+ will be used as the model for this route.
</ins><span class="cx"> */
</span><del>- model: function(params) {
</del><ins>+ model: function(params, transition) {
</ins><span class="cx"> var match, name, sawParams, value;
</span><span class="cx">
</span><span class="cx"> for (var prop in params) {
</span><span class="lines">@@ -23201,21 +34802,59 @@
</span><span class="cx"> if (!name && sawParams) { return params; }
</span><span class="cx"> else if (!name) { return; }
</span><span class="cx">
</span><del>- var className = classify(name),
- namespace = this.router.namespace,
- modelClass = namespace[className];
</del><ins>+ return this.findModel(name, value);
+ },
</ins><span class="cx">
</span><del>- Ember.assert("You used the dynamic segment " + name + "_id in your router, but " + namespace + "." + className + " did not exist and you did not override your state's `model` hook.", modelClass);
- return modelClass.find(value);
</del><ins>+ /**
+
+ @method findModel
+ @param {String} type the model type
+ @param {Object} value the value passed to find
+ */
+ findModel: function(){
+ var store = get(this, 'store');
+ return store.find.apply(store, arguments);
</ins><span class="cx"> },
</span><span class="cx">
</span><span class="cx"> /**
</span><ins>+ Store property provides a hook for data persistence libraries to inject themselves.
+
+ By default, this store property provides the exact same functionality previously
+ in the model hook.
+
+ Currently, the required interface is:
+
+ `store.find(modelName, findArguments)`
+
+ @method store
+ @param {Object} store
+ */
+ store: Ember.computed(function(){
+ var container = this.container;
+ var routeName = this.routeName;
+ var namespace = get(this, 'router.namespace');
+
+ return {
+ find: function(name, value) {
+ var modelClass = container.lookupFactory('model:' + name);
+
+ Ember.assert("You used the dynamic segment " + name + "_id in your route " +
+ routeName + ", but " + namespace + "." + classify(name) +
+ " did not exist and you did not override your route's `model` " +
+ "hook.", modelClass);
+
+ return modelClass.find(value);
+ }
+ };
+ }),
+
+ /**
</ins><span class="cx"> A hook you can implement to convert the route's model into parameters
</span><span class="cx"> for the URL.
</span><span class="cx">
</span><span class="cx"> ```js
</span><del>- App.Route.map(function(match) {
- match("/posts/:post_id").to("post");
</del><ins>+ App.Router.map(function() {
+ this.resource('post', {path: '/posts/:post_id'});
</ins><span class="cx"> });
</span><span class="cx">
</span><span class="cx"> App.PostRoute = Ember.Route.extend({
</span><span class="lines">@@ -23231,8 +34870,10 @@
</span><span class="cx"> });
</span><span class="cx"> ```
</span><span class="cx">
</span><del>- The default `serialize` method inserts the model's `id` into the
- route's dynamic segment (in this case, `:post_id`).
</del><ins>+ The default `serialize` method will insert the model's `id` into the
+ route's dynamic segment (in this case, `:post_id`) if the segment contains '_id'.
+ If the route has multiple dynamic segments or does not contain '_id', `serialize`
+ will return `Ember.getProperties(model, params)`
</ins><span class="cx">
</span><span class="cx"> This method is called when `transitionTo` is called with a context
</span><span class="cx"> in order to populate the URL.
</span><span class="lines">@@ -23244,14 +34885,15 @@
</span><span class="cx"> @return {Object} the serialized parameters
</span><span class="cx"> */
</span><span class="cx"> serialize: function(model, params) {
</span><del>- if (params.length !== 1) { return; }
</del><ins>+ if (params.length < 1) { return; }
+ if (!model) { return; }
</ins><span class="cx">
</span><span class="cx"> var name = params[0], object = {};
</span><span class="cx">
</span><del>- if (/_id$/.test(name)) {
- object[name] = get(model, 'id');
</del><ins>+ if (/_id$/.test(name) && params.length === 1) {
+ object[name] = get(model, "id");
</ins><span class="cx"> } else {
</span><del>- object[name] = model;
</del><ins>+ object = getProperties(model, params);
</ins><span class="cx"> }
</span><span class="cx">
</span><span class="cx"> return object;
</span><span class="lines">@@ -23263,34 +34905,60 @@
</span><span class="cx"> This method is called with the controller for the current route and the
</span><span class="cx"> model supplied by the `model` hook.
</span><span class="cx">
</span><del>- ```js
- App.Route.map(function(match) {
- match("/posts/:post_id").to("post");
- });
- ```
-
- For the `post` route, the controller is `App.PostController`.
-
</del><span class="cx"> By default, the `setupController` hook sets the `content` property of
</span><span class="cx"> the controller to the `model`.
</span><span class="cx">
</span><del>- If no explicit controller is defined, the route will automatically create
- an appropriate controller for the model:
</del><ins>+ This means that your template will get a proxy for the model as its
+ context, and you can act as though the model itself was the context.
</ins><span class="cx">
</span><ins>+ The provided controller will be one resolved based on the name
+ of this route.
+
+ If no explicit controller is defined, Ember will automatically create
+ an appropriate controller for the model.
+
</ins><span class="cx"> * if the model is an `Ember.Array` (including record arrays from Ember
</span><span class="cx"> Data), the controller is an `Ember.ArrayController`.
</span><span class="cx"> * otherwise, the controller is an `Ember.ObjectController`.
</span><span class="cx">
</span><del>- This means that your template will get a proxy for the model as its
- context, and you can act as though the model itself was the context.
</del><ins>+ As an example, consider the router:
</ins><span class="cx">
</span><ins>+ ```js
+ App.Router.map(function() {
+ this.resource('post', {path: '/posts/:post_id'});
+ });
+ ```
+
+ For the `post` route, a controller named `App.PostController` would
+ be used if it is defined. If it is not defined, an `Ember.ObjectController`
+ instance would be used.
+
+ Example
+
+ ```js
+ App.PostRoute = Ember.Route.extend({
+ setupController: function(controller, model) {
+ controller.set('model', model);
+ }
+ });
+ ```
+
</ins><span class="cx"> @method setupController
</span><ins>+ @param {Controller} controller instance
+ @param {Object} model
</ins><span class="cx"> */
</span><del>- setupController: Ember.K,
</del><ins>+ setupController: function(controller, context) {
+ if (controller && (context !== undefined)) {
+ set(controller, 'model', context);
+ }
+ },
</ins><span class="cx">
</span><span class="cx"> /**
</span><del>- Returns the controller for a particular route.
</del><ins>+ Returns the controller for a particular route or name.
</ins><span class="cx">
</span><ins>+ The controller instance must already have been created, either through entering the
+ associated route or using `generateController`.
+
</ins><span class="cx"> ```js
</span><span class="cx"> App.PostRoute = Ember.Route.extend({
</span><span class="cx"> setupController: function(controller, post) {
</span><span class="lines">@@ -23300,41 +34968,105 @@
</span><span class="cx"> });
</span><span class="cx"> ```
</span><span class="cx">
</span><del>- By default, the controller for `post` is the shared instance of
- `App.PostController`.
-
</del><span class="cx"> @method controllerFor
</span><del>- @param {String} name the name of the route
- @param {Object} model the model associated with the route (optional)
</del><ins>+ @param {String} name the name of the route or controller
</ins><span class="cx"> @return {Ember.Controller}
</span><span class="cx"> */
</span><del>- controllerFor: function(name, model) {
- var container = this.router.container,
- controller = container.lookup('controller:' + name);
</del><ins>+ controllerFor: function(name, _skipAssert) {
+ var container = this.container,
+ route = container.lookup('route:'+name),
+ controller;
</ins><span class="cx">
</span><del>- if (!controller) {
- model = model || this.modelFor(name);
</del><ins>+ if (route && route.controllerName) {
+ name = route.controllerName;
+ }
</ins><span class="cx">
</span><del>- Ember.assert("You are trying to look up a controller that you did not define, and for which Ember does not know the model.\n\nThis is not a controller for a route, so you must explicitly define the controller ("+this.router.namespace.toString() + "." + Ember.String.capitalize(Ember.String.camelize(name))+"Controller) or pass a model as the second parameter to `controllerFor`, so that Ember knows which type of controller to create for you.", model || this.container.lookup('route:' + name));
</del><ins>+ controller = container.lookup('controller:' + name);
</ins><span class="cx">
</span><del>- controller = Ember.generateController(container, name, model);
- }
</del><ins>+ // NOTE: We're specifically checking that skipAssert is true, because according
+ // to the old API the second parameter was model. We do not want people who
+ // passed a model to skip the assertion.
+ Ember.assert("The controller named '"+name+"' could not be found. Make sure " +
+ "that this route exists and has already been entered at least " +
+ "once. If you are accessing a controller not associated with a " +
+ "route, make sure the controller class is explicitly defined.",
+ controller || _skipAssert === true);
</ins><span class="cx">
</span><span class="cx"> return controller;
</span><span class="cx"> },
</span><span class="cx">
</span><span class="cx"> /**
</span><del>- Returns the current model for a given route.
</del><ins>+ Generates a controller for a route.
</ins><span class="cx">
</span><del>- This is the object returned by the `model` hook of the route
- in question.
</del><ins>+ If the optional model is passed then the controller type is determined automatically,
+ e.g., an ArrayController for arrays.
</ins><span class="cx">
</span><ins>+ Example
+
+ ```js
+ App.PostRoute = Ember.Route.extend({
+ setupController: function(controller, post) {
+ this._super(controller, post);
+ this.generateController('posts', post);
+ }
+ });
+ ```
+
+ @method generateController
+ @param {String} name the name of the controller
+ @param {Object} model the model to infer the type of the controller (optional)
+ */
+ generateController: function(name, model) {
+ var container = this.container;
+
+ model = model || this.modelFor(name);
+
+ return Ember.generateController(container, name, model);
+ },
+
+ /**
+ Returns the model of a parent (or any ancestor) route
+ in a route hierarchy. During a transition, all routes
+ must resolve a model object, and if a route
+ needs access to a parent route's model in order to
+ resolve a model (or just reuse the model from a parent),
+ it can call `this.modelFor(theNameOfParentRoute)` to
+ retrieve it.
+
+ Example
+
+ ```js
+ App.Router.map(function() {
+ this.resource('post', { path: '/post/:post_id' }, function() {
+ this.resource('comments');
+ });
+ });
+
+ App.CommentsRoute = Ember.Route.extend({
+ afterModel: function() {
+ this.set('post', this.modelFor('post'));
+ }
+ });
+ ```
+
</ins><span class="cx"> @method modelFor
</span><span class="cx"> @param {String} name the name of the route
</span><span class="cx"> @return {Object} the model object
</span><span class="cx"> */
</span><span class="cx"> modelFor: function(name) {
</span><del>- var route = this.container.lookup('route:' + name);
</del><ins>+
+ var route = this.container.lookup('route:' + name),
+ transition = this.router.router.activeTransition;
+
+ // If we are mid-transition, we want to try and look up
+ // resolved parent contexts on the current transitionEvent.
+ if (transition) {
+ var modelLookupName = (route && route.routeName) || name;
+ if (transition.resolvedModels.hasOwnProperty(modelLookupName)) {
+ return transition.resolvedModels[modelLookupName];
+ }
+ }
+
</ins><span class="cx"> return route && route.currentModel;
</span><span class="cx"> },
</span><span class="cx">
</span><span class="lines">@@ -23348,6 +35080,22 @@
</span><span class="cx"> This method can be overridden to set up and render additional or
</span><span class="cx"> alternative templates.
</span><span class="cx">
</span><ins>+ ```js
+ App.PostsRoute = Ember.Route.extend({
+ renderTemplate: function(controller, model) {
+ var favController = this.controllerFor('favoritePost');
+
+ // Render the `favoritePost` template into
+ // the outlet `posts`, and display the `favoritePost`
+ // controller.
+ this.render('favoritePost', {
+ outlet: 'posts',
+ controller: favController
+ });
+ }
+ });
+ ```
+
</ins><span class="cx"> @method renderTemplate
</span><span class="cx"> @param {Object} controller the route's controller
</span><span class="cx"> @param {Object} model the route's model
</span><span class="lines">@@ -23365,9 +35113,9 @@
</span><span class="cx"> For example:
</span><span class="cx">
</span><span class="cx"> ```js
</span><del>- App.Router.map(function(match) {
- match("/").to("index");
- match("/posts/:post_id").to("post");
</del><ins>+ App.Router.map(function() {
+ this.route('index');
+ this.resource('post', {path: '/posts/:post_id'});
</ins><span class="cx"> });
</span><span class="cx">
</span><span class="cx"> App.PostRoute = App.Route.extend({
</span><span class="lines">@@ -23408,19 +35156,45 @@
</span><span class="cx"> @param {Object} options the options
</span><span class="cx"> */
</span><span class="cx"> render: function(name, options) {
</span><ins>+ Ember.assert("The name in the given arguments is undefined", arguments.length > 0 ? !Ember.isNone(arguments[0]) : true);
+
+ var namePassed = !!name;
+
</ins><span class="cx"> if (typeof name === 'object' && !options) {
</span><span class="cx"> options = name;
</span><span class="cx"> name = this.routeName;
</span><span class="cx"> }
</span><span class="cx">
</span><del>- name = name ? name.replace(/\//g, '.') : this.routeName;
</del><ins>+ options = options || {};
</ins><span class="cx">
</span><ins>+ 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;
+
</ins><span class="cx"> var container = this.container,
</span><del>- view = container.lookup('view:' + name),
- template = container.lookup('template:' + name);
</del><ins>+ view = container.lookup('view:' + viewName),
+ template = view ? view.get('template') : null;
</ins><span class="cx">
</span><del>- if (!view && !template) { return; }
</del><ins>+ if (!template) {
+ template = container.lookup('template:' + templateName);
+ }
</ins><span class="cx">
</span><ins>+ 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;
+ }
+
</ins><span class="cx"> options = normalizeOptions(this, name, template, options);
</span><span class="cx"> view = setupView(view, container, options);
</span><span class="cx">
</span><span class="lines">@@ -23429,14 +35203,78 @@
</span><span class="cx"> appendView(this, view, options);
</span><span class="cx"> },
</span><span class="cx">
</span><ins>+ /**
+ 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'
+ });
+ }
+ }
+ });
+ ```
+
+ @method disconnectOutlet
+ @param {Object} options the options
+ */
+ disconnectOutlet: function(options) {
+ options = options || {};
+ options.parentView = options.parentView ? options.parentView.replace(/\//g, '.') : parentTemplate(this);
+ options.outlet = options.outlet || 'main';
+
+ var parentView = this.router._lookupActiveView(options.parentView);
+ parentView.disconnectOutlet(options.outlet);
+ },
+
</ins><span class="cx"> willDestroy: function() {
</span><del>- teardownView(this);
</del><ins>+ 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;
</ins><span class="cx"> }
</span><span class="cx"> });
</span><span class="cx">
</span><span class="cx"> function parentRoute(route) {
</span><del>- var handlerInfos = route.router.router.currentHandlerInfos;
</del><ins>+ var handlerInfos = route.router.router.targetHandlerInfos;
</ins><span class="cx">
</span><ins>+ if (!handlerInfos) { return; }
+
</ins><span class="cx"> var parent, current;
</span><span class="cx">
</span><span class="cx"> for (var i=0, l=handlerInfos.length; i<l; i++) {
</span><span class="lines">@@ -23446,17 +35284,15 @@
</span><span class="cx"> }
</span><span class="cx"> }
</span><span class="cx">
</span><del>-function parentTemplate(route, isRecursive) {
</del><ins>+function parentTemplate(route) {
</ins><span class="cx"> var parent = parentRoute(route), template;
</span><span class="cx">
</span><span class="cx"> if (!parent) { return; }
</span><span class="cx">
</span><del>- Ember.warn("The immediate parent route did not render into the main outlet and the default 'into' option may not be expected", !isRecursive);
-
</del><span class="cx"> if (template = parent.lastRenderedTemplate) {
</span><span class="cx"> return template;
</span><span class="cx"> } else {
</span><del>- return parentTemplate(parent, true);
</del><ins>+ return parentTemplate(parent);
</ins><span class="cx"> }
</span><span class="cx"> }
</span><span class="cx">
</span><span class="lines">@@ -23466,8 +35302,9 @@
</span><span class="cx"> options.outlet = options.outlet || 'main';
</span><span class="cx"> options.name = name;
</span><span class="cx"> options.template = template;
</span><ins>+ options.LOG_VIEW_LOOKUPS = get(route.router, 'namespace.LOG_VIEW_LOOKUPS');
</ins><span class="cx">
</span><del>- Ember.assert("An outlet ("+options.outlet+") was specified but this view will render at the root level.", options.outlet === 'main' || options.into);
</del><ins>+ Ember.assert("An outlet ("+options.outlet+") was specified but was not found.", options.outlet === 'main' || options.into);
</ins><span class="cx">
</span><span class="cx"> var controller = options.controller, namedController;
</span><span class="cx">
</span><span class="lines">@@ -23476,7 +35313,7 @@
</span><span class="cx"> } else if (namedController = route.container.lookup('controller:' + name)) {
</span><span class="cx"> controller = namedController;
</span><span class="cx"> } else {
</span><del>- controller = route.routeName;
</del><ins>+ controller = route.controllerName || route.routeName;
</ins><span class="cx"> }
</span><span class="cx">
</span><span class="cx"> if (typeof controller === 'string') {
</span><span class="lines">@@ -23489,10 +35326,18 @@
</span><span class="cx"> }
</span><span class="cx">
</span><span class="cx"> function setupView(view, container, options) {
</span><del>- var defaultView = options.into ? 'view:default' : 'view:toplevel';
</del><ins>+ if (view) {
+ if (options.LOG_VIEW_LOOKUPS) {
+ Ember.Logger.info("Rendering " + options.name + " with " + view, { fullName: 'view:' + options.name });
+ }
+ } else {
+ var defaultView = options.into ? 'view:default' : 'view:toplevel';
+ view = container.lookup(defaultView);
+ if (options.LOG_VIEW_LOOKUPS) {
+ Ember.Logger.info("Rendering " + options.name + " with default view " + view, { fullName: 'view:' + options.name });
+ }
+ }
</ins><span class="cx">
</span><del>- view = view || container.lookup(defaultView);
-
</del><span class="cx"> if (!get(view, 'templateName')) {
</span><span class="cx"> set(view, 'template', options.template);
</span><span class="cx">
</span><span class="lines">@@ -23508,37 +35353,85 @@
</span><span class="cx"> function appendView(route, view, options) {
</span><span class="cx"> if (options.into) {
</span><span class="cx"> var parentView = route.router._lookupActiveView(options.into);
</span><del>- route.teardownView = teardownOutlet(parentView, options.outlet);
</del><ins>+ var teardownOutletView = generateOutletTeardown(parentView, options.outlet);
+ if (!route.teardownOutletViews) { route.teardownOutletViews = []; }
+ a_replace(route.teardownOutletViews, 0, 0, [teardownOutletView]);
</ins><span class="cx"> parentView.connectOutlet(options.outlet, view);
</span><span class="cx"> } else {
</span><span class="cx"> var rootElement = get(route, 'router.namespace.rootElement');
</span><ins>+ // tear down view if one is already rendered
+ if (route.teardownTopLevelView) {
+ route.teardownTopLevelView();
+ }
</ins><span class="cx"> route.router._connectActiveView(options.name, view);
</span><del>- route.teardownView = teardownTopLevel(view);
</del><ins>+ route.teardownTopLevelView = generateTopLevelTeardown(view);
</ins><span class="cx"> view.appendTo(rootElement);
</span><span class="cx"> }
</span><span class="cx"> }
</span><span class="cx">
</span><del>-function teardownTopLevel(view) {
</del><ins>+function generateTopLevelTeardown(view) {
</ins><span class="cx"> return function() { view.destroy(); };
</span><span class="cx"> }
</span><span class="cx">
</span><del>-function teardownOutlet(parentView, outlet) {
</del><ins>+function generateOutletTeardown(parentView, outlet) {
</ins><span class="cx"> return function() { parentView.disconnectOutlet(outlet); };
</span><span class="cx"> }
</span><span class="cx">
</span><del>-function teardownView(route) {
- if (route.teardownView) { route.teardownView(); }
</del><ins>+})();
</ins><span class="cx">
</span><del>- delete route.teardownView;
- delete route.lastRenderedTemplate;
-}
</del><span class="cx">
</span><ins>+
+(function() {
+
</ins><span class="cx"> })();
</span><span class="cx">
</span><span class="cx">
</span><span class="cx">
</span><span class="cx"> (function() {
</span><ins>+Ember.onLoad('Ember.Handlebars', function() {
+ var handlebarsResolve = Ember.Handlebars.resolveParams,
+ map = Ember.ArrayPolyfills.map,
+ get = Ember.get,
+ handlebarsGet = Ember.Handlebars.get;
</ins><span class="cx">
</span><ins>+ function resolveParams(context, params, options) {
+ return map.call(resolvePaths(context, params, options), function(path, i) {
+ if (null === path) {
+ // Param was string/number, not a path, so just return raw string/number.
+ return params[i];
+ } else {
+ return handlebarsGet(context, path, options);
+ }
+ });
+ }
+
+ function resolvePaths(context, params, options) {
+ var resolved = handlebarsResolve(context, params, options),
+ types = options.types;
+
+ return map.call(resolved, function(object, i) {
+ if (types[i] === 'ID') {
+ return unwrap(object, params[i]);
+ } else {
+ return null;
+ }
+ });
+
+ function unwrap(object, path) {
+ if (path === 'controller') { return path; }
+
+ if (Ember.ControllerMixin.detect(object)) {
+ return unwrap(get(object, 'model'), path ? path + '.model' : 'model');
+ } else {
+ return path;
+ }
+ }
+ }
+
+ Ember.Router.resolveParams = resolveParams;
+ Ember.Router.resolvePaths = resolvePaths;
+});
+
</ins><span class="cx"> })();
</span><span class="cx">
</span><span class="cx">
</span><span class="lines">@@ -23549,106 +35442,741 @@
</span><span class="cx"> @submodule ember-routing
</span><span class="cx"> */
</span><span class="cx">
</span><del>-var get = Ember.get, set = Ember.set;
</del><ins>+var get = Ember.get, set = Ember.set, fmt = Ember.String.fmt;
</ins><span class="cx"> Ember.onLoad('Ember.Handlebars', function(Handlebars) {
</span><span class="cx">
</span><del>- var resolveParams = Ember.Handlebars.resolveParams,
</del><ins>+ var resolveParams = Ember.Router.resolveParams,
+ resolvePaths = Ember.Router.resolvePaths,
</ins><span class="cx"> isSimpleClick = Ember.ViewUtils.isSimpleClick;
</span><span class="cx">
</span><span class="cx"> function fullRouteName(router, name) {
</span><ins>+ var nameWithIndex;
</ins><span class="cx"> if (!router.hasRoute(name)) {
</span><del>- name = name + '.index';
</del><ins>+ nameWithIndex = name + '.index';
+ Ember.assert(fmt("The attempt to link-to route '%@' failed (also tried '%@'). " +
+ "The router did not find '%@' in its possible routes: '%@'",
+ [name, nameWithIndex, name, Ember.keys(router.router.recognizer.names).join("', '")]),
+ router.hasRoute(nameWithIndex));
+ name = nameWithIndex;
</ins><span class="cx"> }
</span><span class="cx">
</span><span class="cx"> return name;
</span><span class="cx"> }
</span><span class="cx">
</span><del>- function resolvedPaths(options) {
- var types = options.options.types.slice(1),
</del><ins>+ function getResolvedPaths(options) {
+
+ var types = options.options.types,
</ins><span class="cx"> data = options.options.data;
</span><span class="cx">
</span><del>- return resolveParams(options.context, options.params, { types: types, data: data });
</del><ins>+ return resolvePaths(options.context, options.params, { types: types, data: data });
</ins><span class="cx"> }
</span><span class="cx">
</span><del>- function args(linkView, router, route) {
- var passedRouteName = route || linkView.namedRoute, routeName;
</del><ins>+ /**
+ `Ember.LinkView` renders an element whose `click` event triggers a
+ transition of the application's instance of `Ember.Router` to
+ a supplied route by name.
</ins><span class="cx">
</span><del>- routeName = fullRouteName(router, passedRouteName);
</del><ins>+ Instances of `LinkView` will most likely be created through
+ the `link-to` Handlebars helper, but properties of this class
+ can be overridden to customize application-wide behavior.
</ins><span class="cx">
</span><del>- Ember.assert("The route " + passedRouteName + " was not found", router.hasRoute(routeName));
</del><ins>+ @class LinkView
+ @namespace Ember
+ @extends Ember.View
+ @see {Handlebars.helpers.link-to}
+ **/
+ var LinkView = Ember.LinkView = Ember.View.extend({
+ tagName: 'a',
+ currentWhen: null,
</ins><span class="cx">
</span><del>- var ret = [ routeName ];
- return ret.concat(resolvedPaths(linkView.parameters));
- }
</del><ins>+ /**
+ Sets the `title` attribute of the `LinkView`'s HTML element.
</ins><span class="cx">
</span><del>- var LinkView = Ember.View.extend({
- tagName: 'a',
- namedRoute: null,
- currentWhen: null,
</del><ins>+ @property title
+ @default null
+ **/
</ins><span class="cx"> title: null,
</span><ins>+
+ /**
+ Sets the `rel` attribute of the `LinkView`'s HTML element.
+
+ @property rel
+ @default null
+ **/
+ rel: null,
+
+ /**
+ The CSS class to apply to `LinkView`'s element when its `active`
+ property is `true`.
+
+ @property activeClass
+ @type String
+ @default active
+ **/
</ins><span class="cx"> activeClass: 'active',
</span><ins>+
+ /**
+ The CSS class to apply to `LinkView`'s element when its `loading`
+ property is `true`.
+
+ @property loadingClass
+ @type String
+ @default loading
+ **/
+ loadingClass: 'loading',
+
+ /**
+ The CSS class to apply to a `LinkView`'s element when its `disabled`
+ property is `true`.
+
+ @property disabledClass
+ @type String
+ @default disabled
+ **/
+ disabledClass: 'disabled',
+ _isDisabled: false,
+
+ /**
+ Determines whether the `LinkView` will trigger routing via
+ the `replaceWith` routing strategy.
+
+ @property replace
+ @type Boolean
+ @default false
+ **/
</ins><span class="cx"> replace: false,
</span><del>- attributeBindings: ['href', 'title'],
- classNameBindings: 'active',
</del><span class="cx">
</span><del>- // Even though this isn't a virtual view, we want to treat it as if it is
- // so that you can access the parent with {{view.prop}}
</del><ins>+ /**
+ By default the `{{link-to}}` helper will bind to the `href` and
+ `title` attributes. It's discourage that you override these defaults,
+ however you can push onto the array if needed.
+
+ @property attributeBindings
+ @type Array | String
+ @default ['href', 'title', 'rel']
+ **/
+ attributeBindings: ['href', 'title', 'rel'],
+
+ /**
+ By default the `{{link-to}}` helper will bind to the `active`, `loading`, and
+ `disabled` classes. It is discouraged to override these directly.
+
+ @property classNameBindings
+ @type Array
+ @default ['active', 'loading', 'disabled']
+ **/
+ classNameBindings: ['active', 'loading', 'disabled'],
+
+ /**
+ By default the `{{link-to}}` helper responds to the `click` event. You
+ can override this globally by setting this property to your custom
+ event name.
+
+ This is particularly useful on mobile when one wants to avoid the 300ms
+ click delay using some sort of custom `tap` event.
+
+ @property eventName
+ @type String
+ @default click
+ */
+ eventName: 'click',
+
+ // this is doc'ed here so it shows up in the events
+ // section of the API documentation, which is where
+ // people will likely go looking for it.
+ /**
+ Triggers the `LinkView`'s routing behavior. If
+ `eventName` is changed to a value other than `click`
+ the routing behavior will trigger on that custom event
+ instead.
+
+ @event click
+ **/
+
+ /**
+ An overridable method called when LinkView objects are instantiated.
+
+ Example:
+
+ ```javascript
+ App.MyLinkView = Ember.LinkView.extend({
+ init: function() {
+ this._super();
+ Ember.Logger.log('Event is ' + this.get('eventName'));
+ }
+ });
+ ```
+
+ NOTE: If you do override `init` for a framework class like `Ember.View` or
+ `Ember.ArrayController`, be sure to call `this._super()` in your
+ `init` declaration! If you don't, Ember may not have an opportunity to
+ do important setup work, and you'll see strange behavior in your
+ application.
+
+ @method init
+ */
+ init: function() {
+ this._super.apply(this, arguments);
+
+ // Map desired event name to invoke function
+ var eventName = get(this, 'eventName'), i;
+ this.on(eventName, this, this._invoke);
+
+ },
+
+ /**
+ This method is invoked by observers installed during `init` that fire
+ whenever the params change
+
+ @private
+ @method _paramsChanged
+ */
+ _paramsChanged: function() {
+ this.notifyPropertyChange('resolvedParams');
+ },
+
+ /**
+ This is called to setup observers that will trigger a rerender.
+
+ @private
+ @method _setupPathObservers
+ **/
+ _setupPathObservers: function(){
+ var helperParameters = this.parameters,
+ linkTextPath = helperParameters.options.linkTextPath,
+ paths = getResolvedPaths(helperParameters),
+ length = paths.length,
+ path, i, normalizedPath;
+
+ if (linkTextPath) {
+ normalizedPath = Ember.Handlebars.normalizePath(helperParameters.context, linkTextPath, helperParameters.options.data);
+ this.registerObserver(normalizedPath.root, normalizedPath.path, this, this.rerender);
+ }
+
+ for(i=0; i < length; i++) {
+ path = paths[i];
+ if (null === path) {
+ // A literal value was provided, not a path, so nothing to observe.
+ continue;
+ }
+
+ normalizedPath = Ember.Handlebars.normalizePath(helperParameters.context, path, helperParameters.options.data);
+ this.registerObserver(normalizedPath.root, normalizedPath.path, this, this._paramsChanged);
+ }
+ },
+
+ afterRender: function(){
+ this._super.apply(this, arguments);
+ this._setupPathObservers();
+ },
+
+ /**
+ This method is invoked by observers installed during `init` that fire
+ whenever the query params change
+ @private
+ */
+ _queryParamsChanged: function (object, path) {
+ this.notifyPropertyChange('queryParams');
+ },
+
+
+ /**
+ Even though this isn't a virtual view, we want to treat it as if it is
+ so that you can access the parent with {{view.prop}}
+
+ @private
+ @method concreteView
+ **/
</ins><span class="cx"> concreteView: Ember.computed(function() {
</span><span class="cx"> return get(this, 'parentView');
</span><del>- }).property('parentView').volatile(),
</del><ins>+ }).property('parentView'),
</ins><span class="cx">
</span><del>- active: Ember.computed(function() {
- var router = this.get('router'),
- params = resolvedPaths(this.parameters),
- currentWithIndex = this.currentWhen + '.index',
- isActive = router.isActive.apply(router, [this.currentWhen].concat(params)) ||
- router.isActive.apply(router, [currentWithIndex].concat(params));
</del><ins>+ /**
</ins><span class="cx">
</span><ins>+ Accessed as a classname binding to apply the `LinkView`'s `disabledClass`
+ CSS `class` to the element when the link is disabled.
+
+ When `true` interactions with the element will not trigger route changes.
+ @property disabled
+ */
+ disabled: Ember.computed(function computeLinkViewDisabled(key, value) {
+ if (value !== undefined) { this.set('_isDisabled', value); }
+
+ return value ? get(this, 'disabledClass') : false;
+ }),
+
+ /**
+ Accessed as a classname binding to apply the `LinkView`'s `activeClass`
+ CSS `class` to the element when the link is active.
+
+ A `LinkView` is considered active when its `currentWhen` property is `true`
+ or the application's current route is the route the `LinkView` would trigger
+ transitions into.
+
+ @property active
+ **/
+ active: Ember.computed(function computeLinkViewActive() {
+ if (get(this, 'loading')) { return false; }
+
+ var router = get(this, 'router'),
+ routeArgs = get(this, 'routeArgs'),
+ contexts = routeArgs.slice(1),
+ resolvedParams = get(this, 'resolvedParams'),
+ currentWhen = this.currentWhen || resolvedParams[0],
+ currentWithIndex = currentWhen + '.index',
+ isActive = router.isActive.apply(router, [currentWhen].concat(contexts)) ||
+ router.isActive.apply(router, [currentWithIndex].concat(contexts));
+
</ins><span class="cx"> if (isActive) { return get(this, 'activeClass'); }
</span><del>- }).property('namedRoute', 'router.url'),
</del><ins>+ }).property('resolvedParams', 'routeArgs', 'router.url'),
</ins><span class="cx">
</span><ins>+ /**
+ 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
+ **/
</ins><span class="cx"> router: Ember.computed(function() {
</span><del>- return this.get('controller').container.lookup('router:main');
</del><ins>+ return get(this, 'controller').container.lookup('router:main');
</ins><span class="cx"> }),
</span><span class="cx">
</span><del>- click: function(event) {
</del><ins>+ /**
+ Event handler that invokes the link, activating the associated route.
+
+ @private
+ @method _invoke
+ @param {Event} event
+ */
+ _invoke: function(event) {
</ins><span class="cx"> if (!isSimpleClick(event)) { return true; }
</span><span class="cx">
</span><del>- event.preventDefault();
</del><ins>+ if (this.preventDefault !== false) { event.preventDefault(); }
</ins><span class="cx"> if (this.bubbles === false) { event.stopPropagation(); }
</span><span class="cx">
</span><del>- var router = this.get('router');
</del><ins>+ if (get(this, '_isDisabled')) { return false; }
</ins><span class="cx">
</span><del>- if (this.get('replace')) {
- router.replaceWith.apply(router, args(this, router));
</del><ins>+ 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');
+
+ if (get(this, 'replace')) {
+ router.replaceWith.apply(router, routeArgs);
</ins><span class="cx"> } else {
</span><del>- router.transitionTo.apply(router, args(this, router));
</del><ins>+ router.transitionTo.apply(router, routeArgs);
</ins><span class="cx"> }
</span><span class="cx"> },
</span><span class="cx">
</span><del>- href: Ember.computed(function() {
- var router = this.get('router');
- return router.generate.apply(router, args(this, router));
- })
</del><ins>+ /**
+ Computed property that returns the resolved parameters.
+
+ @private
+ @property
+ @return {Array}
+ */
+ resolvedParams: Ember.computed(function() {
+ var parameters = this.parameters,
+ options = parameters.options,
+ types = options.types,
+ data = options.data;
+
+
+ // Original implementation if query params not enabled
+ return resolveParams(parameters.context, parameters.params, { types: types, data: data });
+ }).property(),
+
+ /**
+ 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; }
+
+ namedRoute = fullRouteName(router, namedRoute);
+ 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', 'router.url'),
+
+
+ _potentialQueryParams: Ember.computed(function () {
+ var namedRoute = get(this, 'resolvedParams')[0];
+ if (!namedRoute) { return null; }
+ var router = get(this, 'router');
+
+ namedRoute = fullRouteName(router, namedRoute);
+
+ return router.router.queryParamsForHandler(namedRoute);
+ }).property('resolvedParams'),
+
+ queryParams: Ember.computed(function () {
+ var self = this,
+ queryParams = null,
+ allowedQueryParams = get(this, '_potentialQueryParams');
+
+ if (!allowedQueryParams) { return null; }
+ allowedQueryParams.forEach(function (param) {
+ var value = get(self, param);
+ if (typeof value !== 'undefined') {
+ queryParams = queryParams || {};
+ queryParams[param] = value;
+ }
+ });
+
+
+ return queryParams;
+ }).property('_potentialQueryParams.[]'),
+
+ /**
+ 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: '#'
</ins><span class="cx"> });
</span><span class="cx">
</span><span class="cx"> LinkView.toString = function() { return "LinkView"; };
</span><span class="cx">
</span><span class="cx"> /**
</span><del>- @method linkTo
</del><ins>+ 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
+ <a href="/hamster-photos">
+ Great Hamster Photos
+ </a>
+ ```
+
+ ### Supplying a tagName
+ By default `{{link-to}}` renders an `<a>` 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
+ <li>
+ Great Hamster Photos
+ </li>
+ ```
+
+ 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
+ <a href="/hamster-photos/this-week" class="active">
+ Great Hamster Photos
+ </a>
+ ```
+
+ 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
+ <a href="/hamster-photos/this-week" class="current-url">
+ Great Hamster Photos
+ </a>
+ ```
+
+ 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
+ <a href="/hamster-photos/42">
+ Tomster
+ </a>
+ ```
+
+ ### 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 href="/hamster-photos/42/comment/718">
+ A+++ would snuggle again.
+ </a>
+ ```
+
+ ### 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
+ <a href="/hamster-photos/42">
+ Tomster
+ </a>
+ ```
+
+ 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
</ins><span class="cx"> @for Ember.Handlebars.helpers
</span><span class="cx"> @param {String} routeName
</span><span class="cx"> @param {Object} [context]*
</span><ins>+ @param [options] {Object} Handlebars key/value pairs of options, you can override any property of Ember.LinkView
</ins><span class="cx"> @return {String} HTML string
</span><ins>+ @see {Ember.LinkView}
</ins><span class="cx"> */
</span><del>- Ember.Handlebars.registerHelper('linkTo', function(name) {
- var options = [].slice.call(arguments, -1)[0];
- var params = [].slice.call(arguments, 1, -1);
</del><ins>+ Ember.Handlebars.registerHelper('link-to', function linkToHelper(name) {
+ var options = [].slice.call(arguments, -1)[0],
+ params = [].slice.call(arguments, 0, -1),
+ hash = options.hash;
</ins><span class="cx">
</span><del>- var hash = options.hash;
</del><ins>+ hash.disabledBinding = hash.disabledWhen;
</ins><span class="cx">
</span><del>- hash.namedRoute = name;
- hash.currentWhen = hash.currentWhen || name;
</del><ins>+ 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.get(context, linkTitle, options);
+ };
+ } else {
+ options.fn = function() {
+ return linkTitle;
+ };
+ }
+ }
</ins><span class="cx">
</span><span class="cx"> hash.parameters = {
</span><span class="cx"> context: this,
</span><span class="lines">@@ -23659,9 +36187,24 @@
</span><span class="cx"> return Ember.Handlebars.helpers.view.call(this, LinkView, options);
</span><span class="cx"> });
</span><span class="cx">
</span><ins>+ /**
+ 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);
+ });
</ins><span class="cx"> });
</span><span class="cx">
</span><span class="cx">
</span><ins>+
</ins><span class="cx"> })();
</span><span class="cx">
</span><span class="cx">
</span><span class="lines">@@ -23676,66 +36219,109 @@
</span><span class="cx"> Ember.onLoad('Ember.Handlebars', function(Handlebars) {
</span><span class="cx"> /**
</span><span class="cx"> @module ember
</span><del>- @submodule ember-handlebars
</del><ins>+ @submodule ember-routing
</ins><span class="cx"> */
</span><span class="cx">
</span><span class="cx"> Handlebars.OutletView = Ember.ContainerView.extend(Ember._Metamorph);
</span><span class="cx">
</span><span class="cx"> /**
</span><del>- The `outlet` helper allows you to specify that the current
- view's controller will fill in the view for a given area.
</del><ins>+ The `outlet` helper is a placeholder that the router will fill in with
+ the appropriate template based on the current state of the application.
</ins><span class="cx">
</span><span class="cx"> ``` handlebars
</span><span class="cx"> {{outlet}}
</span><span class="cx"> ```
</span><span class="cx">
</span><del>- By default, when the the current controller's `view` property changes, the
- outlet will replace its current view with the new view. You can set the
- `view` property directly, but it's normally best to use `connectOutlet`.
</del><ins>+ 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).
</ins><span class="cx">
</span><ins>+ 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`.
+
</ins><span class="cx"> ``` javascript
</span><del>- # Instantiate App.PostsView and assign to `view`, so as to render into outlet.
- controller.connectOutlet('posts');
</del><ins>+ App.PostsRoute = Ember.Route.extend({
+ renderTemplate: function() {
+ this.render('favoritePost');
+ }
+ });
</ins><span class="cx"> ```
</span><span class="cx">
</span><del>- You can also specify a particular name other than `view`:
</del><ins>+ You can create custom named outlets for more control.
</ins><span class="cx">
</span><span class="cx"> ``` handlebars
</span><del>- {{outlet masterView}}
- {{outlet detailView}}
</del><ins>+ {{outlet 'favoritePost'}}
+ {{outlet 'posts'}}
</ins><span class="cx"> ```
</span><span class="cx">
</span><del>- Then, you can control several outlets from a single controller.
</del><ins>+ Then you can define what template is rendered into each outlet in your
+ route.
</ins><span class="cx">
</span><ins>+
</ins><span class="cx"> ``` javascript
</span><del>- # Instantiate App.PostsView and assign to controller.masterView.
- controller.connectOutlet('masterView', 'posts');
- # Also, instantiate App.PostInfoView and assign to controller.detailView.
- controller.connectOutlet('detailView', 'postInfo');
</del><ins>+ App.PostsRoute = Ember.Route.extend({
+ renderTemplate: function() {
+ this.render('favoritePost', { outlet: 'favoritePost' });
+ this.render('posts', { outlet: 'posts' });
+ }
+ });
</ins><span class="cx"> ```
</span><span class="cx">
</span><ins>+ 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']
+ });
+ ```
+
</ins><span class="cx"> @method outlet
</span><span class="cx"> @for Ember.Handlebars.helpers
</span><span class="cx"> @param {String} property the property on the controller
</span><span class="cx"> that holds the view for this outlet
</span><ins>+ @return {String} HTML string
</ins><span class="cx"> */
</span><del>- Handlebars.registerHelper('outlet', function(property, options) {
- var outletSource;
</del><ins>+ Handlebars.registerHelper('outlet', function outletHelper(property, options) {
+
+ var outletSource,
+ container,
+ viewName,
+ viewClass,
+ viewFullName;
</ins><span class="cx">
</span><span class="cx"> if (property && property.data && property.data.isRenderData) {
</span><span class="cx"> options = property;
</span><span class="cx"> property = 'main';
</span><span class="cx"> }
</span><span class="cx">
</span><ins>+ container = options.data.view.container;
+
</ins><span class="cx"> outletSource = options.data.view;
</span><del>- while (!(outletSource.get('template.isTop'))){
</del><ins>+ while (!outletSource.get('template.isTop')) {
</ins><span class="cx"> outletSource = outletSource.get('_parentView');
</span><span class="cx"> }
</span><span class="cx">
</span><ins>+ // 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;
+
</ins><span class="cx"> options.data.view.set('outletSource', outletSource);
</span><span class="cx"> options.hash.currentViewBinding = '_view.outletSource._outlets.' + property;
</span><span class="cx">
</span><del>- return Handlebars.helpers.view.call(this, Handlebars.OutletView, options);
</del><ins>+ return Handlebars.helpers.view.call(this, viewClass, options);
</ins><span class="cx"> });
</span><span class="cx"> });
</span><span class="cx">
</span><span class="lines">@@ -23753,51 +36339,132 @@
</span><span class="cx"> Ember.onLoad('Ember.Handlebars', function(Handlebars) {
</span><span class="cx">
</span><span class="cx"> /**
</span><del>- Renders the named template in the current context using the singleton
- instance of the same-named controller.
</del><ins>+ 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).
</ins><span class="cx">
</span><del>- If a view class with the same name exists, uses the view class.
</del><ins>+ If a view class with the same name exists, the view class also will be used.
</ins><span class="cx">
</span><del>- If a `model` is specified, it becomes the model for that controller.
</del><ins>+ 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.
</ins><span class="cx">
</span><del>- The default target for `{{action}}`s in the rendered template is the
- named controller.
</del><ins>+ Example:
</ins><span class="cx">
</span><del>- @method action
</del><ins>+ ```javascript
+ App.NavigationController = Ember.Controller.extend({
+ who: "world"
+ });
+ ```
+
+ ```handlebars
+ <!-- navigation.hbs -->
+ Hello, {{who}}.
+ ```
+
+ ```handelbars
+ <!-- application.hbs -->
+ <h1>My great app</h1>
+ {{render navigation}}
+ ```
+
+ ```html
+ <h1>My great app</h1>
+ <div class='ember-view'>
+ Hello, world.
+ </div>
+ ```
+
+ 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
+<div class="author">
+ Written by {{firstName}} {{lastName}}.
+ Total Posts: {{postCount}}
+</div>
+ ```
+
+ You could render it inside the `post` template using the `render` helper.
+
+ ```handlebars
+<div class="post">
+ <h1>{{title}}</h1>
+ <div>{{body}}</div>
+ {{render "author" author}}
+</div>
+ ```
+
+ @method render
</ins><span class="cx"> @for Ember.Handlebars.helpers
</span><del>- @param {String} actionName
- @param {Object?} model
</del><ins>+ @param {String} name
+ @param {Object?} contextString
</ins><span class="cx"> @param {Hash} options
</span><ins>+ @return {String} HTML string
</ins><span class="cx"> */
</span><del>- Ember.Handlebars.registerHelper('render', function(name, contextString, options) {
- Ember.assert("You must pass a template to render", arguments.length >= 2);
- var container, router, controller, view, context;
</del><ins>+ Ember.Handlebars.registerHelper('render', function renderHelper(name, contextString, options) {
+ var length = arguments.length;
+ Ember.assert("You must pass a template to render", length >= 2);
+ var contextProvided = length === 3,
+ container, router, controller, view, context, lookupOptions;
</ins><span class="cx">
</span><del>- if (arguments.length === 2) {
</del><ins>+ container = (options || contextString).data.keywords.controller.container;
+ router = container.lookup('router:main');
+
+ if (length === 2) {
+ // use the singleton controller
</ins><span class="cx"> options = contextString;
</span><span class="cx"> contextString = undefined;
</span><del>- }
-
- if (typeof contextString === 'string') {
</del><ins>+ 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
</ins><span class="cx"> context = Ember.Handlebars.get(options.contexts[1], contextString, options);
</span><ins>+ } else {
+ throw Ember.Error("You must pass a templateName to render");
</ins><span class="cx"> }
</span><span class="cx">
</span><ins>+ // # legacy namespace
</ins><span class="cx"> name = name.replace(/\//g, '.');
</span><del>- container = options.data.keywords.controller.container;
- router = container.lookup('router:main');
</del><ins>+ // \ legacy slash as namespace support
</ins><span class="cx">
</span><del>- Ember.assert("This view is already rendered", !router || !router._lookupActiveView(name));
</del><span class="cx">
</span><span class="cx"> view = container.lookup('view:' + name) || container.lookup('view:default');
</span><span class="cx">
</span><del>- if (controller = options.hash.controller) {
- controller = container.lookup('controller:' + controller);
- } else {
- controller = Ember.controllerFor(container, name, context);
</del><ins>+ // 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));
</ins><span class="cx"> }
</span><span class="cx">
</span><del>- if (controller && context) {
- controller.set('model', context);
</del><ins>+ 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
+ });
</ins><span class="cx"> }
</span><span class="cx">
</span><span class="cx"> var root = options.contexts[1];
</span><span class="lines">@@ -23808,19 +36475,20 @@
</span><span class="cx"> });
</span><span class="cx"> }
</span><span class="cx">
</span><del>- controller.set('target', options.data.keywords.controller);
</del><ins>+ options.hash.viewName = Ember.String.camelize(name);
</ins><span class="cx">
</span><del>- options.hash.viewName = Ember.String.camelize(name);
- options.hash.template = container.lookup('template:' + name);
</del><ins>+ 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.hash.template = container.lookup(templateName);
+
</ins><span class="cx"> options.hash.controller = controller;
</span><span class="cx">
</span><del>- if (router) {
</del><ins>+ if (router && !context) {
</ins><span class="cx"> router._connectActiveView(name, view);
</span><span class="cx"> }
</span><span class="cx">
</span><span class="cx"> Ember.Handlebars.helpers.view.call(this, view, options);
</span><span class="cx"> });
</span><del>-
</del><span class="cx"> });
</span><span class="cx">
</span><span class="cx"> })();
</span><span class="lines">@@ -23834,12 +36502,13 @@
</span><span class="cx"> */
</span><span class="cx"> Ember.onLoad('Ember.Handlebars', function(Handlebars) {
</span><span class="cx">
</span><del>- var resolveParams = Ember.Handlebars.resolveParams,
</del><ins>+ var resolveParams = Ember.Router.resolveParams,
</ins><span class="cx"> isSimpleClick = Ember.ViewUtils.isSimpleClick;
</span><span class="cx">
</span><span class="cx"> var EmberHandlebars = Ember.Handlebars,
</span><span class="cx"> handlebarsGet = EmberHandlebars.get,
</span><span class="cx"> SafeString = EmberHandlebars.SafeString,
</span><ins>+ forEach = Ember.ArrayPolyfills.forEach,
</ins><span class="cx"> get = Ember.get,
</span><span class="cx"> a_slice = Array.prototype.slice;
</span><span class="cx">
</span><span class="lines">@@ -23857,22 +36526,51 @@
</span><span class="cx"> registeredActions: {}
</span><span class="cx"> };
</span><span class="cx">
</span><del>- ActionHelper.registerAction = function(actionName, options) {
</del><ins>+ 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(actionName, options, allowedKeys) {
</ins><span class="cx"> var actionId = (++Ember.uuid).toString();
</span><span class="cx">
</span><span class="cx"> ActionHelper.registeredActions[actionId] = {
</span><span class="cx"> eventName: options.eventName,
</span><del>- handler: function(event) {
- if (!isSimpleClick(event)) { return true; }
- event.preventDefault();
</del><ins>+ handler: function handleRegisteredAction(event) {
+ if (!isAllowedEvent(event, allowedKeys)) { return true; }
</ins><span class="cx">
</span><ins>+ if (options.preventDefault !== false) {
+ event.preventDefault();
+ }
+
</ins><span class="cx"> if (options.bubbles === false) {
</span><span class="cx"> event.stopPropagation();
</span><span class="cx"> }
</span><span class="cx">
</span><del>- var view = options.view,
- contexts = options.contexts,
- target = options.target;
</del><ins>+ var target = options.target;
</ins><span class="cx">
</span><span class="cx"> if (target.target) {
</span><span class="cx"> target = handlebarsGet(target.root, target.target, target.options);
</span><span class="lines">@@ -23880,7 +36578,7 @@
</span><span class="cx"> target = target.root;
</span><span class="cx"> }
</span><span class="cx">
</span><del>- Ember.run(function() {
</del><ins>+ Ember.run(function runRegisteredAction() {
</ins><span class="cx"> if (target.send) {
</span><span class="cx"> target.send.apply(target, args(options.parameters, actionName));
</span><span class="cx"> } else {
</span><span class="lines">@@ -23900,42 +36598,35 @@
</span><span class="cx">
</span><span class="cx"> /**
</span><span class="cx"> The `{{action}}` helper registers an HTML element within a template for DOM
</span><del>- event handling and forwards that interaction to the view's controller
</del><ins>+ event handling and forwards that interaction to the templates's controller
</ins><span class="cx"> or supplied `target` option (see 'Specifying a Target').
</span><span class="cx">
</span><del>- If the view's controller does not implement the event, the event is sent
</del><ins>+ If the controller does not implement the event, the event is sent
</ins><span class="cx"> to the current route, and it bubbles up the route hierarchy from there.
</span><span class="cx">
</span><span class="cx"> User interaction with that element will invoke the supplied action name on
</span><span class="cx"> the appropriate target.
</span><span class="cx">
</span><del>- Given the following Handlebars template on the page
</del><ins>+ Given the following application Handlebars template on the page
</ins><span class="cx">
</span><span class="cx"> ```handlebars
</span><del>- <script type="text/x-handlebars" data-template-name='a-template'>
- <div {{action anActionName}}>
- click me
- </div>
- </script>
</del><ins>+ <div {{action 'anActionName'}}>
+ click me
+ </div>
</ins><span class="cx"> ```
</span><span class="cx">
</span><span class="cx"> And application code
</span><span class="cx">
</span><span class="cx"> ```javascript
</span><del>- AController = Ember.Controller.extend({
- anActionName: function() {}
</del><ins>+ App.ApplicationController = Ember.Controller.extend({
+ actions: {
+ anActionName: function() {
+ }
+ }
</ins><span class="cx"> });
</span><del>-
- AView = Ember.View.extend({
- controller: AController.create(),
- templateName: 'a-template'
- });
-
- aView = AView.create();
- aView.appendTo('body');
</del><span class="cx"> ```
</span><span class="cx">
</span><del>- Will results in the following rendered HTML
</del><ins>+ Will result in the following rendered HTML
</ins><span class="cx">
</span><span class="cx"> ```html
</span><span class="cx"> <div class="ember-view">
</span><span class="lines">@@ -23945,8 +36636,8 @@
</span><span class="cx"> </div>
</span><span class="cx"> ```
</span><span class="cx">
</span><del>- Clicking "click me" will trigger the `anActionName` method of the
- `AController`. In this case, no additional parameters will be passed.
</del><ins>+ Clicking "click me" will trigger the `anActionName` action of the
+ `App.ApplicationController`. In this case, no additional parameters will be passed.
</ins><span class="cx">
</span><span class="cx"> If you provide additional parameters to the helper:
</span><span class="cx">
</span><span class="lines">@@ -23961,16 +36652,24 @@
</span><span class="cx">
</span><span class="cx"> Events triggered through the action helper will automatically have
</span><span class="cx"> `.preventDefault()` called on them. You do not need to do so in your event
</span><del>- handlers.
</del><ins>+ 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:
</ins><span class="cx">
</span><del>- To also disable bubbling, pass `bubbles=false` to the helper:
</del><ins>+ ```handlebars
+ <div {{action "sayHello" preventDefault=false}}>
+ <input type="file" />
+ <input type="checkbox" />
+ </div>
+ ```
</ins><span class="cx">
</span><ins>+ To disable bubbling, pass `bubbles=false` to the helper:
+
</ins><span class="cx"> ```handlebars
</span><span class="cx"> <button {{action 'edit' post bubbles=false}}>Edit</button>
</span><span class="cx"> ```
</span><span class="cx">
</span><span class="cx"> If you need the default handler to trigger you should either register your
</span><del>- own event handler, or use event methods on your view class. See `Ember.View`
</del><ins>+ own event handler, or use event methods on your view class. See [Ember.View](/api/classes/Ember.View.html)
</ins><span class="cx"> 'Responding to Browser Events' for more information.
</span><span class="cx">
</span><span class="cx"> ### Specifying DOM event type
</span><span class="lines">@@ -23979,11 +36678,9 @@
</span><span class="cx"> supply an `on` option to the helper to specify a different DOM event name:
</span><span class="cx">
</span><span class="cx"> ```handlebars
</span><del>- <script type="text/x-handlebars" data-template-name='a-template'>
- <div {{action anActionName on="doubleClick"}}>
- click me
- </div>
- </script>
</del><ins>+ <div {{action "anActionName" on="doubleClick"}}>
+ click me
+ </div>
</ins><span class="cx"> ```
</span><span class="cx">
</span><span class="cx"> See `Ember.View` 'Responding to Browser Events' for a list of
</span><span class="lines">@@ -23995,6 +36692,27 @@
</span><span class="cx"> is created. Having an instance of `Ember.Application` will satisfy this
</span><span class="cx"> requirement.
</span><span class="cx">
</span><ins>+ ### 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
+ <div {{action "anActionName" allowedKeys="alt"}}>
+ click me
+ </div>
+ ```
+
+ 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
+ <div {{action "anActionName" allowedKeys="any"}}>
+ click me with any key pressed
+ </div>
+ ```
+
</ins><span class="cx"> ### Specifying a Target
</span><span class="cx">
</span><span class="cx"> There are several possible target objects for `{{action}}` helpers:
</span><span class="lines">@@ -24005,46 +36723,24 @@
</span><span class="cx">
</span><span class="cx"> Alternatively, a `target` option can be provided to the helper to change
</span><span class="cx"> which object will receive the method call. This option must be a path
</span><del>- path to an object, accessible in the current context:
</del><ins>+ to an object, accessible in the current context:
</ins><span class="cx">
</span><span class="cx"> ```handlebars
</span><del>- <script type="text/x-handlebars" data-template-name='a-template'>
- <div {{action anActionName target="MyApplication.someObject"}}>
- click me
- </div>
- </script>
</del><ins>+ {{! the application template }}
+ <div {{action "anActionName" target=view}}>
+ click me
+ </div>
</ins><span class="cx"> ```
</span><span class="cx">
</span><del>- Clicking "click me" in the rendered HTML of the above template will trigger
- the `anActionName` method of the object at `MyApplication.someObject`.
-
- If an action's target does not implement a method that matches the supplied
- action name an error will be thrown.
-
- ```handlebars
- <script type="text/x-handlebars" data-template-name='a-template'>
- <div {{action aMethodNameThatIsMissing}}>
- click me
- </div>
- </script>
- ```
-
- With the following application code
-
</del><span class="cx"> ```javascript
</span><del>- AView = Ember.View.extend({
- templateName; 'a-template',
- // note: no method 'aMethodNameThatIsMissing'
- anActionName: function(event) {}
</del><ins>+ App.ApplicationView = Ember.View.extend({
+ actions: {
+ anActionName: function(){}
+ }
</ins><span class="cx"> });
</span><span class="cx">
</span><del>- aView = AView.create();
- aView.appendTo('body');
</del><span class="cx"> ```
</span><span class="cx">
</span><del>- Will throw `Uncaught TypeError: Cannot call method 'call' of undefined` when
- "click me" is clicked.
-
</del><span class="cx"> ### Additional Parameters
</span><span class="cx">
</span><span class="cx"> You may specify additional parameters to the `{{action}}` helper. These
</span><span class="lines">@@ -24052,17 +36748,15 @@
</span><span class="cx"> implementing the action.
</span><span class="cx">
</span><span class="cx"> ```handlebars
</span><del>- <script type="text/x-handlebars" data-template-name='a-template'>
- {{#each person in people}}
- <div {{action edit person}}>
- click me
- </div>
- {{/each}}
- </script>
</del><ins>+ {{#each person in people}}
+ <div {{action "edit" person}}>
+ click me
+ </div>
+ {{/each}}
</ins><span class="cx"> ```
</span><span class="cx">
</span><del>- Clicking "click me" will trigger the `edit` method on the current view's
- controller with the current person as a parameter.
</del><ins>+ Clicking "click me" will trigger the `edit` method on the current controller
+ with the value of `person` as a parameter.
</ins><span class="cx">
</span><span class="cx"> @method action
</span><span class="cx"> @for Ember.Handlebars.helpers
</span><span class="lines">@@ -24070,13 +36764,12 @@
</span><span class="cx"> @param {Object} [context]*
</span><span class="cx"> @param {Hash} options
</span><span class="cx"> */
</span><del>- EmberHandlebars.registerHelper('action', function(actionName) {
</del><ins>+ EmberHandlebars.registerHelper('action', function actionHelper(actionName) {
</ins><span class="cx"> var options = arguments[arguments.length - 1],
</span><span class="cx"> contexts = a_slice.call(arguments, 1, -1);
</span><span class="cx">
</span><span class="cx"> var hash = options.hash,
</span><del>- view = options.data.view,
- controller, link;
</del><ins>+ controller;
</ins><span class="cx">
</span><span class="cx"> // create a hash to pass along to registerAction
</span><span class="cx"> var action = {
</span><span class="lines">@@ -24089,7 +36782,7 @@
</span><span class="cx"> params: contexts
</span><span class="cx"> };
</span><span class="cx">
</span><del>- action.view = view = get(view, 'concreteView');
</del><ins>+ action.view = options.data.view;
</ins><span class="cx">
</span><span class="cx"> var root, target;
</span><span class="cx">
</span><span class="lines">@@ -24102,8 +36795,9 @@
</span><span class="cx">
</span><span class="cx"> action.target = { root: root, target: target, options: options };
</span><span class="cx"> action.bubbles = hash.bubbles;
</span><ins>+ action.preventDefault = hash.preventDefault;
</ins><span class="cx">
</span><del>- var actionId = ActionHelper.registerAction(actionName, action);
</del><ins>+ var actionId = ActionHelper.registerAction(actionName, action, hash.allowedKeys);
</ins><span class="cx"> return new SafeString('data-ember-action="' + actionId + '"');
</span><span class="cx"> });
</span><span class="cx">
</span><span class="lines">@@ -24114,100 +36808,56 @@
</span><span class="cx">
</span><span class="cx">
</span><span class="cx"> (function() {
</span><ins>+
+})();
+
+
+
+(function() {
</ins><span class="cx"> /**
</span><span class="cx"> @module ember
</span><span class="cx"> @submodule ember-routing
</span><span class="cx"> */
</span><span class="cx">
</span><del>-if (Ember.ENV.EXPERIMENTAL_CONTROL_HELPER) {
- var get = Ember.get, set = Ember.set;
</del><ins>+var get = Ember.get, set = Ember.set;
</ins><span class="cx">
</span><ins>+Ember.ControllerMixin.reopen({
</ins><span class="cx"> /**
</span><del>- The control helper is currently under development and is considered experimental.
- To enable it, set `ENV.EXPERIMENTAL_CONTROL_HELPER = true` before requiring Ember.
</del><ins>+ Transition the application into another route. The route may
+ be either a single route or route path:
</ins><span class="cx">
</span><del>- @method control
- @for Ember.Handlebars.helpers
- @param {String} path
- @param {String} modelPath
- @param {Hash} options
- @return {String} HTML string
- */
- Ember.Handlebars.registerHelper('control', function(path, modelPath, options) {
- if (arguments.length === 2) {
- options = modelPath;
- modelPath = undefined;
- }
</del><ins>+ ```javascript
+ aController.transitionToRoute('blogPosts');
+ aController.transitionToRoute('blogPosts.recentEntries');
+ ```
</ins><span class="cx">
</span><del>- var model;
</del><ins>+ Optionally supply a model for the route in question. The model
+ will be serialized into the URL using the `serialize` hook of
+ the route:
</ins><span class="cx">
</span><del>- if (modelPath) {
- model = Ember.Handlebars.get(this, modelPath, options);
- }
</del><ins>+ ```javascript
+ aController.transitionToRoute('blogPost', aPost);
+ ```
</ins><span class="cx">
</span><del>- var controller = options.data.keywords.controller,
- view = options.data.keywords.view,
- children = get(controller, '_childContainers'),
- controlID = options.hash.controlID,
- container, subContainer;
</del><ins>+ Multiple models will be applied last to first recursively up the
+ resource tree.
</ins><span class="cx">
</span><del>- if (children.hasOwnProperty(controlID)) {
- subContainer = children[controlID];
- } else {
- container = get(controller, 'container'),
- subContainer = container.child();
- children[controlID] = subContainer;
- }
-
- var normalizedPath = path.replace(/\//g, '.');
-
- var childView = subContainer.lookup('view:' + normalizedPath) || subContainer.lookup('view:default'),
- childController = subContainer.lookup('controller:' + normalizedPath),
- childTemplate = subContainer.lookup('template:' + path);
-
- Ember.assert("Could not find controller for path: " + normalizedPath, childController);
- Ember.assert("Could not find view for path: " + normalizedPath, childView);
-
- set(childController, 'target', controller);
- set(childController, 'model', model);
-
- options.hash.template = childTemplate;
- options.hash.controller = childController;
-
- function observer() {
- var model = Ember.Handlebars.get(this, modelPath, options);
- set(childController, 'model', model);
- childView.rerender();
- }
-
- Ember.addObserver(this, modelPath, observer);
- childView.one('willDestroyElement', this, function() {
- Ember.removeObserver(this, modelPath, observer);
</del><ins>+ ```javascript
+ this.resource('blogPost', {path:':blogPostId'}, function(){
+ this.resource('blogComment', {path: ':blogCommentId'});
</ins><span class="cx"> });
</span><span class="cx">
</span><del>- Ember.Handlebars.helpers.view.call(this, childView, options);
- });
-}
</del><ins>+ aController.transitionToRoute('blogComment', aPost, aComment);
+ ```
</ins><span class="cx">
</span><del>-})();
</del><ins>+ See also 'replaceRoute'.
</ins><span class="cx">
</span><del>-
-
-(function() {
-
-})();
-
-
-
-(function() {
-/**
-@module ember
-@submodule ember-routing
-*/
-
-var get = Ember.get, set = Ember.set;
-
-Ember.ControllerMixin.reopen({
</del><ins>+ @param {String} name the name of the route
+ @param {...Object} models the model(s) to be used while transitioning
+ to the route.
+ @for Ember.ControllerMixin
+ @method transitionToRoute
+ */
</ins><span class="cx"> transitionToRoute: function() {
</span><span class="cx"> // target may be either another controller or a router
</span><span class="cx"> var target = get(this, 'target'),
</span><span class="lines">@@ -24215,11 +36865,51 @@
</span><span class="cx"> return method.apply(target, arguments);
</span><span class="cx"> },
</span><span class="cx">
</span><ins>+ /**
+ @deprecated
+ @for Ember.ControllerMixin
+ @method transitionTo
+ */
</ins><span class="cx"> transitionTo: function() {
</span><span class="cx"> Ember.deprecate("transitionTo is deprecated. Please use transitionToRoute.");
</span><span class="cx"> return this.transitionToRoute.apply(this, arguments);
</span><span class="cx"> },
</span><span class="cx">
</span><ins>+ /**
+ 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);
+ ```
+
+ Multiple models will be applied last to first recursively up the
+ resource tree.
+
+ ```javascript
+ this.resource('blogPost', {path:':blogPostId'}, function(){
+ this.resource('blogComment', {path: ':blogCommentId'});
+ });
+
+ aController.replaceRoute('blogComment', aPost, aComment);
+ ```
+
+ @param {String} name the name of the route
+ @param {...Object} models the model(s) to be used while transitioning
+ to the route.
+ @for Ember.ControllerMixin
+ @method replaceRoute
+ */
</ins><span class="cx"> replaceRoute: function() {
</span><span class="cx"> // target may be either another controller or a router
</span><span class="cx"> var target = get(this, 'target'),
</span><span class="lines">@@ -24227,6 +36917,11 @@
</span><span class="cx"> return method.apply(target, arguments);
</span><span class="cx"> },
</span><span class="cx">
</span><ins>+ /**
+ @deprecated
+ @for Ember.ControllerMixin
+ @method replaceWith
+ */
</ins><span class="cx"> replaceWith: function() {
</span><span class="cx"> Ember.deprecate("replaceWith is deprecated. Please use replaceRoute.");
</span><span class="cx"> return this.replaceRoute.apply(this, arguments);
</span><span class="lines">@@ -24246,12 +36941,56 @@
</span><span class="cx"> var get = Ember.get, set = Ember.set;
</span><span class="cx">
</span><span class="cx"> Ember.View.reopen({
</span><ins>+
+ /**
+ Sets the private `_outlets` object on the view.
+
+ @method init
+ */
</ins><span class="cx"> init: function() {
</span><span class="cx"> set(this, '_outlets', {});
</span><span class="cx"> this._super();
</span><span class="cx"> },
</span><span class="cx">
</span><ins>+ /**
+ 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:
+ // <div id="ember228" class="ember-view">Child view: </div>
+
+ var FooView = Ember.View.extend({
+ template: Ember.Handlebars.compile('<h1>Foo</h1> ')
+ });
+ var fooView = FooView.create();
+ myView.connectOutlet('main', fooView);
+ // The html for myView now looks like:
+ // <div id="ember228" class="ember-view">Child view:
+ // <div id="ember234" class="ember-view"><h1>Foo</h1> </div>
+ // </div>
+ ```
+ @method connectOutlet
+ @param {String} outletName A unique name for the outlet
+ @param {Object} view An Ember.View
+ */
</ins><span class="cx"> connectOutlet: function(outletName, view) {
</span><ins>+ if (this._pendingDisconnections) {
+ delete this._pendingDisconnections[outletName];
+ }
+
+ if (this._hasEquivalentView(outletName, view)) {
+ view.destroy();
+ return;
+ }
+
</ins><span class="cx"> var outlets = get(this, '_outlets'),
</span><span class="cx"> container = get(this, 'container'),
</span><span class="cx"> router = container && container.lookup('router:main'),
</span><span class="lines">@@ -24264,10 +37003,80 @@
</span><span class="cx"> }
</span><span class="cx"> },
</span><span class="cx">
</span><ins>+ /**
+ 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:
+ // <div id="ember228" class="ember-view">Child view: </div>
+
+ var FooView = Ember.View.extend({
+ template: Ember.Handlebars.compile('<h1>Foo</h1> ')
+ });
+ var fooView = FooView.create();
+ myView.connectOutlet('main', fooView);
+ // myView's html:
+ // <div id="ember228" class="ember-view">Child view:
+ // <div id="ember234" class="ember-view"><h1>Foo</h1> </div>
+ // </div>
+
+ myView.disconnectOutlet('main');
+ // myView's html:
+ // <div id="ember228" class="ember-view">Child view: </div>
+ ```
+
+ @method disconnectOutlet
+ @param {String} outletName The name of the outlet to be removed
+ */
</ins><span class="cx"> disconnectOutlet: function(outletName) {
</span><ins>+ 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() {
</ins><span class="cx"> var outlets = get(this, '_outlets');
</span><ins>+ var pendingDisconnections = this._pendingDisconnections;
+ this._pendingDisconnections = null;
</ins><span class="cx">
</span><del>- set(outlets, outletName, null);
</del><ins>+ for (var outletName in pendingDisconnections) {
+ set(outlets, outletName, null);
+ }
</ins><span class="cx"> }
</span><span class="cx"> });
</span><span class="cx">
</span><span class="lines">@@ -24276,7 +37085,20 @@
</span><span class="cx">
</span><span class="cx">
</span><span class="cx"> (function() {
</span><ins>+/**
+@module ember
+@submodule ember-views
+*/
</ins><span class="cx">
</span><ins>+// 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');
+
</ins><span class="cx"> })();
</span><span class="cx">
</span><span class="cx">
</span><span class="lines">@@ -24289,34 +37111,102 @@
</span><span class="cx">
</span><span class="cx"> var get = Ember.get, set = Ember.set;
</span><span class="cx">
</span><del>-/*
- This file implements the `location` API used by Ember's router.
</del><ins>+/**
+ Ember.Location returns an instance of the correct implementation of
+ the `location` API.
</ins><span class="cx">
</span><del>- That API is:
</del><ins>+ ## Implementations
</ins><span class="cx">
</span><del>- 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
</del><ins>+ You can pass an implementation name (`hash`, `history`, `none`) to force a
+ particular implementation to be used in your application.
</ins><span class="cx">
</span><del>- Calling setURL or replaceURL will not trigger onUpdateURL callbacks.
</del><ins>+ ### HashLocation
</ins><span class="cx">
</span><del>- TODO: This should perhaps be moved so that it's visible in the doc output.
-*/
</del><ins>+ 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.
</ins><span class="cx">
</span><del>-/**
- Ember.Location returns an instance of the correct implementation of
- the `location` API.
</del><ins>+ Example:
</ins><span class="cx">
</span><del>- You can pass it a `implementation` ('hash', 'history', 'none') to force a
- particular implementation.
</del><ins>+ ```javascript
+ App.Router.map(function() {
+ this.resource('posts', function() {
+ this.route('new');
+ });
+ });
</ins><span class="cx">
</span><ins>+ 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`.
+
+ ### 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.
+
</ins><span class="cx"> @class Location
</span><span class="cx"> @namespace Ember
</span><span class="cx"> @static
</span><span class="cx"> */
</span><span class="cx"> Ember.Location = {
</span><ins>+ /**
+ 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.
+ */
</ins><span class="cx"> create: function(options) {
</span><span class="cx"> var implementation = options && options.implementation;
</span><span class="cx"> Ember.assert("Ember.Location.create: you must specify a 'implementation' option", !!implementation);
</span><span class="lines">@@ -24327,6 +37217,28 @@
</span><span class="cx"> return implementationClass.create.apply(implementationClass, arguments);
</span><span class="cx"> },
</span><span class="cx">
</span><ins>+ /**
+ 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.
+ */
</ins><span class="cx"> registerImplementation: function(name, implementation) {
</span><span class="cx"> this.implementations[name] = implementation;
</span><span class="cx"> },
</span><span class="lines">@@ -24359,23 +37271,66 @@
</span><span class="cx"> Ember.NoneLocation = Ember.Object.extend({
</span><span class="cx"> path: '',
</span><span class="cx">
</span><ins>+ /**
+ Returns the current path.
+
+ @private
+ @method getURL
+ @return {String} path
+ */
</ins><span class="cx"> getURL: function() {
</span><span class="cx"> return get(this, 'path');
</span><span class="cx"> },
</span><span class="cx">
</span><ins>+ /**
+ 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}
+ */
</ins><span class="cx"> setURL: function(path) {
</span><span class="cx"> set(this, 'path', path);
</span><span class="cx"> },
</span><span class="cx">
</span><ins>+ /**
+ 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}
+ */
</ins><span class="cx"> onUpdateURL: function(callback) {
</span><span class="cx"> this.updateCallback = callback;
</span><span class="cx"> },
</span><span class="cx">
</span><ins>+ /**
+ Sets the path and calls the `updateURL` callback.
+
+ @private
+ @method handleURL
+ @param callback {Function}
+ */
</ins><span class="cx"> handleURL: function(url) {
</span><span class="cx"> set(this, 'path', url);
</span><span class="cx"> this.updateCallback(url);
</span><span class="cx"> },
</span><span class="cx">
</span><ins>+ /**
+ 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
+ */
</ins><span class="cx"> formatURL: function(url) {
</span><span class="cx"> // The return value is not overly meaningful, but we do not want to throw
</span><span class="cx"> // errors when test code renders templates containing {{action href=true}}
</span><span class="lines">@@ -24399,8 +37354,8 @@
</span><span class="cx"> var get = Ember.get, set = Ember.set;
</span><span class="cx">
</span><span class="cx"> /**
</span><del>- Ember.HashLocation implements the location API using the browser's
- hash. At present, it relies on a hashchange event existing in the
</del><ins>+ `Ember.HashLocation` implements the location API using the browser's
+ hash. At present, it relies on a `hashchange` event existing in the
</ins><span class="cx"> browser.
</span><span class="cx">
</span><span class="cx"> @class HashLocation
</span><span class="lines">@@ -24414,23 +37369,22 @@
</span><span class="cx"> },
</span><span class="cx">
</span><span class="cx"> /**
</span><del>- @private
-
</del><span class="cx"> Returns the current `location.hash`, minus the '#' at the front.
</span><span class="cx">
</span><ins>+ @private
</ins><span class="cx"> @method getURL
</span><span class="cx"> */
</span><span class="cx"> getURL: function() {
</span><ins>+ // Default implementation without feature flag enabled
</ins><span class="cx"> return get(this, 'location').hash.substr(1);
</span><span class="cx"> },
</span><span class="cx">
</span><span class="cx"> /**
</span><del>- @private
-
</del><span class="cx"> Set the `location.hash` and remembers what was set. This prevents
</span><span class="cx"> `onUpdateURL` callbacks from triggering when the hash was set by
</span><span class="cx"> `HashLocation`.
</span><span class="cx">
</span><ins>+ @private
</ins><span class="cx"> @method setURL
</span><span class="cx"> @param path {String}
</span><span class="cx"> */
</span><span class="lines">@@ -24440,12 +37394,23 @@
</span><span class="cx"> },
</span><span class="cx">
</span><span class="cx"> /**
</span><ins>+ Uses location.replace to update the url without a page reload
+ or history modification.
+
</ins><span class="cx"> @private
</span><ins>+ @method replaceURL
+ @param path {String}
+ */
+ replaceURL: function(path) {
+ get(this, 'location').replace('#' + path);
+ },
</ins><span class="cx">
</span><ins>+ /**
</ins><span class="cx"> Register a callback to be invoked when the hash changes. These
</span><span class="cx"> callbacks will execute when the user presses the back or forward
</span><span class="cx"> button, but not after `setURL` is invoked.
</span><span class="cx">
</span><ins>+ @private
</ins><span class="cx"> @method onUpdateURL
</span><span class="cx"> @param callback {Function}
</span><span class="cx"> */
</span><span class="lines">@@ -24453,27 +37418,26 @@
</span><span class="cx"> var self = this;
</span><span class="cx"> var guid = Ember.guidFor(this);
</span><span class="cx">
</span><del>- Ember.$(window).bind('hashchange.ember-location-'+guid, function() {
</del><ins>+ Ember.$(window).on('hashchange.ember-location-'+guid, function() {
</ins><span class="cx"> Ember.run(function() {
</span><span class="cx"> var path = location.hash.substr(1);
</span><span class="cx"> if (get(self, 'lastSetURL') === path) { return; }
</span><span class="cx">
</span><span class="cx"> set(self, 'lastSetURL', null);
</span><span class="cx">
</span><del>- callback(location.hash.substr(1));
</del><ins>+ callback(path);
</ins><span class="cx"> });
</span><span class="cx"> });
</span><span class="cx"> },
</span><span class="cx">
</span><span class="cx"> /**
</span><del>- @private
-
</del><span class="cx"> Given a URL, formats it to be placed into the page as part
</span><span class="cx"> of an element's `href` attribute.
</span><span class="cx">
</span><span class="cx"> This is used, for example, when using the {{action}} helper
</span><span class="cx"> to generate a URL based on an event.
</span><span class="cx">
</span><ins>+ @private
</ins><span class="cx"> @method formatURL
</span><span class="cx"> @param url {String}
</span><span class="cx"> */
</span><span class="lines">@@ -24481,10 +37445,16 @@
</span><span class="cx"> return '#'+url;
</span><span class="cx"> },
</span><span class="cx">
</span><ins>+ /**
+ Cleans up the HashLocation event listener.
+
+ @private
+ @method willDestroy
+ */
</ins><span class="cx"> willDestroy: function() {
</span><span class="cx"> var guid = Ember.guidFor(this);
</span><span class="cx">
</span><del>- Ember.$(window).unbind('hashchange.ember-location-'+guid);
</del><ins>+ Ember.$(window).off('hashchange.ember-location-'+guid);
</ins><span class="cx"> }
</span><span class="cx"> });
</span><span class="cx">
</span><span class="lines">@@ -24501,7 +37471,8 @@
</span><span class="cx"> */
</span><span class="cx">
</span><span class="cx"> var get = Ember.get, set = Ember.set;
</span><del>-var popstateReady = false;
</del><ins>+var popstateFired = false;
+var supportsHistoryState = window.history && 'state' in window.history;
</ins><span class="cx">
</span><span class="cx"> /**
</span><span class="cx"> Ember.HistoryLocation implements the location API using the browser's
</span><span class="lines">@@ -24515,19 +37486,17 @@
</span><span class="cx">
</span><span class="cx"> init: function() {
</span><span class="cx"> set(this, 'location', get(this, 'location') || window.location);
</span><del>- this.initState();
</del><span class="cx"> },
</span><span class="cx">
</span><span class="cx"> /**
</span><del>- @private
-
</del><span class="cx"> Used to set state on first call to setURL
</span><span class="cx">
</span><ins>+ @private
</ins><span class="cx"> @method initState
</span><span class="cx"> */
</span><span class="cx"> initState: function() {
</span><ins>+ set(this, 'history', get(this, 'history') || window.history);
</ins><span class="cx"> this.replaceState(this.formatURL(this.getURL()));
</span><del>- set(this, 'history', window.history);
</del><span class="cx"> },
</span><span class="cx">
</span><span class="cx"> /**
</span><span class="lines">@@ -24539,98 +37508,117 @@
</span><span class="cx"> rootURL: '/',
</span><span class="cx">
</span><span class="cx"> /**
</span><ins>+ Returns the current `location.pathname` without `rootURL`.
+
</ins><span class="cx"> @private
</span><del>-
- Returns the current `location.pathname` without rootURL
-
</del><span class="cx"> @method getURL
</span><ins>+ @return url {String}
</ins><span class="cx"> */
</span><span class="cx"> getURL: function() {
</span><span class="cx"> var rootURL = get(this, 'rootURL'),
</span><del>- url = get(this, 'location').pathname;
</del><ins>+ location = get(this, 'location'),
+ path = location.pathname;
</ins><span class="cx">
</span><span class="cx"> rootURL = rootURL.replace(/\/$/, '');
</span><del>- url = url.replace(rootURL, '');
</del><ins>+ var url = path.replace(rootURL, '');
</ins><span class="cx">
</span><ins>+
</ins><span class="cx"> return url;
</span><span class="cx"> },
</span><span class="cx">
</span><span class="cx"> /**
</span><del>- @private
-
</del><span class="cx"> Uses `history.pushState` to update the url without a page reload.
</span><span class="cx">
</span><ins>+ @private
</ins><span class="cx"> @method setURL
</span><span class="cx"> @param path {String}
</span><span class="cx"> */
</span><span class="cx"> setURL: function(path) {
</span><ins>+ var state = this.getState();
</ins><span class="cx"> path = this.formatURL(path);
</span><span class="cx">
</span><del>- if (this.getState() && this.getState().path !== path) {
- popstateReady = true;
</del><ins>+ if (state && state.path !== path) {
</ins><span class="cx"> this.pushState(path);
</span><span class="cx"> }
</span><span class="cx"> },
</span><span class="cx">
</span><span class="cx"> /**
</span><del>- @private
-
</del><span class="cx"> Uses `history.replaceState` to update the url without a page reload
</span><span class="cx"> or history modification.
</span><span class="cx">
</span><ins>+ @private
</ins><span class="cx"> @method replaceURL
</span><span class="cx"> @param path {String}
</span><span class="cx"> */
</span><span class="cx"> replaceURL: function(path) {
</span><ins>+ var state = this.getState();
</ins><span class="cx"> path = this.formatURL(path);
</span><span class="cx">
</span><del>- if (this.getState() && this.getState().path !== path) {
- popstateReady = true;
</del><ins>+ if (state && state.path !== path) {
</ins><span class="cx"> this.replaceState(path);
</span><span class="cx"> }
</span><span class="cx"> },
</span><span class="cx">
</span><span class="cx"> /**
</span><del>- @private
-
</del><span class="cx"> Get the current `history.state`
</span><ins>+ Polyfill checks for native browser support and falls back to retrieving
+ from a private _historyState variable
</ins><span class="cx">
</span><ins>+ @private
</ins><span class="cx"> @method getState
</span><ins>+ @return state {Object}
</ins><span class="cx"> */
</span><span class="cx"> getState: function() {
</span><del>- return get(this, 'history').state;
</del><ins>+ return supportsHistoryState ? get(this, 'history').state : this._historyState;
</ins><span class="cx"> },
</span><span class="cx">
</span><span class="cx"> /**
</span><ins>+ Pushes a new state.
+
</ins><span class="cx"> @private
</span><del>-
- Pushes a new state
-
</del><span class="cx"> @method pushState
</span><span class="cx"> @param path {String}
</span><span class="cx"> */
</span><span class="cx"> pushState: function(path) {
</span><del>- window.history.pushState({ path: path }, null, path);
</del><ins>+ 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();
</ins><span class="cx"> },
</span><span class="cx">
</span><span class="cx"> /**
</span><ins>+ Replaces the current state.
+
</ins><span class="cx"> @private
</span><del>-
- Replaces the current state
-
</del><span class="cx"> @method replaceState
</span><span class="cx"> @param path {String}
</span><span class="cx"> */
</span><span class="cx"> replaceState: function(path) {
</span><del>- window.history.replaceState({ path: path }, null, path);
</del><ins>+ 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();
</ins><span class="cx"> },
</span><span class="cx">
</span><span class="cx"> /**
</span><del>- @private
-
</del><span class="cx"> Register a callback to be invoked whenever the browser
</span><span class="cx"> history changes, including using forward and back buttons.
</span><span class="cx">
</span><ins>+ @private
</ins><span class="cx"> @method onUpdateURL
</span><span class="cx"> @param callback {Function}
</span><span class="cx"> */
</span><span class="lines">@@ -24638,21 +37626,23 @@
</span><span class="cx"> var guid = Ember.guidFor(this),
</span><span class="cx"> self = this;
</span><span class="cx">
</span><del>- Ember.$(window).bind('popstate.ember-location-'+guid, function(e) {
- if(!popstateReady) {
- return;
</del><ins>+ 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; }
</ins><span class="cx"> }
</span><span class="cx"> callback(self.getURL());
</span><span class="cx"> });
</span><span class="cx"> },
</span><span class="cx">
</span><span class="cx"> /**
</span><del>- @private
-
</del><span class="cx"> Used when using `{{action}}` helper. The url is always appended to the rootURL.
</span><span class="cx">
</span><ins>+ @private
</ins><span class="cx"> @method formatURL
</span><span class="cx"> @param url {String}
</span><ins>+ @return formatted url {String}
</ins><span class="cx"> */
</span><span class="cx"> formatURL: function(url) {
</span><span class="cx"> var rootURL = get(this, 'rootURL');
</span><span class="lines">@@ -24664,10 +37654,16 @@
</span><span class="cx"> return rootURL + url;
</span><span class="cx"> },
</span><span class="cx">
</span><ins>+ /**
+ Cleans up the HistoryLocation event listener.
+
+ @private
+ @method willDestroy
+ */
</ins><span class="cx"> willDestroy: function() {
</span><span class="cx"> var guid = Ember.guidFor(this);
</span><span class="cx">
</span><del>- Ember.$(window).unbind('popstate.ember-location-'+guid);
</del><ins>+ Ember.$(window).off('popstate.ember-location-'+guid);
</ins><span class="cx"> }
</span><span class="cx"> });
</span><span class="cx">
</span><span class="lines">@@ -24689,7 +37685,6 @@
</span><span class="cx">
</span><span class="cx"> @module ember
</span><span class="cx"> @submodule ember-routing
</span><del>-@requires ember-states
</del><span class="cx"> @requires ember-views
</span><span class="cx"> */
</span><span class="cx">
</span><span class="lines">@@ -24752,7 +37747,7 @@
</span><span class="cx"> }
</span><span class="cx"> function checkCycle(vertex, path) {
</span><span class="cx"> if (vertex.name === toName) {
</span><del>- throw new Error("cycle detected: " + toName + " <- " + path.join(" <- "));
</del><ins>+ throw new Ember.Error("cycle detected: " + toName + " <- " + path.join(" <- "));
</ins><span class="cx"> }
</span><span class="cx"> }
</span><span class="cx"> visit(from, checkCycle);
</span><span class="lines">@@ -24810,11 +37805,341 @@
</span><span class="cx"> @submodule ember-application
</span><span class="cx"> */
</span><span class="cx">
</span><del>-var get = Ember.get, set = Ember.set,
</del><ins>+var get = Ember.get,
</ins><span class="cx"> classify = Ember.String.classify,
</span><ins>+ capitalize = Ember.String.capitalize,
</ins><span class="cx"> decamelize = Ember.String.decamelize;
</span><span class="cx">
</span><span class="cx"> /**
</span><ins>+ 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),
+ typeSpecificResolveMethod = this[parsedName.resolveMethodName];
+
+ if (!parsedName.name || !parsedName.type) {
+ throw new TypeError("Invalid fullName: `" + fullName + "`, must be of the form `type:name` ");
+ }
+
+ if (typeSpecificResolveMethod) {
+ var resolved = typeSpecificResolveMethod.call(this, 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)
+ };
+ },
+ /**
+ 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];
+ }
+ },
+ /**
+ 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 = '';
+ }
+ },
+ /**
+ 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 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);
+ },
+
+ resolveHelper: function(parsedName) {
+ return this.resolveOther(parsedName) || Ember.Handlebars.helpers[parsedName.fullNameWithoutType];
+ },
+
+ /**
+ 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 resolveOther
+ */
+ resolveOther: function(parsedName) {
+ var className = classify(parsedName.name) + classify(parsedName.type),
+ factory = get(parsedName.root, className);
+ if (factory) { return factory; }
+ },
+
+ /**
+ 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();
+ }
+});
+
+})();
+
+
+
+(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')
+};
+
+/**
</ins><span class="cx"> An instance of `Ember.Application` is the starting point for every Ember
</span><span class="cx"> application. It helps to instantiate, initialize and coordinate the many
</span><span class="cx"> objects that make up your app.
</span><span class="lines">@@ -24837,11 +38162,14 @@
</span><span class="cx"> ```
</span><span class="cx">
</span><span class="cx"> By default, calling `Ember.Application.create()` will automatically initialize
</span><del>- your application by calling the `Ember.Application.initialize()` method. If
</del><ins>+ your application by calling the `Ember.Application.initialize()` method. If
</ins><span class="cx"> you need to delay initialization, you can call your app's `deferReadiness()`
</span><span class="cx"> method. When you are ready for your app to be initialized, call its
</span><span class="cx"> `advanceReadiness()` method.
</span><span class="cx">
</span><ins>+ You can define a `ready` method on the `Ember.Application` instance, which
+ will be run by Ember when the application is initialized.
+
</ins><span class="cx"> Because `Ember.Application` inherits from `Ember.Namespace`, any classes
</span><span class="cx"> you create will have useful string representations when calling `toString()`.
</span><span class="cx"> See the `Ember.Namespace` documentation for more information.
</span><span class="lines">@@ -24867,16 +38195,15 @@
</span><span class="cx"> example, the `keypress` event causes the `keyPress` method on the view to be
</span><span class="cx"> called, the `dblclick` event causes `doubleClick` to be called, and so on.
</span><span class="cx">
</span><del>- If there is a 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:
</del><ins>+ 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:
</ins><span class="cx">
</span><span class="cx"> ```javascript
</span><span class="cx"> App = Ember.Application.create({
</span><span class="cx"> customEvents: {
</span><del>- // add support for the loadedmetadata media
- // player event
- 'loadedmetadata': "loadedMetadata"
</del><ins>+ // add support for the paste event
+ paste: "paste"
</ins><span class="cx"> }
</span><span class="cx"> });
</span><span class="cx"> ```
</span><span class="lines">@@ -24902,7 +38229,7 @@
</span><span class="cx">
</span><span class="cx"> To learn more about the advantages of event delegation and the Ember view
</span><span class="cx"> layer, and a list of the event listeners that are setup by default, visit the
</span><del>- [Ember View Layer guide](http://emberjs.com/guides/view_layer#toc_event-delegation).
</del><ins>+ [Ember View Layer guide](http://emberjs.com/guides/understanding-ember/the-view-layer/#toc_event-delegation).
</ins><span class="cx">
</span><span class="cx"> ### Initializers
</span><span class="cx">
</span><span class="lines">@@ -24913,7 +38240,7 @@
</span><span class="cx"> name: "store",
</span><span class="cx">
</span><span class="cx"> initialize: function(container, application) {
</span><del>- container.register('store', 'main', application.Store);
</del><ins>+ container.register('store:main', application.Store);
</ins><span class="cx"> }
</span><span class="cx"> });
</span><span class="cx"> ```
</span><span class="lines">@@ -24922,11 +38249,14 @@
</span><span class="cx">
</span><span class="cx"> In addition to creating your application's router, `Ember.Application` is
</span><span class="cx"> also responsible for telling the router when to start routing. Transitions
</span><del>- between routes can be logged with the LOG_TRANSITIONS flag:
</del><ins>+ 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:
</ins><span class="cx">
</span><span class="cx"> ```javascript
</span><span class="cx"> window.App = Ember.Application.create({
</span><del>- LOG_TRANSITIONS: true
</del><ins>+ LOG_TRANSITIONS: true, // basic logging of successful transitions
+ LOG_TRANSITIONS_INTERNAL: true // detailed logging of all routing steps
</ins><span class="cx"> });
</span><span class="cx"> ```
</span><span class="cx">
</span><span class="lines">@@ -24938,25 +38268,15 @@
</span><span class="cx"> If there is any setup required before routing begins, you can implement a
</span><span class="cx"> `ready()` method on your app that will be invoked immediately before routing
</span><span class="cx"> begins.
</span><del>-
- To begin routing, you must have at a minimum a top-level controller and view.
- You define these as `App.ApplicationController` and `App.ApplicationView`,
- respectively. Your application will not work if you do not define these two
- mandatory classes. For example:
-
- ```javascript
- App.ApplicationView = Ember.View.extend({
- templateName: 'application'
- });
- App.ApplicationController = Ember.Controller.extend();
</del><span class="cx"> ```
</span><span class="cx">
</span><span class="cx"> @class Application
</span><span class="cx"> @namespace Ember
</span><span class="cx"> @extends Ember.Namespace
</span><span class="cx"> */
</span><del>-var Application = Ember.Application = Ember.Namespace.extend({
</del><span class="cx">
</span><ins>+var Application = Ember.Application = Ember.Namespace.extend(Ember.DeferredMixin, {
+
</ins><span class="cx"> /**
</span><span class="cx"> The root DOM element of the Application. This can be specified as an
</span><span class="cx"> element or a
</span><span class="lines">@@ -24996,7 +38316,7 @@
</span><span class="cx"> `keyup`, and delegates them to your application's `Ember.View`
</span><span class="cx"> instances.
</span><span class="cx">
</span><del>- If you would like additional events to be delegated to your
</del><ins>+ If you would like additional bubbling events to be delegated to your
</ins><span class="cx"> views, set your `Ember.Application`'s `customEvents` property
</span><span class="cx"> to a hash containing the DOM event name as the key and the
</span><span class="cx"> corresponding view method name as the value. For example:
</span><span class="lines">@@ -25004,9 +38324,8 @@
</span><span class="cx"> ```javascript
</span><span class="cx"> App = Ember.Application.create({
</span><span class="cx"> customEvents: {
</span><del>- // add support for the loadedmetadata media
- // player event
- 'loadedmetadata': "loadedMetadata"
</del><ins>+ // add support for the paste event
+ paste: "paste"
</ins><span class="cx"> }
</span><span class="cx"> });
</span><span class="cx"> ```
</span><span class="lines">@@ -25017,8 +38336,6 @@
</span><span class="cx"> */
</span><span class="cx"> customEvents: null,
</span><span class="cx">
</span><del>- isInitialized: false,
-
</del><span class="cx"> // Start off the number of deferrals at 1. This will be
</span><span class="cx"> // decremented by the Application's own `initialize` method.
</span><span class="cx"> _readinessDeferrals: 1,
</span><span class="lines">@@ -25027,29 +38344,35 @@
</span><span class="cx"> if (!this.$) { this.$ = Ember.$; }
</span><span class="cx"> this.__container__ = this.buildContainer();
</span><span class="cx">
</span><del>- this.Router = this.Router || this.defaultRouter();
- if (this.Router) { this.Router.namespace = this; }
</del><ins>+ this.Router = this.defaultRouter();
</ins><span class="cx">
</span><span class="cx"> this._super();
</span><span class="cx">
</span><del>- this.deferUntilDOMReady();
</del><span class="cx"> this.scheduleInitialize();
</span><span class="cx">
</span><del>- Ember.debug('-------------------------------');
- Ember.debug('Ember.VERSION : ' + Ember.VERSION);
- Ember.debug('Handlebars.VERSION : ' + Ember.Handlebars.VERSION);
- Ember.debug('jQuery.VERSION : ' + Ember.$().jquery);
- Ember.debug('-------------------------------');
</del><ins>+ 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('-------------------------------');
+ }
</ins><span class="cx"> },
</span><span class="cx">
</span><span class="cx"> /**
</span><del>- @private
-
</del><span class="cx"> Build the container for the current application.
</span><span class="cx">
</span><span class="cx"> Also register a default application view in case the application
</span><span class="cx"> itself does not.
</span><span class="cx">
</span><ins>+ @private
</ins><span class="cx"> @method buildContainer
</span><span class="cx"> @return {Ember.Container} the configured container
</span><span class="cx"> */
</span><span class="lines">@@ -25060,8 +38383,6 @@
</span><span class="cx"> },
</span><span class="cx">
</span><span class="cx"> /**
</span><del>- @private
-
</del><span class="cx"> If the application has not opted out of routing and has not explicitly
</span><span class="cx"> defined a router, supply a default router for the application author
</span><span class="cx"> to configure.
</span><span class="lines">@@ -25069,50 +38390,35 @@
</span><span class="cx"> This allows application developers to do:
</span><span class="cx">
</span><span class="cx"> ```javascript
</span><del>- App = Ember.Application.create();
</del><ins>+ var App = Ember.Application.create();
</ins><span class="cx">
</span><del>- App.Router.map(function(match) {
- match("/").to("index");
</del><ins>+ App.Router.map(function() {
+ this.resource('posts');
</ins><span class="cx"> });
</span><span class="cx"> ```
</span><span class="cx">
</span><ins>+ @private
</ins><span class="cx"> @method defaultRouter
</span><span class="cx"> @return {Ember.Router} the default router
</span><span class="cx"> */
</span><ins>+
</ins><span class="cx"> defaultRouter: function() {
</span><del>- // Create a default App.Router if one was not supplied to make
- // it possible to do App.Router.map(...) without explicitly
- // creating a router first.
- if (this.router === undefined) {
- return Ember.Router.extend();
</del><ins>+ if (this.Router === false) { return; }
+ var container = this.__container__;
+
+ if (this.Router) {
+ container.unregister('router:main');
+ container.register('router:main', this.Router);
</ins><span class="cx"> }
</span><del>- },
</del><span class="cx">
</span><del>- /**
- @private
-
- Defer Ember readiness until DOM readiness. By default, Ember
- will wait for both DOM readiness and application initialization,
- as well as any deferrals registered by initializers.
-
- @method deferUntilDOMReady
- */
- deferUntilDOMReady: function() {
- this.deferReadiness();
-
- var self = this;
- this.$().ready(function() {
- self.advanceReadiness();
- });
</del><ins>+ return container.lookupFactory('router:main');
</ins><span class="cx"> },
</span><span class="cx">
</span><span class="cx"> /**
</span><del>- @private
-
</del><span class="cx"> Automatically initialize the application once the DOM has
</span><span class="cx"> become ready.
</span><span class="cx">
</span><del>- The initialization itself is deferred using Ember.run.once,
</del><ins>+ The initialization itself is scheduled on the actions queue
</ins><span class="cx"> which ensures that application loading finishes before
</span><span class="cx"> booting.
</span><span class="cx">
</span><span class="lines">@@ -25121,14 +38427,19 @@
</span><span class="cx"> `advanceReadiness()` once all of your code has finished
</span><span class="cx"> loading.
</span><span class="cx">
</span><ins>+ @private
</ins><span class="cx"> @method scheduleInitialize
</span><span class="cx"> */
</span><span class="cx"> scheduleInitialize: function() {
</span><span class="cx"> var self = this;
</span><del>- this.$().ready(function() {
- if (self.isDestroyed || self.isInitialized) return;
- Ember.run.once(self, 'initialize');
- });
</del><ins>+
+ if (!this.$ || this.$.isReady) {
+ Ember.run.schedule('actions', self, '_initialize');
+ } else {
+ this.$().ready(function runInitialize() {
+ Ember.run(self, '_initialize');
+ });
+ }
</ins><span class="cx"> },
</span><span class="cx">
</span><span class="cx"> /**
</span><span class="lines">@@ -25155,15 +38466,21 @@
</span><span class="cx"> @method deferReadiness
</span><span class="cx"> */
</span><span class="cx"> deferReadiness: function() {
</span><ins>+ Ember.assert("You must call deferReadiness on an instance of Ember.Application", this instanceof Ember.Application);
</ins><span class="cx"> Ember.assert("You cannot defer readiness since the `ready()` hook has already been called.", this._readinessDeferrals > 0);
</span><span class="cx"> this._readinessDeferrals++;
</span><span class="cx"> },
</span><span class="cx">
</span><span class="cx"> /**
</span><ins>+ 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.
+
</ins><span class="cx"> @method advanceReadiness
</span><span class="cx"> @see {Ember.Application#deferReadiness}
</span><span class="cx"> */
</span><span class="cx"> advanceReadiness: function() {
</span><ins>+ Ember.assert("You must call advanceReadiness on an instance of Ember.Application", this instanceof Ember.Application);
</ins><span class="cx"> this._readinessDeferrals--;
</span><span class="cx">
</span><span class="cx"> if (this._readinessDeferrals === 0) {
</span><span class="lines">@@ -25179,19 +38496,20 @@
</span><span class="cx"> ```javascript
</span><span class="cx"> App = Ember.Application.create();
</span><span class="cx">
</span><del>- App.Person = Ember.Object.extend({});
- App.Orange = Ember.Object.extend({});
- App.Email = Ember.Object.extend({});
</del><ins>+ App.Person = Ember.Object.extend({});
+ App.Orange = Ember.Object.extend({});
+ App.Email = Ember.Object.extend({});
+ App.session = Ember.Object.create({});
</ins><span class="cx">
</span><span class="cx"> App.register('model:user', App.Person, {singleton: false });
</span><span class="cx"> App.register('fruit:favorite', App.Orange);
</span><span class="cx"> App.register('communication:main', App.Email, {singleton: false});
</span><ins>+ App.register('session', App.session, {instantiate: false});
</ins><span class="cx"> ```
</span><span class="cx">
</span><span class="cx"> @method register
</span><del>- @param type {String}
- @param name {String}
- @param factory {String}
</del><ins>+ @param fullName {String} type:name (e.g., 'model:user')
+ @param factory {Function} (e.g., App.Person)
</ins><span class="cx"> @param options {String} (optional)
</span><span class="cx"> **/
</span><span class="cx"> register: function() {
</span><span class="lines">@@ -25214,29 +38532,43 @@
</span><span class="cx"> @param property {String}
</span><span class="cx"> @param injectionName {String}
</span><span class="cx"> **/
</span><del>- inject: function(){
</del><ins>+ inject: function() {
</ins><span class="cx"> var container = this.__container__;
</span><span class="cx"> container.injection.apply(container, arguments);
</span><span class="cx"> },
</span><span class="cx">
</span><span class="cx"> /**
</span><ins>+ Calling initialize manually is not supported.
+
+ Please see Ember.Application#advanceReadiness and
+ Ember.Application#deferReadiness.
+
</ins><span class="cx"> @private
</span><del>-
</del><ins>+ @deprecated
+ @method initialize
+ **/
+ initialize: function() {
+ Ember.deprecate('Calling initialize manually is not supported. Please see Ember.Application#advanceReadiness and Ember.Application#deferReadiness');
+ },
+ /**
</ins><span class="cx"> Initialize the application. This happens automatically.
</span><span class="cx">
</span><span class="cx"> Run any initializers and run the application load hook. These hooks may
</span><span class="cx"> choose to defer readiness. For example, an authentication hook might want
</span><span class="cx"> to defer readiness until the auth token has been retrieved.
</span><span class="cx">
</span><del>- @method initialize
</del><ins>+ @private
+ @method _initialize
</ins><span class="cx"> */
</span><del>- initialize: function() {
- Ember.assert("Application initialize may only be called once", !this.isInitialized);
- Ember.assert("Cannot initialize a destroyed application", !this.isDestroyed);
- this.isInitialized = true;
</del><ins>+ _initialize: function() {
+ if (this.isDestroyed) { return; }
</ins><span class="cx">
</span><span class="cx"> // At this point, the App.Router must already be assigned
</span><del>- this.__container__.register('router', 'main', this.Router);
</del><ins>+ if (this.Router) {
+ var container = this.__container__;
+ container.unregister('router:main');
+ container.register('router:main', this.Router);
+ }
</ins><span class="cx">
</span><span class="cx"> this.runInitializers();
</span><span class="cx"> Ember.runLoadHooks('application', this);
</span><span class="lines">@@ -25249,13 +38581,92 @@
</span><span class="cx"> return this;
</span><span class="cx"> },
</span><span class="cx">
</span><ins>+ /**
+ 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
+ **/
</ins><span class="cx"> reset: function() {
</span><del>- get(this, '__container__').destroy();
- this.buildContainer();
</del><ins>+ this._readinessDeferrals = 1;
</ins><span class="cx">
</span><del>- this.isInitialized = false;
- this.initialize();
- this.startRouting();
</del><ins>+ 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);
</ins><span class="cx"> },
</span><span class="cx">
</span><span class="cx"> /**
</span><span class="lines">@@ -25267,15 +38678,16 @@
</span><span class="cx"> container = this.__container__,
</span><span class="cx"> graph = new Ember.DAG(),
</span><span class="cx"> namespace = this,
</span><del>- properties, i, initializer;
</del><ins>+ name, initializer;
</ins><span class="cx">
</span><del>- for (i=0; i<initializers.length; i++) {
- initializer = initializers[i];
</del><ins>+ for (name in initializers) {
+ initializer = initializers[name];
</ins><span class="cx"> graph.addEdges(initializer.name, initializer.initialize, initializer.before, initializer.after);
</span><span class="cx"> }
</span><span class="cx">
</span><span class="cx"> graph.topsort(function (vertex) {
</span><span class="cx"> var initializer = vertex.value;
</span><ins>+ Ember.assert("No application initializer named '"+vertex.name+"'", initializer);
</ins><span class="cx"> initializer(container, namespace);
</span><span class="cx"> });
</span><span class="cx"> },
</span><span class="lines">@@ -25294,47 +38706,32 @@
</span><span class="cx"> Ember.Namespace.processAll();
</span><span class="cx"> Ember.BOOTED = true;
</span><span class="cx"> }
</span><ins>+
+ this.resolve(this);
</ins><span class="cx"> },
</span><span class="cx">
</span><span class="cx"> /**
</span><del>- @private
-
</del><span class="cx"> Setup up the event dispatcher to receive events on the
</span><span class="cx"> application's `rootElement` with any registered
</span><span class="cx"> `customEvents`.
</span><span class="cx">
</span><ins>+ @private
</ins><span class="cx"> @method setupEventDispatcher
</span><span class="cx"> */
</span><span class="cx"> setupEventDispatcher: function() {
</span><del>- var eventDispatcher = this.createEventDispatcher(),
- customEvents = get(this, 'customEvents');
</del><ins>+ var customEvents = get(this, 'customEvents'),
+ rootElement = get(this, 'rootElement'),
+ dispatcher = this.__container__.lookup('event_dispatcher:main');
</ins><span class="cx">
</span><del>- eventDispatcher.setup(customEvents);
</del><ins>+ set(this, 'eventDispatcher', dispatcher);
+ dispatcher.setup(customEvents, rootElement);
</ins><span class="cx"> },
</span><span class="cx">
</span><span class="cx"> /**
</span><del>- @private
</del><ins>+ 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
</ins><span class="cx">
</span><del>- Create an event dispatcher for the application's `rootElement`.
-
- @method createEventDispatcher
- */
- createEventDispatcher: function() {
- var rootElement = get(this, 'rootElement'),
- eventDispatcher = Ember.EventDispatcher.create({
- rootElement: rootElement
- });
-
- set(this, 'eventDispatcher', eventDispatcher);
- return eventDispatcher;
- },
-
- /**
</del><span class="cx"> @private
</span><del>-
- If the application has a router, use it to route to the current URL, and
- trigger a new call to `route` whenever the URL changes.
-
</del><span class="cx"> @method startRouting
</span><span class="cx"> @property router {Ember.Router}
</span><span class="cx"> */
</span><span class="lines">@@ -25359,13 +38756,26 @@
</span><span class="cx"> */
</span><span class="cx"> ready: Ember.K,
</span><span class="cx">
</span><ins>+ /**
+ @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,
+
</ins><span class="cx"> willDestroy: function() {
</span><span class="cx"> Ember.BOOTED = false;
</span><span class="cx">
</span><del>- var eventDispatcher = get(this, 'eventDispatcher');
- if (eventDispatcher) { eventDispatcher.destroy(); }
-
- get(this, '__container__').destroy();
</del><ins>+ this.__container__.destroy();
</ins><span class="cx"> },
</span><span class="cx">
</span><span class="cx"> initializer: function(options) {
</span><span class="lines">@@ -25374,21 +38784,26 @@
</span><span class="cx"> });
</span><span class="cx">
</span><span class="cx"> Ember.Application.reopenClass({
</span><del>- concatenatedProperties: ['initializers'],
- initializers: Ember.A(),
</del><ins>+ initializers: {},
</ins><span class="cx"> initializer: function(initializer) {
</span><del>- var initializers = get(this, 'initializers');
</del><ins>+ // 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)
+ });
+ }
</ins><span class="cx">
</span><del>- Ember.assert("The initializer '" + initializer.name + "' has already been registered", !initializers.findProperty('name', initializers.name));
- Ember.assert("An injection cannot be registered with both a before and an after", !(initializer.before && initializer.after));
- Ember.assert("An injection cannot be registered without an injection function", Ember.canInvoke(initializer, 'initialize'));
</del><ins>+ 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'));
</ins><span class="cx">
</span><del>- initializers.push(initializer);
</del><ins>+ this.initializers[initializer.name] = initializer;
</ins><span class="cx"> },
</span><span class="cx">
</span><span class="cx"> /**
</span><del>- @private
-
</del><span class="cx"> This creates a container with the default Ember naming conventions.
</span><span class="cx">
</span><span class="cx"> It also configures the container:
</span><span class="lines">@@ -25406,6 +38821,7 @@
</span><span class="cx"> * the application view receives the application template as its
</span><span class="cx"> `defaultTemplate` property
</span><span class="cx">
</span><ins>+ @private
</ins><span class="cx"> @method buildContainer
</span><span class="cx"> @static
</span><span class="cx"> @param {Ember.Application} namespace the application to build the
</span><span class="lines">@@ -25414,27 +38830,41 @@
</span><span class="cx"> */
</span><span class="cx"> buildContainer: function(namespace) {
</span><span class="cx"> var container = new Ember.Container();
</span><del>- Ember.Container.defaultContainer = Ember.Container.defaultContainer || container;
</del><span class="cx">
</span><ins>+ Ember.Container.defaultContainer = new DeprecatedContainer(container);
+
</ins><span class="cx"> container.set = Ember.set;
</span><del>- container.resolver = resolverFor(namespace);
</del><ins>+ container.resolver = resolverFor(namespace);
+ container.normalize = container.resolver.normalize;
+ container.describe = container.resolver.describe;
+ container.makeToString = container.resolver.makeToString;
+
+ container.optionsForType('component', { singleton: false });
</ins><span class="cx"> container.optionsForType('view', { singleton: false });
</span><span class="cx"> container.optionsForType('template', { instantiate: false });
</span><del>- container.register('application', 'main', namespace, { instantiate: false });
</del><ins>+ 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);
</ins><span class="cx"> container.injection('router:main', 'namespace', 'application:main');
</span><span class="cx">
</span><del>- container.typeInjection('controller', 'target', 'router:main');
- container.typeInjection('controller', 'namespace', 'application:main');
</del><ins>+ container.injection('controller', 'target', 'router:main');
+ container.injection('controller', 'namespace', 'application:main');
</ins><span class="cx">
</span><del>- container.typeInjection('route', 'router', 'router:main');
</del><ins>+ container.injection('route', 'router', 'router:main');
</ins><span class="cx">
</span><span class="cx"> return container;
</span><span class="cx"> }
</span><span class="cx"> });
</span><span class="cx">
</span><span class="cx"> /**
</span><del>- @private
-
</del><span class="cx"> This function defines the default lookup rules for container lookups:
</span><span class="cx">
</span><span class="cx"> * templates are looked up on `Ember.TEMPLATES`
</span><span class="lines">@@ -25445,40 +38875,47 @@
</span><span class="cx"> This allows the application to register default injections in the container
</span><span class="cx"> that could be overridden by the normal naming convention.
</span><span class="cx">
</span><ins>+ @private
+ @method resolverFor
</ins><span class="cx"> @param {Ember.Namespace} namespace the namespace to look for classes
</span><del>- @return {any} the resolved value for a given lookup
</del><ins>+ @return {*} the resolved value for a given lookup
</ins><span class="cx"> */
</span><span class="cx"> function resolverFor(namespace) {
</span><del>- return function(fullName) {
- var nameParts = fullName.split(":"),
- type = nameParts[0], name = nameParts[1];
</del><ins>+ if (namespace.get('resolver')) {
+ Ember.deprecate('Application.resolver is deprecated in favor of Application.Resolver', false);
+ }
</ins><span class="cx">
</span><del>- if (type === 'template') {
- var templateName = name.replace(/\./g, '/');
- if (Ember.TEMPLATES[templateName]) {
- return Ember.TEMPLATES[templateName];
- }
</del><ins>+ var ResolverClass = namespace.get('resolver') || namespace.get('Resolver') || Ember.DefaultResolver;
+ var resolver = ResolverClass.create({
+ namespace: namespace
+ });
</ins><span class="cx">
</span><del>- templateName = decamelize(templateName);
- if (Ember.TEMPLATES[templateName]) {
- return Ember.TEMPLATES[templateName];
- }
- }
</del><ins>+ function resolve(fullName) {
+ return resolver.resolve(fullName);
+ }
</ins><span class="cx">
</span><del>- if (type === 'controller' || type === 'route' || type === 'view') {
- name = name.replace(/\./g, '_');
- }
</del><ins>+ resolve.describe = function(fullName) {
+ return resolver.lookupDescription(fullName);
+ };
</ins><span class="cx">
</span><del>- var className = classify(name) + classify(type);
- var factory = get(namespace, className);
</del><ins>+ resolve.makeToString = function(factory, fullName) {
+ return resolver.makeToString(factory, fullName);
+ };
</ins><span class="cx">
</span><del>- if (factory) { return factory; }
</del><ins>+ 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;
+ }
</ins><span class="cx"> };
</span><ins>+
+ return resolve;
</ins><span class="cx"> }
</span><span class="cx">
</span><span class="cx"> Ember.runLoadHooks('Ember.Application', Ember.Application);
</span><span class="cx">
</span><del>-
</del><span class="cx"> })();
</span><span class="cx">
</span><span class="cx">
</span><span class="lines">@@ -25492,70 +38929,165 @@
</span><span class="cx"> (function() {
</span><span class="cx"> /**
</span><span class="cx"> @module ember
</span><del>-@submodule ember-routing
</del><ins>+@submodule ember-application
</ins><span class="cx"> */
</span><span class="cx">
</span><span class="cx"> var get = Ember.get, set = Ember.set;
</span><del>-var ControllersProxy = Ember.Object.extend({
- controller: null,
</del><span class="cx">
</span><del>- unknownProperty: function(controllerName) {
- var controller = get(this, 'controller'),
- needs = get(controller, 'needs'),
- container = controller.get('container'),
- dependency;
</del><ins>+function verifyNeedsDependencies(controller, container, needs) {
+ var dependency, i, l, missing = [];
</ins><span class="cx">
</span><del>- for (var i=0, l=needs.length; i<l; i++) {
- dependency = needs[i];
- if (dependency === controllerName) {
- return container.lookup('controller:' + controllerName);
- }
- }
- }
-});
</del><ins>+ for (i=0, l=needs.length; i<l; i++) {
+ dependency = needs[i];
</ins><span class="cx">
</span><del>-function verifyDependencies(controller) {
- var needs = get(controller, 'needs'),
- container = get(controller, 'container'),
- dependency, satisfied = true;
</del><ins>+ Ember.assert(Ember.inspect(controller) + "#needs must not specify dependencies with periods in their names (" + dependency + ")", dependency.indexOf('.') === -1);
</ins><span class="cx">
</span><del>- for (var i=0, l=needs.length; i<l; i++) {
- dependency = needs[i];
</del><span class="cx"> if (dependency.indexOf(':') === -1) {
</span><span class="cx"> dependency = "controller:" + dependency;
</span><span class="cx"> }
</span><span class="cx">
</span><ins>+ // Structure assert to still do verification but not string concat in production
</ins><span class="cx"> if (!container.has(dependency)) {
</span><del>- satisfied = false;
- Ember.assert(controller + " needs " + dependency + " but it does not exist", false);
</del><ins>+ missing.push(dependency);
</ins><span class="cx"> }
</span><span class="cx"> }
</span><del>-
- return satisfied;
</del><ins>+ if (missing.length) {
+ throw new Ember.Error(Ember.inspect(controller) + " needs [ " + missing.join(', ') + " ] but " + (missing.length > 1 ? 'they' : 'it') + " could not be found");
+ }
</ins><span class="cx"> }
</span><span class="cx">
</span><ins>+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<l; i++) {
+ dependency = needs[i];
+ if (dependency === controllerName) {
+ return this.container.lookup('controller:' + controllerName);
+ }
+ }
+
+ var errorMessage = Ember.inspect(controller) + '#needs does not include `' + controllerName + '`. To access the ' + controllerName + ' controller from ' + Ember.inspect(controller) + ', ' + Ember.inspect(controller) + ' should have a `needs` property that is an array of the controllers it has access to.';
+ throw new ReferenceError(errorMessage);
+ },
+ setUnknownProperty: function (key, value) {
+ throw new Error("You cannot overwrite the value of `controllers." + key + "` of " + Ember.inspect(controller));
+ }
+ };
+});
+
+/**
+ @class ControllerMixin
+ @namespace Ember
+*/
</ins><span class="cx"> Ember.ControllerMixin.reopen({
</span><span class="cx"> concatenatedProperties: ['needs'],
</span><ins>+
+ /**
+ An array of other controller objects available inside
+ instances of this controller via the `controllers`
+ property:
+
+ For example, when you define a controller:
+
+ ```javascript
+ App.CommentsController = Ember.ArrayController.extend({
+ needs: ['post']
+ });
+ ```
+
+ The application's single instance of these other
+ controllers are accessible by name through the
+ `controllers` property:
+
+ ```javascript
+ this.get('controllers.post'); // instance of App.PostController
+ ```
+
+ Given that you have a nested controller (nested resource):
+
+ ```javascript
+ App.CommentsNewController = Ember.ObjectController.extend({
+ });
+ ```
+
+ When you define a controller that requires access to a nested one:
+
+ ```javascript
+ App.IndexController = Ember.ObjectController.extend({
+ needs: ['commentsNew']
+ });
+ ```
+
+ You will be able to get access to it:
+
+ ```javascript
+ this.get('controllers.commentsNew'); // instance of App.CommentsNewController
+ ```
+
+ This is only available for singleton controllers.
+
+ @property {Array} needs
+ @default []
+ */
</ins><span class="cx"> needs: [],
</span><span class="cx">
</span><span class="cx"> init: function() {
</span><del>- this._super.apply(this, arguments);
</del><ins>+ var needs = get(this, 'needs'),
+ length = get(needs, 'length');
</ins><span class="cx">
</span><del>- // Structure asserts to still do verification but not string concat in production
- if(!verifyDependencies(this)) {
- Ember.assert("Missing dependencies", false);
</del><ins>+ if (length > 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');
</ins><span class="cx"> }
</span><ins>+
+ this._super.apply(this, arguments);
</ins><span class="cx"> },
</span><span class="cx">
</span><ins>+ /**
+ @method controllerFor
+ @see {Ember.Route#controllerFor}
+ @deprecated Use `needs` instead
+ */
</ins><span class="cx"> controllerFor: function(controllerName) {
</span><del>- Ember.deprecate("Controller#controllerFor is depcrecated, please use Controller#needs instead");
- var container = get(this, 'container');
- return container.lookup('controller:' + controllerName);
</del><ins>+ Ember.deprecate("Controller#controllerFor is deprecated, please use Controller#needs instead");
+ return Ember.controllerFor(get(this, 'container'), controllerName);
</ins><span class="cx"> },
</span><span class="cx">
</span><del>- controllers: Ember.computed(function() {
- return ControllersProxy.create({ controller: this });
- })
</del><ins>+ /**
+ 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
</ins><span class="cx"> });
</span><span class="cx">
</span><span class="cx"> })();
</span><span class="lines">@@ -25574,1260 +39106,1437 @@
</span><span class="cx">
</span><span class="cx"> @module ember
</span><span class="cx"> @submodule ember-application
</span><del>-@requires ember-views, ember-states, ember-routing
</del><ins>+@requires ember-views, ember-routing
</ins><span class="cx"> */
</span><span class="cx">
</span><span class="cx"> })();
</span><span class="cx">
</span><span class="cx"> (function() {
</span><del>-var get = Ember.get, set = Ember.set;
-
</del><span class="cx"> /**
</span><span class="cx"> @module ember
</span><del>-@submodule ember-states
</del><ins>+@submodule ember-extension-support
</ins><span class="cx"> */
</span><ins>+/**
+ 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.
</ins><span class="cx">
</span><del>-/**
- @class State
</del><ins>+ 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: "dataAdapter",
+
+ initialize: function(container, application) {
+ application.register('dataAdapter:main', DS.DataAdapter);
+ }
+ });
+ ```
+
+ @class DataAdapter
</ins><span class="cx"> @namespace Ember
</span><span class="cx"> @extends Ember.Object
</span><del>- @uses Ember.Evented
</del><span class="cx"> */
</span><del>-Ember.State = Ember.Object.extend(Ember.Evented,
-/** @scope Ember.State.prototype */{
- isState: true,
</del><ins>+Ember.DataAdapter = Ember.Object.extend({
+ init: function() {
+ this._super();
+ this.releaseMethods = Ember.A();
+ },
</ins><span class="cx">
</span><span class="cx"> /**
</span><del>- A reference to the parent state.
</del><ins>+ The container of the application being debugged.
+ This property will be injected
+ on creation.
</ins><span class="cx">
</span><del>- @property parentState
- @type Ember.State
</del><ins>+ @property container
+ @default null
</ins><span class="cx"> */
</span><del>- parentState: null,
- start: null,
</del><ins>+ container: null,
</ins><span class="cx">
</span><span class="cx"> /**
</span><del>- The name of this state.
</del><ins>+ Number of attributes to send
+ as columns. (Enough to make the record
+ identifiable).
</ins><span class="cx">
</span><del>- @property name
- @type String
</del><ins>+ @private
+ @property attributeLimit
+ @default 3
</ins><span class="cx"> */
</span><del>- name: null,
</del><ins>+ attributeLimit: 3,
</ins><span class="cx">
</span><span class="cx"> /**
</span><del>- The full path to this state.
</del><ins>+ Stores all methods that clear observers.
+ These methods will be called on destruction.
</ins><span class="cx">
</span><del>- @property path
- @type String
</del><ins>+ @private
+ @property releaseMethods
</ins><span class="cx"> */
</span><del>- path: Ember.computed(function() {
- var parentPath = get(this, 'parentState.path'),
- path = get(this, 'name');
</del><ins>+ releaseMethods: Ember.A(),
</ins><span class="cx">
</span><del>- if (parentPath) {
- path = parentPath + '.' + path;
- }
</del><ins>+ /**
+ 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.
</ins><span class="cx">
</span><del>- return path;
- }),
</del><ins>+ @public
+ @method getFilters
+ @return {Array} List of objects defining filters.
+ The object should have a `name` and `desc` property.
+ */
+ getFilters: function() {
+ return Ember.A();
+ },
</ins><span class="cx">
</span><span class="cx"> /**
</span><del>- @private
</del><ins>+ Fetch the model types and observe them for changes.
</ins><span class="cx">
</span><del>- Override the default event firing from `Ember.Evented` to
- also call methods with the given name.
</del><ins>+ @public
+ @method watchModelTypes
</ins><span class="cx">
</span><del>- @method trigger
- @param name
</del><ins>+ @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
</ins><span class="cx"> */
</span><del>- trigger: function(name) {
- if (this[name]) {
- this[name].apply(this, [].slice.call(arguments, 1));
- }
- this._super.apply(this, arguments);
</del><ins>+ watchModelTypes: function(typesAdded, typesUpdated) {
+ var modelTypes = this.getModelTypes(),
+ self = this, typesToSend, releaseMethods = Ember.A();
+
+ typesToSend = modelTypes.map(function(type) {
+ var wrapped = self.wrapModelType(type);
+ releaseMethods.push(self.observeModelType(type, typesUpdated));
+ return wrapped;
+ });
+
+ typesAdded(typesToSend);
+
+ var release = function() {
+ releaseMethods.forEach(function(fn) { fn(); });
+ self.releaseMethods.removeObject(release);
+ };
+ this.releaseMethods.pushObject(release);
+ return release;
</ins><span class="cx"> },
</span><span class="cx">
</span><del>- init: function() {
- var states = get(this, 'states'), foundStates;
- set(this, 'childStates', Ember.A());
- set(this, 'eventTransitions', get(this, 'eventTransitions') || {});
</del><ins>+ /**
+ Fetch the records of a given type and observe them for changes.
</ins><span class="cx">
</span><del>- var name, value, transitionTarget;
</del><ins>+ @public
+ @method watchRecords
</ins><span class="cx">
</span><del>- // As a convenience, loop over the properties
- // of this state and look for any that are other
- // Ember.State instances or classes, and move them
- // to the `states` hash. This avoids having to
- // create an explicit separate hash.
</del><ins>+ @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
</ins><span class="cx">
</span><del>- if (!states) {
- states = {};
</del><ins>+ @param {Function} recordsUpdated Callback to call when a record has changed.
+ Takes an array of objects containing wrapped records.
</ins><span class="cx">
</span><del>- for (name in this) {
- if (name === "constructor") { continue; }
</del><ins>+ @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
</ins><span class="cx">
</span><del>- if (value = this[name]) {
- if (transitionTarget = value.transitionTarget) {
- this.eventTransitions[name] = transitionTarget;
- }
</del><ins>+ @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;
</ins><span class="cx">
</span><del>- this.setupChild(states, name, value);
- }
</del><ins>+ 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]);
</ins><span class="cx"> }
</span><span class="cx">
</span><del>- set(this, 'states', states);
- } else {
- for (name in states) {
- this.setupChild(states, name, states[name]);
</del><ins>+ if (removedCount) {
+ recordsRemoved(idx, removedCount);
</ins><span class="cx"> }
</span><del>- }
</del><ins>+ };
</ins><span class="cx">
</span><del>- set(this, 'pathsCache', {});
- set(this, 'pathsCacheNoContext', {});
- },
</del><ins>+ var observer = { didChange: contentDidChange, willChange: Ember.K };
+ records.addArrayObserver(self, observer);
</ins><span class="cx">
</span><del>- setupChild: function(states, name, value) {
- if (!value) { return false; }
</del><ins>+ release = function() {
+ releaseMethods.forEach(function(fn) { fn(); });
+ records.removeArrayObserver(self, observer);
+ self.releaseMethods.removeObject(release);
+ };
</ins><span class="cx">
</span><del>- if (value.isState) {
- set(value, 'name', name);
- } else if (Ember.State.detect(value)) {
- value = value.create({
- name: name
- });
- }
</del><ins>+ recordsAdded(recordsToSend);
</ins><span class="cx">
</span><del>- if (value.isState) {
- set(value, 'parentState', this);
- get(this, 'childStates').pushObject(value);
- states[name] = value;
- return value;
- }
</del><ins>+ this.releaseMethods.pushObject(release);
+ return release;
</ins><span class="cx"> },
</span><span class="cx">
</span><del>- lookupEventTransition: function(name) {
- var path, state = this;
</del><ins>+ /**
+ Clear all observers before destruction
+ @private
+ */
+ willDestroy: function() {
+ this._super();
+ this.releaseMethods.forEach(function(fn) {
+ fn();
+ });
+ },
</ins><span class="cx">
</span><del>- while(state && !path) {
- path = state.eventTransitions[name];
- state = state.get('parentState');
- }
</del><ins>+ /**
+ Detect whether a class is a model.
</ins><span class="cx">
</span><del>- return path;
</del><ins>+ 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;
</ins><span class="cx"> },
</span><span class="cx">
</span><span class="cx"> /**
</span><del>- A Boolean value indicating whether the state is a leaf state
- in the state hierarchy. This is `false` if the state has child
- states; otherwise it is true.
</del><ins>+ Get the columns for a given model type.
</ins><span class="cx">
</span><del>- @property isLeaf
- @type Boolean
</del><ins>+ @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)
</ins><span class="cx"> */
</span><del>- isLeaf: Ember.computed(function() {
- return !get(this, 'childStates').length;
- }),
</del><ins>+ columnsForType: function(type) {
+ return Ember.A();
+ },
</ins><span class="cx">
</span><span class="cx"> /**
</span><del>- A boolean value indicating whether the state takes a context.
- By default we assume all states take contexts.
</del><ins>+ Adds observers to a model type class.
</ins><span class="cx">
</span><del>- @property hasContext
- @default true
</del><ins>+ @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
</ins><span class="cx"> */
</span><del>- hasContext: true,
</del><span class="cx">
</span><ins>+ 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;
+ },
+
+
</ins><span class="cx"> /**
</span><del>- This is the default transition event.
</del><ins>+ Wraps a given model type and observes changes to it.
</ins><span class="cx">
</span><del>- @event setup
- @param {Ember.StateManager} manager
- @param context
- @see Ember.StateManager#transitionEvent
</del><ins>+ @private
+ @method wrapModelType
+ @param {Class} type A model class
+ @param {Function} typesUpdated callback to call when the type changes
+ @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
</ins><span class="cx"> */
</span><del>- setup: Ember.K,
</del><ins>+ wrapModelType: function(type, typesUpdated) {
+ var release, records = this.getRecords(type),
+ typeToSend, self = this;
</ins><span class="cx">
</span><ins>+ typeToSend = {
+ name: type.toString(),
+ count: Ember.get(records, 'length'),
+ columns: this.columnsForType(type),
+ object: type
+ };
+
+
+ return typeToSend;
+ },
+
+
</ins><span class="cx"> /**
</span><del>- This event fires when the state is entered.
</del><ins>+ Fetches all models defined in the application.
</ins><span class="cx">
</span><del>- @event enter
- @param {Ember.StateManager} manager
</del><ins>+ @private
+ @method getModelTypes
+ @return {Array} Array of model types
</ins><span class="cx"> */
</span><del>- enter: Ember.K,
</del><span class="cx">
</span><ins>+ // TODO: Use the resolver instead of looping over namespaces.
+ getModelTypes: function() {
+ var namespaces = Ember.A(Ember.Namespace.NAMESPACES), types = Ember.A(), self = this;
+
+ namespaces.forEach(function(namespace) {
+ for (var key in namespace) {
+ if (!namespace.hasOwnProperty(key)) { continue; }
+ var klass = namespace[key];
+ if (self.detect(klass)) {
+ types.push(klass);
+ }
+ }
+ });
+ return types;
+ },
+
</ins><span class="cx"> /**
</span><del>- This event fires when the state is exited.
</del><ins>+ Fetches all loaded records for a given type.
</ins><span class="cx">
</span><del>- @event exit
- @param {Ember.StateManager} manager
</del><ins>+ @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.
</ins><span class="cx"> */
</span><del>- exit: Ember.K
-});
</del><ins>+ getRecords: function(type) {
+ return Ember.A();
+ },
</ins><span class="cx">
</span><del>-Ember.State.reopenClass({
-
</del><span class="cx"> /**
</span><del>- Creates an action function for transitioning to the named state while
- preserving context.
</del><ins>+ Wraps a record and observers changes to it.
</ins><span class="cx">
</span><del>- The following example StateManagers are equivalent:
</del><ins>+ @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;
</ins><span class="cx">
</span><del>- ```javascript
- aManager = Ember.StateManager.create({
- stateOne: Ember.State.create({
- changeToStateTwo: Ember.State.transitionTo('stateTwo')
- }),
- stateTwo: Ember.State.create({})
- })
</del><ins>+ recordToSend.columnValues = this.getRecordColumnValues(record);
+ recordToSend.searchKeywords = this.getRecordKeywords(record);
+ recordToSend.filterValues = this.getRecordFilterValues(record);
+ recordToSend.color = this.getRecordColor(record);
</ins><span class="cx">
</span><del>- bManager = Ember.StateManager.create({
- stateOne: Ember.State.create({
- changeToStateTwo: function(manager, context){
- manager.transitionTo('stateTwo', context)
- }
- }),
- stateTwo: Ember.State.create({})
- })
- ```
</del><ins>+ return recordToSend;
+ },
</ins><span class="cx">
</span><del>- @method transitionTo
- @static
- @param {String} target
</del><ins>+ /**
+ Gets the values for each column.
+
+ @private
+ @method getRecordColumnValues
+ @return {Object} Keys should match column names defined
+ by the model type.
</ins><span class="cx"> */
</span><ins>+ getRecordColumnValues: function(record) {
+ return {};
+ },
</ins><span class="cx">
</span><del>- transitionTo: function(target) {
</del><ins>+ /**
+ Returns keywords to match when searching records.
</ins><span class="cx">
</span><del>- var transitionFunction = function(stateManager, contextOrEvent) {
- var contexts = [], transitionArgs,
- Event = Ember.$ && Ember.$.Event;
</del><ins>+ @private
+ @method getRecordKeywords
+ @return {Array} Relevant keywords for search.
+ */
+ getRecordKeywords: function(record) {
+ return Ember.A();
+ },
</ins><span class="cx">
</span><del>- if (contextOrEvent && (Event && contextOrEvent instanceof Event)) {
- if (contextOrEvent.hasOwnProperty('contexts')) {
- contexts = contextOrEvent.contexts.slice();
- }
- }
- else {
- contexts = [].slice.call(arguments, 1);
- }
</del><ins>+ /**
+ Returns the values of filters defined by `getFilters`.
</ins><span class="cx">
</span><del>- contexts.unshift(target);
- stateManager.transitionTo.apply(stateManager, contexts);
- };
</del><ins>+ @private
+ @method getRecordFilterValues
+ @param {Object} record The record instance
+ @return {Object} The filter values
+ */
+ getRecordFilterValues: function(record) {
+ return {};
+ },
</ins><span class="cx">
</span><del>- transitionFunction.transitionTarget = target;
</del><ins>+ /**
+ Each record can have a color that represents its state.
</ins><span class="cx">
</span><del>- return transitionFunction;
</del><ins>+ @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(){};
</ins><span class="cx"> }
</span><span class="cx">
</span><span class="cx"> });
</span><span class="cx">
</span><ins>+
</ins><span class="cx"> })();
</span><span class="cx">
</span><span class="cx">
</span><span class="cx">
</span><span class="cx"> (function() {
</span><span class="cx"> /**
</span><ins>+Ember Extension Support
+
</ins><span class="cx"> @module ember
</span><del>-@submodule ember-states
</del><ins>+@submodule ember-extension-support
+@requires ember-application
</ins><span class="cx"> */
</span><span class="cx">
</span><del>-var get = Ember.get, set = Ember.set, fmt = Ember.String.fmt;
-var arrayForEach = Ember.ArrayPolyfills.forEach;
</del><ins>+})();
+
+(function() {
</ins><span class="cx"> /**
</span><del>- A Transition takes the enter, exit and resolve states and normalizes
- them:
</del><ins>+ @module ember
+ @submodule ember-testing
+ */
+var slice = [].slice,
+ helpers = {},
+ injectHelpersCallbacks = [];
</ins><span class="cx">
</span><del>- * takes any passed in contexts into consideration
- * adds in `initialState`s
</del><ins>+/**
+ This is a container for an assortment of testing related functionality:
</ins><span class="cx">
</span><del>- @class Transition
- @private
</del><ins>+ * 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
</ins><span class="cx"> */
</span><del>-var Transition = function(raw) {
- this.enterStates = raw.enterStates.slice();
- this.exitStates = raw.exitStates.slice();
- this.resolveState = raw.resolveState;
</del><ins>+Ember.Test = {
</ins><span class="cx">
</span><del>- this.finalState = raw.enterStates[raw.enterStates.length - 1] || raw.resolveState;
-};
-
-Transition.prototype = {
</del><span class="cx"> /**
</span><del>- Normalize the passed in enter, exit and resolve states.
</del><ins>+ `registerHelper` is used to register a test helper that will be injected
+ when `App.injectTestHelpers` is called.
</ins><span class="cx">
</span><del>- This process also adds `finalState` and `contexts` to the Transition object.
</del><ins>+ The helper method will always be called with the current Application as
+ the first parameter.
</ins><span class="cx">
</span><del>- @method normalize
- @param {Ember.StateManager} manager the state manager running the transition
- @param {Array} contexts a list of contexts passed into `transitionTo`
</del><ins>+ 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}
</ins><span class="cx"> */
</span><del>- normalize: function(manager, contexts) {
- this.matchContextsToStates(contexts);
- this.addInitialStates();
- this.removeUnchangedContexts(manager);
- return this;
</del><ins>+ registerHelper: function(name, helperMethod) {
+ helpers[name] = {
+ method: helperMethod,
+ meta: { wait: false }
+ };
</ins><span class="cx"> },
</span><span class="cx">
</span><span class="cx"> /**
</span><del>- Match each of the contexts passed to `transitionTo` to a state.
- This process may also require adding additional enter and exit
- states if there are more contexts than enter states.
</del><ins>+ `registerAsyncHelper` is used to register an async test helper that will be injected
+ when `App.injectTestHelpers` is called.
</ins><span class="cx">
</span><del>- @method matchContextsToStates
- @param {Array} contexts a list of contexts passed into `transitionTo`
- */
- matchContextsToStates: function(contexts) {
- var stateIdx = this.enterStates.length - 1,
- matchedContexts = [],
- state,
- context;
</del><ins>+ The helper method will always be called with the current Application as
+ the first parameter.
</ins><span class="cx">
</span><del>- // Next, we will match the passed in contexts to the states they
- // represent.
- //
- // First, assign a context to each enter state in reverse order. If
- // any contexts are left, add a parent state to the list of states
- // to enter and exit, and assign a context to the parent state.
- //
- // If there are still contexts left when the state manager is
- // reached, raise an exception.
- //
- // This allows the following:
- //
- // |- root
- // | |- post
- // | | |- comments
- // | |- about (* current state)
- //
- // For `transitionTo('post.comments', post, post.get('comments')`,
- // the first context (`post`) will be assigned to `root.post`, and
- // the second context (`post.get('comments')`) will be assigned
- // to `root.post.comments`.
- //
- // For the following:
- //
- // |- root
- // | |- post
- // | | |- index (* current state)
- // | | |- comments
- //
- // For `transitionTo('post.comments', otherPost, otherPost.get('comments')`,
- // the `<root.post>` state will be added to the list of enter and exit
- // states because its context has changed.
</del><ins>+ For example:
</ins><span class="cx">
</span><del>- while (contexts.length > 0) {
- if (stateIdx >= 0) {
- state = this.enterStates[stateIdx--];
- } else {
- if (this.enterStates.length) {
- state = get(this.enterStates[0], 'parentState');
- if (!state) { throw "Cannot match all contexts to states"; }
- } else {
- // If re-entering the current state with a context, the resolve
- // state will be the current state.
- state = this.resolveState;
- }
</del><ins>+ ```javascript
+ Ember.Test.registerAsyncHelper('boot', function(app) {
+ Ember.run(app, app.advanceReadiness);
+ });
+ ```
</ins><span class="cx">
</span><del>- this.enterStates.unshift(state);
- this.exitStates.unshift(state);
- }
</del><ins>+ 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.
</ins><span class="cx">
</span><del>- // in routers, only states with dynamic segments have a context
- if (get(state, 'hasContext')) {
- context = contexts.pop();
- } else {
- context = null;
- }
</del><span class="cx">
</span><del>- matchedContexts.unshift(context);
- }
</del><ins>+ For example:
</ins><span class="cx">
</span><del>- this.contexts = matchedContexts;
</del><ins>+ ```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 }
+ };
</ins><span class="cx"> },
</span><span class="cx">
</span><span class="cx"> /**
</span><del>- Add any `initialState`s to the list of enter states.
</del><ins>+ Remove a previously added helper method.
</ins><span class="cx">
</span><del>- @method addInitialStates
</del><ins>+ Example:
+
+ ```
+ Ember.Test.unregisterHelper('wait');
+ ```
+
+ @public
+ @method unregisterHelper
+ @param {String} name The helper to remove.
</ins><span class="cx"> */
</span><del>- addInitialStates: function() {
- var finalState = this.finalState, initialState;
</del><ins>+ unregisterHelper: function(name) {
+ delete helpers[name];
+ delete Ember.Test.Promise.prototype[name];
+ },
</ins><span class="cx">
</span><del>- while(true) {
- initialState = get(finalState, 'initialState') || 'start';
- finalState = get(finalState, 'states.' + initialState);
</del><ins>+ /**
+ Used to register callbacks to be fired whenever `App.injectTestHelpers`
+ is called.
</ins><span class="cx">
</span><del>- if (!finalState) { break; }
</del><ins>+ The callback will receive the current application as an argument.
</ins><span class="cx">
</span><del>- this.finalState = finalState;
- this.enterStates.push(finalState);
- this.contexts.push(undefined);
- }
</del><ins>+ Example:
+ ```
+ Ember.Test.onInjectHelpers(function() {
+ Ember.$(document).ajaxStart(function() {
+ Test.pendingAjaxRequests++;
+ });
+
+ Ember.$(document).ajaxStop(function() {
+ Test.pendingAjaxRequests--;
+ });
+ });
+ ```
+
+ @public
+ @method onInjectHelpers
+ @param {Function} callback The function to be called.
+ */
+ onInjectHelpers: function(callback) {
+ injectHelpersCallbacks.push(callback);
</ins><span class="cx"> },
</span><span class="cx">
</span><span class="cx"> /**
</span><del>- Remove any states that were added because the number of contexts
- exceeded the number of explicit enter states, but the context has
- not changed since the last time the state was entered.
</del><ins>+ 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.
</ins><span class="cx">
</span><del>- @method removeUnchangedContexts
- @param {Ember.StateManager} manager passed in to look up the last
- context for a states
</del><ins>+ This method should be returned by async helpers such as `wait`.
+
+ @public
+ @method promise
+ @param {Function} resolver The function used to resolve the promise.
</ins><span class="cx"> */
</span><del>- removeUnchangedContexts: function(manager) {
- // Start from the beginning of the enter states. If the state was added
- // to the list during the context matching phase, make sure the context
- // has actually changed since the last time the state was entered.
- while (this.enterStates.length > 0) {
- if (this.enterStates[0] !== this.exitStates[0]) { break; }
</del><ins>+ promise: function(resolver) {
+ return new Ember.Test.Promise(resolver);
+ },
</ins><span class="cx">
</span><del>- if (this.enterStates.length === this.contexts.length) {
- if (manager.getStateMeta(this.enterStates[0], 'context') !== this.contexts[0]) { break; }
- this.contexts.shift();
- }
</del><ins>+ /**
+ Used to allow ember-testing to communicate with a specific testing
+ framework.
</ins><span class="cx">
</span><del>- this.resolveState = this.enterStates.shift();
- this.exitStates.shift();
- }
- }
-};
</del><ins>+ You can manually set it before calling `App.setupForTesting()`.
</ins><span class="cx">
</span><del>-var sendRecursively = function(event, currentState, isUnhandledPass) {
- var log = this.enableLogging,
- eventName = isUnhandledPass ? 'unhandledEvent' : event,
- action = currentState[eventName],
- contexts, sendRecursiveArguments, actionArguments;
</del><ins>+ Example:
</ins><span class="cx">
</span><del>- contexts = [].slice.call(arguments, 3);
</del><ins>+ ```
+ Ember.Test.adapter = MyCustomAdapter.create()
+ ```
</ins><span class="cx">
</span><del>- // Test to see if the action is a method that
- // can be invoked. Don't blindly check just for
- // existence, because it is possible the state
- // manager has a child state of the given name,
- // and we should still raise an exception in that
- // case.
- if (typeof action === 'function') {
- if (log) {
- if (isUnhandledPass) {
- Ember.Logger.log(fmt("STATEMANAGER: Unhandled event '%@' being sent to state %@.", [event, get(currentState, 'path')]));
- } else {
- Ember.Logger.log(fmt("STATEMANAGER: Sending event '%@' to state %@.", [event, get(currentState, 'path')]));
- }
- }
</del><ins>+ If you do not set it, ember-testing will default to `Ember.Test.QUnitAdapter`.
</ins><span class="cx">
</span><del>- actionArguments = contexts;
- if (isUnhandledPass) {
- actionArguments.unshift(event);
- }
- actionArguments.unshift(this);
</del><ins>+ @public
+ @property adapter
+ @type {Class} The adapter to be used.
+ @default Ember.Test.QUnitAdapter
+ */
+ adapter: null,
</ins><span class="cx">
</span><del>- return action.apply(currentState, actionArguments);
- } else {
- var parentState = get(currentState, 'parentState');
- if (parentState) {
</del><ins>+ /**
+ Replacement for `Ember.RSVP.resolve`
+ The only difference is this uses
+ and instance of `Ember.Test.Promise`
</ins><span class="cx">
</span><del>- sendRecursiveArguments = contexts;
- sendRecursiveArguments.unshift(event, parentState, isUnhandledPass);
</del><ins>+ @public
+ @method resolve
+ @param {Mixed} The value to resolve
+ */
+ resolve: function(val) {
+ return Ember.Test.promise(function(resolve) {
+ return resolve(val);
+ });
+ },
</ins><span class="cx">
</span><del>- return sendRecursively.apply(this, sendRecursiveArguments);
- } else if (!isUnhandledPass) {
- return sendEvent.call(this, event, contexts, true);
</del><ins>+ /**
+ 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;
</ins><span class="cx"> }
</span><ins>+ 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;
+ }));
</ins><span class="cx"> }
</span><span class="cx"> };
</span><span class="cx">
</span><del>-var sendEvent = function(eventName, sendRecursiveArguments, isUnhandledPass) {
- sendRecursiveArguments.unshift(eventName, get(this, 'currentState'), isUnhandledPass);
- return sendRecursively.apply(this, sendRecursiveArguments);
-};
</del><ins>+function helper(app, name) {
+ var fn = helpers[name].method,
+ meta = helpers[name].meta;
</ins><span class="cx">
</span><del>-/**
- StateManager is part of Ember's implementation of a finite state machine. A
- StateManager instance manages a number of properties that are instances of
- `Ember.State`,
- tracks the current active state, and triggers callbacks when states have changed.
</del><ins>+ return function() {
+ var args = slice.call(arguments),
+ lastPromise = Ember.Test.lastPromise;
</ins><span class="cx">
</span><del>- ## Defining States
</del><ins>+ args.unshift(app);
</ins><span class="cx">
</span><del>- The states of StateManager can be declared in one of two ways. First, you can
- define a `states` property that contains all the states:
</del><ins>+ // some helpers are not async and
+ // need to return a value immediately.
+ // example: `find`
+ if (!meta.wait) {
+ return fn.apply(app, args);
+ }
</ins><span class="cx">
</span><del>- ```javascript
- managerA = Ember.StateManager.create({
- states: {
- stateOne: Ember.State.create(),
- stateTwo: Ember.State.create()
</del><ins>+ 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);
+ });
+ });
</ins><span class="cx"> }
</span><del>- })
</del><span class="cx">
</span><del>- managerA.get('states')
- // {
- // stateOne: Ember.State.create(),
- // stateTwo: Ember.State.create()
- // }
- ```
</del><ins>+ return lastPromise;
+ };
+}
</ins><span class="cx">
</span><del>- You can also add instances of `Ember.State` (or an `Ember.State` subclass)
- directly as properties of a StateManager. These states will be collected into
- the `states` property for you.
</del><ins>+function run(fn) {
+ if (!Ember.run.currentRunLoop) {
+ Ember.run(fn);
+ } else {
+ fn();
+ }
+}
</ins><span class="cx">
</span><del>- ```javascript
- managerA = Ember.StateManager.create({
- stateOne: Ember.State.create(),
- stateTwo: Ember.State.create()
- })
</del><ins>+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.
</ins><span class="cx">
</span><del>- managerA.get('states')
- // {
- // stateOne: Ember.State.create(),
- // stateTwo: Ember.State.create()
- // }
- ```
</del><ins>+ @property testHelpers
+ @type {Object}
+ @default {}
+ */
+ testHelpers: {},
</ins><span class="cx">
</span><del>- ## The Initial State
</del><ins>+ /**
+ This property will contain the original methods that were registered
+ on the `helperContainer` before `injectTestHelpers` is called.
</ins><span class="cx">
</span><del>- When created a StateManager instance will immediately enter into the state
- defined as its `start` property or the state referenced by name in its
- `initialState` property:
</del><ins>+ When `removeTestHelpers` is called, these methods are restored to the
+ `helperContainer`.
</ins><span class="cx">
</span><del>- ```javascript
- managerA = Ember.StateManager.create({
- start: Ember.State.create({})
- })
</del><ins>+ @property originalMethods
+ @type {Object}
+ @default {}
+ @private
+ */
+ originalMethods: {},
</ins><span class="cx">
</span><del>- managerA.get('currentState.name') // 'start'
</del><span class="cx">
</span><del>- managerB = Ember.StateManager.create({
- initialState: 'beginHere',
- beginHere: Ember.State.create({})
- })
</del><ins>+ /**
+ This property indicates whether or not this application is currently in
+ testing mode. This is set when `setupForTesting` is called on the current
+ application.
</ins><span class="cx">
</span><del>- managerB.get('currentState.name') // 'beginHere'
- ```
</del><ins>+ @property testing
+ @type {Boolean}
+ @default false
+ */
+ testing: false,
</ins><span class="cx">
</span><del>- Because it is a property you may also provide a computed function if you wish
- to derive an `initialState` programmatically:
</del><ins>+ /**
+ 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).
</ins><span class="cx">
</span><del>- ```javascript
- managerC = Ember.StateManager.create({
- initialState: function(){
- if (someLogic) {
- return 'active';
- } else {
- return 'passive';
- }
- }.property(),
- active: Ember.State.create({}),
- passive: Ember.State.create({})
- })
</del><ins>+ Example:
+
</ins><span class="cx"> ```
</span><ins>+ App.setupForTesting();
+ ```
</ins><span class="cx">
</span><del>- ## Moving Between States
</del><ins>+ @method setupForTesting
+ */
+ setupForTesting: function() {
+ Ember.testing = true;
</ins><span class="cx">
</span><del>- A StateManager can have any number of `Ember.State` objects as properties
- and can have a single one of these states as its current state.
</del><ins>+ this.testing = true;
</ins><span class="cx">
</span><del>- Calling `transitionTo` transitions between states:
</del><ins>+ this.Router.reopen({
+ location: 'none'
+ });
</ins><span class="cx">
</span><del>- ```javascript
- robotManager = Ember.StateManager.create({
- initialState: 'poweredDown',
- poweredDown: Ember.State.create({}),
- poweredUp: Ember.State.create({})
- })
</del><ins>+ // if adapter is not manually set default to QUnit
+ if (!Ember.Test.adapter) {
+ Ember.Test.adapter = Ember.Test.QUnitAdapter.create();
+ }
</ins><span class="cx">
</span><del>- robotManager.get('currentState.name') // 'poweredDown'
- robotManager.transitionTo('poweredUp')
- robotManager.get('currentState.name') // 'poweredUp'
- ```
</del><ins>+ },
</ins><span class="cx">
</span><del>- Before transitioning into a new state the existing `currentState` will have
- its `exit` method called with the StateManager instance as its first argument
- and an object representing the transition as its second argument.
</del><ins>+ /**
+ This will be used as the container to inject the test helpers into. By
+ default the helpers are injected into `window`.
</ins><span class="cx">
</span><del>- After transitioning into a new state the new `currentState` will have its
- `enter` method called with the StateManager instance as its first argument
- and an object representing the transition as its second argument.
</del><ins>+ @property helperContainer
+ @type {Object} The object to be used for test helpers.
+ @default window
+ */
+ helperContainer: window,
</ins><span class="cx">
</span><del>- ```javascript
- robotManager = Ember.StateManager.create({
- initialState: 'poweredDown',
- poweredDown: Ember.State.create({
- exit: function(stateManager){
- console.log("exiting the poweredDown state")
- }
- }),
- poweredUp: Ember.State.create({
- enter: function(stateManager){
- console.log("entering the poweredUp state. Destroy all humans.")
- }
- })
- })
</del><ins>+ /**
+ 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`).
</ins><span class="cx">
</span><del>- robotManager.get('currentState.name') // 'poweredDown'
- robotManager.transitionTo('poweredUp')
</del><ins>+ Any callbacks registered with `onInjectHelpers` will be called once the
+ helpers have been injected.
</ins><span class="cx">
</span><del>- // will log
- // 'exiting the poweredDown state'
- // 'entering the poweredUp state. Destroy all humans.'
</del><ins>+ Example:
</ins><span class="cx"> ```
</span><ins>+ App.injectTestHelpers();
+ ```
</ins><span class="cx">
</span><del>- Once a StateManager is already in a state, subsequent attempts to enter that
- state will not trigger enter or exit method calls. Attempts to transition
- into a state that the manager does not have will result in no changes in the
- StateManager's current state:
</del><ins>+ @method injectTestHelpers
+ */
+ injectTestHelpers: function(helperContainer) {
+ if (helperContainer) { this.helperContainer = helperContainer; }
</ins><span class="cx">
</span><del>- ```javascript
- robotManager = Ember.StateManager.create({
- initialState: 'poweredDown',
- poweredDown: Ember.State.create({
- exit: function(stateManager){
- console.log("exiting the poweredDown state")
- }
- }),
- poweredUp: Ember.State.create({
- enter: function(stateManager){
- console.log("entering the poweredUp state. Destroy all humans.")
- }
- })
- })
</del><ins>+ 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);
+ }
</ins><span class="cx">
</span><del>- robotManager.get('currentState.name') // 'poweredDown'
- robotManager.transitionTo('poweredUp')
- // will log
- // 'exiting the poweredDown state'
- // 'entering the poweredUp state. Destroy all humans.'
- robotManager.transitionTo('poweredUp') // no logging, no state change
</del><ins>+ for(var i = 0, l = injectHelpersCallbacks.length; i < l; i++) {
+ injectHelpersCallbacks[i](this);
+ }
+ },
</ins><span class="cx">
</span><del>- robotManager.transitionTo('someUnknownState') // silently fails
- robotManager.get('currentState.name') // 'poweredUp'
- ```
</del><ins>+ /**
+ This removes all helpers that have been registered, and resets and functions
+ that were overridden by the helpers.
</ins><span class="cx">
</span><del>- Each state property may itself contain properties that are instances of
- `Ember.State`. The StateManager can transition to specific sub-states in a
- series of transitionTo method calls or via a single transitionTo with the
- full path to the specific state. The StateManager will also keep track of the
- full path to its currentState
</del><ins>+ Example:
</ins><span class="cx">
</span><del>- ```javascript
- robotManager = Ember.StateManager.create({
- initialState: 'poweredDown',
- poweredDown: Ember.State.create({
- charging: Ember.State.create(),
- charged: Ember.State.create()
- }),
- poweredUp: Ember.State.create({
- mobile: Ember.State.create(),
- stationary: Ember.State.create()
- })
- })
</del><ins>+ ```javascript
+ App.removeTestHelpers();
+ ```
</ins><span class="cx">
</span><del>- robotManager.get('currentState.name') // 'poweredDown'
</del><ins>+ @public
+ @method removeTestHelpers
+ */
+ removeTestHelpers: function() {
+ for (var name in helpers) {
+ this.helperContainer[name] = this.originalMethods[name];
+ delete this.testHelpers[name];
+ delete this.originalMethods[name];
+ }
+ }
+});
</ins><span class="cx">
</span><del>- robotManager.transitionTo('poweredUp')
- robotManager.get('currentState.name') // 'poweredUp'
</del><ins>+// 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);
+ });
+ }
+ };
+}
</ins><span class="cx">
</span><del>- robotManager.transitionTo('mobile')
- robotManager.get('currentState.name') // 'mobile'
</del><ins>+Ember.Test.Promise = function() {
+ Ember.RSVP.Promise.apply(this, arguments);
+ Ember.Test.lastPromise = this;
+};
</ins><span class="cx">
</span><del>- // transition via a state path
- robotManager.transitionTo('poweredDown.charging')
- robotManager.get('currentState.name') // 'charging'
</del><ins>+Ember.Test.Promise.prototype = Ember.create(Ember.RSVP.Promise.prototype);
+Ember.Test.Promise.prototype.constructor = Ember.Test.Promise;
</ins><span class="cx">
</span><del>- robotManager.get('currentState.path') // 'poweredDown.charging'
- ```
</del><ins>+// 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);
+};
</ins><span class="cx">
</span><del>- Enter transition methods will be called for each state and nested child state
- in their hierarchical order. Exit methods will be called for each state and
- its nested states in reverse hierarchical order.
</del><ins>+// 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;
</ins><span class="cx">
</span><del>- Exit transitions for a parent state are not called when entering into one of
- its child states, only when transitioning to a new section of possible states
- in the hierarchy.
</del><ins>+ // Reset lastPromise for nested helpers
+ Ember.Test.lastPromise = null;
</ins><span class="cx">
</span><del>- ```javascript
- robotManager = Ember.StateManager.create({
- initialState: 'poweredDown',
- poweredDown: Ember.State.create({
- enter: function(){},
- exit: function(){
- console.log("exited poweredDown state")
- },
- charging: Ember.State.create({
- enter: function(){},
- exit: function(){}
- }),
- charged: Ember.State.create({
- enter: function(){
- console.log("entered charged state")
- },
- exit: function(){
- console.log("exited charged state")
- }
- })
- }),
- poweredUp: Ember.State.create({
- enter: function(){
- console.log("entered poweredUp state")
- },
- exit: function(){},
- mobile: Ember.State.create({
- enter: function(){
- console.log("entered mobile state")
- },
- exit: function(){}
- }),
- stationary: Ember.State.create({
- enter: function(){},
- exit: function(){}
- })
- })
- })
</del><ins>+ value = fn.call(null, val);
</ins><span class="cx">
</span><ins>+ lastPromise = Ember.Test.lastPromise;
</ins><span class="cx">
</span><del>- robotManager.get('currentState.path') // 'poweredDown'
- robotManager.transitionTo('charged')
- // logs 'entered charged state'
- // but does *not* log 'exited poweredDown state'
- robotManager.get('currentState.name') // 'charged
</del><ins>+ // 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;
+ }
+}
</ins><span class="cx">
</span><del>- robotManager.transitionTo('poweredUp.mobile')
- // logs
- // 'exited charged state'
- // 'exited poweredDown state'
- // 'entered poweredUp state'
- // 'entered mobile state'
- ```
</del><ins>+})();
</ins><span class="cx">
</span><del>- During development you can set a StateManager's `enableLogging` property to
- `true` to receive console messages of state transitions.
</del><span class="cx">
</span><del>- ```javascript
- robotManager = Ember.StateManager.create({
- enableLogging: true
- })
- ```
</del><span class="cx">
</span><del>- ## Managing currentState with Actions
</del><ins>+(function() {
+Ember.onLoad('Ember.Application', function(Application) {
+ Application.initializer({
+ name: 'deferReadiness in `testing` mode',
</ins><span class="cx">
</span><del>- To control which transitions are possible for a given state, and
- appropriately handle external events, the StateManager can receive and
- route action messages to its states via the `send` method. Calling to
- `send` with an action name will begin searching for a method with the same
- name starting at the current state and moving up through the parent states
- in a state hierarchy until an appropriate method is found or the StateManager
- instance itself is reached.
</del><ins>+ initialize: function(container, application){
+ if (application.testing) {
+ application.deferReadiness();
+ }
+ }
+ });
</ins><span class="cx">
</span><del>- If an appropriately named method is found it will be called with the state
- manager as the first argument and an optional `context` object as the second
- argument.
</del><ins>+ });
</ins><span class="cx">
</span><del>- ```javascript
- managerA = Ember.StateManager.create({
- initialState: 'stateOne.substateOne.subsubstateOne',
- stateOne: Ember.State.create({
- substateOne: Ember.State.create({
- anAction: function(manager, context){
- console.log("an action was called")
- },
- subsubstateOne: Ember.State.create({})
- })
- })
- })
</del><ins>+})();
</ins><span class="cx">
</span><del>- managerA.get('currentState.name') // 'subsubstateOne'
- managerA.send('anAction')
- // 'stateOne.substateOne.subsubstateOne' has no anAction method
- // so the 'anAction' method of 'stateOne.substateOne' is called
- // and logs "an action was called"
- // with managerA as the first argument
- // and no second argument
</del><span class="cx">
</span><del>- someObject = {}
- managerA.send('anAction', someObject)
- // the 'anAction' method of 'stateOne.substateOne' is called again
- // with managerA as the first argument and
- // someObject as the second argument.
- ```
</del><span class="cx">
</span><del>- If the StateManager attempts to send an action but does not find an appropriately named
- method in the current state or while moving upwards through the state hierarchy, it will
- repeat the process looking for a `unhandledEvent` method. If an `unhandledEvent` method is
- found, it will be called with the original event name as the second argument. If an
- `unhandledEvent` method is not found, the StateManager will throw a new Ember.Error.
</del><ins>+(function() {
+/**
+ @module ember
+ @submodule ember-testing
+ */
</ins><span class="cx">
</span><del>- ```javascript
- managerB = Ember.StateManager.create({
- initialState: 'stateOne.substateOne.subsubstateOne',
- stateOne: Ember.State.create({
- substateOne: Ember.State.create({
- subsubstateOne: Ember.State.create({}),
- unhandledEvent: function(manager, eventName, context) {
- console.log("got an unhandledEvent with name " + eventName);
- }
- })
- })
- })
</del><ins>+var $ = Ember.$;
</ins><span class="cx">
</span><del>- managerB.get('currentState.name') // 'subsubstateOne'
- managerB.send('anAction')
- // neither `stateOne.substateOne.subsubstateOne` nor any of it's
- // parent states have a handler for `anAction`. `subsubstateOne`
- // also does not have a `unhandledEvent` method, but its parent
- // state, `substateOne`, does, and it gets fired. It will log
- // "got an unhandledEvent with name anAction"
- ```
</del><ins>+/**
+ 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).
</ins><span class="cx">
</span><del>- Action detection only moves upwards through the state hierarchy from the current state.
- It does not search in other portions of the hierarchy.
</del><ins>+ @private
+ @method testCheckboxClick
+*/
+function testCheckboxClick(handler) {
+ $('<input type="checkbox">')
+ .css({ position: 'absolute', left: '-1000px', top: '-1000px' })
+ .appendTo('body')
+ .on('click', handler)
+ .trigger('click')
+ .remove();
+}
</ins><span class="cx">
</span><del>- ```javascript
- managerC = Ember.StateManager.create({
- initialState: 'stateOne.substateOne.subsubstateOne',
- stateOne: Ember.State.create({
- substateOne: Ember.State.create({
- subsubstateOne: Ember.State.create({})
- })
- }),
- stateTwo: Ember.State.create({
- anAction: function(manager, context){
- // will not be called below because it is
- // not a parent of the current state
- }
- })
- })
</del><ins>+$(function() {
+ /*
+ Determine whether a checkbox checked using jQuery's "click" method will have
+ the correct value for its checked property.
</ins><span class="cx">
</span><del>- managerC.get('currentState.name') // 'subsubstateOne'
- managerC.send('anAction')
- // Error: <Ember.StateManager:ember132> could not
- // respond to event anAction in state stateOne.substateOne.subsubstateOne.
- ```
-
- Inside of an action method the given state should delegate `transitionTo` calls on its
- StateManager.
-
- ```javascript
- robotManager = Ember.StateManager.create({
- initialState: 'poweredDown.charging',
- poweredDown: Ember.State.create({
- charging: Ember.State.create({
- chargeComplete: function(manager, context){
- manager.transitionTo('charged')
</del><ins>+ 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;
+ }
</ins><span class="cx"> }
</span><del>- }),
- charged: Ember.State.create({
- boot: function(manager, context){
- manager.transitionTo('poweredUp')
- }
- })
- }),
- poweredUp: Ember.State.create({
- beginExtermination: function(manager, context){
- manager.transitionTo('rampaging')
- },
- rampaging: Ember.State.create()
- })
- })
</del><ins>+ };
+ }
+ });
</ins><span class="cx">
</span><del>- robotManager.get('currentState.name') // 'charging'
- robotManager.send('boot') // throws error, no boot action
- // in current hierarchy
- robotManager.get('currentState.name') // remains 'charging'
</del><ins>+ // 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);
+ });
+});
</ins><span class="cx">
</span><del>- robotManager.send('beginExtermination') // throws error, no beginExtermination
- // action in current hierarchy
- robotManager.get('currentState.name') // remains 'charging'
</del><ins>+})();
</ins><span class="cx">
</span><del>- robotManager.send('chargeComplete')
- robotManager.get('currentState.name') // 'charged'
</del><span class="cx">
</span><del>- robotManager.send('boot')
- robotManager.get('currentState.name') // 'poweredUp'
</del><span class="cx">
</span><del>- robotManager.send('beginExtermination', allHumans)
- robotManager.get('currentState.name') // 'rampaging'
- ```
</del><ins>+(function() {
+/**
+ @module ember
+ @submodule ember-testing
+*/
</ins><span class="cx">
</span><del>- Transition actions can also be created using the `transitionTo` method of the `Ember.State` class. The
- following example StateManagers are equivalent:
</del><ins>+var Test = Ember.Test;
</ins><span class="cx">
</span><del>- ```javascript
- aManager = Ember.StateManager.create({
- stateOne: Ember.State.create({
- changeToStateTwo: Ember.State.transitionTo('stateTwo')
- }),
- stateTwo: Ember.State.create({})
- })
</del><ins>+/**
+ The primary purpose of this class is to create hooks that can be implemented
+ by an adapter for various test frameworks.
</ins><span class="cx">
</span><del>- bManager = Ember.StateManager.create({
- stateOne: Ember.State.create({
- changeToStateTwo: function(manager, context){
- manager.transitionTo('stateTwo', context)
- }
- }),
- stateTwo: Ember.State.create({})
- })
- ```
-
- @class StateManager
- @namespace Ember
- @extends Ember.State
-**/
-Ember.StateManager = Ember.State.extend({
</del><ins>+ @class Adapter
+ @namespace Ember.Test
+*/
+Test.Adapter = Ember.Object.extend({
</ins><span class="cx"> /**
</span><del>- @private
</del><ins>+ This callback will be called whenever an async operation is about to start.
</ins><span class="cx">
</span><del>- When creating a new statemanager, look for a default state to transition
- into. This state can either be named `start`, or can be specified using the
- `initialState` property.
</del><ins>+ Override this to call your framework's methods that handle async
+ operations.
</ins><span class="cx">
</span><del>- @method init
</del><ins>+ @public
+ @method asyncStart
</ins><span class="cx"> */
</span><del>- init: function() {
- this._super();
</del><ins>+ asyncStart: Ember.K,
</ins><span class="cx">
</span><del>- set(this, 'stateMeta', Ember.Map.create());
</del><ins>+ /**
+ This callback will be called whenever an async operation has completed.
</ins><span class="cx">
</span><del>- var initialState = get(this, 'initialState');
</del><ins>+ @public
+ @method asyncEnd
+ */
+ asyncEnd: Ember.K,
</ins><span class="cx">
</span><del>- if (!initialState && get(this, 'states.start')) {
- initialState = 'start';
- }
</del><ins>+ /**
+ Override this method with your testing framework's false assertion.
+ This function is called whenever an exception occurs causing the testing
+ promise to fail.
</ins><span class="cx">
</span><del>- if (initialState) {
- this.transitionTo(initialState);
- Ember.assert('Failed to transition to initial state "' + initialState + '"', !!get(this, 'currentState'));
- }
- },
</del><ins>+ QUnit example:
</ins><span class="cx">
</span><del>- stateMetaFor: function(state) {
- var meta = get(this, 'stateMeta'),
- stateMeta = meta.get(state);
</del><ins>+ ```javascript
+ exception: function(error) {
+ ok(false, error);
+ };
+ ```
</ins><span class="cx">
</span><del>- if (!stateMeta) {
- stateMeta = {};
- meta.set(state, stateMeta);
- }
</del><ins>+ @public
+ @method exception
+ @param {String} error The exception to be raised.
+ */
+ exception: function(error) {
+ throw error;
+ }
+});
</ins><span class="cx">
</span><del>- return stateMeta;
- },
</del><ins>+/**
+ This class implements the methods defined by Ember.Test.Adapter for the
+ QUnit testing framework.
</ins><span class="cx">
</span><del>- setStateMeta: function(state, key, value) {
- return set(this.stateMetaFor(state), key, value);
</del><ins>+ @class QUnitAdapter
+ @namespace Ember.Test
+ @extends Ember.Test.Adapter
+*/
+Test.QUnitAdapter = Test.Adapter.extend({
+ asyncStart: function() {
+ stop();
</ins><span class="cx"> },
</span><del>-
- getStateMeta: function(state, key) {
- return get(this.stateMetaFor(state), key);
</del><ins>+ asyncEnd: function() {
+ start();
</ins><span class="cx"> },
</span><ins>+ exception: function(error) {
+ ok(false, Ember.inspect(error));
+ }
+});
</ins><span class="cx">
</span><del>- /**
- The current state from among the manager's possible states. This property should
- not be set directly. Use `transitionTo` to move between states by name.
</del><ins>+})();
</ins><span class="cx">
</span><del>- @property currentState
- @type Ember.State
- */
- currentState: null,
</del><span class="cx">
</span><del>- /**
- The path of the current state. Returns a string representation of the current
- state.
</del><span class="cx">
</span><del>- @property currentPath
- @type String
- */
- currentPath: Ember.computed.alias('currentState.path'),
</del><ins>+(function() {
+/**
+* @module ember
+* @submodule ember-testing
+*/
</ins><span class="cx">
</span><del>- /**
- The name of transitionEvent that this stateManager will dispatch
</del><ins>+var get = Ember.get,
+ Test = Ember.Test,
+ helper = Test.registerHelper,
+ asyncHelper = Test.registerAsyncHelper,
+ countAsync = 0;
</ins><span class="cx">
</span><del>- @property transitionEvent
- @type String
- @default 'setup'
- */
- transitionEvent: 'setup',
</del><ins>+Test.pendingAjaxRequests = 0;
</ins><span class="cx">
</span><del>- /**
- If set to true, `errorOnUnhandledEvents` will cause an exception to be
- raised if you attempt to send an event to a state manager that is not
- handled by the current state or any of its parent states.
</del><ins>+Test.onInjectHelpers(function() {
+ Ember.$(document).ajaxStart(function() {
+ Test.pendingAjaxRequests++;
+ });
</ins><span class="cx">
</span><del>- @property errorOnUnhandledEvents
- @type Boolean
- @default true
- */
- errorOnUnhandledEvent: true,
</del><ins>+ Ember.$(document).ajaxStop(function() {
+ Ember.assert("An ajaxStop 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--;
+ });
+});
</ins><span class="cx">
</span><del>- send: function(event) {
- var contexts = [].slice.call(arguments, 1);
- Ember.assert('Cannot send event "' + event + '" while currentState is ' + get(this, 'currentState'), get(this, 'currentState'));
- return sendEvent.call(this, event, contexts, false);
- },
- unhandledEvent: function(manager, event) {
- if (get(this, 'errorOnUnhandledEvent')) {
- throw new Ember.Error(this.toString() + " could not respond to event " + event + " in state " + get(this, 'currentState.path') + ".");
- }
- },
</del><ins>+function currentRouteName(app){
+ var appController = app.__container__.lookup('controller:application');
</ins><span class="cx">
</span><del>- /**
- Finds a state by its state path.
</del><ins>+ return get(appController, 'currentRouteName');
+}
</ins><span class="cx">
</span><del>- Example:
</del><ins>+function currentPath(app){
+ var appController = app.__container__.lookup('controller:application');
</ins><span class="cx">
</span><del>- ```javascript
- manager = Ember.StateManager.create({
- root: Ember.State.create({
- dashboard: Ember.State.create()
- })
- });
</del><ins>+ return get(appController, 'currentPath');
+}
</ins><span class="cx">
</span><del>- manager.getStateByPath(manager, "root.dashboard")
</del><ins>+function currentURL(app){
+ var router = app.__container__.lookup('router:main');
</ins><span class="cx">
</span><del>- // returns the dashboard state
- ```
</del><ins>+ return get(router, 'location').getURL();
+}
</ins><span class="cx">
</span><del>- @method getStateByPath
- @param {Ember.State} root the state to start searching from
- @param {String} path the state path to follow
- @return {Ember.State} the state at the end of the path
- */
- getStateByPath: function(root, path) {
- var parts = path.split('.'),
- state = root;
</del><ins>+function visit(app, url) {
+ Ember.run(app, 'advanceReadiness');
</ins><span class="cx">
</span><del>- for (var i=0, len=parts.length; i<len; i++) {
- state = get(get(state, 'states'), parts[i]);
- if (!state) { break; }
</del><ins>+ app.__container__.lookup('router:main').location.setURL(url);
+ 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');
+ }
+ });
</ins><span class="cx"> }
</span><ins>+ }
</ins><span class="cx">
</span><del>- return state;
- },
</del><ins>+ Ember.run($el, 'mouseup');
+ Ember.run($el, 'click');
</ins><span class="cx">
</span><del>- findStateByPath: function(state, path) {
- var possible;
</del><ins>+ return wait(app);
+}
</ins><span class="cx">
</span><del>- while (!possible && state) {
- possible = this.getStateByPath(state, path);
- state = get(state, 'parentState');
- }
</del><ins>+function triggerEvent(app, selector, context, event){
+ if (typeof method === 'undefined') {
+ event = context;
+ context = null;
+ }
</ins><span class="cx">
</span><del>- return possible;
- },
</del><ins>+ var $el = findWithAssert(app, selector, context);
</ins><span class="cx">
</span><del>- /**
- A state stores its child states in its `states` hash.
- This code takes a path like `posts.show` and looks
- up `root.states.posts.states.show`.
</del><ins>+ Ember.run($el, 'trigger', event);
</ins><span class="cx">
</span><del>- It returns a list of all of the states from the
- root, which is the list of states to call `enter`
- on.
</del><ins>+ return wait(app);
+}
</ins><span class="cx">
</span><del>- @method getStatesInPath
- @param root
- @param path
- */
- getStatesInPath: function(root, path) {
- if (!path || path === "") { return undefined; }
- var parts = path.split('.'),
- result = [],
- states,
- state;
</del><ins>+function keyEvent(app, selector, context, type, keyCode) {
+ var $el;
+ if (typeof keyCode === 'undefined') {
+ keyCode = type;
+ type = context;
+ context = null;
+ }
+ $el = findWithAssert(app, selector, context);
+ var event = Ember.$.Event(type, { keyCode: keyCode });
+ Ember.run($el, 'trigger', event);
+ return wait(app);
+}
</ins><span class="cx">
</span><del>- for (var i=0, len=parts.length; i<len; i++) {
- states = get(root, 'states');
- if (!states) { return undefined; }
- state = get(states, parts[i]);
- if (state) { root = state; result.push(state); }
- else { return undefined; }
</del><ins>+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();
</ins><span class="cx"> }
</span><span class="cx">
</span><del>- return result;
- },
</del><ins>+ // 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.isLoading;
+ if (routerIsLoading) { return; }
</ins><span class="cx">
</span><del>- goToState: function() {
- // not deprecating this yet so people don't constantly need to
- // make trivial changes for little reason.
- return this.transitionTo.apply(this, arguments);
- },
</del><ins>+ // 2. If there are pending Ajax requests, keep polling
+ if (Test.pendingAjaxRequests) { return; }
</ins><span class="cx">
</span><del>- transitionTo: function(path, context) {
- // XXX When is transitionTo called with no path
- if (Ember.isEmpty(path)) { return; }
</del><ins>+ // 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);
</ins><span class="cx">
</span><del>- // The ES6 signature of this function is `path, ...contexts`
- var contexts = context ? Array.prototype.slice.call(arguments, 1) : [],
- currentState = get(this, 'currentState') || this;
</del><ins>+ // If this is the last async promise, end the async test
+ if (--countAsync === 0) {
+ Test.adapter.asyncEnd();
+ }
</ins><span class="cx">
</span><del>- // First, get the enter, exit and resolve states for the current state
- // and specified path. If possible, use an existing cache.
- var hash = this.contextFreeTransition(currentState, path);
</del><ins>+ // Synchronously resolve the promise
+ Ember.run(null, resolve, value);
+ }, 10);
+ });
</ins><span class="cx">
</span><del>- // Next, process the raw state information for the contexts passed in.
- var transition = new Transition(hash).normalize(this, contexts);
</del><ins>+}
</ins><span class="cx">
</span><del>- this.enterState(transition);
- this.triggerSetupContext(transition);
- },
</del><span class="cx">
</span><del>- contextFreeTransition: function(currentState, path) {
- var cache = currentState.pathsCache[path];
- if (cache) { return cache; }
</del><ins>+/**
+* 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);
</ins><span class="cx">
</span><del>- var enterStates = this.getStatesInPath(currentState, path),
- exitStates = [],
- resolveState = currentState;
</del><ins>+/**
+* 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);
</ins><span class="cx">
</span><del>- // Walk up the states. For each state, check whether a state matching
- // the `path` is nested underneath. This will find the closest
- // parent state containing `path`.
- //
- // This allows the user to pass in a relative path. For example, for
- // the following state hierarchy:
- //
- // | |root
- // | |- posts
- // | | |- show (* current)
- // | |- comments
- // | | |- show
- //
- // If the current state is `<root.posts.show>`, an attempt to
- // transition to `comments.show` will match `<root.comments.show>`.
- //
- // First, this code will look for root.posts.show.comments.show.
- // Next, it will look for root.posts.comments.show. Finally,
- // it will look for `root.comments.show`, and find the state.
- //
- // After this process, the following variables will exist:
- //
- // * resolveState: a common parent state between the current
- // and target state. In the above example, `<root>` is the
- // `resolveState`.
- // * enterStates: a list of all of the states represented
- // by the path from the `resolveState`. For example, for
- // the path `root.comments.show`, `enterStates` would have
- // `[<root.comments>, <root.comments.show>]`
- // * exitStates: a list of all of the states from the
- // `resolveState` to the `currentState`. In the above
- // example, `exitStates` would have
- // `[<root.posts>`, `<root.posts.show>]`.
- while (resolveState && !enterStates) {
- exitStates.unshift(resolveState);
</del><ins>+/**
+* 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} the type of key event, e.g. `keypress`, `keydown`, `keyup`
+* @param {Number} the keyCode of the simulated key event
+* @return {RSVP.Promise}
+*/
+asyncHelper('keyEvent', keyEvent);
</ins><span class="cx">
</span><del>- resolveState = get(resolveState, 'parentState');
- if (!resolveState) {
- enterStates = this.getStatesInPath(this, path);
- if (!enterStates) {
- Ember.assert('Could not find state for path: "'+path+'"');
- return;
- }
- }
- enterStates = this.getStatesInPath(resolveState, path);
- }
</del><ins>+/**
+* 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);
</ins><span class="cx">
</span><del>- // If the path contains some states that are parents of both the
- // current state and the target state, remove them.
- //
- // For example, in the following hierarchy:
- //
- // |- root
- // | |- post
- // | | |- index (* current)
- // | | |- show
- //
- // If the `path` is `root.post.show`, the three variables will
- // be:
- //
- // * resolveState: `<state manager>`
- // * enterStates: `[<root>, <root.post>, <root.post.show>]`
- // * exitStates: `[<root>, <root.post>, <root.post.index>]`
- //
- // The goal of this code is to remove the common states, so we
- // have:
- //
- // * resolveState: `<root.post>`
- // * enterStates: `[<root.post.show>]`
- // * exitStates: `[<root.post.index>]`
- //
- // This avoid unnecessary calls to the enter and exit transitions.
- while (enterStates.length > 0 && enterStates[0] === exitStates[0]) {
- resolveState = enterStates.shift();
- exitStates.shift();
- }
</del><ins>+/**
+* 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);
</ins><span class="cx">
</span><del>- // Cache the enterStates, exitStates, and resolveState for the
- // current state and the `path`.
- var transitions = currentState.pathsCache[path] = {
- exitStates: exitStates,
- enterStates: enterStates,
- resolveState: resolveState
- };
</del><ins>+/**
+* 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);
</ins><span class="cx">
</span><del>- return transitions;
- },
</del><ins>+/**
+ 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.
</ins><span class="cx">
</span><del>- triggerSetupContext: function(transitions) {
- var contexts = transitions.contexts,
- offset = transitions.enterStates.length - contexts.length,
- enterStates = transitions.enterStates,
- transitionEvent = get(this, 'transitionEvent');
</del><ins>+ This is most often used as the return value for the helper functions (see 'click',
+ 'fillIn','visit',etc).
</ins><span class="cx">
</span><del>- Ember.assert("More contexts provided than states", offset >= 0);
</del><ins>+ Example:
</ins><span class="cx">
</span><del>- arrayForEach.call(enterStates, function(state, idx) {
- state.trigger(transitionEvent, this, contexts[idx-offset]);
- }, this);
- },
</del><ins>+ ```javascript
+ Ember.Test.registerAsyncHelper('loginUser', function(app, username, password) {
+ visit('secured/path/here')
+ .fillIn('#username', username)
+ .fillIn('#password', username)
+ .click('.submit')
</ins><span class="cx">
</span><del>- getState: function(name) {
- var state = get(this, name),
- parentState = get(this, 'parentState');
</del><ins>+ return wait();
+ });
</ins><span class="cx">
</span><del>- if (state) {
- return state;
- } else if (parentState) {
- return parentState.getState(name);
- }
- },
</del><ins>+ @method wait
+ @param {Object} value The value to be returned.
+ @return {RSVP.Promise}
+*/
+asyncHelper('wait', wait);
+asyncHelper('andThen', andThen);
</ins><span class="cx">
</span><del>- enterState: function(transition) {
- var log = this.enableLogging;
</del><span class="cx">
</span><del>- var exitStates = transition.exitStates.slice(0).reverse();
- arrayForEach.call(exitStates, function(state) {
- state.trigger('exit', this);
- }, this);
</del><span class="cx">
</span><del>- arrayForEach.call(transition.enterStates, function(state) {
- if (log) { Ember.Logger.log("STATEMANAGER: Entering " + get(state, 'path')); }
- state.trigger('enter', this);
- }, this);
</del><span class="cx">
</span><del>- set(this, 'currentState', transition.finalState);
- }
-});
-
</del><span class="cx"> })();
</span><span class="cx">
</span><span class="cx">
</span><span class="cx">
</span><span class="cx"> (function() {
</span><span class="cx"> /**
</span><del>-Ember States
</del><ins>+ Ember Testing
</ins><span class="cx">
</span><del>-@module ember
-@submodule ember-states
-@requires ember-runtime
</del><ins>+ @module ember
+ @submodule ember-testing
+ @requires ember-application
</ins><span class="cx"> */
</span><span class="cx">
</span><span class="cx"> })();
</span><span class="cx">
</span><del>-
-})();
-// Version: v1.0.0-rc.1
-// Last commit: 8b061b4 (2013-02-15 12:10:22 -0800)
-
-
</del><span class="cx"> (function() {
</span><span class="cx"> /**
</span><span class="cx"> Ember
</span><span class="lines">@@ -26835,5 +40544,40 @@
</span><span class="cx"> @module ember
</span><span class="cx"> */
</span><span class="cx">
</span><ins>+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
+*/
+
</ins><span class="cx"> })();
</span><span class="cx">
</span><ins>+
+})();
</ins></span></pre></div>
<a id="trunkPerformanceTestsDoYouEvenBenchresourcestodomvcarchitectureexamplesemberjsbower_componentsemberdataemberdatajs"></a>
<div class="addfile"><h4>Added: trunk/PerformanceTests/DoYouEvenBench/resources/todomvc/architecture-examples/emberjs/bower_components/ember-data/ember-data.js (0 => 163429)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/PerformanceTests/DoYouEvenBench/resources/todomvc/architecture-examples/emberjs/bower_components/ember-data/ember-data.js         (rev 0)
+++ trunk/PerformanceTests/DoYouEvenBench/resources/todomvc/architecture-examples/emberjs/bower_components/ember-data/ember-data.js        2014-02-05 05:36:04 UTC (rev 163429)
</span><span class="lines">@@ -0,0 +1,10620 @@
</span><ins>+/*!
+ * @overview Ember Data
+ * @copyright Copyright 2011-2014 Tilde Inc. and contributors.
+ * Portions Copyright 2011 LivingSocial Inc.
+ * @license Licensed under MIT license (see license.js)
+ * @version 1.0.0-beta.6
+ */
+
+
+(function() {
+var define, requireModule;
+
+(function() {
+ var registry = {}, seen = {};
+
+ define = function(name, deps, callback) {
+ registry[name] = { deps: deps, callback: callback };
+ };
+
+ requireModule = function(name) {
+ if (seen[name]) { return seen[name]; }
+ seen[name] = {};
+
+ var mod, deps, callback, reified , exports;
+
+ mod = registry[name];
+
+ if (!mod) {
+ throw new Error("Module '" + name + "' not found.");
+ }
+
+ deps = mod.deps;
+ callback = mod.callback;
+ reified = [];
+ exports;
+
+ for (var i=0, l=deps.length; i<l; i++) {
+ if (deps[i] === 'exports') {
+ reified.push(exports = {});
+ } else {
+ reified.push(requireModule(deps[i]));
+ }
+ }
+
+ var value = callback.apply(this, reified);
+ return seen[name] = exports || value;
+ };
+})();
+(function() {
+/**
+ @module ember-data
+*/
+
+/**
+ All Ember Data methods and functions are defined inside of this namespace.
+
+ @class DS
+ @static
+*/
+var DS;
+if ('undefined' === typeof DS) {
+ /**
+ @property VERSION
+ @type String
+ @default '1.0.0-beta.6'
+ @static
+ */
+ DS = Ember.Namespace.create({
+ VERSION: '1.0.0-beta.6'
+ });
+
+ if ('undefined' !== typeof window) {
+ window.DS = DS;
+ }
+
+ if (Ember.libraries) {
+ Ember.libraries.registerCoreLibrary('Ember Data', DS.VERSION);
+ }
+}
+
+})();
+
+
+
+(function() {
+/**
+ This is used internally to enable deprecation of container paths and provide
+ a decent message to the user indicating how to fix the issue.
+
+ @class ContainerProxy
+ @namespace DS
+ @private
+*/
+var ContainerProxy = function (container){
+ this.container = container;
+};
+
+ContainerProxy.prototype.aliasedFactory = function(path, preLookup) {
+ var _this = this;
+
+ return {create: function(){
+ if (preLookup) { preLookup(); }
+
+ return _this.container.lookup(path);
+ }};
+};
+
+ContainerProxy.prototype.registerAlias = function(source, dest, preLookup) {
+ var factory = this.aliasedFactory(dest, preLookup);
+
+ return this.container.register(source, factory);
+};
+
+ContainerProxy.prototype.registerDeprecation = function(deprecated, valid) {
+ var preLookupCallback = function(){
+ Ember.deprecate("You tried to look up '" + deprecated + "', " +
+ "but this has been deprecated in favor of '" + valid + "'.", false);
+ };
+
+ return this.registerAlias(deprecated, valid, preLookupCallback);
+};
+
+ContainerProxy.prototype.registerDeprecations = function(proxyPairs) {
+ for (var i = proxyPairs.length; i > 0; i--) {
+ var proxyPair = proxyPairs[i - 1],
+ deprecated = proxyPair['deprecated'],
+ valid = proxyPair['valid'];
+
+ this.registerDeprecation(deprecated, valid);
+ }
+};
+
+DS.ContainerProxy = ContainerProxy;
+
+})();
+
+
+
+(function() {
+var get = Ember.get, set = Ember.set, isNone = Ember.isNone;
+
+// Simple dispatcher to support overriding the aliased
+// method in subclasses.
+function aliasMethod(methodName) {
+ return function() {
+ return this[methodName].apply(this, arguments);
+ };
+}
+
+/**
+ In Ember Data a Serializer is used to serialize and deserialize
+ records when they are transferred in and out of an external source.
+ This process involves normalizing property names, transforming
+ attribute values and serializing relationships.
+
+ For maximum performance Ember Data recommends you use the
+ [RESTSerializer](DS.RESTSerializer.html) or one of its subclasses.
+
+ `JSONSerializer` is useful for simpler or legacy backends that may
+ not support the http://jsonapi.org/ spec.
+
+ @class JSONSerializer
+ @namespace DS
+*/
+DS.JSONSerializer = Ember.Object.extend({
+ /**
+ The primaryKey is used when serializing and deserializing
+ data. Ember Data always uses the `id` property to store the id of
+ the record. The external source may not always follow this
+ convention. In these cases it is useful to override the
+ primaryKey property to match the primaryKey of your external
+ store.
+
+ Example
+
+ ```javascript
+ App.ApplicationSerializer = DS.JSONSerializer.extend({
+ primaryKey: '_id'
+ });
+ ```
+
+ @property primaryKey
+ @type {String}
+ @default 'id'
+ */
+ primaryKey: 'id',
+
+ /**
+ Given a subclass of `DS.Model` and a JSON object this method will
+ iterate through each attribute of the `DS.Model` and invoke the
+ `DS.Transform#deserialize` method on the matching property of the
+ JSON object. This method is typically called after the
+ serializer's `normalize` method.
+
+ @method applyTransforms
+ @private
+ @param {subclass of DS.Model} type
+ @param {Object} data The data to transform
+ @return {Object} data The transformed data object
+ */
+ applyTransforms: function(type, data) {
+ type.eachTransformedAttribute(function(key, type) {
+ var transform = this.transformFor(type);
+ data[key] = transform.deserialize(data[key]);
+ }, this);
+
+ return data;
+ },
+
+ /**
+ Normalizes a part of the JSON payload returned by
+ the server. You should override this method, munge the hash
+ and call super if you have generic normalization to do.
+
+ It takes the type of the record that is being normalized
+ (as a DS.Model class), the property where the hash was
+ originally found, and the hash to normalize.
+
+ You can use this method, for example, to normalize underscored keys to camelized
+ or other general-purpose normalizations.
+
+ Example
+
+ ```javascript
+ App.ApplicationSerializer = DS.JSONSerializer.extend({
+ normalize: function(type, hash) {
+ var fields = Ember.get(type, 'fields');
+ fields.forEach(function(field) {
+ var payloadField = Ember.String.underscore(field);
+ if (field === payloadField) { return; }
+
+ hash[field] = hash[payloadField];
+ delete hash[payloadField];
+ });
+ return this._super.apply(this, arguments);
+ }
+ });
+ ```
+
+ @method normalize
+ @param {subclass of DS.Model} type
+ @param {Object} hash
+ @return {Object}
+ */
+ normalize: function(type, hash) {
+ if (!hash) { return hash; }
+
+ this.applyTransforms(type, hash);
+ return hash;
+ },
+
+ // SERIALIZE
+ /**
+ Called when a record is saved in order to convert the
+ record into JSON.
+
+ By default, it creates a JSON object with a key for
+ each attribute and belongsTo relationship.
+
+ For example, consider this model:
+
+ ```javascript
+ App.Comment = DS.Model.extend({
+ title: DS.attr(),
+ body: DS.attr(),
+
+ author: DS.belongsTo('user')
+ });
+ ```
+
+ The default serialization would create a JSON object like:
+
+ ```javascript
+ {
+ "title": "Rails is unagi",
+ "body": "Rails? Omakase? O_O",
+ "author": 12
+ }
+ ```
+
+ By default, attributes are passed through as-is, unless
+ you specified an attribute type (`DS.attr('date')`). If
+ you specify a transform, the JavaScript value will be
+ serialized when inserted into the JSON hash.
+
+ By default, belongs-to relationships are converted into
+ IDs when inserted into the JSON hash.
+
+ ## IDs
+
+ `serialize` takes an options hash with a single option:
+ `includeId`. If this option is `true`, `serialize` will,
+ by default include the ID in the JSON object it builds.
+
+ The adapter passes in `includeId: true` when serializing
+ a record for `createRecord`, but not for `updateRecord`.
+
+ ## Customization
+
+ Your server may expect a different JSON format than the
+ built-in serialization format.
+
+ In that case, you can implement `serialize` yourself and
+ return a JSON hash of your choosing.
+
+ ```javascript
+ App.PostSerializer = DS.JSONSerializer.extend({
+ serialize: function(post, options) {
+ var json = {
+ POST_TTL: post.get('title'),
+ POST_BDY: post.get('body'),
+ POST_CMS: post.get('comments').mapProperty('id')
+ }
+
+ if (options.includeId) {
+ json.POST_ID_ = post.get('id');
+ }
+
+ return json;
+ }
+ });
+ ```
+
+ ## Customizing an App-Wide Serializer
+
+ If you want to define a serializer for your entire
+ application, you'll probably want to use `eachAttribute`
+ and `eachRelationship` on the record.
+
+ ```javascript
+ App.ApplicationSerializer = DS.JSONSerializer.extend({
+ serialize: function(record, options) {
+ var json = {};
+
+ record.eachAttribute(function(name) {
+ json[serverAttributeName(name)] = record.get(name);
+ })
+
+ record.eachRelationship(function(name, relationship) {
+ if (relationship.kind === 'hasMany') {
+ json[serverHasManyName(name)] = record.get(name).mapBy('id');
+ }
+ });
+
+ if (options.includeId) {
+ json.ID_ = record.get('id');
+ }
+
+ return json;
+ }
+ });
+
+ function serverAttributeName(attribute) {
+ return attribute.underscore().toUpperCase();
+ }
+
+ function serverHasManyName(name) {
+ return serverAttributeName(name.singularize()) + "_IDS";
+ }
+ ```
+
+ This serializer will generate JSON that looks like this:
+
+ ```javascript
+ {
+ "TITLE": "Rails is omakase",
+ "BODY": "Yep. Omakase.",
+ "COMMENT_IDS": [ 1, 2, 3 ]
+ }
+ ```
+
+ ## Tweaking the Default JSON
+
+ If you just want to do some small tweaks on the default JSON,
+ you can call super first and make the tweaks on the returned
+ JSON.
+
+ ```javascript
+ App.PostSerializer = DS.JSONSerializer.extend({
+ serialize: function(record, options) {
+ var json = this._super.apply(this, arguments);
+
+ json.subject = json.title;
+ delete json.title;
+
+ return json;
+ }
+ });
+ ```
+
+ @method serialize
+ @param {subclass of DS.Model} record
+ @param {Object} options
+ @return {Object} json
+ */
+ serialize: function(record, options) {
+ var json = {};
+
+ if (options && options.includeId) {
+ var id = get(record, 'id');
+
+ if (id) {
+ json[get(this, 'primaryKey')] = id;
+ }
+ }
+
+ record.eachAttribute(function(key, attribute) {
+ this.serializeAttribute(record, json, key, attribute);
+ }, this);
+
+ record.eachRelationship(function(key, relationship) {
+ if (relationship.kind === 'belongsTo') {
+ this.serializeBelongsTo(record, json, relationship);
+ } else if (relationship.kind === 'hasMany') {
+ this.serializeHasMany(record, json, relationship);
+ }
+ }, this);
+
+ return json;
+ },
+
+ /**
+ `serializeAttribute` can be used to customize how `DS.attr`
+ properties are serialized
+
+ For example if you wanted to ensure all you attributes were always
+ serialized as properties on an `attributes` object you could
+ write:
+
+ ```javascript
+ App.ApplicationSerializer = DS.JSONSerializer.extend({
+ serializeAttribute: function(record, json, key, attributes) {
+ json.attributes = json.attributes || {};
+ this._super(record, json.attributes, key, attributes);
+ }
+ });
+ ```
+
+ @method serializeAttribute
+ @param {DS.Model} record
+ @param {Object} json
+ @param {String} key
+ @param {Object} attribute
+ */
+ serializeAttribute: function(record, json, key, attribute) {
+ var attrs = get(this, 'attrs');
+ var value = get(record, key), type = attribute.type;
+
+ if (type) {
+ var transform = this.transformFor(type);
+ value = transform.serialize(value);
+ }
+
+ // if provided, use the mapping provided by `attrs` in
+ // the serializer
+ key = attrs && attrs[key] || (this.keyForAttribute ? this.keyForAttribute(key) : key);
+
+ json[key] = value;
+ },
+
+ /**
+ `serializeBelongsTo` can be used to customize how `DS.belongsTo`
+ properties are serialized.
+
+ Example
+
+ ```javascript
+ App.PostSerializer = DS.JSONSerializer.extend({
+ serializeBelongsTo: function(record, json, relationship) {
+ var key = relationship.key;
+
+ var belongsTo = get(record, key);
+
+ key = this.keyForRelationship ? this.keyForRelationship(key, "belongsTo") : key;
+
+ json[key] = Ember.isNone(belongsTo) ? belongsTo : belongsTo.toJSON();
+ }
+ });
+ ```
+
+ @method serializeBelongsTo
+ @param {DS.Model} record
+ @param {Object} json
+ @param {Object} relationship
+ */
+ serializeBelongsTo: function(record, json, relationship) {
+ var key = relationship.key;
+
+ var belongsTo = get(record, key);
+
+ key = this.keyForRelationship ? this.keyForRelationship(key, "belongsTo") : key;
+
+ if (isNone(belongsTo)) {
+ json[key] = belongsTo;
+ } else {
+ json[key] = get(belongsTo, 'id');
+ }
+
+ if (relationship.options.polymorphic) {
+ this.serializePolymorphicType(record, json, relationship);
+ }
+ },
+
+ /**
+ `serializeHasMany` can be used to customize how `DS.hasMany`
+ properties are serialized.
+
+ Example
+
+ ```javascript
+ App.PostSerializer = DS.JSONSerializer.extend({
+ serializeHasMany: function(record, json, relationship) {
+ var key = relationship.key;
+ if (key === 'comments') {
+ return;
+ } else {
+ this._super.apply(this, arguments);
+ }
+ }
+ });
+ ```
+
+ @method serializeHasMany
+ @param {DS.Model} record
+ @param {Object} json
+ @param {Object} relationship
+ */
+ serializeHasMany: function(record, json, relationship) {
+ var key = relationship.key;
+
+ var relationshipType = DS.RelationshipChange.determineRelationshipType(record.constructor, relationship);
+
+ if (relationshipType === 'manyToNone' || relationshipType === 'manyToMany') {
+ json[key] = get(record, key).mapBy('id');
+ // TODO support for polymorphic manyToNone and manyToMany relationships
+ }
+ },
+
+ /**
+ You can use this method to customize how polymorphic objects are
+ serialized. Objects are considered to be polymorphic if
+ `{polymorphic: true}` is pass as the second argument to the
+ `DS.belongsTo` function.
+
+ Example
+
+ ```javascript
+ App.CommentSerializer = DS.JSONSerializer.extend({
+ serializePolymorphicType: function(record, json, relationship) {
+ var key = relationship.key,
+ belongsTo = get(record, key);
+ key = this.keyForAttribute ? this.keyForAttribute(key) : key;
+ json[key + "_type"] = belongsTo.constructor.typeKey;
+ }
+ });
+ ```
+
+ @method serializePolymorphicType
+ @param {DS.Model} record
+ @param {Object} json
+ @param {Object} relationship
+ */
+ serializePolymorphicType: Ember.K,
+
+ // EXTRACT
+
+ /**
+ The `extract` method is used to deserialize payload data from the
+ server. By default the `JSONSerializer` does not push the records
+ into the store. However records that subclass `JSONSerializer`
+ such as the `RESTSerializer` may push records into the store as
+ part of the extract call.
+
+ This method delegates to a more specific extract method based on
+ the `requestType`.
+
+ Example
+
+ ```javascript
+ var get = Ember.get;
+ socket.on('message', function(message) {
+ var modelName = message.model;
+ var data = message.data;
+ var type = store.modelFor(modelName);
+ var serializer = store.serializerFor(type.typeKey);
+ var record = serializer.extract(store, type, data, get(data, 'id'), 'single');
+ store.push(modelName, record);
+ });
+ ```
+
+ @method extract
+ @param {DS.Store} store
+ @param {subclass of DS.Model} type
+ @param {Object} payload
+ @param {String or Number} id
+ @param {String} requestType
+ @return {Object} json The deserialized payload
+ */
+ extract: function(store, type, payload, id, requestType) {
+ this.extractMeta(store, type, payload);
+
+ var specificExtract = "extract" + requestType.charAt(0).toUpperCase() + requestType.substr(1);
+ return this[specificExtract](store, type, payload, id, requestType);
+ },
+
+ /**
+ `extractFindAll` is a hook into the extract method used when a
+ call is made to `DS.Store#findAll`. By default this method is an
+ alias for [extractArray](#method_extractArray).
+
+ @method extractFindAll
+ @param {DS.Store} store
+ @param {subclass of DS.Model} type
+ @param {Object} payload
+ @return {Array} array An array of deserialized objects
+ */
+ extractFindAll: aliasMethod('extractArray'),
+ /**
+ `extractFindQuery` is a hook into the extract method used when a
+ call is made to `DS.Store#findQuery`. By default this method is an
+ alias for [extractArray](#method_extractArray).
+
+ @method extractFindQuery
+ @param {DS.Store} store
+ @param {subclass of DS.Model} type
+ @param {Object} payload
+ @return {Array} array An array of deserialized objects
+ */
+ extractFindQuery: aliasMethod('extractArray'),
+ /**
+ `extractFindMany` is a hook into the extract method used when a
+ call is made to `DS.Store#findMany`. By default this method is
+ alias for [extractArray](#method_extractArray).
+
+ @method extractFindMany
+ @param {DS.Store} store
+ @param {subclass of DS.Model} type
+ @param {Object} payload
+ @return {Array} array An array of deserialized objects
+ */
+ extractFindMany: aliasMethod('extractArray'),
+ /**
+ `extractFindHasMany` is a hook into the extract method used when a
+ call is made to `DS.Store#findHasMany`. By default this method is
+ alias for [extractArray](#method_extractArray).
+
+ @method extractFindHasMany
+ @param {DS.Store} store
+ @param {subclass of DS.Model} type
+ @param {Object} payload
+ @return {Array} array An array of deserialized objects
+ */
+ extractFindHasMany: aliasMethod('extractArray'),
+
+ /**
+ `extractCreateRecord` is a hook into the extract method used when a
+ call is made to `DS.Store#createRecord`. By default this method is
+ alias for [extractSave](#method_extractSave).
+
+ @method extractCreateRecord
+ @param {DS.Store} store
+ @param {subclass of DS.Model} type
+ @param {Object} payload
+ @return {Object} json The deserialized payload
+ */
+ extractCreateRecord: aliasMethod('extractSave'),
+ /**
+ `extractUpdateRecord` is a hook into the extract method used when
+ a call is made to `DS.Store#update`. By default this method is alias
+ for [extractSave](#method_extractSave).
+
+ @method extractUpdateRecord
+ @param {DS.Store} store
+ @param {subclass of DS.Model} type
+ @param {Object} payload
+ @return {Object} json The deserialized payload
+ */
+ extractUpdateRecord: aliasMethod('extractSave'),
+ /**
+ `extractDeleteRecord` is a hook into the extract method used when
+ a call is made to `DS.Store#deleteRecord`. By default this method is
+ alias for [extractSave](#method_extractSave).
+
+ @method extractDeleteRecord
+ @param {DS.Store} store
+ @param {subclass of DS.Model} type
+ @param {Object} payload
+ @return {Object} json The deserialized payload
+ */
+ extractDeleteRecord: aliasMethod('extractSave'),
+
+ /**
+ `extractFind` is a hook into the extract method used when
+ a call is made to `DS.Store#find`. By default this method is
+ alias for [extractSingle](#method_extractSingle).
+
+ @method extractFind
+ @param {DS.Store} store
+ @param {subclass of DS.Model} type
+ @param {Object} payload
+ @return {Object} json The deserialized payload
+ */
+ extractFind: aliasMethod('extractSingle'),
+ /**
+ `extractFindBelongsTo` is a hook into the extract method used when
+ a call is made to `DS.Store#findBelongsTo`. By default this method is
+ alias for [extractSingle](#method_extractSingle).
+
+ @method extractFindBelongsTo
+ @param {DS.Store} store
+ @param {subclass of DS.Model} type
+ @param {Object} payload
+ @return {Object} json The deserialized payload
+ */
+ extractFindBelongsTo: aliasMethod('extractSingle'),
+ /**
+ `extractSave` is a hook into the extract method used when a call
+ is made to `DS.Model#save`. By default this method is alias
+ for [extractSingle](#method_extractSingle).
+
+ @method extractSave
+ @param {DS.Store} store
+ @param {subclass of DS.Model} type
+ @param {Object} payload
+ @return {Object} json The deserialized payload
+ */
+ extractSave: aliasMethod('extractSingle'),
+
+ /**
+ `extractSingle` is used to deserialize a single record returned
+ from the adapter.
+
+ Example
+
+ ```javascript
+ App.PostSerializer = DS.JSONSerializer.extend({
+ extractSingle: function(store, type, payload) {
+ payload.comments = payload._embedded.comment;
+ delete payload._embedded;
+
+ return this._super(store, type, payload);
+ },
+ });
+ ```
+
+ @method extractSingle
+ @param {DS.Store} store
+ @param {subclass of DS.Model} type
+ @param {Object} payload
+ @return {Object} json The deserialized payload
+ */
+ extractSingle: function(store, type, payload) {
+ return this.normalize(type, payload);
+ },
+
+ /**
+ `extractArray` is used to deserialize an array of records
+ returned from the adapter.
+
+ Example
+
+ ```javascript
+ App.PostSerializer = DS.JSONSerializer.extend({
+ extractArray: function(store, type, payload) {
+ return payload.map(function(json) {
+ return this.extractSingle(json);
+ }, this);
+ }
+ });
+ ```
+
+ @method extractArray
+ @param {DS.Store} store
+ @param {subclass of DS.Model} type
+ @param {Object} payload
+ @return {Array} array An array of deserialized objects
+ */
+ extractArray: function(store, type, payload) {
+ return this.normalize(type, payload);
+ },
+
+ /**
+ `extractMeta` is used to deserialize any meta information in the
+ adapter payload. By default Ember Data expects meta information to
+ be located on the `meta` property of the payload object.
+
+ Example
+
+ ```javascript
+ App.PostSerializer = DS.JSONSerializer.extend({
+ extractMeta: function(store, type, payload) {
+ if (payload && payload._pagination) {
+ store.metaForType(type, payload._pagination);
+ delete payload._pagination;
+ }
+ }
+ });
+ ```
+
+ @method extractMeta
+ @param {DS.Store} store
+ @param {subclass of DS.Model} type
+ @param {Object} payload
+ */
+ extractMeta: function(store, type, payload) {
+ if (payload && payload.meta) {
+ store.metaForType(type, payload.meta);
+ delete payload.meta;
+ }
+ },
+
+ /**
+ `keyForAttribute` can be used to define rules for how to convert an
+ attribute name in your model to a key in your JSON.
+
+ Example
+
+ ```javascript
+ App.ApplicationSerializer = DS.RESTSerializer.extend({
+ keyForAttribute: function(attr) {
+ return Ember.String.underscore(attr).toUpperCase();
+ }
+ });
+ ```
+
+ @method keyForAttribute
+ @param {String} key
+ @return {String} normalized key
+ */
+
+
+ /**
+ `keyForRelationship` can be used to define a custom key when
+ serializing relationship properties. By default `JSONSerializer`
+ does not provide an implementation of this method.
+
+ Example
+
+ ```javascript
+ App.PostSerializer = DS.JSONSerializer.extend({
+ keyForRelationship: function(key, relationship) {
+ return 'rel_' + Ember.String.underscore(key);
+ }
+ });
+ ```
+
+ @method keyForRelationship
+ @param {String} key
+ @param {String} relationship type
+ @return {String} normalized key
+ */
+
+ // HELPERS
+
+ /**
+ @method transformFor
+ @private
+ @param {String} attributeType
+ @param {Boolean} skipAssertion
+ @return {DS.Transform} transform
+ */
+ transformFor: function(attributeType, skipAssertion) {
+ var transform = this.container.lookup('transform:' + attributeType);
+ Ember.assert("Unable to find transform for '" + attributeType + "'", skipAssertion || !!transform);
+ return transform;
+ }
+});
+
+})();
+
+
+
+(function() {
+/**
+ @module ember-data
+*/
+var get = Ember.get, capitalize = Ember.String.capitalize, underscore = Ember.String.underscore, DS = window.DS ;
+
+/**
+ Extend `Ember.DataAdapter` with ED specific code.
+
+ @class DebugAdapter
+ @namespace DS
+ @extends Ember.DataAdapter
+ @private
+*/
+DS.DebugAdapter = Ember.DataAdapter.extend({
+ getFilters: function() {
+ return [
+ { name: 'isNew', desc: 'New' },
+ { name: 'isModified', desc: 'Modified' },
+ { name: 'isClean', desc: 'Clean' }
+ ];
+ },
+
+ detect: function(klass) {
+ return klass !== DS.Model && DS.Model.detect(klass);
+ },
+
+ columnsForType: function(type) {
+ var columns = [{ name: 'id', desc: 'Id' }], count = 0, self = this;
+ get(type, 'attributes').forEach(function(name, meta) {
+ if (count++ > self.attributeLimit) { return false; }
+ var desc = capitalize(underscore(name).replace('_', ' '));
+ columns.push({ name: name, desc: desc });
+ });
+ return columns;
+ },
+
+ getRecords: function(type) {
+ return this.get('store').all(type);
+ },
+
+ getRecordColumnValues: function(record) {
+ var self = this, count = 0,
+ columnValues = { id: get(record, 'id') };
+
+ record.eachAttribute(function(key) {
+ if (count++ > self.attributeLimit) {
+ return false;
+ }
+ var value = get(record, key);
+ columnValues[key] = value;
+ });
+ return columnValues;
+ },
+
+ getRecordKeywords: function(record) {
+ var keywords = [], keys = Ember.A(['id']);
+ record.eachAttribute(function(key) {
+ keys.push(key);
+ });
+ keys.forEach(function(key) {
+ keywords.push(get(record, key));
+ });
+ return keywords;
+ },
+
+ getRecordFilterValues: function(record) {
+ return {
+ isNew: record.get('isNew'),
+ isModified: record.get('isDirty') && !record.get('isNew'),
+ isClean: !record.get('isDirty')
+ };
+ },
+
+ getRecordColor: function(record) {
+ var color = 'black';
+ if (record.get('isNew')) {
+ color = 'green';
+ } else if (record.get('isDirty')) {
+ color = 'blue';
+ }
+ return color;
+ },
+
+ observeRecord: function(record, recordUpdated) {
+ var releaseMethods = Ember.A(), self = this,
+ keysToObserve = Ember.A(['id', 'isNew', 'isDirty']);
+
+ record.eachAttribute(function(key) {
+ keysToObserve.push(key);
+ });
+
+ keysToObserve.forEach(function(key) {
+ var handler = function() {
+ recordUpdated(self.wrapRecord(record));
+ };
+ Ember.addObserver(record, key, handler);
+ releaseMethods.push(function() {
+ Ember.removeObserver(record, key, handler);
+ });
+ });
+
+ var release = function() {
+ releaseMethods.forEach(function(fn) { fn(); } );
+ };
+
+ return release;
+ }
+
+});
+
+})();
+
+
+
+(function() {
+/**
+ The `DS.Transform` class is used to serialize and deserialize model
+ attributes when they are saved or loaded from an
+ adapter. Subclassing `DS.Transform` is useful for creating custom
+ attributes. All subclasses of `DS.Transform` must implement a
+ `serialize` and a `deserialize` method.
+
+ Example
+
+ ```javascript
+ App.RawTransform = DS.Transform.extend({
+ deserialize: function(serialized) {
+ return serialized;
+ },
+ serialize: function(deserialized) {
+ return deserialized;
+ }
+ });
+ ```
+
+ Usage
+
+ ```javascript
+ var attr = DS.attr;
+ App.Requirement = DS.Model.extend({
+ name: attr('string'),
+ optionsArray: attr('raw')
+ });
+ ```
+
+ @class Transform
+ @namespace DS
+ */
+DS.Transform = Ember.Object.extend({
+ /**
+ When given a deserialized value from a record attribute this
+ method must return the serialized value.
+
+ Example
+
+ ```javascript
+ serialize: function(deserialized) {
+ return Ember.isEmpty(deserialized) ? null : Number(deserialized);
+ }
+ ```
+
+ @method serialize
+ @param deserialized The deserialized value
+ @return The serialized value
+ */
+ serialize: Ember.required(),
+
+ /**
+ When given a serialize value from a JSON object this method must
+ return the deserialized value for the record attribute.
+
+ Example
+
+ ```javascript
+ deserialize: function(serialized) {
+ return empty(serialized) ? null : Number(serialized);
+ }
+ ```
+
+ @method deserialize
+ @param serialized The serialized value
+ @return The deserialized value
+ */
+ deserialize: Ember.required()
+
+});
+
+})();
+
+
+
+(function() {
+
+/**
+ The `DS.BooleanTransform` class is used to serialize and deserialize
+ boolean attributes on Ember Data record objects. This transform is
+ used when `boolean` is passed as the type parameter to the
+ [DS.attr](../../data#method_attr) function.
+
+ Usage
+
+ ```javascript
+ var attr = DS.attr;
+ App.User = DS.Model.extend({
+ isAdmin: attr('boolean'),
+ name: attr('string'),
+ email: attr('string')
+ });
+ ```
+
+ @class BooleanTransform
+ @extends DS.Transform
+ @namespace DS
+ */
+DS.BooleanTransform = DS.Transform.extend({
+ deserialize: function(serialized) {
+ var type = typeof serialized;
+
+ if (type === "boolean") {
+ return serialized;
+ } else if (type === "string") {
+ return serialized.match(/^true$|^t$|^1$/i) !== null;
+ } else if (type === "number") {
+ return serialized === 1;
+ } else {
+ return false;
+ }
+ },
+
+ serialize: function(deserialized) {
+ return Boolean(deserialized);
+ }
+});
+
+})();
+
+
+
+(function() {
+/**
+ The `DS.DateTransform` class is used to serialize and deserialize
+ date attributes on Ember Data record objects. This transform is used
+ when `date` is passed as the type parameter to the
+ [DS.attr](../../data#method_attr) function.
+
+ ```javascript
+ var attr = DS.attr;
+ App.Score = DS.Model.extend({
+ value: attr('number'),
+ player: DS.belongsTo('player'),
+ date: attr('date')
+ });
+ ```
+
+ @class DateTransform
+ @extends DS.Transform
+ @namespace DS
+ */
+DS.DateTransform = DS.Transform.extend({
+
+ deserialize: function(serialized) {
+ var type = typeof serialized;
+
+ if (type === "string") {
+ return new Date(Ember.Date.parse(serialized));
+ } else if (type === "number") {
+ return new Date(serialized);
+ } else if (serialized === null || serialized === undefined) {
+ // if the value is not present in the data,
+ // return undefined, not null.
+ return serialized;
+ } else {
+ return null;
+ }
+ },
+
+ serialize: function(date) {
+ if (date instanceof Date) {
+ // Serialize it as a number to maintain millisecond precision
+ return Number(date);
+ } else {
+ return null;
+ }
+ }
+
+});
+
+})();
+
+
+
+(function() {
+var empty = Ember.isEmpty;
+/**
+ The `DS.NumberTransform` class is used to serialize and deserialize
+ numeric attributes on Ember Data record objects. This transform is
+ used when `number` is passed as the type parameter to the
+ [DS.attr](../../data#method_attr) function.
+
+ Usage
+
+ ```javascript
+ var attr = DS.attr;
+ App.Score = DS.Model.extend({
+ value: attr('number'),
+ player: DS.belongsTo('player'),
+ date: attr('date')
+ });
+ ```
+
+ @class NumberTransform
+ @extends DS.Transform
+ @namespace DS
+ */
+DS.NumberTransform = DS.Transform.extend({
+
+ deserialize: function(serialized) {
+ return empty(serialized) ? null : Number(serialized);
+ },
+
+ serialize: function(deserialized) {
+ return empty(deserialized) ? null : Number(deserialized);
+ }
+});
+
+})();
+
+
+
+(function() {
+var none = Ember.isNone;
+
+/**
+ The `DS.StringTransform` class is used to serialize and deserialize
+ string attributes on Ember Data record objects. This transform is
+ used when `string` is passed as the type parameter to the
+ [DS.attr](../../data#method_attr) function.
+
+ Usage
+
+ ```javascript
+ var attr = DS.attr;
+ App.User = DS.Model.extend({
+ isAdmin: attr('boolean'),
+ name: attr('string'),
+ email: attr('string')
+ });
+ ```
+
+ @class StringTransform
+ @extends DS.Transform
+ @namespace DS
+ */
+DS.StringTransform = DS.Transform.extend({
+
+ deserialize: function(serialized) {
+ return none(serialized) ? null : String(serialized);
+ },
+
+ serialize: function(deserialized) {
+ return none(deserialized) ? null : String(deserialized);
+ }
+
+});
+
+})();
+
+
+
+(function() {
+
+})();
+
+
+
+(function() {
+/**
+ @module ember-data
+*/
+
+var set = Ember.set;
+
+/*
+ This code registers an injection for Ember.Application.
+
+ If an Ember.js developer defines a subclass of DS.Store on their application,
+ this code will automatically instantiate it and make it available on the
+ router.
+
+ Additionally, after an application's controllers have been injected, they will
+ each have the store made available to them.
+
+ For example, imagine an Ember.js application with the following classes:
+
+ App.Store = DS.Store.extend({
+ adapter: 'custom'
+ });
+
+ App.PostsController = Ember.ArrayController.extend({
+ // ...
+ });
+
+ When the application is initialized, `App.Store` will automatically be
+ instantiated, and the instance of `App.PostsController` will have its `store`
+ property set to that instance.
+
+ Note that this code will only be run if the `ember-application` package is
+ loaded. If Ember Data is being used in an environment other than a
+ typical application (e.g., node.js where only `ember-runtime` is available),
+ this code will be ignored.
+*/
+
+Ember.onLoad('Ember.Application', function(Application) {
+ Application.initializer({
+ name: "store",
+
+ initialize: function(container, application) {
+ application.register('store:main', application.Store || DS.Store);
+
+ // allow older names to be looked up
+
+ var proxy = new DS.ContainerProxy(container);
+ proxy.registerDeprecations([
+ {deprecated: 'serializer:_default', valid: 'serializer:-default'},
+ {deprecated: 'serializer:_rest', valid: 'serializer:-rest'},
+ {deprecated: 'adapter:_rest', valid: 'adapter:-rest'}
+ ]);
+
+ // new go forward paths
+ application.register('serializer:-default', DS.JSONSerializer);
+ application.register('serializer:-rest', DS.RESTSerializer);
+ application.register('adapter:-rest', DS.RESTAdapter);
+
+ // Eagerly generate the store so defaultStore is populated.
+ // TODO: Do this in a finisher hook
+ container.lookup('store:main');
+ }
+ });
+
+ Application.initializer({
+ name: "transforms",
+ before: "store",
+
+ initialize: function(container, application) {
+ application.register('transform:boolean', DS.BooleanTransform);
+ application.register('transform:date', DS.DateTransform);
+ application.register('transform:number', DS.NumberTransform);
+ application.register('transform:string', DS.StringTransform);
+ }
+ });
+
+ Application.initializer({
+ name: "data-adapter",
+ before: "store",
+
+ initialize: function(container, application) {
+ application.register('data-adapter:main', DS.DebugAdapter);
+ }
+ });
+
+ Application.initializer({
+ name: "injectStore",
+ before: "store",
+
+ initialize: function(container, application) {
+ application.inject('controller', 'store', 'store:main');
+ application.inject('route', 'store', 'store:main');
+ application.inject('serializer', 'store', 'store:main');
+ application.inject('data-adapter', 'store', 'store:main');
+ }
+ });
+
+});
+
+})();
+
+
+
+(function() {
+/**
+ @module ember-data
+*/
+
+/**
+ Date.parse with progressive enhancement for ISO 8601 <https://github.com/csnover/js-iso8601>
+
+ © 2011 Colin Snover <http://zetafleet.com>
+
+ Released under MIT license.
+
+ @class Date
+ @namespace Ember
+ @static
+*/
+Ember.Date = Ember.Date || {};
+
+var origParse = Date.parse, numericKeys = [ 1, 4, 5, 6, 7, 10, 11 ];
+
+/**
+ @method parse
+ @param date
+*/
+Ember.Date.parse = function (date) {
+ var timestamp, struct, minutesOffset = 0;
+
+ // ES5 §15.9.4.2 states that the string should attempt to be parsed as a Date Time String Format string
+ // before falling back to any implementation-specific date parsing, so that’s what we do, even if native
+ // implementations could be faster
+ // 1 YYYY 2 MM 3 DD 4 HH 5 mm 6 ss 7 msec 8 Z 9 ± 10 tzHH 11 tzmm
+ if ((struct = /^(\d{4}|[+\-]\d{6})(?:-(\d{2})(?:-(\d{2}))?)?(?:T(\d{2}):(\d{2})(?::(\d{2})(?:\.(\d{3}))?)?(?:(Z)|([+\-])(\d{2})(?::(\d{2}))?)?)?$/.exec(date))) {
+ // avoid NaN timestamps caused by “undefined” values being passed to Date.UTC
+ for (var i = 0, k; (k = numericKeys[i]); ++i) {
+ struct[k] = +struct[k] || 0;
+ }
+
+ // allow undefined days and months
+ struct[2] = (+struct[2] || 1) - 1;
+ struct[3] = +struct[3] || 1;
+
+ if (struct[8] !== 'Z' && struct[9] !== undefined) {
+ minutesOffset = struct[10] * 60 + struct[11];
+
+ if (struct[9] === '+') {
+ minutesOffset = 0 - minutesOffset;
+ }
+ }
+
+ timestamp = Date.UTC(struct[1], struct[2], struct[3], struct[4], struct[5] + minutesOffset, struct[6], struct[7]);
+ }
+ else {
+ timestamp = origParse ? origParse(date) : NaN;
+ }
+
+ return timestamp;
+};
+
+if (Ember.EXTEND_PROTOTYPES === true || Ember.EXTEND_PROTOTYPES.Date) {
+ Date.parse = Ember.Date.parse;
+}
+
+})();
+
+
+
+(function() {
+
+})();
+
+
+
+(function() {
+/**
+ @module ember-data
+*/
+
+var get = Ember.get, set = Ember.set;
+
+/**
+ A record array is an array that contains records of a certain type. The record
+ array materializes records as needed when they are retrieved for the first
+ time. You should not create record arrays yourself. Instead, an instance of
+ `DS.RecordArray` or its subclasses will be returned by your application's store
+ in response to queries.
+
+ @class RecordArray
+ @namespace DS
+ @extends Ember.ArrayProxy
+ @uses Ember.Evented
+*/
+
+DS.RecordArray = Ember.ArrayProxy.extend(Ember.Evented, {
+ /**
+ The model type contained by this record array.
+
+ @property type
+ @type DS.Model
+ */
+ type: null,
+
+ /**
+ The array of client ids backing the record array. When a
+ record is requested from the record array, the record
+ for the client id at the same index is materialized, if
+ necessary, by the store.
+
+ @property content
+ @private
+ @type Ember.Array
+ */
+ content: null,
+
+ /**
+ The flag to signal a `RecordArray` is currently loading data.
+
+ Example
+
+ ```javascript
+ var people = store.all(App.Person);
+ people.get('isLoaded'); // true
+ ```
+
+ @property isLoaded
+ @type Boolean
+ */
+ isLoaded: false,
+ /**
+ The flag to signal a `RecordArray` is currently loading data.
+
+ Example
+
+ ```javascript
+ var people = store.all(App.Person);
+ people.get('isUpdating'); // false
+ people.update();
+ people.get('isUpdating'); // true
+ ```
+
+ @property isUpdating
+ @type Boolean
+ */
+ isUpdating: false,
+
+ /**
+ The store that created this record array.
+
+ @property store
+ @private
+ @type DS.Store
+ */
+ store: null,
+
+ /**
+ Retrieves an object from the content by index.
+
+ @method objectAtContent
+ @private
+ @param {Number} index
+ @return {DS.Model} record
+ */
+ objectAtContent: function(index) {
+ var content = get(this, 'content');
+
+ return content.objectAt(index);
+ },
+
+ /**
+ Used to get the latest version of all of the records in this array
+ from the adapter.
+
+ Example
+
+ ```javascript
+ var people = store.all(App.Person);
+ people.get('isUpdating'); // false
+ people.update();
+ people.get('isUpdating'); // true
+ ```
+
+ @method update
+ */
+ update: function() {
+ if (get(this, 'isUpdating')) { return; }
+
+ var store = get(this, 'store'),
+ type = get(this, 'type');
+
+ return store.fetchAll(type, this);
+ },
+
+ /**
+ Adds a record to the `RecordArray`.
+
+ @method addRecord
+ @private
+ @param {DS.Model} record
+ */
+ addRecord: function(record) {
+ get(this, 'content').addObject(record);
+ },
+
+ /**
+ Removes a record to the `RecordArray`.
+
+ @method removeRecord
+ @private
+ @param {DS.Model} record
+ */
+ removeRecord: function(record) {
+ get(this, 'content').removeObject(record);
+ },
+
+ /**
+ Saves all of the records in the `RecordArray`.
+
+ Example
+
+ ```javascript
+ var messages = store.all(App.Message);
+ messages.forEach(function(message) {
+ message.set('hasBeenSeen', true);
+ });
+ messages.save();
+ ```
+
+ @method save
+ @return {DS.PromiseArray} promise
+ */
+ save: function() {
+ var promiseLabel = "DS: RecordArray#save " + get(this, 'type');
+ var promise = Ember.RSVP.all(this.invoke("save"), promiseLabel).then(function(array) {
+ return Ember.A(array);
+ }, null, "DS: RecordArray#save apply Ember.NativeArray");
+
+ return DS.PromiseArray.create({ promise: promise });
+ }
+});
+
+})();
+
+
+
+(function() {
+/**
+ @module ember-data
+*/
+
+var get = Ember.get;
+
+/**
+ Represents a list of records whose membership is determined by the
+ store. As records are created, loaded, or modified, the store
+ evaluates them to determine if they should be part of the record
+ array.
+
+ @class FilteredRecordArray
+ @namespace DS
+ @extends DS.RecordArray
+*/
+DS.FilteredRecordArray = DS.RecordArray.extend({
+ /**
+ The filterFunction is a function used to test records from the store to
+ determine if they should be part of the record array.
+
+ Example
+
+ ```javascript
+ var allPeople = store.all('person');
+ allPeople.mapBy('name'); // ["Tom Dale", "Yehuda Katz", "Trek Glowacki"]
+
+ var people = store.filter('person', function(person) {
+ if (person.get('name').match(/Katz$/)) { return true; }
+ });
+ people.mapBy('name'); // ["Yehuda Katz"]
+
+ var notKatzFilter = function(person) {
+ return !person.get('name').match(/Katz$/);
+ };
+ people.set('filterFunction', notKatzFilter);
+ people.mapBy('name'); // ["Tom Dale", "Trek Glowacki"]
+ ```
+
+ @method filterFunction
+ @param {DS.Model} record
+ @return {Boolean} `true` if the record should be in the array
+ */
+ filterFunction: null,
+ isLoaded: true,
+
+ replace: function() {
+ var type = get(this, 'type').toString();
+ throw new Error("The result of a client-side filter (on " + type + ") is immutable.");
+ },
+
+ /**
+ @method updateFilter
+ @private
+ */
+ updateFilter: Ember.observer(function() {
+ var manager = get(this, 'manager');
+ manager.updateFilter(this, get(this, 'type'), get(this, 'filterFunction'));
+ }, 'filterFunction')
+});
+
+})();
+
+
+
+(function() {
+/**
+ @module ember-data
+*/
+
+var get = Ember.get, set = Ember.set;
+
+/**
+ Represents an ordered list of records whose order and membership is
+ determined by the adapter. For example, a query sent to the adapter
+ may trigger a search on the server, whose results would be loaded
+ into an instance of the `AdapterPopulatedRecordArray`.
+
+ @class AdapterPopulatedRecordArray
+ @namespace DS
+ @extends DS.RecordArray
+*/
+DS.AdapterPopulatedRecordArray = DS.RecordArray.extend({
+ query: null,
+
+ replace: function() {
+ var type = get(this, 'type').toString();
+ throw new Error("The result of a server query (on " + type + ") is immutable.");
+ },
+
+ /**
+ @method load
+ @private
+ @param {Array} data
+ */
+ load: function(data) {
+ var store = get(this, 'store'),
+ type = get(this, 'type'),
+ records = store.pushMany(type, data),
+ meta = store.metadataFor(type);
+
+ this.setProperties({
+ content: Ember.A(records),
+ isLoaded: true,
+ meta: meta
+ });
+
+ // TODO: does triggering didLoad event should be the last action of the runLoop?
+ Ember.run.once(this, 'trigger', 'didLoad');
+ }
+});
+
+})();
+
+
+
+(function() {
+/**
+ @module ember-data
+*/
+
+var get = Ember.get, set = Ember.set;
+var map = Ember.EnumerableUtils.map;
+
+/**
+ A `ManyArray` is a `RecordArray` that represents the contents of a has-many
+ relationship.
+
+ The `ManyArray` is instantiated lazily the first time the relationship is
+ requested.
+
+ ### Inverses
+
+ Often, the relationships in Ember Data applications will have
+ an inverse. For example, imagine the following models are
+ defined:
+
+ ```javascript
+ App.Post = DS.Model.extend({
+ comments: DS.hasMany('comment')
+ });
+
+ App.Comment = DS.Model.extend({
+ post: DS.belongsTo('post')
+ });
+ ```
+
+ If you created a new instance of `App.Post` and added
+ a `App.Comment` record to its `comments` has-many
+ relationship, you would expect the comment's `post`
+ property to be set to the post that contained
+ the has-many.
+
+ We call the record to which a relationship belongs the
+ relationship's _owner_.
+
+ @class ManyArray
+ @namespace DS
+ @extends DS.RecordArray
+*/
+DS.ManyArray = DS.RecordArray.extend({
+ init: function() {
+ this._super.apply(this, arguments);
+ this._changesToSync = Ember.OrderedSet.create();
+ },
+
+ /**
+ The property name of the relationship
+
+ @property {String} name
+ @private
+ */
+ name: null,
+
+ /**
+ The record to which this relationship belongs.
+
+ @property {DS.Model} owner
+ @private
+ */
+ owner: null,
+
+ /**
+ `true` if the relationship is polymorphic, `false` otherwise.
+
+ @property {Boolean} isPolymorphic
+ @private
+ */
+ isPolymorphic: false,
+
+ // LOADING STATE
+
+ isLoaded: false,
+
+ /**
+ Used for async `hasMany` arrays
+ to keep track of when they will resolve.
+
+ @property {Ember.RSVP.Promise} promise
+ @private
+ */
+ promise: null,
+
+ /**
+ @method loadingRecordsCount
+ @param {Number} count
+ @private
+ */
+ loadingRecordsCount: function(count) {
+ this.loadingRecordsCount = count;
+ },
+
+ /**
+ @method loadedRecord
+ @private
+ */
+ loadedRecord: function() {
+ this.loadingRecordsCount--;
+ if (this.loadingRecordsCount === 0) {
+ set(this, 'isLoaded', true);
+ this.trigger('didLoad');
+ }
+ },
+
+ /**
+ @method fetch
+ @private
+ */
+ fetch: function() {
+ var records = get(this, 'content'),
+ store = get(this, 'store'),
+ owner = get(this, 'owner'),
+ resolver = Ember.RSVP.defer("DS: ManyArray#fetch " + get(this, 'type'));
+
+ var unloadedRecords = records.filterProperty('isEmpty', true);
+ store.fetchMany(unloadedRecords, owner, resolver);
+ },
+
+ // Overrides Ember.Array's replace method to implement
+ replaceContent: function(index, removed, added) {
+ // Map the array of record objects into an array of client ids.
+ added = map(added, function(record) {
+ Ember.assert("You cannot add '" + record.constructor.typeKey + "' records to this relationship (only '" + this.type.typeKey + "' allowed)", !this.type || record instanceof this.type);
+ return record;
+ }, this);
+
+ this._super(index, removed, added);
+ },
+
+ arrangedContentDidChange: function() {
+ Ember.run.once(this, 'fetch');
+ },
+
+ arrayContentWillChange: function(index, removed, added) {
+ var owner = get(this, 'owner'),
+ name = get(this, 'name');
+
+ if (!owner._suspendedRelationships) {
+ // This code is the first half of code that continues inside
+ // of arrayContentDidChange. It gets or creates a change from
+ // the child object, adds the current owner as the old
+ // parent if this is the first time the object was removed
+ // from a ManyArray, and sets `newParent` to null.
+ //
+ // Later, if the object is added to another ManyArray,
+ // the `arrayContentDidChange` will set `newParent` on
+ // the change.
+ for (var i=index; i<index+removed; i++) {
+ var record = get(this, 'content').objectAt(i);
+
+ var change = DS.RelationshipChange.createChange(owner, record, get(this, 'store'), {
+ parentType: owner.constructor,
+ changeType: "remove",
+ kind: "hasMany",
+ key: name
+ });
+
+ this._changesToSync.add(change);
+ }
+ }
+
+ return this._super.apply(this, arguments);
+ },
+
+ arrayContentDidChange: function(index, removed, added) {
+ this._super.apply(this, arguments);
+
+ var owner = get(this, 'owner'),
+ name = get(this, 'name'),
+ store = get(this, 'store');
+
+ if (!owner._suspendedRelationships) {
+ // This code is the second half of code that started in
+ // `arrayContentWillChange`. It gets or creates a change
+ // from the child object, and adds the current owner as
+ // the new parent.
+ for (var i=index; i<index+added; i++) {
+ var record = get(this, 'content').objectAt(i);
+
+ var change = DS.RelationshipChange.createChange(owner, record, store, {
+ parentType: owner.constructor,
+ changeType: "add",
+ kind:"hasMany",
+ key: name
+ });
+ change.hasManyName = name;
+
+ this._changesToSync.add(change);
+ }
+
+ // We wait until the array has finished being
+ // mutated before syncing the OneToManyChanges created
+ // in arrayContentWillChange, so that the array
+ // membership test in the sync() logic operates
+ // on the final results.
+ this._changesToSync.forEach(function(change) {
+ change.sync();
+ });
+
+ this._changesToSync.clear();
+ }
+ },
+
+ /**
+ Create a child record within the owner
+
+ @method createRecord
+ @private
+ @param {Object} hash
+ @return {DS.Model} record
+ */
+ createRecord: function(hash) {
+ var owner = get(this, 'owner'),
+ store = get(owner, 'store'),
+ type = get(this, 'type'),
+ record;
+
+ Ember.assert("You cannot add '" + type.typeKey + "' records to this polymorphic relationship.", !get(this, 'isPolymorphic'));
+
+ record = store.createRecord.call(store, type, hash);
+ this.pushObject(record);
+
+ return record;
+ }
+
+});
+
+})();
+
+
+
+(function() {
+/**
+ @module ember-data
+*/
+
+})();
+
+
+
+(function() {
+/*globals Ember*/
+/*jshint eqnull:true*/
+/**
+ @module ember-data
+*/
+
+var get = Ember.get, set = Ember.set;
+var once = Ember.run.once;
+var isNone = Ember.isNone;
+var forEach = Ember.EnumerableUtils.forEach;
+var indexOf = Ember.EnumerableUtils.indexOf;
+var map = Ember.EnumerableUtils.map;
+var resolve = Ember.RSVP.resolve;
+var copy = Ember.copy;
+
+// Implementors Note:
+//
+// The variables in this file are consistently named according to the following
+// scheme:
+//
+// * +id+ means an identifier managed by an external source, provided inside
+// the data provided by that source. These are always coerced to be strings
+// before being used internally.
+// * +clientId+ means a transient numerical identifier generated at runtime by
+// the data store. It is important primarily because newly created objects may
+// not yet have an externally generated id.
+// * +reference+ means a record reference object, which holds metadata about a
+// record, even if it has not yet been fully materialized.
+// * +type+ means a subclass of DS.Model.
+
+// Used by the store to normalize IDs entering the store. Despite the fact
+// that developers may provide IDs as numbers (e.g., `store.find(Person, 1)`),
+// it is important that internally we use strings, since IDs may be serialized
+// and lose type information. For example, Ember's router may put a record's
+// ID into the URL, and if we later try to deserialize that URL and find the
+// corresponding record, we will not know if it is a string or a number.
+var coerceId = function(id) {
+ return id == null ? null : id+'';
+};
+
+/**
+ The store contains all of the data for records loaded from the server.
+ It is also responsible for creating instances of `DS.Model` that wrap
+ the individual data for a record, so that they can be bound to in your
+ Handlebars templates.
+
+ Define your application's store like this:
+
+ ```javascript
+ MyApp.Store = DS.Store.extend();
+ ```
+
+ Most Ember.js applications will only have a single `DS.Store` that is
+ automatically created by their `Ember.Application`.
+
+ You can retrieve models from the store in several ways. To retrieve a record
+ for a specific id, use `DS.Store`'s `find()` method:
+
+ ```javascript
+ var person = store.find('person', 123);
+ ```
+
+ If your application has multiple `DS.Store` instances (an unusual case), you can
+ specify which store should be used:
+
+ ```javascript
+ var person = store.find(App.Person, 123);
+ ```
+
+ By default, the store will talk to your backend using a standard
+ REST mechanism. You can customize how the store talks to your
+ backend by specifying a custom adapter:
+
+ ```javascript
+ MyApp.store = DS.Store.create({
+ adapter: 'MyApp.CustomAdapter'
+ });
+ ```
+
+ You can learn more about writing a custom adapter by reading the `DS.Adapter`
+ documentation.
+
+ @class Store
+ @namespace DS
+ @extends Ember.Object
+*/
+DS.Store = Ember.Object.extend({
+
+ /**
+ @method init
+ @private
+ */
+ init: function() {
+ // internal bookkeeping; not observable
+ this.typeMaps = {};
+ this.recordArrayManager = DS.RecordArrayManager.create({
+ store: this
+ });
+ this._relationshipChanges = {};
+ this._pendingSave = [];
+ },
+
+ /**
+ The adapter to use to communicate to a backend server or other persistence layer.
+
+ This can be specified as an instance, class, or string.
+
+ If you want to specify `App.CustomAdapter` as a string, do:
+
+ ```js
+ adapter: 'custom'
+ ```
+
+ @property adapter
+ @default DS.RESTAdapter
+ @type {DS.Adapter|String}
+ */
+ adapter: '-rest',
+
+ /**
+ Returns a JSON representation of the record using a custom
+ type-specific serializer, if one exists.
+
+ The available options are:
+
+ * `includeId`: `true` if the record's ID should be included in
+ the JSON representation
+
+ @method serialize
+ @private
+ @param {DS.Model} record the record to serialize
+ @param {Object} options an options hash
+ */
+ serialize: function(record, options) {
+ return this.serializerFor(record.constructor.typeKey).serialize(record, options);
+ },
+
+ /**
+ This property returns the adapter, after resolving a possible
+ string key.
+
+ If the supplied `adapter` was a class, or a String property
+ path resolved to a class, this property will instantiate the
+ class.
+
+ This property is cacheable, so the same instance of a specified
+ adapter class should be used for the lifetime of the store.
+
+ @property defaultAdapter
+ @private
+ @returns DS.Adapter
+ */
+ defaultAdapter: Ember.computed('adapter', function() {
+ var adapter = get(this, 'adapter');
+
+ Ember.assert('You tried to set `adapter` property to an instance of `DS.Adapter`, where it should be a name or a factory', !(adapter instanceof DS.Adapter));
+
+ if (typeof adapter === 'string') {
+ adapter = this.container.lookup('adapter:' + adapter) || this.container.lookup('adapter:application') || this.container.lookup('adapter:-rest');
+ }
+
+ if (DS.Adapter.detect(adapter)) {
+ adapter = adapter.create({ container: this.container });
+ }
+
+ return adapter;
+ }),
+
+ // .....................
+ // . CREATE NEW RECORD .
+ // .....................
+
+ /**
+ Create a new record in the current store. The properties passed
+ to this method are set on the newly created record.
+
+ To create a new instance of `App.Post`:
+
+ ```js
+ store.createRecord('post', {
+ title: "Rails is omakase"
+ });
+ ```
+
+ @method createRecord
+ @param {String} type
+ @param {Object} properties a hash of properties to set on the
+ newly created record.
+ @returns {DS.Model} record
+ */
+ createRecord: function(type, properties) {
+ type = this.modelFor(type);
+
+ properties = copy(properties) || {};
+
+ // If the passed properties do not include a primary key,
+ // give the adapter an opportunity to generate one. Typically,
+ // client-side ID generators will use something like uuid.js
+ // to avoid conflicts.
+
+ if (isNone(properties.id)) {
+ properties.id = this._generateId(type);
+ }
+
+ // Coerce ID to a string
+ properties.id = coerceId(properties.id);
+
+ var record = this.buildRecord(type, properties.id);
+
+ // Move the record out of its initial `empty` state into
+ // the `loaded` state.
+ record.loadedData();
+
+ // Set the properties specified on the record.
+ record.setProperties(properties);
+
+ return record;
+ },
+
+ /**
+ If possible, this method asks the adapter to generate an ID for
+ a newly created record.
+
+ @method _generateId
+ @private
+ @param {String} type
+ @returns {String} if the adapter can generate one, an ID
+ */
+ _generateId: function(type) {
+ var adapter = this.adapterFor(type);
+
+ if (adapter && adapter.generateIdForRecord) {
+ return adapter.generateIdForRecord(this);
+ }
+
+ return null;
+ },
+
+ // .................
+ // . DELETE RECORD .
+ // .................
+
+ /**
+ For symmetry, a record can be deleted via the store.
+
+ Example
+
+ ```javascript
+ var post = store.createRecord('post', {
+ title: "Rails is omakase"
+ });
+
+ store.deleteRecord(post);
+ ```
+
+ @method deleteRecord
+ @param {DS.Model} record
+ */
+ deleteRecord: function(record) {
+ record.deleteRecord();
+ },
+
+ /**
+ For symmetry, a record can be unloaded via the store. Only
+ non-dirty records can be unloaded.
+
+ Example
+
+ ```javascript
+ store.find('post', 1).then(function(post) {
+ store.unloadRecord(post);
+ });
+ ```
+
+ @method unloadRecord
+ @param {DS.Model} record
+ */
+ unloadRecord: function(record) {
+ record.unloadRecord();
+ },
+
+ // ................
+ // . FIND RECORDS .
+ // ................
+
+ /**
+ This is the main entry point into finding records. The first parameter to
+ this method is the model's name as a string.
+
+ ---
+
+ To find a record by ID, pass the `id` as the second parameter:
+
+ ```javascript
+ store.find('person', 1);
+ ```
+
+ The `find` method will always return a **promise** that will be resolved
+ with the record. If the record was already in the store, the promise will
+ be resolved immediately. Otherwise, the store will ask the adapter's `find`
+ method to find the necessary data.
+
+ The `find` method will always resolve its promise with the same object for
+ a given type and `id`.
+
+ ---
+
+ To find all records for a type, call `find` with no additional parameters:
+
+ ```javascript
+ store.find('person');
+ ```
+
+ This will ask the adapter's `findAll` method to find the records for the
+ given type, and return a promise that will be resolved once the server
+ returns the values.
+
+ ---
+
+ To find a record by a query, call `find` with a hash as the second
+ parameter:
+
+ ```javascript
+ store.find(App.Person, { page: 1 });
+ ```
+
+ This will ask the adapter's `findQuery` method to find the records for
+ the query, and return a promise that will be resolved once the server
+ responds.
+
+ @method find
+ @param {String or subclass of DS.Model} type
+ @param {Object|String|Integer|null} id
+ @return {Promise} promise
+ */
+ find: function(type, id) {
+ if (id === undefined) {
+ return this.findAll(type);
+ }
+
+ // We are passed a query instead of an id.
+ if (Ember.typeOf(id) === 'object') {
+ return this.findQuery(type, id);
+ }
+
+ return this.findById(type, coerceId(id));
+ },
+
+ /**
+ This method returns a record for a given type and id combination.
+
+ @method findById
+ @private
+ @param {String or subclass of DS.Model} type
+ @param {String|Integer} id
+ @return {Promise} promise
+ */
+ findById: function(type, id) {
+ type = this.modelFor(type);
+
+ var record = this.recordForId(type, id);
+
+ var promise = this.fetchRecord(record) || resolve(record, "DS: Store#findById " + type + " with id: " + id);
+ return promiseObject(promise);
+ },
+
+ /**
+ This method makes a series of requests to the adapter's `find` method
+ and returns a promise that resolves once they are all loaded.
+
+ @private
+ @method findByIds
+ @param {String} type
+ @param {Array} ids
+ @returns {Promise} promise
+ */
+ findByIds: function(type, ids) {
+ var store = this;
+ var promiseLabel = "DS: Store#findByIds " + type;
+ return promiseArray(Ember.RSVP.all(map(ids, function(id) {
+ return store.findById(type, id);
+ })).then(Ember.A, null, "DS: Store#findByIds of " + type + " complete"));
+ },
+
+ /**
+ This method is called by `findById` if it discovers that a particular
+ type/id pair hasn't been loaded yet to kick off a request to the
+ adapter.
+
+ @method fetchRecord
+ @private
+ @param {DS.Model} record
+ @returns {Promise} promise
+ */
+ fetchRecord: function(record) {
+ if (isNone(record)) { return null; }
+ if (record._loadingPromise) { return record._loadingPromise; }
+ if (!get(record, 'isEmpty')) { return null; }
+
+ var type = record.constructor,
+ id = get(record, 'id');
+
+ var adapter = this.adapterFor(type);
+
+ Ember.assert("You tried to find a record but you have no adapter (for " + type + ")", adapter);
+ Ember.assert("You tried to find a record but your adapter (for " + type + ") does not implement 'find'", adapter.find);
+
+ var promise = _find(adapter, this, type, id);
+ record.loadingData(promise);
+ return promise;
+ },
+
+ /**
+ Get a record by a given type and ID without triggering a fetch.
+
+ This method will synchronously return the record if it's available.
+ Otherwise, it will return null.
+
+ ```js
+ var post = store.getById('post', 1);
+ ```
+
+ @method getById
+ @param {String or subclass of DS.Model} type
+ @param {String|Integer} id
+ @param {DS.Model} record
+ */
+ getById: function(type, id) {
+ if (this.hasRecordForId(type, id)) {
+ return this.recordForId(type, id);
+ } else {
+ return null;
+ }
+ },
+
+ /**
+ This method is called by the record's `reload` method.
+
+ This method calls the adapter's `find` method, which returns a promise. When
+ **that** promise resolves, `reloadRecord` will resolve the promise returned
+ by the record's `reload`.
+
+ @method reloadRecord
+ @private
+ @param {DS.Model} record
+ @return {Promise} promise
+ */
+ reloadRecord: function(record) {
+ var type = record.constructor,
+ adapter = this.adapterFor(type),
+ id = get(record, 'id');
+
+ Ember.assert("You cannot reload a record without an ID", id);
+ Ember.assert("You tried to reload a record but you have no adapter (for " + type + ")", adapter);
+ Ember.assert("You tried to reload a record but your adapter does not implement `find`", adapter.find);
+
+ return _find(adapter, this, type, id);
+ },
+
+ /**
+ This method takes a list of records, groups the records by type,
+ converts the records into IDs, and then invokes the adapter's `findMany`
+ method.
+
+ The records are grouped by type to invoke `findMany` on adapters
+ for each unique type in records.
+
+ It is used both by a brand new relationship (via the `findMany`
+ method) or when the data underlying an existing relationship
+ changes.
+
+ @method fetchMany
+ @private
+ @param {Array} records
+ @param {DS.Model} owner
+ @param {Resolver} resolver
+ */
+ fetchMany: function(records, owner, resolver) {
+ if (!records.length) { return; }
+
+ // Group By Type
+ var recordsByTypeMap = Ember.MapWithDefault.create({
+ defaultValue: function() { return Ember.A(); }
+ });
+
+ forEach(records, function(record) {
+ recordsByTypeMap.get(record.constructor).push(record);
+ });
+
+ forEach(recordsByTypeMap, function(type, records) {
+ var ids = records.mapProperty('id'),
+ adapter = this.adapterFor(type);
+
+ Ember.assert("You tried to load many records but you have no adapter (for " + type + ")", adapter);
+ Ember.assert("You tried to load many records but your adapter does not implement `findMany`", adapter.findMany);
+
+ resolver.resolve(_findMany(adapter, this, type, ids, owner));
+ }, this);
+ },
+
+ /**
+ Returns true if a record for a given type and ID is already loaded.
+
+ @method hasRecordForId
+ @param {String or subclass of DS.Model} type
+ @param {String|Integer} id
+ @returns {Boolean}
+ */
+ hasRecordForId: function(type, id) {
+ id = coerceId(id);
+ type = this.modelFor(type);
+ return !!this.typeMapFor(type).idToRecord[id];
+ },
+
+ /**
+ Returns id record for a given type and ID. If one isn't already loaded,
+ it builds a new record and leaves it in the `empty` state.
+
+ @method recordForId
+ @private
+ @param {String or subclass of DS.Model} type
+ @param {String|Integer} id
+ @returns {DS.Model} record
+ */
+ recordForId: function(type, id) {
+ type = this.modelFor(type);
+
+ id = coerceId(id);
+
+ var record = this.typeMapFor(type).idToRecord[id];
+
+ if (!record) {
+ record = this.buildRecord(type, id);
+ }
+
+ return record;
+ },
+
+ /**
+ @method findMany
+ @private
+ @param {DS.Model} owner
+ @param {Array} records
+ @param {String or subclass of DS.Model} type
+ @param {Resolver} resolver
+ @return {DS.ManyArray} records
+ */
+ findMany: function(owner, records, type, resolver) {
+ type = this.modelFor(type);
+
+ records = Ember.A(records);
+
+ var unloadedRecords = records.filterProperty('isEmpty', true),
+ manyArray = this.recordArrayManager.createManyArray(type, records);
+
+ forEach(unloadedRecords, function(record) {
+ record.loadingData();
+ });
+
+ manyArray.loadingRecordsCount = unloadedRecords.length;
+
+ if (unloadedRecords.length) {
+ forEach(unloadedRecords, function(record) {
+ this.recordArrayManager.registerWaitingRecordArray(record, manyArray);
+ }, this);
+
+ this.fetchMany(unloadedRecords, owner, resolver);
+ } else {
+ if (resolver) { resolver.resolve(); }
+ manyArray.set('isLoaded', true);
+ Ember.run.once(manyArray, 'trigger', 'didLoad');
+ }
+
+ return manyArray;
+ },
+
+ /**
+ If a relationship was originally populated by the adapter as a link
+ (as opposed to a list of IDs), this method is called when the
+ relationship is fetched.
+
+ The link (which is usually a URL) is passed through unchanged, so the
+ adapter can make whatever request it wants.
+
+ The usual use-case is for the server to register a URL as a link, and
+ then use that URL in the future to make a request for the relationship.
+
+ @method findHasMany
+ @private
+ @param {DS.Model} owner
+ @param {any} link
+ @param {String or subclass of DS.Model} type
+ @param {Resolver} resolver
+ @return {DS.ManyArray}
+ */
+ findHasMany: function(owner, link, relationship, resolver) {
+ var adapter = this.adapterFor(owner.constructor);
+
+ Ember.assert("You tried to load a hasMany relationship but you have no adapter (for " + owner.constructor + ")", adapter);
+ Ember.assert("You tried to load a hasMany relationship from a specified `link` in the original payload but your adapter does not implement `findHasMany`", adapter.findHasMany);
+
+ var records = this.recordArrayManager.createManyArray(relationship.type, Ember.A([]));
+ resolver.resolve(_findHasMany(adapter, this, owner, link, relationship));
+ return records;
+ },
+
+ /**
+ @method findBelongsTo
+ @private
+ @param {DS.Model} owner
+ @param {any} link
+ @param {Relationship} relationship
+ @param {Resolver} resolver
+ */
+ findBelongsTo: function(owner, link, relationship, resolver) {
+ var adapter = this.adapterFor(owner.constructor);
+
+ Ember.assert("You tried to load a belongsTo relationship but you have no adapter (for " + owner.constructor + ")", adapter);
+ Ember.assert("You tried to load a belongsTo relationship from a specified `link` in the original payload but your adapter does not implement `findBelongsTo`", adapter.findBelongsTo);
+
+ resolver.resolve(_findBelongsTo(adapter, this, owner, link, relationship));
+ },
+
+ /**
+ This method delegates a query to the adapter. This is the one place where
+ adapter-level semantics are exposed to the application.
+
+ Exposing queries this way seems preferable to creating an abstract query
+ language for all server-side queries, and then require all adapters to
+ implement them.
+
+ This method returns a promise, which is resolved with a `RecordArray`
+ once the server returns.
+
+ @method findQuery
+ @private
+ @param {String or subclass of DS.Model} type
+ @param {any} query an opaque query to be used by the adapter
+ @return {Promise} promise
+ */
+ findQuery: function(type, query) {
+ type = this.modelFor(type);
+
+ var array = this.recordArrayManager
+ .createAdapterPopulatedRecordArray(type, query);
+
+ var adapter = this.adapterFor(type),
+ promiseLabel = "DS: Store#findQuery " + type,
+ resolver = Ember.RSVP.defer(promiseLabel);
+
+ Ember.assert("You tried to load a query but you have no adapter (for " + type + ")", adapter);
+ Ember.assert("You tried to load a query but your adapter does not implement `findQuery`", adapter.findQuery);
+
+ resolver.resolve(_findQuery(adapter, this, type, query, array));
+
+ return promiseArray(resolver.promise);
+ },
+
+ /**
+ This method returns an array of all records adapter can find.
+ It triggers the adapter's `findAll` method to give it an opportunity to populate
+ the array with records of that type.
+
+ @method findAll
+ @private
+ @param {String or subclass of DS.Model} type
+ @return {DS.AdapterPopulatedRecordArray}
+ */
+ findAll: function(type) {
+ type = this.modelFor(type);
+
+ return this.fetchAll(type, this.all(type));
+ },
+
+ /**
+ @method fetchAll
+ @private
+ @param {DS.Model} type
+ @param {DS.RecordArray} array
+ @returns {Promise} promise
+ */
+ fetchAll: function(type, array) {
+ var adapter = this.adapterFor(type),
+ sinceToken = this.typeMapFor(type).metadata.since;
+
+ set(array, 'isUpdating', true);
+
+ Ember.assert("You tried to load all records but you have no adapter (for " + type + ")", adapter);
+ Ember.assert("You tried to load all records but your adapter does not implement `findAll`", adapter.findAll);
+
+ return promiseArray(_findAll(adapter, this, type, sinceToken));
+ },
+
+ /**
+ @method didUpdateAll
+ @param {DS.Model} type
+ */
+ didUpdateAll: function(type) {
+ var findAllCache = this.typeMapFor(type).findAllCache;
+ set(findAllCache, 'isUpdating', false);
+ },
+
+ /**
+ This method returns a filtered array that contains all of the known records
+ for a given type.
+
+ Note that because it's just a filter, it will have any locally
+ created records of the type.
+
+ Also note that multiple calls to `all` for a given type will always
+ return the same RecordArray.
+
+ Example
+
+ ```javascript
+ var local_posts = store.all(App.Post);
+ ```
+
+ @method all
+ @param {String or subclass of DS.Model} type
+ @return {DS.RecordArray}
+ */
+ all: function(type) {
+ type = this.modelFor(type);
+
+ var typeMap = this.typeMapFor(type),
+ findAllCache = typeMap.findAllCache;
+
+ if (findAllCache) { return findAllCache; }
+
+ var array = this.recordArrayManager.createRecordArray(type);
+
+ typeMap.findAllCache = array;
+ return array;
+ },
+
+
+ /**
+ This method unloads all of the known records for a given type.
+
+ ```javascript
+ store.unloadAll(App.Post);
+ ```
+
+ @method unloadAll
+ @param {String or subclass of DS.Model} type
+ */
+ unloadAll: function(type) {
+ type = this.modelFor(type);
+
+ var typeMap = this.typeMapFor(type),
+ records = typeMap.records.splice(0), record;
+
+ while(record = records.pop()) {
+ record.unloadRecord();
+ }
+
+ typeMap.findAllCache = null;
+ },
+
+ /**
+ Takes a type and filter function, and returns a live RecordArray that
+ remains up to date as new records are loaded into the store or created
+ locally.
+
+ The callback function takes a materialized record, and returns true
+ if the record should be included in the filter and false if it should
+ not.
+
+ The filter function is called once on all records for the type when
+ it is created, and then once on each newly loaded or created record.
+
+ If any of a record's properties change, or if it changes state, the
+ filter function will be invoked again to determine whether it should
+ still be in the array.
+
+ Optionally you can pass a query which will be triggered at first. The
+ results returned by the server could then appear in the filter if they
+ match the filter function.
+
+ Example
+
+ ```javascript
+ store.filter(App.Post, {unread: true}, function(post) {
+ return post.get('unread');
+ }).then(function(unreadPosts) {
+ unreadPosts.get('length'); // 5
+ var unreadPost = unreadPosts.objectAt(0);
+ unreadPost.set('unread', false);
+ unreadPosts.get('length'); // 4
+ });
+ ```
+
+ @method filter
+ @param {String or subclass of DS.Model} type
+ @param {Object} query optional query
+ @param {Function} filter
+ @return {DS.PromiseArray}
+ */
+ filter: function(type, query, filter) {
+ var promise;
+
+ // allow an optional server query
+ if (arguments.length === 3) {
+ promise = this.findQuery(type, query);
+ } else if (arguments.length === 2) {
+ filter = query;
+ }
+
+ type = this.modelFor(type);
+
+ var array = this.recordArrayManager
+ .createFilteredRecordArray(type, filter);
+ promise = promise || resolve(array);
+
+ return promiseArray(promise.then(function() {
+ return array;
+ }, null, "DS: Store#filter of " + type));
+ },
+
+ /**
+ This method returns if a certain record is already loaded
+ in the store. Use this function to know beforehand if a find()
+ will result in a request or that it will be a cache hit.
+
+ Example
+
+ ```javascript
+ store.recordIsLoaded(App.Post, 1); // false
+ store.find(App.Post, 1).then(function() {
+ store.recordIsLoaded(App.Post, 1); // true
+ });
+ ```
+
+ @method recordIsLoaded
+ @param {String or subclass of DS.Model} type
+ @param {string} id
+ @return {boolean}
+ */
+ recordIsLoaded: function(type, id) {
+ if (!this.hasRecordForId(type, id)) { return false; }
+ return !get(this.recordForId(type, id), 'isEmpty');
+ },
+
+ /**
+ This method returns the metadata for a specific type.
+
+ @method metadataFor
+ @param {String or subclass of DS.Model} type
+ @return {object}
+ */
+ metadataFor: function(type) {
+ type = this.modelFor(type);
+ return this.typeMapFor(type).metadata;
+ },
+
+ // ............
+ // . UPDATING .
+ // ............
+
+ /**
+ If the adapter updates attributes or acknowledges creation
+ or deletion, the record will notify the store to update its
+ membership in any filters.
+ To avoid thrashing, this method is invoked only once per
+
+ run loop per record.
+
+ @method dataWasUpdated
+ @private
+ @param {Class} type
+ @param {DS.Model} record
+ */
+ dataWasUpdated: function(type, record) {
+ this.recordArrayManager.recordDidChange(record);
+ },
+
+ // ..............
+ // . PERSISTING .
+ // ..............
+
+ /**
+ This method is called by `record.save`, and gets passed a
+ resolver for the promise that `record.save` returns.
+
+ It schedules saving to happen at the end of the run loop.
+
+ @method scheduleSave
+ @private
+ @param {DS.Model} record
+ @param {Resolver} resolver
+ */
+ scheduleSave: function(record, resolver) {
+ record.adapterWillCommit();
+ this._pendingSave.push([record, resolver]);
+ once(this, 'flushPendingSave');
+ },
+
+ /**
+ This method is called at the end of the run loop, and
+ flushes any records passed into `scheduleSave`
+
+ @method flushPendingSave
+ @private
+ */
+ flushPendingSave: function() {
+ var pending = this._pendingSave.slice();
+ this._pendingSave = [];
+
+ forEach(pending, function(tuple) {
+ var record = tuple[0], resolver = tuple[1],
+ adapter = this.adapterFor(record.constructor),
+ operation;
+
+ if (get(record, 'isNew')) {
+ operation = 'createRecord';
+ } else if (get(record, 'isDeleted')) {
+ operation = 'deleteRecord';
+ } else {
+ operation = 'updateRecord';
+ }
+
+ resolver.resolve(_commit(adapter, this, operation, record));
+ }, this);
+ },
+
+ /**
+ This method is called once the promise returned by an
+ adapter's `createRecord`, `updateRecord` or `deleteRecord`
+ is resolved.
+
+ If the data provides a server-generated ID, it will
+ update the record and the store's indexes.
+
+ @method didSaveRecord
+ @private
+ @param {DS.Model} record the in-flight record
+ @param {Object} data optional data (see above)
+ */
+ didSaveRecord: function(record, data) {
+ if (data) {
+ // normalize relationship IDs into records
+ data = normalizeRelationships(this, record.constructor, data, record);
+
+ this.updateId(record, data);
+ }
+
+ record.adapterDidCommit(data);
+ },
+
+ /**
+ This method is called once the promise returned by an
+ adapter's `createRecord`, `updateRecord` or `deleteRecord`
+ is rejected with a `DS.InvalidError`.
+
+ @method recordWasInvalid
+ @private
+ @param {DS.Model} record
+ @param {Object} errors
+ */
+ recordWasInvalid: function(record, errors) {
+ record.adapterDidInvalidate(errors);
+ },
+
+ /**
+ This method is called once the promise returned by an
+ adapter's `createRecord`, `updateRecord` or `deleteRecord`
+ is rejected (with anything other than a `DS.InvalidError`).
+
+ @method recordWasError
+ @private
+ @param {DS.Model} record
+ */
+ recordWasError: function(record) {
+ record.adapterDidError();
+ },
+
+ /**
+ When an adapter's `createRecord`, `updateRecord` or `deleteRecord`
+ resolves with data, this method extracts the ID from the supplied
+ data.
+
+ @method updateId
+ @private
+ @param {DS.Model} record
+ @param {Object} data
+ */
+ updateId: function(record, data) {
+ var oldId = get(record, 'id'),
+ id = coerceId(data.id);
+
+ Ember.assert("An adapter cannot assign a new id to a record that already has an id. " + record + " had id: " + oldId + " and you tried to update it with " + id + ". This likely happened because your server returned data in response to a find or update that had a different id than the one you sent.", oldId === null || id === oldId);
+
+ this.typeMapFor(record.constructor).idToRecord[id] = record;
+
+ set(record, 'id', id);
+ },
+
+ /**
+ Returns a map of IDs to client IDs for a given type.
+
+ @method typeMapFor
+ @private
+ @param type
+ @return {Object} typeMap
+ */
+ typeMapFor: function(type) {
+ var typeMaps = get(this, 'typeMaps'),
+ guid = Ember.guidFor(type),
+ typeMap;
+
+ typeMap = typeMaps[guid];
+
+ if (typeMap) { return typeMap; }
+
+ typeMap = {
+ idToRecord: {},
+ records: [],
+ metadata: {}
+ };
+
+ typeMaps[guid] = typeMap;
+
+ return typeMap;
+ },
+
+ // ................
+ // . LOADING DATA .
+ // ................
+
+ /**
+ This internal method is used by `push`.
+
+ @method _load
+ @private
+ @param {String or subclass of DS.Model} type
+ @param {Object} data
+ @param {Boolean} partial the data should be merged into
+ the existing data, not replace it.
+ */
+ _load: function(type, data, partial) {
+ var id = coerceId(data.id),
+ record = this.recordForId(type, id);
+
+ record.setupData(data, partial);
+ this.recordArrayManager.recordDidChange(record);
+
+ return record;
+ },
+
+ /**
+ Returns a model class for a particular key. Used by
+ methods that take a type key (like `find`, `createRecord`,
+ etc.)
+
+ @method modelFor
+ @param {String or subclass of DS.Model} key
+ @returns {subclass of DS.Model}
+ */
+ modelFor: function(key) {
+ var factory;
+
+
+ if (typeof key === 'string') {
+ var normalizedKey = this.container.normalize('model:' + key);
+
+ factory = this.container.lookupFactory(normalizedKey);
+ if (!factory) { throw new Ember.Error("No model was found for '" + key + "'"); }
+ factory.typeKey = normalizedKey.split(':', 2)[1];
+ } else {
+ // A factory already supplied.
+ factory = key;
+ }
+
+ factory.store = this;
+ return factory;
+ },
+
+ /**
+ Push some data for a given type into the store.
+
+ This method expects normalized data:
+
+ * The ID is a key named `id` (an ID is mandatory)
+ * The names of attributes are the ones you used in
+ your model's `DS.attr`s.
+ * Your relationships must be:
+ * represented as IDs or Arrays of IDs
+ * represented as model instances
+ * represented as URLs, under the `links` key
+
+ For this model:
+
+ ```js
+ App.Person = DS.Model.extend({
+ firstName: DS.attr(),
+ lastName: DS.attr(),
+
+ children: DS.hasMany('person')
+ });
+ ```
+
+ To represent the children as IDs:
+
+ ```js
+ {
+ id: 1,
+ firstName: "Tom",
+ lastName: "Dale",
+ children: [1, 2, 3]
+ }
+ ```
+
+ To represent the children relationship as a URL:
+
+ ```js
+ {
+ id: 1,
+ firstName: "Tom",
+ lastName: "Dale",
+ links: {
+ children: "/people/1/children"
+ }
+ }
+ ```
+
+ If you're streaming data or implementing an adapter,
+ make sure that you have converted the incoming data
+ into this form.
+
+ This method can be used both to push in brand new
+ records, as well as to update existing records.
+
+ @method push
+ @param {String or subclass of DS.Model} type
+ @param {Object} data
+ @returns {DS.Model} the record that was created or
+ updated.
+ */
+ push: function(type, data, _partial) {
+ // _partial is an internal param used by `update`.
+ // If passed, it means that the data should be
+ // merged into the existing data, not replace it.
+
+ Ember.assert("You must include an `id` in a hash passed to `push`", data.id != null);
+
+ type = this.modelFor(type);
+
+ // normalize relationship IDs into records
+ data = normalizeRelationships(this, type, data);
+
+ this._load(type, data, _partial);
+
+ return this.recordForId(type, data.id);
+ },
+
+ /**
+ Push some raw data into the store.
+
+ The data will be automatically deserialized using the
+ serializer for the `type` param.
+
+ This method can be used both to push in brand new
+ records, as well as to update existing records.
+
+ You can push in more than one type of object at once.
+ All objects should be in the format expected by the
+ serializer.
+
+ ```js
+ App.ApplicationSerializer = DS.ActiveModelSerializer;
+
+ var pushData = {
+ posts: [
+ {id: 1, post_title: "Great post", comment_ids: [2]}
+ ],
+ comments: [
+ {id: 2, comment_body: "Insightful comment"}
+ ]
+ }
+
+ store.pushPayload('post', pushData);
+ ```
+
+ @method pushPayload
+ @param {String} type
+ @param {Object} payload
+ @return {DS.Model} the record that was created or updated.
+ */
+ pushPayload: function (type, payload) {
+ var serializer;
+ if (!payload) {
+ payload = type;
+ serializer = defaultSerializer(this.container);
+ Ember.assert("You cannot use `store#pushPayload` without a type unless your default serializer defines `pushPayload`", serializer.pushPayload);
+ } else {
+ serializer = this.serializerFor(type);
+ }
+ serializer.pushPayload(this, payload);
+ },
+
+ update: function(type, data) {
+ Ember.assert("You must include an `id` in a hash passed to `update`", data.id != null);
+
+ return this.push(type, data, true);
+ },
+
+ /**
+ If you have an Array of normalized data to push,
+ you can call `pushMany` with the Array, and it will
+ call `push` repeatedly for you.
+
+ @method pushMany
+ @param {String or subclass of DS.Model} type
+ @param {Array} datas
+ @return {Array}
+ */
+ pushMany: function(type, datas) {
+ return map(datas, function(data) {
+ return this.push(type, data);
+ }, this);
+ },
+
+ /**
+ If you have some metadata to set for a type
+ you can call `metaForType`.
+
+ @method metaForType
+ @param {String or subclass of DS.Model} type
+ @param {Object} metadata
+ */
+ metaForType: function(type, metadata) {
+ type = this.modelFor(type);
+
+ Ember.merge(this.typeMapFor(type).metadata, metadata);
+ },
+
+ /**
+ Build a brand new record for a given type, ID, and
+ initial data.
+
+ @method buildRecord
+ @private
+ @param {subclass of DS.Model} type
+ @param {String} id
+ @param {Object} data
+ @returns {DS.Model} record
+ */
+ buildRecord: function(type, id, data) {
+ var typeMap = this.typeMapFor(type),
+ idToRecord = typeMap.idToRecord;
+
+ Ember.assert('The id ' + id + ' has already been used with another record of type ' + type.toString() + '.', !id || !idToRecord[id]);
+
+ // lookupFactory should really return an object that creates
+ // instances with the injections applied
+ var record = type._create({
+ id: id,
+ store: this,
+ container: this.container
+ });
+
+ if (data) {
+ record.setupData(data);
+ }
+
+ // if we're creating an item, this process will be done
+ // later, once the object has been persisted.
+ if (id) {
+ idToRecord[id] = record;
+ }
+
+ typeMap.records.push(record);
+
+ return record;
+ },
+
+ // ...............
+ // . DESTRUCTION .
+ // ...............
+
+ /**
+ When a record is destroyed, this un-indexes it and
+ removes it from any record arrays so it can be GCed.
+
+ @method dematerializeRecord
+ @private
+ @param {DS.Model} record
+ */
+ dematerializeRecord: function(record) {
+ var type = record.constructor,
+ typeMap = this.typeMapFor(type),
+ id = get(record, 'id');
+
+ record.updateRecordArrays();
+
+ if (id) {
+ delete typeMap.idToRecord[id];
+ }
+
+ var loc = indexOf(typeMap.records, record);
+ typeMap.records.splice(loc, 1);
+ },
+
+ // ........................
+ // . RELATIONSHIP CHANGES .
+ // ........................
+
+ addRelationshipChangeFor: function(childRecord, childKey, parentRecord, parentKey, change) {
+ var clientId = childRecord.clientId,
+ parentClientId = parentRecord ? parentRecord : parentRecord;
+ var key = childKey + parentKey;
+ var changes = this._relationshipChanges;
+ if (!(clientId in changes)) {
+ changes[clientId] = {};
+ }
+ if (!(parentClientId in changes[clientId])) {
+ changes[clientId][parentClientId] = {};
+ }
+ if (!(key in changes[clientId][parentClientId])) {
+ changes[clientId][parentClientId][key] = {};
+ }
+ changes[clientId][parentClientId][key][change.changeType] = change;
+ },
+
+ removeRelationshipChangeFor: function(clientRecord, childKey, parentRecord, parentKey, type) {
+ var clientId = clientRecord.clientId,
+ parentClientId = parentRecord ? parentRecord.clientId : parentRecord;
+ var changes = this._relationshipChanges;
+ var key = childKey + parentKey;
+ if (!(clientId in changes) || !(parentClientId in changes[clientId]) || !(key in changes[clientId][parentClientId])){
+ return;
+ }
+ delete changes[clientId][parentClientId][key][type];
+ },
+
+ relationshipChangePairsFor: function(record){
+ var toReturn = [];
+
+ if( !record ) { return toReturn; }
+
+ //TODO(Igor) What about the other side
+ var changesObject = this._relationshipChanges[record.clientId];
+ for (var objKey in changesObject){
+ if(changesObject.hasOwnProperty(objKey)){
+ for (var changeKey in changesObject[objKey]){
+ if(changesObject[objKey].hasOwnProperty(changeKey)){
+ toReturn.push(changesObject[objKey][changeKey]);
+ }
+ }
+ }
+ }
+ return toReturn;
+ },
+
+ // ......................
+ // . PER-TYPE ADAPTERS
+ // ......................
+
+ /**
+ Returns the adapter for a given type.
+
+ @method adapterFor
+ @private
+ @param {subclass of DS.Model} type
+ @returns DS.Adapter
+ */
+ adapterFor: function(type) {
+ var container = this.container, adapter;
+
+ if (container) {
+ adapter = container.lookup('adapter:' + type.typeKey) || container.lookup('adapter:application');
+ }
+
+ return adapter || get(this, 'defaultAdapter');
+ },
+
+ // ..............................
+ // . RECORD CHANGE NOTIFICATION .
+ // ..............................
+
+ /**
+ Returns an instance of the serializer for a given type. For
+ example, `serializerFor('person')` will return an instance of
+ `App.PersonSerializer`.
+
+ If no `App.PersonSerializer` is found, this method will look
+ for an `App.ApplicationSerializer` (the default serializer for
+ your entire application).
+
+ If no `App.ApplicationSerializer` is found, it will fall back
+ to an instance of `DS.JSONSerializer`.
+
+ @method serializerFor
+ @private
+ @param {String} type the record to serialize
+ @return {DS.Serializer}
+ */
+ serializerFor: function(type) {
+ type = this.modelFor(type);
+ var adapter = this.adapterFor(type);
+
+ return serializerFor(this.container, type.typeKey, adapter && adapter.defaultSerializer);
+ }
+});
+
+function normalizeRelationships(store, type, data, record) {
+ type.eachRelationship(function(key, relationship) {
+ // A link (usually a URL) was already provided in
+ // normalized form
+ if (data.links && data.links[key]) {
+ if (record && relationship.options.async) { record._relationships[key] = null; }
+ return;
+ }
+
+ var kind = relationship.kind,
+ value = data[key];
+
+ if (value == null) { return; }
+
+ if (kind === 'belongsTo') {
+ deserializeRecordId(store, data, key, relationship, value);
+ } else if (kind === 'hasMany') {
+ deserializeRecordIds(store, data, key, relationship, value);
+ addUnsavedRecords(record, key, value);
+ }
+ });
+
+ return data;
+}
+
+function deserializeRecordId(store, data, key, relationship, id) {
+ if (isNone(id) || id instanceof DS.Model) {
+ return;
+ }
+
+ var type;
+
+ if (typeof id === 'number' || typeof id === 'string') {
+ type = typeFor(relationship, key, data);
+ data[key] = store.recordForId(type, id);
+ } else if (typeof id === 'object') {
+ // polymorphic
+ data[key] = store.recordForId(id.type, id.id);
+ }
+}
+
+function typeFor(relationship, key, data) {
+ if (relationship.options.polymorphic) {
+ return data[key + "Type"];
+ } else {
+ return relationship.type;
+ }
+}
+
+function deserializeRecordIds(store, data, key, relationship, ids) {
+ for (var i=0, l=ids.length; i<l; i++) {
+ deserializeRecordId(store, ids, i, relationship, ids[i]);
+ }
+}
+
+// If there are any unsaved records that are in a hasMany they won't be
+// in the payload, so add them back in manually.
+function addUnsavedRecords(record, key, data) {
+ if(record) {
+ data.pushObjects(record.get(key).filterBy('isNew'));
+ }
+}
+
+// Delegation to the adapter and promise management
+/**
+ A `PromiseArray` is an object that acts like both an `Ember.Array`
+ and a promise. When the promise is resolved the the resulting value
+ will be set to the `PromiseArray`'s `content` property. This makes
+ it easy to create data bindings with the `PromiseArray` that will be
+ updated when the promise resolves.
+
+ For more information see the [Ember.PromiseProxyMixin
+ documentation](/api/classes/Ember.PromiseProxyMixin.html).
+
+ Example
+
+ ```javascript
+ var promiseArray = DS.PromiseArray.create({
+ promise: $.getJSON('/some/remote/data.json')
+ });
+
+ promiseArray.get('length'); // 0
+
+ promiseArray.then(function() {
+ promiseArray.get('length'); // 100
+ });
+ ```
+
+ @class PromiseArray
+ @namespace DS
+ @extends Ember.ArrayProxy
+ @uses Ember.PromiseProxyMixin
+*/
+DS.PromiseArray = Ember.ArrayProxy.extend(Ember.PromiseProxyMixin);
+/**
+ A `PromiseObject` is an object that acts like both an `Ember.Object`
+ and a promise. When the promise is resolved the the resulting value
+ will be set to the `PromiseObject`'s `content` property. This makes
+ it easy to create data bindings with the `PromiseObject` that will
+ be updated when the promise resolves.
+
+ For more information see the [Ember.PromiseProxyMixin
+ documentation](/api/classes/Ember.PromiseProxyMixin.html).
+
+ Example
+
+ ```javascript
+ var promiseObject = DS.PromiseObject.create({
+ promise: $.getJSON('/some/remote/data.json')
+ });
+
+ promiseObject.get('name'); // null
+
+ promiseObject.then(function() {
+ promiseObject.get('name'); // 'Tomster'
+ });
+ ```
+
+ @class PromiseObject
+ @namespace DS
+ @extends Ember.ObjectProxy
+ @uses Ember.PromiseProxyMixin
+*/
+DS.PromiseObject = Ember.ObjectProxy.extend(Ember.PromiseProxyMixin);
+
+function promiseObject(promise) {
+ return DS.PromiseObject.create({ promise: promise });
+}
+
+function promiseArray(promise) {
+ return DS.PromiseArray.create({ promise: promise });
+}
+
+function isThenable(object) {
+ return object && typeof object.then === 'function';
+}
+
+function serializerFor(container, type, defaultSerializer) {
+ return container.lookup('serializer:'+type) ||
+ container.lookup('serializer:application') ||
+ container.lookup('serializer:' + defaultSerializer) ||
+ container.lookup('serializer:-default');
+}
+
+function defaultSerializer(container) {
+ return container.lookup('serializer:application') ||
+ container.lookup('serializer:-default');
+}
+
+function serializerForAdapter(adapter, type) {
+ var serializer = adapter.serializer,
+ defaultSerializer = adapter.defaultSerializer,
+ container = adapter.container;
+
+ if (container && serializer === undefined) {
+ serializer = serializerFor(container, type.typeKey, defaultSerializer);
+ }
+
+ if (serializer === null || serializer === undefined) {
+ serializer = {
+ extract: function(store, type, payload) { return payload; }
+ };
+ }
+
+ return serializer;
+}
+
+function _find(adapter, store, type, id) {
+ var promise = adapter.find(store, type, id),
+ serializer = serializerForAdapter(adapter, type);
+
+ return resolve(promise, "DS: Handle Adapter#find of " + type + " with id: " + id).then(function(payload) {
+ Ember.assert("You made a request for a " + type.typeKey + " with id " + id + ", but the adapter's response did not have any data", payload);
+ payload = serializer.extract(store, type, payload, id, 'find');
+
+ return store.push(type, payload);
+ }, function(error) {
+ var record = store.getById(type, id);
+ record.notFound();
+ throw error;
+ }, "DS: Extract payload of '" + type + "'");
+}
+
+function _findMany(adapter, store, type, ids, owner) {
+ var promise = adapter.findMany(store, type, ids, owner),
+ serializer = serializerForAdapter(adapter, type);
+
+ return resolve(promise, "DS: Handle Adapter#findMany of " + type).then(function(payload) {
+ payload = serializer.extract(store, type, payload, null, 'findMany');
+
+ Ember.assert("The response from a findMany must be an Array, not " + Ember.inspect(payload), Ember.typeOf(payload) === 'array');
+
+ store.pushMany(type, payload);
+ }, null, "DS: Extract payload of " + type);
+}
+
+function _findHasMany(adapter, store, record, link, relationship) {
+ var promise = adapter.findHasMany(store, record, link, relationship),
+ serializer = serializerForAdapter(adapter, relationship.type);
+
+ return resolve(promise, "DS: Handle Adapter#findHasMany of " + record + " : " + relationship.type).then(function(payload) {
+ payload = serializer.extract(store, relationship.type, payload, null, 'findHasMany');
+
+ Ember.assert("The response from a findHasMany must be an Array, not " + Ember.inspect(payload), Ember.typeOf(payload) === 'array');
+
+ var records = store.pushMany(relationship.type, payload);
+ record.updateHasMany(relationship.key, records);
+ }, null, "DS: Extract payload of " + record + " : hasMany " + relationship.type);
+}
+
+function _findBelongsTo(adapter, store, record, link, relationship) {
+ var promise = adapter.findBelongsTo(store, record, link, relationship),
+ serializer = serializerForAdapter(adapter, relationship.type);
+
+ return resolve(promise, "DS: Handle Adapter#findBelongsTo of " + record + " : " + relationship.type).then(function(payload) {
+ payload = serializer.extract(store, relationship.type, payload, null, 'findBelongsTo');
+
+ var record = store.push(relationship.type, payload);
+ record.updateBelongsTo(relationship.key, record);
+ return record;
+ }, null, "DS: Extract payload of " + record + " : " + relationship.type);
+}
+
+function _findAll(adapter, store, type, sinceToken) {
+ var promise = adapter.findAll(store, type, sinceToken),
+ serializer = serializerForAdapter(adapter, type);
+
+ return resolve(promise, "DS: Handle Adapter#findAll of " + type).then(function(payload) {
+ payload = serializer.extract(store, type, payload, null, 'findAll');
+
+ Ember.assert("The response from a findAll must be an Array, not " + Ember.inspect(payload), Ember.typeOf(payload) === 'array');
+
+ store.pushMany(type, payload);
+ store.didUpdateAll(type);
+ return store.all(type);
+ }, null, "DS: Extract payload of findAll " + type);
+}
+
+function _findQuery(adapter, store, type, query, recordArray) {
+ var promise = adapter.findQuery(store, type, query, recordArray),
+ serializer = serializerForAdapter(adapter, type);
+
+ return resolve(promise, "DS: Handle Adapter#findQuery of " + type).then(function(payload) {
+ payload = serializer.extract(store, type, payload, null, 'findQuery');
+
+ Ember.assert("The response from a findQuery must be an Array, not " + Ember.inspect(payload), Ember.typeOf(payload) === 'array');
+
+ recordArray.load(payload);
+ return recordArray;
+ }, null, "DS: Extract payload of findQuery " + type);
+}
+
+function _commit(adapter, store, operation, record) {
+ var type = record.constructor,
+ promise = adapter[operation](store, type, record),
+ serializer = serializerForAdapter(adapter, type);
+
+ Ember.assert("Your adapter's '" + operation + "' method must return a promise, but it returned " + promise, isThenable(promise));
+
+ return promise.then(function(payload) {
+ if (payload) { payload = serializer.extract(store, type, payload, get(record, 'id'), operation); }
+ store.didSaveRecord(record, payload);
+ return record;
+ }, function(reason) {
+ if (reason instanceof DS.InvalidError) {
+ store.recordWasInvalid(record, reason.errors);
+ } else {
+ store.recordWasError(record, reason);
+ }
+
+ throw reason;
+ }, "DS: Extract and notify about " + operation + " completion of " + record);
+}
+
+})();
+
+
+
+(function() {
+/**
+ @module ember-data
+*/
+
+var get = Ember.get, set = Ember.set;
+/*
+ This file encapsulates the various states that a record can transition
+ through during its lifecycle.
+*/
+/**
+ ### State
+
+ Each record has a `currentState` property that explicitly tracks what
+ state a record is in at any given time. For instance, if a record is
+ newly created and has not yet been sent to the adapter to be saved,
+ it would be in the `root.loaded.created.uncommitted` state. If a
+ record has had local modifications made to it that are in the
+ process of being saved, the record would be in the
+ `root.loaded.updated.inFlight` state. (These state paths will be
+ explained in more detail below.)
+
+ Events are sent by the record or its store to the record's
+ `currentState` property. How the state reacts to these events is
+ dependent on which state it is in. In some states, certain events
+ will be invalid and will cause an exception to be raised.
+
+ States are hierarchical and every state is a substate of the
+ `RootState`. For example, a record can be in the
+ `root.deleted.uncommitted` state, then transition into the
+ `root.deleted.inFlight` state. If a child state does not implement
+ an event handler, the state manager will attempt to invoke the event
+ on all parent states until the root state is reached. The state
+ hierarchy of a record is described in terms of a path string. You
+ can determine a record's current state by getting the state's
+ `stateName` property:
+
+ ```javascript
+ record.get('currentState.stateName');
+ //=> "root.created.uncommitted"
+ ```
+
+ The hierarchy of valid states that ship with ember data looks like
+ this:
+
+ ```text
+ * root
+ * deleted
+ * saved
+ * uncommitted
+ * inFlight
+ * empty
+ * loaded
+ * created
+ * uncommitted
+ * inFlight
+ * saved
+ * updated
+ * uncommitted
+ * inFlight
+ * loading
+ ```
+
+ The `DS.Model` states are themselves stateless. What we mean is
+ that, the hierarchical states that each of *those* points to is a
+ shared data structure. For performance reasons, instead of each
+ record getting its own copy of the hierarchy of states, each record
+ points to this global, immutable shared instance. How does a state
+ know which record it should be acting on? We pass the record
+ instance into the state's event handlers as the first argument.
+
+ The record passed as the first parameter is where you should stash
+ state about the record if needed; you should never store data on the state
+ object itself.
+
+ ### Events and Flags
+
+ A state may implement zero or more events and flags.
+
+ #### Events
+
+ Events are named functions that are invoked when sent to a record. The
+ record will first look for a method with the given name on the
+ current state. If no method is found, it will search the current
+ state's parent, and then its grandparent, and so on until reaching
+ the top of the hierarchy. If the root is reached without an event
+ handler being found, an exception will be raised. This can be very
+ helpful when debugging new features.
+
+ Here's an example implementation of a state with a `myEvent` event handler:
+
+ ```javascript
+ aState: DS.State.create({
+ myEvent: function(manager, param) {
+ console.log("Received myEvent with", param);
+ }
+ })
+ ```
+
+ To trigger this event:
+
+ ```javascript
+ record.send('myEvent', 'foo');
+ //=> "Received myEvent with foo"
+ ```
+
+ Note that an optional parameter can be sent to a record's `send()` method,
+ which will be passed as the second parameter to the event handler.
+
+ Events should transition to a different state if appropriate. This can be
+ done by calling the record's `transitionTo()` method with a path to the
+ desired state. The state manager will attempt to resolve the state path
+ relative to the current state. If no state is found at that path, it will
+ attempt to resolve it relative to the current state's parent, and then its
+ parent, and so on until the root is reached. For example, imagine a hierarchy
+ like this:
+
+ * created
+ * uncommitted <-- currentState
+ * inFlight
+ * updated
+ * inFlight
+
+ If we are currently in the `uncommitted` state, calling
+ `transitionTo('inFlight')` would transition to the `created.inFlight` state,
+ while calling `transitionTo('updated.inFlight')` would transition to
+ the `updated.inFlight` state.
+
+ Remember that *only events* should ever cause a state transition. You should
+ never call `transitionTo()` from outside a state's event handler. If you are
+ tempted to do so, create a new event and send that to the state manager.
+
+ #### Flags
+
+ Flags are Boolean values that can be used to introspect a record's current
+ state in a more user-friendly way than examining its state path. For example,
+ instead of doing this:
+
+ ```javascript
+ var statePath = record.get('stateManager.currentPath');
+ if (statePath === 'created.inFlight') {
+ doSomething();
+ }
+ ```
+
+ You can say:
+
+ ```javascript
+ if (record.get('isNew') && record.get('isSaving')) {
+ doSomething();
+ }
+ ```
+
+ If your state does not set a value for a given flag, the value will
+ be inherited from its parent (or the first place in the state hierarchy
+ where it is defined).
+
+ The current set of flags are defined below. If you want to add a new flag,
+ in addition to the area below, you will also need to declare it in the
+ `DS.Model` class.
+
+
+ * [isEmpty](DS.Model.html#property_isEmpty)
+ * [isLoading](DS.Model.html#property_isLoading)
+ * [isLoaded](DS.Model.html#property_isLoaded)
+ * [isDirty](DS.Model.html#property_isDirty)
+ * [isSaving](DS.Model.html#property_isSaving)
+ * [isDeleted](DS.Model.html#property_isDeleted)
+ * [isNew](DS.Model.html#property_isNew)
+ * [isValid](DS.Model.html#property_isValid)
+
+ @namespace DS
+ @class RootState
+*/
+
+var hasDefinedProperties = function(object) {
+ // Ignore internal property defined by simulated `Ember.create`.
+ var names = Ember.keys(object);
+ var i, l, name;
+ for (i = 0, l = names.length; i < l; i++ ) {
+ name = names[i];
+ if (object.hasOwnProperty(name) && object[name]) { return true; }
+ }
+
+ return false;
+};
+
+var didSetProperty = function(record, context) {
+ if (context.value === context.originalValue) {
+ delete record._attributes[context.name];
+ record.send('propertyWasReset', context.name);
+ } else if (context.value !== context.oldValue) {
+ record.send('becomeDirty');
+ }
+
+ record.updateRecordArraysLater();
+};
+
+// Implementation notes:
+//
+// Each state has a boolean value for all of the following flags:
+//
+// * isLoaded: The record has a populated `data` property. When a
+// record is loaded via `store.find`, `isLoaded` is false
+// until the adapter sets it. When a record is created locally,
+// its `isLoaded` property is always true.
+// * isDirty: The record has local changes that have not yet been
+// saved by the adapter. This includes records that have been
+// created (but not yet saved) or deleted.
+// * isSaving: The record has been committed, but
+// the adapter has not yet acknowledged that the changes have
+// been persisted to the backend.
+// * isDeleted: The record was marked for deletion. When `isDeleted`
+// is true and `isDirty` is true, the record is deleted locally
+// but the deletion was not yet persisted. When `isSaving` is
+// true, the change is in-flight. When both `isDirty` and
+// `isSaving` are false, the change has persisted.
+// * isError: The adapter reported that it was unable to save
+// local changes to the backend. This may also result in the
+// record having its `isValid` property become false if the
+// adapter reported that server-side validations failed.
+// * isNew: The record was created on the client and the adapter
+// did not yet report that it was successfully saved.
+// * isValid: No client-side validations have failed and the
+// adapter did not report any server-side validation failures.
+
+// The dirty state is a abstract state whose functionality is
+// shared between the `created` and `updated` states.
+//
+// The deleted state shares the `isDirty` flag with the
+// subclasses of `DirtyState`, but with a very different
+// implementation.
+//
+// Dirty states have three child states:
+//
+// `uncommitted`: the store has not yet handed off the record
+// to be saved.
+// `inFlight`: the store has handed off the record to be saved,
+// but the adapter has not yet acknowledged success.
+// `invalid`: the record has invalid information and cannot be
+// send to the adapter yet.
+var DirtyState = {
+ initialState: 'uncommitted',
+
+ // FLAGS
+ isDirty: true,
+
+ // SUBSTATES
+
+ // When a record first becomes dirty, it is `uncommitted`.
+ // This means that there are local pending changes, but they
+ // have not yet begun to be saved, and are not invalid.
+ uncommitted: {
+ // EVENTS
+ didSetProperty: didSetProperty,
+
+ propertyWasReset: function(record, name) {
+ var stillDirty = false;
+
+ for (var prop in record._attributes) {
+ stillDirty = true;
+ break;
+ }
+
+ if (!stillDirty) { record.send('rolledBack'); }
+ },
+
+ pushedData: Ember.K,
+
+ becomeDirty: Ember.K,
+
+ willCommit: function(record) {
+ record.transitionTo('inFlight');
+ },
+
+ reloadRecord: function(record, resolve) {
+ resolve(get(record, 'store').reloadRecord(record));
+ },
+
+ rolledBack: function(record) {
+ record.transitionTo('loaded.saved');
+ },
+
+ becameInvalid: function(record) {
+ record.transitionTo('invalid');
+ },
+
+ rollback: function(record) {
+ record.rollback();
+ }
+ },
+
+ // Once a record has been handed off to the adapter to be
+ // saved, it is in the 'in flight' state. Changes to the
+ // record cannot be made during this window.
+ inFlight: {
+ // FLAGS
+ isSaving: true,
+
+ // EVENTS
+ didSetProperty: didSetProperty,
+ becomeDirty: Ember.K,
+ pushedData: Ember.K,
+
+ // TODO: More robust semantics around save-while-in-flight
+ willCommit: Ember.K,
+
+ didCommit: function(record) {
+ var dirtyType = get(this, 'dirtyType');
+
+ record.transitionTo('saved');
+ record.send('invokeLifecycleCallbacks', dirtyType);
+ },
+
+ becameInvalid: function(record) {
+ record.transitionTo('invalid');
+ record.send('invokeLifecycleCallbacks');
+ },
+
+ becameError: function(record) {
+ record.transitionTo('uncommitted');
+ record.triggerLater('becameError', record);
+ }
+ },
+
+ // A record is in the `invalid` state when its client-side
+ // invalidations have failed, or if the adapter has indicated
+ // the the record failed server-side invalidations.
+ invalid: {
+ // FLAGS
+ isValid: false,
+
+ // EVENTS
+ deleteRecord: function(record) {
+ record.transitionTo('deleted.uncommitted');
+ record.clearRelationships();
+ },
+
+ didSetProperty: function(record, context) {
+ get(record, 'errors').remove(context.name);
+
+ didSetProperty(record, context);
+ },
+
+ becomeDirty: Ember.K,
+
+ rolledBack: function(record) {
+ get(record, 'errors').clear();
+ },
+
+ becameValid: function(record) {
+ record.transitionTo('uncommitted');
+ },
+
+ invokeLifecycleCallbacks: function(record) {
+ record.triggerLater('becameInvalid', record);
+ }
+ }
+};
+
+// The created and updated states are created outside the state
+// chart so we can reopen their substates and add mixins as
+// necessary.
+
+function deepClone(object) {
+ var clone = {}, value;
+
+ for (var prop in object) {
+ value = object[prop];
+ if (value && typeof value === 'object') {
+ clone[prop] = deepClone(value);
+ } else {
+ clone[prop] = value;
+ }
+ }
+
+ return clone;
+}
+
+function mixin(original, hash) {
+ for (var prop in hash) {
+ original[prop] = hash[prop];
+ }
+
+ return original;
+}
+
+function dirtyState(options) {
+ var newState = deepClone(DirtyState);
+ return mixin(newState, options);
+}
+
+var createdState = dirtyState({
+ dirtyType: 'created',
+
+ // FLAGS
+ isNew: true
+});
+
+createdState.uncommitted.rolledBack = function(record) {
+ record.transitionTo('deleted.saved');
+};
+
+var updatedState = dirtyState({
+ dirtyType: 'updated'
+});
+
+createdState.uncommitted.deleteRecord = function(record) {
+ record.clearRelationships();
+ record.transitionTo('deleted.saved');
+};
+
+createdState.uncommitted.rollback = function(record) {
+ DirtyState.uncommitted.rollback.apply(this, arguments);
+ record.transitionTo('deleted.saved');
+};
+
+createdState.uncommitted.propertyWasReset = Ember.K;
+
+updatedState.uncommitted.deleteRecord = function(record) {
+ record.transitionTo('deleted.uncommitted');
+ record.clearRelationships();
+};
+
+var RootState = {
+ // FLAGS
+ isEmpty: false,
+ isLoading: false,
+ isLoaded: false,
+ isDirty: false,
+ isSaving: false,
+ isDeleted: false,
+ isNew: false,
+ isValid: true,
+
+ // DEFAULT EVENTS
+
+ // Trying to roll back if you're not in the dirty state
+ // doesn't change your state. For example, if you're in the
+ // in-flight state, rolling back the record doesn't move
+ // you out of the in-flight state.
+ rolledBack: Ember.K,
+
+ propertyWasReset: Ember.K,
+
+ // SUBSTATES
+
+ // A record begins its lifecycle in the `empty` state.
+ // If its data will come from the adapter, it will
+ // transition into the `loading` state. Otherwise, if
+ // the record is being created on the client, it will
+ // transition into the `created` state.
+ empty: {
+ isEmpty: true,
+
+ // EVENTS
+ loadingData: function(record, promise) {
+ record._loadingPromise = promise;
+ record.transitionTo('loading');
+ },
+
+ loadedData: function(record) {
+ record.transitionTo('loaded.created.uncommitted');
+
+ record.suspendRelationshipObservers(function() {
+ record.notifyPropertyChange('data');
+ });
+ },
+
+ pushedData: function(record) {
+ record.transitionTo('loaded.saved');
+ record.triggerLater('didLoad');
+ }
+ },
+
+ // A record enters this state when the store asks
+ // the adapter for its data. It remains in this state
+ // until the adapter provides the requested data.
+ //
+ // Usually, this process is asynchronous, using an
+ // XHR to retrieve the data.
+ loading: {
+ // FLAGS
+ isLoading: true,
+
+ exit: function(record) {
+ record._loadingPromise = null;
+ },
+
+ // EVENTS
+ pushedData: function(record) {
+ record.transitionTo('loaded.saved');
+ record.triggerLater('didLoad');
+ set(record, 'isError', false);
+ },
+
+ becameError: function(record) {
+ record.triggerLater('becameError', record);
+ },
+
+ notFound: function(record) {
+ record.transitionTo('empty');
+ }
+ },
+
+ // A record enters this state when its data is populated.
+ // Most of a record's lifecycle is spent inside substates
+ // of the `loaded` state.
+ loaded: {
+ initialState: 'saved',
+
+ // FLAGS
+ isLoaded: true,
+
+ // SUBSTATES
+
+ // If there are no local changes to a record, it remains
+ // in the `saved` state.
+ saved: {
+ setup: function(record) {
+ var attrs = record._attributes,
+ isDirty = false;
+
+ for (var prop in attrs) {
+ if (attrs.hasOwnProperty(prop)) {
+ isDirty = true;
+ break;
+ }
+ }
+
+ if (isDirty) {
+ record.adapterDidDirty();
+ }
+ },
+
+ // EVENTS
+ didSetProperty: didSetProperty,
+
+ pushedData: Ember.K,
+
+ becomeDirty: function(record) {
+ record.transitionTo('updated.uncommitted');
+ },
+
+ willCommit: function(record) {
+ record.transitionTo('updated.inFlight');
+ },
+
+ reloadRecord: function(record, resolve) {
+ resolve(get(record, 'store').reloadRecord(record));
+ },
+
+ deleteRecord: function(record) {
+ record.transitionTo('deleted.uncommitted');
+ record.clearRelationships();
+ },
+
+ unloadRecord: function(record) {
+ // clear relationships before moving to deleted state
+ // otherwise it fails
+ record.clearRelationships();
+ record.transitionTo('deleted.saved');
+ },
+
+ didCommit: function(record) {
+ record.send('invokeLifecycleCallbacks', get(record, 'lastDirtyType'));
+ },
+
+ // loaded.saved.notFound would be triggered by a failed
+ // `reload()` on an unchanged record
+ notFound: Ember.K
+
+ },
+
+ // A record is in this state after it has been locally
+ // created but before the adapter has indicated that
+ // it has been saved.
+ created: createdState,
+
+ // A record is in this state if it has already been
+ // saved to the server, but there are new local changes
+ // that have not yet been saved.
+ updated: updatedState
+ },
+
+ // A record is in this state if it was deleted from the store.
+ deleted: {
+ initialState: 'uncommitted',
+ dirtyType: 'deleted',
+
+ // FLAGS
+ isDeleted: true,
+ isLoaded: true,
+ isDirty: true,
+
+ // TRANSITIONS
+ setup: function(record) {
+ record.updateRecordArrays();
+ },
+
+ // SUBSTATES
+
+ // When a record is deleted, it enters the `start`
+ // state. It will exit this state when the record
+ // starts to commit.
+ uncommitted: {
+
+ // EVENTS
+
+ willCommit: function(record) {
+ record.transitionTo('inFlight');
+ },
+
+ rollback: function(record) {
+ record.rollback();
+ },
+
+ becomeDirty: Ember.K,
+ deleteRecord: Ember.K,
+
+ rolledBack: function(record) {
+ record.transitionTo('loaded.saved');
+ }
+ },
+
+ // After a record starts committing, but
+ // before the adapter indicates that the deletion
+ // has saved to the server, a record is in the
+ // `inFlight` substate of `deleted`.
+ inFlight: {
+ // FLAGS
+ isSaving: true,
+
+ // EVENTS
+
+ // TODO: More robust semantics around save-while-in-flight
+ willCommit: Ember.K,
+ didCommit: function(record) {
+ record.transitionTo('saved');
+
+ record.send('invokeLifecycleCallbacks');
+ },
+
+ becameError: function(record) {
+ record.transitionTo('uncommitted');
+ record.triggerLater('becameError', record);
+ }
+ },
+
+ // Once the adapter indicates that the deletion has
+ // been saved, the record enters the `saved` substate
+ // of `deleted`.
+ saved: {
+ // FLAGS
+ isDirty: false,
+
+ setup: function(record) {
+ var store = get(record, 'store');
+ store.dematerializeRecord(record);
+ },
+
+ invokeLifecycleCallbacks: function(record) {
+ record.triggerLater('didDelete', record);
+ record.triggerLater('didCommit', record);
+ }
+ }
+ },
+
+ invokeLifecycleCallbacks: function(record, dirtyType) {
+ if (dirtyType === 'created') {
+ record.triggerLater('didCreate', record);
+ } else {
+ record.triggerLater('didUpdate', record);
+ }
+
+ record.triggerLater('didCommit', record);
+ }
+};
+
+function wireState(object, parent, name) {
+ /*jshint proto:true*/
+ // TODO: Use Object.create and copy instead
+ object = mixin(parent ? Ember.create(parent) : {}, object);
+ object.parentState = parent;
+ object.stateName = name;
+
+ for (var prop in object) {
+ if (!object.hasOwnProperty(prop) || prop === 'parentState' || prop === 'stateName') { continue; }
+ if (typeof object[prop] === 'object') {
+ object[prop] = wireState(object[prop], object, name + "." + prop);
+ }
+ }
+
+ return object;
+}
+
+RootState = wireState(RootState, null, "root");
+
+DS.RootState = RootState;
+
+})();
+
+
+
+(function() {
+var get = Ember.get, isEmpty = Ember.isEmpty;
+
+/**
+@module ember-data
+*/
+
+/**
+ Holds validation errors for a given record organized by attribute names.
+
+ @class Errors
+ @namespace DS
+ @extends Ember.Object
+ @uses Ember.Enumerable
+ @uses Ember.Evented
+ */
+DS.Errors = Ember.Object.extend(Ember.Enumerable, Ember.Evented, {
+ /**
+ Register with target handler
+
+ @method registerHandlers
+ @param {Object} target
+ @param {Function} becameInvalid
+ @param {Function} becameValid
+ */
+ registerHandlers: function(target, becameInvalid, becameValid) {
+ this.on('becameInvalid', target, becameInvalid);
+ this.on('becameValid', target, becameValid);
+ },
+
+ /**
+ @property errorsByAttributeName
+ @type {Ember.MapWithDefault}
+ @private
+ */
+ errorsByAttributeName: Ember.reduceComputed("content", {
+ initialValue: function() {
+ return Ember.MapWithDefault.create({
+ defaultValue: function() {
+ return Ember.A();
+ }
+ });
+ },
+
+ addedItem: function(errors, error) {
+ errors.get(error.attribute).pushObject(error);
+
+ return errors;
+ },
+
+ removedItem: function(errors, error) {
+ errors.get(error.attribute).removeObject(error);
+
+ return errors;
+ }
+ }),
+
+ /**
+ Returns errors for a given attribute
+
+ @method errorsFor
+ @param {String} attribute
+ @returns {Array}
+ */
+ errorsFor: function(attribute) {
+ return get(this, 'errorsByAttributeName').get(attribute);
+ },
+
+ /**
+ */
+ messages: Ember.computed.mapBy('content', 'message'),
+
+ /**
+ @property content
+ @type {Array}
+ @private
+ */
+ content: Ember.computed(function() {
+ return Ember.A();
+ }),
+
+ /**
+ @method unknownProperty
+ @private
+ */
+ unknownProperty: function(attribute) {
+ var errors = this.errorsFor(attribute);
+ if (isEmpty(errors)) { return null; }
+ return errors;
+ },
+
+ /**
+ @method nextObject
+ @private
+ */
+ nextObject: function(index, previousObject, context) {
+ return get(this, 'content').objectAt(index);
+ },
+
+ /**
+ Total number of errors.
+
+ @property length
+ @type {Number}
+ @readOnly
+ */
+ length: Ember.computed.oneWay('content.length').readOnly(),
+
+ /**
+ @property isEmpty
+ @type {Boolean}
+ @readOnly
+ */
+ isEmpty: Ember.computed.not('length').readOnly(),
+
+ /**
+ Adds error messages to a given attribute and sends
+ `becameInvalid` event to the record.
+
+ @method add
+ @param {String} attribute
+ @param {Array|String} messages
+ */
+ add: function(attribute, messages) {
+ var wasEmpty = get(this, 'isEmpty');
+
+ messages = this._findOrCreateMessages(attribute, messages);
+ get(this, 'content').addObjects(messages);
+
+ this.notifyPropertyChange(attribute);
+ this.enumerableContentDidChange();
+
+ if (wasEmpty && !get(this, 'isEmpty')) {
+ this.trigger('becameInvalid');
+ }
+ },
+
+ /**
+ @method _findOrCreateMessages
+ @private
+ */
+ _findOrCreateMessages: function(attribute, messages) {
+ var errors = this.errorsFor(attribute);
+
+ return Ember.makeArray(messages).map(function(message) {
+ return errors.findBy('message', message) || {
+ attribute: attribute,
+ message: message
+ };
+ });
+ },
+
+ /**
+ Removes all error messages from the given attribute and sends
+ `becameValid` event to the record if there no more errors left.
+
+ @method remove
+ @param {String} attribute
+ */
+ remove: function(attribute) {
+ if (get(this, 'isEmpty')) { return; }
+
+ var content = get(this, 'content').rejectBy('attribute', attribute);
+ get(this, 'content').setObjects(content);
+
+ this.notifyPropertyChange(attribute);
+ this.enumerableContentDidChange();
+
+ if (get(this, 'isEmpty')) {
+ this.trigger('becameValid');
+ }
+ },
+
+ /**
+ Removes all error messages and sends `becameValid` event
+ to the record.
+
+ @method clear
+ */
+ clear: function() {
+ if (get(this, 'isEmpty')) { return; }
+
+ get(this, 'content').clear();
+ this.enumerableContentDidChange();
+
+ this.trigger('becameValid');
+ },
+
+ /**
+ Checks if there is error messages for the given attribute.
+
+ @method has
+ @param {String} attribute
+ @returns {Boolean} true if there some errors on given attribute
+ */
+ has: function(attribute) {
+ return !isEmpty(this.errorsFor(attribute));
+ }
+});
+
+})();
+
+
+
+(function() {
+/**
+ @module ember-data
+*/
+
+var get = Ember.get, set = Ember.set,
+ merge = Ember.merge;
+
+var retrieveFromCurrentState = Ember.computed('currentState', function(key, value) {
+ return get(get(this, 'currentState'), key);
+}).readOnly();
+
+/**
+
+ The model class that all Ember Data records descend from.
+
+ @class Model
+ @namespace DS
+ @extends Ember.Object
+ @uses Ember.Evented
+*/
+DS.Model = Ember.Object.extend(Ember.Evented, {
+ /**
+ If this property is `true` the record is in the `empty`
+ state. Empty is the first state all records enter after they have
+ been created. Most records created by the store will quickly
+ transition to the `loading` state if data needs to be fetched from
+ the server or the `created` state if the record is created on the
+ client. A record can also enter the empty state if the adapter is
+ unable to locate the record.
+
+ @property isEmpty
+ @type {Boolean}
+ @readOnly
+ */
+ isEmpty: retrieveFromCurrentState,
+ /**
+ If this property is `true` the record is in the `loading` state. A
+ record enters this state when the store asks the adapter for its
+ data. It remains in this state until the adapter provides the
+ requested data.
+
+ @property isLoading
+ @type {Boolean}
+ @readOnly
+ */
+ isLoading: retrieveFromCurrentState,
+ /**
+ If this property is `true` the record is in the `loaded` state. A
+ record enters this state when its data is populated. Most of a
+ record's lifecycle is spent inside substates of the `loaded`
+ state.
+
+ Example
+
+ ```javascript
+ var record = store.createRecord(App.Model);
+ record.get('isLoaded'); // true
+
+ store.find('model', 1).then(function(model) {
+ model.get('isLoaded'); // true
+ });
+ ```
+
+ @property isLoaded
+ @type {Boolean}
+ @readOnly
+ */
+ isLoaded: retrieveFromCurrentState,
+ /**
+ If this property is `true` the record is in the `dirty` state. The
+ record has local changes that have not yet been saved by the
+ adapter. This includes records that have been created (but not yet
+ saved) or deleted.
+
+ Example
+
+ ```javascript
+ var record = store.createRecord(App.Model);
+ record.get('isDirty'); // true
+
+ store.find('model', 1).then(function(model) {
+ model.get('isDirty'); // false
+ model.set('foo', 'some value');
+ model.set('isDirty'); // true
+ });
+ ```
+
+ @property isDirty
+ @type {Boolean}
+ @readOnly
+ */
+ isDirty: retrieveFromCurrentState,
+ /**
+ If this property is `true` the record is in the `saving` state. A
+ record enters the saving state when `save` is called, but the
+ adapter has not yet acknowledged that the changes have been
+ persisted to the backend.
+
+ Example
+
+ ```javascript
+ var record = store.createRecord(App.Model);
+ record.get('isSaving'); // false
+ var promise = record.save();
+ record.get('isSaving'); // true
+ promise.then(function() {
+ record.get('isSaving'); // false
+ });
+ ```
+
+ @property isSaving
+ @type {Boolean}
+ @readOnly
+ */
+ isSaving: retrieveFromCurrentState,
+ /**
+ If this property is `true` the record is in the `deleted` state
+ and has been marked for deletion. When `isDeleted` is true and
+ `isDirty` is true, the record is deleted locally but the deletion
+ was not yet persisted. When `isSaving` is true, the change is
+ in-flight. When both `isDirty` and `isSaving` are false, the
+ change has persisted.
+
+ Example
+
+ ```javascript
+ var record = store.createRecord(App.Model);
+ record.get('isDeleted'); // false
+ record.deleteRecord();
+ record.get('isDeleted'); // true
+ ```
+
+ @property isDeleted
+ @type {Boolean}
+ @readOnly
+ */
+ isDeleted: retrieveFromCurrentState,
+ /**
+ If this property is `true` the record is in the `new` state. A
+ record will be in the `new` state when it has been created on the
+ client and the adapter has not yet report that it was successfully
+ saved.
+
+ Example
+
+ ```javascript
+ var record = store.createRecord(App.Model);
+ record.get('isNew'); // true
+
+ record.save().then(function(model) {
+ model.get('isNew'); // false
+ });
+ ```
+
+ @property isNew
+ @type {Boolean}
+ @readOnly
+ */
+ isNew: retrieveFromCurrentState,
+ /**
+ If this property is `true` the record is in the `valid` state. A
+ record will be in the `valid` state when no client-side
+ validations have failed and the adapter did not report any
+ server-side validation failures.
+
+ @property isValid
+ @type {Boolean}
+ @readOnly
+ */
+ isValid: retrieveFromCurrentState,
+ /**
+ If the record is in the dirty state this property will report what
+ kind of change has caused it to move into the dirty
+ state. Possible values are:
+
+ - `created` The record has been created by the client and not yet saved to the adapter.
+ - `updated` The record has been updated by the client and not yet saved to the adapter.
+ - `deleted` The record has been deleted by the client and not yet saved to the adapter.
+
+ Example
+
+ ```javascript
+ var record = store.createRecord(App.Model);
+ record.get('dirtyType'); // 'created'
+ ```
+
+ @property dirtyType
+ @type {String}
+ @readOnly
+ */
+ dirtyType: retrieveFromCurrentState,
+
+ /**
+ If `true` the adapter reported that it was unable to save local
+ changes to the backend. This may also result in the record having
+ its `isValid` property become false if the adapter reported that
+ server-side validations failed.
+
+ Example
+
+ ```javascript
+ record.get('isError'); // false
+ record.set('foo', 'invalid value');
+ record.save().then(null, function() {
+ record.get('isError'); // true
+ });
+ ```
+
+ @property isError
+ @type {Boolean}
+ @readOnly
+ */
+ isError: false,
+ /**
+ If `true` the store is attempting to reload the record form the adapter.
+
+ Example
+
+ ```javascript
+ record.get('isReloading'); // false
+ record.reload();
+ record.get('isReloading'); // true
+ ```
+
+ @property isReloading
+ @type {Boolean}
+ @readOnly
+ */
+ isReloading: false,
+
+ /**
+ The `clientId` property is a transient numerical identifier
+ generated at runtime by the data store. It is important
+ primarily because newly created objects may not yet have an
+ externally generated id.
+
+ @property clientId
+ @private
+ @type {Number|String}
+ */
+ clientId: null,
+ /**
+ All ember models have an id property. This is an identifier
+ managed by an external source. These are always coerced to be
+ strings before being used internally. Note when declaring the
+ attributes for a model it is an error to declare an id
+ attribute.
+
+ ```javascript
+ var record = store.createRecord(App.Model);
+ record.get('id'); // null
+
+ store.find('model', 1).then(function(model) {
+ model.get('id'); // '1'
+ });
+ ```
+
+ @property id
+ @type {String}
+ */
+ id: null,
+ transaction: null,
+ /**
+ @property currentState
+ @private
+ @type {Object}
+ */
+ currentState: null,
+ /**
+ When the record is in the `invalid` state this object will contain
+ any errors returned by the adapter. When present the errors hash
+ typically contains keys corresponding to the invalid property names
+ and values which are an array of error messages.
+
+ ```javascript
+ record.get('errors.length'); // 0
+ record.set('foo', 'invalid value');
+ record.save().then(null, function() {
+ record.get('errors').get('foo'); // ['foo should be a number.']
+ });
+ ```
+
+ @property errors
+ @type {Object}
+ */
+ errors: null,
+
+ /**
+ Create a JSON representation of the record, using the serialization
+ strategy of the store's adapter.
+
+ `serialize` takes an optional hash as a parameter, currently
+ supported options are:
+
+ - `includeId`: `true` if the record's ID should be included in the
+ JSON representation.
+
+ @method serialize
+ @param {Object} options
+ @returns {Object} an object whose values are primitive JSON values only
+ */
+ serialize: function(options) {
+ var store = get(this, 'store');
+ return store.serialize(this, options);
+ },
+
+ /**
+ Use [DS.JSONSerializer](DS.JSONSerializer.html) to
+ get the JSON representation of a record.
+
+ `toJSON` takes an optional hash as a parameter, currently
+ supported options are:
+
+ - `includeId`: `true` if the record's ID should be included in the
+ JSON representation.
+
+ @method toJSON
+ @param {Object} options
+ @returns {Object} A JSON representation of the object.
+ */
+ toJSON: function(options) {
+ // container is for lazy transform lookups
+ var serializer = DS.JSONSerializer.create({ container: this.container });
+ return serializer.serialize(this, options);
+ },
+
+ /**
+ Fired when the record is loaded from the server.
+
+ @event didLoad
+ */
+ didLoad: Ember.K,
+
+ /**
+ Fired when the record is updated.
+
+ @event didUpdate
+ */
+ didUpdate: Ember.K,
+
+ /**
+ Fired when the record is created.
+
+ @event didCreate
+ */
+ didCreate: Ember.K,
+
+ /**
+ Fired when the record is deleted.
+
+ @event didDelete
+ */
+ didDelete: Ember.K,
+
+ /**
+ Fired when the record becomes invalid.
+
+ @event becameInvalid
+ */
+ becameInvalid: Ember.K,
+
+ /**
+ Fired when the record enters the error state.
+
+ @event becameError
+ */
+ becameError: Ember.K,
+
+ /**
+ @property data
+ @private
+ @type {Object}
+ */
+ data: Ember.computed(function() {
+ this._data = this._data || {};
+ return this._data;
+ }).property(),
+
+ _data: null,
+
+ init: function() {
+ set(this, 'currentState', DS.RootState.empty);
+ var errors = DS.Errors.create();
+ errors.registerHandlers(this, function() {
+ this.send('becameInvalid');
+ }, function() {
+ this.send('becameValid');
+ });
+ set(this, 'errors', errors);
+ this._super();
+ this._setup();
+ },
+
+ _setup: function() {
+ this._changesToSync = {};
+ this._deferredTriggers = [];
+ this._data = {};
+ this._attributes = {};
+ this._inFlightAttributes = {};
+ this._relationships = {};
+ },
+
+ /**
+ @method send
+ @private
+ @param {String} name
+ @param {Object} context
+ */
+ send: function(name, context) {
+ var currentState = get(this, 'currentState');
+
+ if (!currentState[name]) {
+ this._unhandledEvent(currentState, name, context);
+ }
+
+ return currentState[name](this, context);
+ },
+
+ /**
+ @method transitionTo
+ @private
+ @param {String} name
+ */
+ transitionTo: function(name) {
+ // POSSIBLE TODO: Remove this code and replace with
+ // always having direct references to state objects
+
+ var pivotName = name.split(".", 1),
+ currentState = get(this, 'currentState'),
+ state = currentState;
+
+ do {
+ if (state.exit) { state.exit(this); }
+ state = state.parentState;
+ } while (!state.hasOwnProperty(pivotName));
+
+ var path = name.split(".");
+
+ var setups = [], enters = [], i, l;
+
+ for (i=0, l=path.length; i<l; i++) {
+ state = state[path[i]];
+
+ if (state.enter) { enters.push(state); }
+ if (state.setup) { setups.push(state); }
+ }
+
+ for (i=0, l=enters.length; i<l; i++) {
+ enters[i].enter(this);
+ }
+
+ set(this, 'currentState', state);
+
+ for (i=0, l=setups.length; i<l; i++) {
+ setups[i].setup(this);
+ }
+
+ this.updateRecordArraysLater();
+ },
+
+ _unhandledEvent: function(state, name, context) {
+ var errorMessage = "Attempted to handle event `" + name + "` ";
+ errorMessage += "on " + String(this) + " while in state ";
+ errorMessage += state.stateName + ". ";
+
+ if (context !== undefined) {
+ errorMessage += "Called with " + Ember.inspect(context) + ".";
+ }
+
+ throw new Ember.Error(errorMessage);
+ },
+
+ withTransaction: function(fn) {
+ var transaction = get(this, 'transaction');
+ if (transaction) { fn(transaction); }
+ },
+
+ /**
+ @method loadingData
+ @private
+ @param {Promise} promise
+ */
+ loadingData: function(promise) {
+ this.send('loadingData', promise);
+ },
+
+ /**
+ @method loadedData
+ @private
+ */
+ loadedData: function() {
+ this.send('loadedData');
+ },
+
+ /**
+ @method notFound
+ @private
+ */
+ notFound: function() {
+ this.send('notFound');
+ },
+
+ /**
+ @method pushedData
+ @private
+ */
+ pushedData: function() {
+ this.send('pushedData');
+ },
+
+ /**
+ Marks the record as deleted but does not save it. You must call
+ `save` afterwards if you want to persist it. You might use this
+ method if you want to allow the user to still `rollback()` a
+ delete after it was made.
+
+ Example
+
+ ```javascript
+ App.ModelDeleteRoute = Ember.Route.extend({
+ actions: {
+ softDelete: function() {
+ this.get('model').deleteRecord();
+ },
+ confirm: function() {
+ this.get('model').save();
+ },
+ undo: function() {
+ this.get('model').rollback();
+ }
+ }
+ });
+ ```
+
+ @method deleteRecord
+ */
+ deleteRecord: function() {
+ this.send('deleteRecord');
+ },
+
+ /**
+ Same as `deleteRecord`, but saves the record immediately.
+
+ Example
+
+ ```javascript
+ App.ModelDeleteRoute = Ember.Route.extend({
+ actions: {
+ delete: function() {
+ var controller = this.controller;
+ this.get('model').destroyRecord().then(function() {
+ controller.transitionToRoute('model.index');
+ });
+ }
+ }
+ });
+ ```
+
+ @method destroyRecord
+ @return {Promise} a promise that will be resolved when the adapter returns
+ successfully or rejected if the adapter returns with an error.
+ */
+ destroyRecord: function() {
+ this.deleteRecord();
+ return this.save();
+ },
+
+ /**
+ @method unloadRecord
+ @private
+ */
+ unloadRecord: function() {
+ Ember.assert("You can only unload a loaded, non-dirty record.", !get(this, 'isDirty'));
+
+ this.send('unloadRecord');
+ },
+
+ /**
+ @method clearRelationships
+ @private
+ */
+ clearRelationships: function() {
+ this.eachRelationship(function(name, relationship) {
+ if (relationship.kind === 'belongsTo') {
+ set(this, name, null);
+ } else if (relationship.kind === 'hasMany') {
+ var hasMany = this._relationships[relationship.name];
+ if (hasMany) { hasMany.clear(); }
+ }
+ }, this);
+ },
+
+ /**
+ @method updateRecordArrays
+ @private
+ */
+ updateRecordArrays: function() {
+ this._updatingRecordArraysLater = false;
+ get(this, 'store').dataWasUpdated(this.constructor, this);
+ },
+
+ /**
+ Returns an object, whose keys are changed properties, and value is
+ an [oldProp, newProp] array.
+
+ Example
+
+ ```javascript
+ App.Mascot = DS.Model.extend({
+ name: attr('string')
+ });
+
+ var person = store.createRecord('person');
+ person.changedAttributes(); // {}
+ person.set('name', 'Tomster');
+ person.changedAttributes(); // {name: [undefined, 'Tomster']}
+ ```
+
+ @method changedAttributes
+ @return {Object} an object, whose keys are changed properties,
+ and value is an [oldProp, newProp] array.
+ */
+ changedAttributes: function() {
+ var oldData = get(this, '_data'),
+ newData = get(this, '_attributes'),
+ diffData = {},
+ prop;
+
+ for (prop in newData) {
+ diffData[prop] = [oldData[prop], newData[prop]];
+ }
+
+ return diffData;
+ },
+
+ /**
+ @method adapterWillCommit
+ @private
+ */
+ adapterWillCommit: function() {
+ this.send('willCommit');
+ },
+
+ /**
+ If the adapter did not return a hash in response to a commit,
+ merge the changed attributes and relationships into the existing
+ saved data.
+
+ @method adapterDidCommit
+ */
+ adapterDidCommit: function(data) {
+ set(this, 'isError', false);
+
+ if (data) {
+ this._data = data;
+ } else {
+ Ember.mixin(this._data, this._inFlightAttributes);
+ }
+
+ this._inFlightAttributes = {};
+
+ this.send('didCommit');
+ this.updateRecordArraysLater();
+
+ if (!data) { return; }
+
+ this.suspendRelationshipObservers(function() {
+ this.notifyPropertyChange('data');
+ });
+ },
+
+ /**
+ @method adapterDidDirty
+ @private
+ */
+ adapterDidDirty: function() {
+ this.send('becomeDirty');
+ this.updateRecordArraysLater();
+ },
+
+ dataDidChange: Ember.observer(function() {
+ this.reloadHasManys();
+ }, 'data'),
+
+ reloadHasManys: function() {
+ var relationships = get(this.constructor, 'relationshipsByName');
+ this.updateRecordArraysLater();
+ relationships.forEach(function(name, relationship) {
+ if (this._data.links && this._data.links[name]) { return; }
+ if (relationship.kind === 'hasMany') {
+ this.hasManyDidChange(relationship.key);
+ }
+ }, this);
+ },
+
+ hasManyDidChange: function(key) {
+ var hasMany = this._relationships[key];
+
+ if (hasMany) {
+ var records = this._data[key] || [];
+
+ set(hasMany, 'content', Ember.A(records));
+ set(hasMany, 'isLoaded', true);
+ hasMany.trigger('didLoad');
+ }
+ },
+
+ /**
+ @method updateRecordArraysLater
+ @private
+ */
+ updateRecordArraysLater: function() {
+ // quick hack (something like this could be pushed into run.once
+ if (this._updatingRecordArraysLater) { return; }
+ this._updatingRecordArraysLater = true;
+
+ Ember.run.schedule('actions', this, this.updateRecordArrays);
+ },
+
+ /**
+ @method setupData
+ @private
+ @param {Object} data
+ @param {Boolean} partial the data should be merged into
+ the existing data, not replace it.
+ */
+ setupData: function(data, partial) {
+ if (partial) {
+ Ember.merge(this._data, data);
+ } else {
+ this._data = data;
+ }
+
+ var relationships = this._relationships;
+
+ this.eachRelationship(function(name, rel) {
+ if (data.links && data.links[name]) { return; }
+ if (rel.options.async) { relationships[name] = null; }
+ });
+
+ if (data) { this.pushedData(); }
+
+ this.suspendRelationshipObservers(function() {
+ this.notifyPropertyChange('data');
+ });
+ },
+
+ materializeId: function(id) {
+ set(this, 'id', id);
+ },
+
+ materializeAttributes: function(attributes) {
+ Ember.assert("Must pass a hash of attributes to materializeAttributes", !!attributes);
+ merge(this._data, attributes);
+ },
+
+ materializeAttribute: function(name, value) {
+ this._data[name] = value;
+ },
+
+ /**
+ @method updateHasMany
+ @private
+ @param {String} name
+ @param {Array} records
+ */
+ updateHasMany: function(name, records) {
+ this._data[name] = records;
+ this.hasManyDidChange(name);
+ },
+
+ /**
+ @method updateBelongsTo
+ @private
+ @param {String} name
+ @param {DS.Model} record
+ */
+ updateBelongsTo: function(name, record) {
+ this._data[name] = record;
+ },
+
+ /**
+ If the model `isDirty` this function will discard any unsaved
+ changes
+
+ Example
+
+ ```javascript
+ record.get('name'); // 'Untitled Document'
+ record.set('name', 'Doc 1');
+ record.get('name'); // 'Doc 1'
+ record.rollback();
+ record.get('name'); // 'Untitled Document'
+ ```
+
+ @method rollback
+ */
+ rollback: function() {
+ this._attributes = {};
+
+ if (get(this, 'isError')) {
+ this._inFlightAttributes = {};
+ set(this, 'isError', false);
+ }
+
+ if (!get(this, 'isValid')) {
+ this._inFlightAttributes = {};
+ }
+
+ this.send('rolledBack');
+
+ this.suspendRelationshipObservers(function() {
+ this.notifyPropertyChange('data');
+ });
+ },
+
+ toStringExtension: function() {
+ return get(this, 'id');
+ },
+
+ /**
+ The goal of this method is to temporarily disable specific observers
+ that take action in response to application changes.
+
+ This allows the system to make changes (such as materialization and
+ rollback) that should not trigger secondary behavior (such as setting an
+ inverse relationship or marking records as dirty).
+
+ The specific implementation will likely change as Ember proper provides
+ better infrastructure for suspending groups of observers, and if Array
+ observation becomes more unified with regular observers.
+
+ @method suspendRelationshipObservers
+ @private
+ @param callback
+ @param binding
+ */
+ suspendRelationshipObservers: function(callback, binding) {
+ var observers = get(this.constructor, 'relationshipNames').belongsTo;
+ var self = this;
+
+ try {
+ this._suspendedRelationships = true;
+ Ember._suspendObservers(self, observers, null, 'belongsToDidChange', function() {
+ Ember._suspendBeforeObservers(self, observers, null, 'belongsToWillChange', function() {
+ callback.call(binding || self);
+ });
+ });
+ } finally {
+ this._suspendedRelationships = false;
+ }
+ },
+
+ /**
+ Save the record and persist any changes to the record to an
+ extenal source via the adapter.
+
+ Example
+
+ ```javascript
+ record.set('name', 'Tomster');
+ record.save().then(function(){
+ // Success callback
+ }, function() {
+ // Error callback
+ });
+ ```
+ @method save
+ @return {Promise} a promise that will be resolved when the adapter returns
+ successfully or rejected if the adapter returns with an error.
+ */
+ save: function() {
+ var promiseLabel = "DS: Model#save " + this;
+ var resolver = Ember.RSVP.defer(promiseLabel);
+
+ this.get('store').scheduleSave(this, resolver);
+ this._inFlightAttributes = this._attributes;
+ this._attributes = {};
+
+ return DS.PromiseObject.create({ promise: resolver.promise });
+ },
+
+ /**
+ Reload the record from the adapter.
+
+ This will only work if the record has already finished loading
+ and has not yet been modified (`isLoaded` but not `isDirty`,
+ or `isSaving`).
+
+ Example
+
+ ```javascript
+ App.ModelViewRoute = Ember.Route.extend({
+ actions: {
+ reload: function() {
+ this.get('model').reload();
+ }
+ }
+ });
+ ```
+
+ @method reload
+ @return {Promise} a promise that will be resolved with the record when the
+ adapter returns successfully or rejected if the adapter returns
+ with an error.
+ */
+ reload: function() {
+ set(this, 'isReloading', true);
+
+ var record = this;
+
+ var promiseLabel = "DS: Model#reload of " + this;
+ var promise = new Ember.RSVP.Promise(function(resolve){
+ record.send('reloadRecord', resolve);
+ }, promiseLabel).then(function() {
+ record.set('isReloading', false);
+ record.set('isError', false);
+ return record;
+ }, function(reason) {
+ record.set('isError', true);
+ throw reason;
+ }, "DS: Model#reload complete, update flags");
+
+ return DS.PromiseObject.create({ promise: promise });
+ },
+
+ // FOR USE DURING COMMIT PROCESS
+
+ adapterDidUpdateAttribute: function(attributeName, value) {
+
+ // If a value is passed in, update the internal attributes and clear
+ // the attribute cache so it picks up the new value. Otherwise,
+ // collapse the current value into the internal attributes because
+ // the adapter has acknowledged it.
+ if (value !== undefined) {
+ this._data[attributeName] = value;
+ this.notifyPropertyChange(attributeName);
+ } else {
+ this._data[attributeName] = this._inFlightAttributes[attributeName];
+ }
+
+ this.updateRecordArraysLater();
+ },
+
+ /**
+ @method adapterDidInvalidate
+ @private
+ */
+ adapterDidInvalidate: function(errors) {
+ var recordErrors = get(this, 'errors');
+ function addError(name) {
+ if (errors[name]) {
+ recordErrors.add(name, errors[name]);
+ }
+ }
+
+ this.eachAttribute(addError);
+ this.eachRelationship(addError);
+ },
+
+ /**
+ @method adapterDidError
+ @private
+ */
+ adapterDidError: function() {
+ this.send('becameError');
+ set(this, 'isError', true);
+ },
+
+ /**
+ Override the default event firing from Ember.Evented to
+ also call methods with the given name.
+
+ @method trigger
+ @private
+ @param name
+ */
+ trigger: function(name) {
+ Ember.tryInvoke(this, name, [].slice.call(arguments, 1));
+ this._super.apply(this, arguments);
+ },
+
+ triggerLater: function() {
+ if (this._deferredTriggers.push(arguments) !== 1) { return; }
+ Ember.run.schedule('actions', this, '_triggerDeferredTriggers');
+ },
+
+ _triggerDeferredTriggers: function() {
+ for (var i=0, l=this._deferredTriggers.length; i<l; i++) {
+ this.trigger.apply(this, this._deferredTriggers[i]);
+ }
+
+ this._deferredTriggers.length = 0;
+ }
+});
+
+DS.Model.reopenClass({
+
+ /**
+ Alias DS.Model's `create` method to `_create`. This allows us to create DS.Model
+ instances from within the store, but if end users accidentally call `create()`
+ (instead of `createRecord()`), we can raise an error.
+
+ @method _create
+ @private
+ @static
+ */
+ _create: DS.Model.create,
+
+ /**
+ Override the class' `create()` method to raise an error. This
+ prevents end users from inadvertently calling `create()` instead
+ of `createRecord()`. The store is still able to create instances
+ by calling the `_create()` method. To create an instance of a
+ `DS.Model` use [store.createRecord](DS.Store.html#method_createRecord).
+
+ @method create
+ @private
+ @static
+ */
+ create: function() {
+ throw new Ember.Error("You should not call `create` on a model. Instead, call `store.createRecord` with the attributes you would like to set.");
+ }
+});
+
+})();
+
+
+
+(function() {
+/**
+ @module ember-data
+*/
+
+var get = Ember.get;
+
+/**
+ @class Model
+ @namespace DS
+*/
+DS.Model.reopenClass({
+ /**
+ A map whose keys are the attributes of the model (properties
+ described by DS.attr) and whose values are the meta object for the
+ property.
+
+ Example
+
+ ```javascript
+
+ App.Person = DS.Model.extend({
+ firstName: attr('string'),
+ lastName: attr('string'),
+ birthday: attr('date')
+ });
+
+ var attributes = Ember.get(App.Person, 'attributes')
+
+ attributes.forEach(function(name, meta) {
+ console.log(name, meta);
+ });
+
+ // prints:
+ // firstName {type: "string", isAttribute: true, options: Object, parentType: function, name: "firstName"}
+ // lastName {type: "string", isAttribute: true, options: Object, parentType: function, name: "lastName"}
+ // birthday {type: "date", isAttribute: true, options: Object, parentType: function, name: "birthday"}
+ ```
+
+ @property attributes
+ @static
+ @type {Ember.Map}
+ @readOnly
+ */
+ attributes: Ember.computed(function() {
+ var map = Ember.Map.create();
+
+ this.eachComputedProperty(function(name, meta) {
+ if (meta.isAttribute) {
+ Ember.assert("You may not set `id` as an attribute on your model. Please remove any lines that look like: `id: DS.attr('<type>')` from " + this.toString(), name !== 'id');
+
+ meta.name = name;
+ map.set(name, meta);
+ }
+ });
+
+ return map;
+ }),
+
+ /**
+ A map whose keys are the attributes of the model (properties
+ described by DS.attr) and whose values are type of transformation
+ applied to each attribute. This map does not include any
+ attributes that do not have an transformation type.
+
+ Example
+
+ ```javascript
+ App.Person = DS.Model.extend({
+ firstName: attr(),
+ lastName: attr('string'),
+ birthday: attr('date')
+ });
+
+ var transformedAttributes = Ember.get(App.Person, 'transformedAttributes')
+
+ transformedAttributes.forEach(function(field, type) {
+ console.log(field, type);
+ });
+
+ // prints:
+ // lastName string
+ // birthday date
+ ```
+
+ @property transformedAttributes
+ @static
+ @type {Ember.Map}
+ @readOnly
+ */
+ transformedAttributes: Ember.computed(function() {
+ var map = Ember.Map.create();
+
+ this.eachAttribute(function(key, meta) {
+ if (meta.type) {
+ map.set(key, meta.type);
+ }
+ });
+
+ return map;
+ }),
+
+ /**
+ Iterates through the attributes of the model, calling the passed function on each
+ attribute.
+
+ The callback method you provide should have the following signature (all
+ parameters are optional):
+
+ ```javascript
+ function(name, meta);
+ ```
+
+ - `name` the name of the current property in the iteration
+ - `meta` the meta object for the attribute property in the iteration
+
+ Note that in addition to a callback, you can also pass an optional target
+ object that will be set as `this` on the context.
+
+ Example
+
+ ```javascript
+ App.Person = DS.Model.extend({
+ firstName: attr('string'),
+ lastName: attr('string'),
+ birthday: attr('date')
+ });
+
+ App.Person.eachAttribute(function(name, meta) {
+ console.log(name, meta);
+ });
+
+ // prints:
+ // firstName {type: "string", isAttribute: true, options: Object, parentType: function, name: "firstName"}
+ // lastName {type: "string", isAttribute: true, options: Object, parentType: function, name: "lastName"}
+ // birthday {type: "date", isAttribute: true, options: Object, parentType: function, name: "birthday"}
+ ```
+
+ @method eachAttribute
+ @param {Function} callback The callback to execute
+ @param {Object} [target] The target object to use
+ @static
+ */
+ eachAttribute: function(callback, binding) {
+ get(this, 'attributes').forEach(function(name, meta) {
+ callback.call(binding, name, meta);
+ }, binding);
+ },
+
+ /**
+ Iterates through the transformedAttributes of the model, calling
+ the passed function on each attribute. Note the callback will not be
+ called for any attributes that do not have an transformation type.
+
+ The callback method you provide should have the following signature (all
+ parameters are optional):
+
+ ```javascript
+ function(name, type);
+ ```
+
+ - `name` the name of the current property in the iteration
+ - `type` a string containing the name of the type of transformed
+ applied to the attribute
+
+ Note that in addition to a callback, you can also pass an optional target
+ object that will be set as `this` on the context.
+
+ Example
+
+ ```javascript
+ App.Person = DS.Model.extend({
+ firstName: attr(),
+ lastName: attr('string'),
+ birthday: attr('date')
+ });
+
+ App.Person.eachTransformedAttribute(function(name, type) {
+ console.log(name, type);
+ });
+
+ // prints:
+ // lastName string
+ // birthday date
+ ```
+
+ @method eachTransformedAttribute
+ @param {Function} callback The callback to execute
+ @param {Object} [target] The target object to use
+ @static
+ */
+ eachTransformedAttribute: function(callback, binding) {
+ get(this, 'transformedAttributes').forEach(function(name, type) {
+ callback.call(binding, name, type);
+ });
+ }
+});
+
+
+DS.Model.reopen({
+ eachAttribute: function(callback, binding) {
+ this.constructor.eachAttribute(callback, binding);
+ }
+});
+
+function getDefaultValue(record, options, key) {
+ if (typeof options.defaultValue === "function") {
+ return options.defaultValue();
+ } else {
+ return options.defaultValue;
+ }
+}
+
+function hasValue(record, key) {
+ return record._attributes.hasOwnProperty(key) ||
+ record._inFlightAttributes.hasOwnProperty(key) ||
+ record._data.hasOwnProperty(key);
+}
+
+function getValue(record, key) {
+ if (record._attributes.hasOwnProperty(key)) {
+ return record._attributes[key];
+ } else if (record._inFlightAttributes.hasOwnProperty(key)) {
+ return record._inFlightAttributes[key];
+ } else {
+ return record._data[key];
+ }
+}
+
+/**
+ `DS.attr` defines an attribute on a [DS.Model](DS.Model.html).
+ By default, attributes are passed through as-is, however you can specify an
+ optional type to have the value automatically transformed.
+ Ember Data ships with four basic transform types: `string`, `number`,
+ `boolean` and `date`. You can define your own transforms by subclassing
+ [DS.Transform](DS.Transform.html).
+
+ `DS.attr` takes an optional hash as a second parameter, currently
+ supported options are:
+
+ - `defaultValue`: Pass a string or a function to be called to set the attribute
+ to a default value if none is supplied.
+
+ Example
+
+ ```javascript
+ var attr = DS.attr;
+
+ App.User = DS.Model.extend({
+ username: attr('string'),
+ email: attr('string'),
+ verified: attr('boolean', {defaultValue: false})
+ });
+ ```
+
+ @namespace
+ @method attr
+ @for DS
+ @param {String} type the attribute type
+ @param {Object} options a hash of options
+ @return {Attribute}
+*/
+
+DS.attr = function(type, options) {
+ options = options || {};
+
+ var meta = {
+ type: type,
+ isAttribute: true,
+ options: options
+ };
+
+ return Ember.computed(function(key, value) {
+ if (arguments.length > 1) {
+ Ember.assert("You may not set `id` as an attribute on your model. Please remove any lines that look like: `id: DS.attr('<type>')` from " + this.constructor.toString(), key !== 'id');
+ var oldValue = this._attributes[key] || this._inFlightAttributes[key] || this._data[key];
+
+ this.send('didSetProperty', {
+ name: key,
+ oldValue: oldValue,
+ originalValue: this._data[key],
+ value: value
+ });
+
+ this._attributes[key] = value;
+ return value;
+ } else if (hasValue(this, key)) {
+ return getValue(this, key);
+ } else {
+ return getDefaultValue(this, options, key);
+ }
+
+ // `data` is never set directly. However, it may be
+ // invalidated from the state manager's setData
+ // event.
+ }).property('data').meta(meta);
+};
+
+
+})();
+
+
+
+(function() {
+/**
+ @module ember-data
+*/
+
+})();
+
+
+
+(function() {
+/**
+ @module ember-data
+*/
+
+/**
+ An AttributeChange object is created whenever a record's
+ attribute changes value. It is used to track changes to a
+ record between transaction commits.
+
+ @class AttributeChange
+ @namespace DS
+ @private
+ @constructor
+*/
+var AttributeChange = DS.AttributeChange = function(options) {
+ this.record = options.record;
+ this.store = options.store;
+ this.name = options.name;
+ this.value = options.value;
+ this.oldValue = options.oldValue;
+};
+
+AttributeChange.createChange = function(options) {
+ return new AttributeChange(options);
+};
+
+AttributeChange.prototype = {
+ sync: function() {
+ if (this.value !== this.oldValue) {
+ this.record.send('becomeDirty');
+ this.record.updateRecordArraysLater();
+ }
+
+ // TODO: Use this object in the commit process
+ this.destroy();
+ },
+
+ /**
+ If the AttributeChange is destroyed (either by being rolled back
+ or being committed), remove it from the list of pending changes
+ on the record.
+
+ @method destroy
+ */
+ destroy: function() {
+ delete this.record._changesToSync[this.name];
+ }
+};
+
+})();
+
+
+
+(function() {
+/**
+ @module ember-data
+*/
+
+var get = Ember.get, set = Ember.set;
+var forEach = Ember.EnumerableUtils.forEach;
+
+/**
+ @class RelationshipChange
+ @namespace DS
+ @private
+ @constructor
+*/
+DS.RelationshipChange = function(options) {
+ this.parentRecord = options.parentRecord;
+ this.childRecord = options.childRecord;
+ this.firstRecord = options.firstRecord;
+ this.firstRecordKind = options.firstRecordKind;
+ this.firstRecordName = options.firstRecordName;
+ this.secondRecord = options.secondRecord;
+ this.secondRecordKind = options.secondRecordKind;
+ this.secondRecordName = options.secondRecordName;
+ this.changeType = options.changeType;
+ this.store = options.store;
+
+ this.committed = {};
+};
+
+/**
+ @class RelationshipChangeAdd
+ @namespace DS
+ @private
+ @constructor
+*/
+DS.RelationshipChangeAdd = function(options){
+ DS.RelationshipChange.call(this, options);
+};
+
+/**
+ @class RelationshipChangeRemove
+ @namespace DS
+ @private
+ @constructor
+*/
+DS.RelationshipChangeRemove = function(options){
+ DS.RelationshipChange.call(this, options);
+};
+
+DS.RelationshipChange.create = function(options) {
+ return new DS.RelationshipChange(options);
+};
+
+DS.RelationshipChangeAdd.create = function(options) {
+ return new DS.RelationshipChangeAdd(options);
+};
+
+DS.RelationshipChangeRemove.create = function(options) {
+ return new DS.RelationshipChangeRemove(options);
+};
+
+DS.OneToManyChange = {};
+DS.OneToNoneChange = {};
+DS.ManyToNoneChange = {};
+DS.OneToOneChange = {};
+DS.ManyToManyChange = {};
+
+DS.RelationshipChange._createChange = function(options){
+ if(options.changeType === "add"){
+ return DS.RelationshipChangeAdd.create(options);
+ }
+ if(options.changeType === "remove"){
+ return DS.RelationshipChangeRemove.create(options);
+ }
+};
+
+
+DS.RelationshipChange.determineRelationshipType = function(recordType, knownSide){
+ var knownKey = knownSide.key, key, otherKind;
+ var knownKind = knownSide.kind;
+
+ var inverse = recordType.inverseFor(knownKey);
+
+ if (inverse){
+ key = inverse.name;
+ otherKind = inverse.kind;
+ }
+
+ if (!inverse){
+ return knownKind === "belongsTo" ? "oneToNone" : "manyToNone";
+ }
+ else{
+ if(otherKind === "belongsTo"){
+ return knownKind === "belongsTo" ? "oneToOne" : "manyToOne";
+ }
+ else{
+ return knownKind === "belongsTo" ? "oneToMany" : "manyToMany";
+ }
+ }
+
+};
+
+DS.RelationshipChange.createChange = function(firstRecord, secondRecord, store, options){
+ // Get the type of the child based on the child's client ID
+ var firstRecordType = firstRecord.constructor, changeType;
+ changeType = DS.RelationshipChange.determineRelationshipType(firstRecordType, options);
+ if (changeType === "oneToMany"){
+ return DS.OneToManyChange.createChange(firstRecord, secondRecord, store, options);
+ }
+ else if (changeType === "manyToOne"){
+ return DS.OneToManyChange.createChange(secondRecord, firstRecord, store, options);
+ }
+ else if (changeType === "oneToNone"){
+ return DS.OneToNoneChange.createChange(firstRecord, secondRecord, store, options);
+ }
+ else if (changeType === "manyToNone"){
+ return DS.ManyToNoneChange.createChange(firstRecord, secondRecord, store, options);
+ }
+ else if (changeType === "oneToOne"){
+ return DS.OneToOneChange.createChange(firstRecord, secondRecord, store, options);
+ }
+ else if (changeType === "manyToMany"){
+ return DS.ManyToManyChange.createChange(firstRecord, secondRecord, store, options);
+ }
+};
+
+DS.OneToNoneChange.createChange = function(childRecord, parentRecord, store, options) {
+ var key = options.key;
+ var change = DS.RelationshipChange._createChange({
+ parentRecord: parentRecord,
+ childRecord: childRecord,
+ firstRecord: childRecord,
+ store: store,
+ changeType: options.changeType,
+ firstRecordName: key,
+ firstRecordKind: "belongsTo"
+ });
+
+ store.addRelationshipChangeFor(childRecord, key, parentRecord, null, change);
+
+ return change;
+};
+
+DS.ManyToNoneChange.createChange = function(childRecord, parentRecord, store, options) {
+ var key = options.key;
+ var change = DS.RelationshipChange._createChange({
+ parentRecord: childRecord,
+ childRecord: parentRecord,
+ secondRecord: childRecord,
+ store: store,
+ changeType: options.changeType,
+ secondRecordName: options.key,
+ secondRecordKind: "hasMany"
+ });
+
+ store.addRelationshipChangeFor(childRecord, key, parentRecord, null, change);
+ return change;
+};
+
+
+DS.ManyToManyChange.createChange = function(childRecord, parentRecord, store, options) {
+ // If the name of the belongsTo side of the relationship is specified,
+ // use that
+ // If the type of the parent is specified, look it up on the child's type
+ // definition.
+ var key = options.key;
+
+ var change = DS.RelationshipChange._createChange({
+ parentRecord: parentRecord,
+ childRecord: childRecord,
+ firstRecord: childRecord,
+ secondRecord: parentRecord,
+ firstRecordKind: "hasMany",
+ secondRecordKind: "hasMany",
+ store: store,
+ changeType: options.changeType,
+ firstRecordName: key
+ });
+
+ store.addRelationshipChangeFor(childRecord, key, parentRecord, null, change);
+
+
+ return change;
+};
+
+DS.OneToOneChange.createChange = function(childRecord, parentRecord, store, options) {
+ var key;
+
+ // If the name of the belongsTo side of the relationship is specified,
+ // use that
+ // If the type of the parent is specified, look it up on the child's type
+ // definition.
+ if (options.parentType) {
+ key = options.parentType.inverseFor(options.key).name;
+ } else if (options.key) {
+ key = options.key;
+ } else {
+ Ember.assert("You must pass either a parentType or belongsToName option to OneToManyChange.forChildAndParent", false);
+ }
+
+ var change = DS.RelationshipChange._createChange({
+ parentRecord: parentRecord,
+ childRecord: childRecord,
+ firstRecord: childRecord,
+ secondRecord: parentRecord,
+ firstRecordKind: "belongsTo",
+ secondRecordKind: "belongsTo",
+ store: store,
+ changeType: options.changeType,
+ firstRecordName: key
+ });
+
+ store.addRelationshipChangeFor(childRecord, key, parentRecord, null, change);
+
+
+ return change;
+};
+
+DS.OneToOneChange.maintainInvariant = function(options, store, childRecord, key){
+ if (options.changeType === "add" && store.recordIsMaterialized(childRecord)) {
+ var oldParent = get(childRecord, key);
+ if (oldParent){
+ var correspondingChange = DS.OneToOneChange.createChange(childRecord, oldParent, store, {
+ parentType: options.parentType,
+ hasManyName: options.hasManyName,
+ changeType: "remove",
+ key: options.key
+ });
+ store.addRelationshipChangeFor(childRecord, key, options.parentRecord , null, correspondingChange);
+ correspondingChange.sync();
+ }
+ }
+};
+
+DS.OneToManyChange.createChange = function(childRecord, parentRecord, store, options) {
+ var key;
+
+ // If the name of the belongsTo side of the relationship is specified,
+ // use that
+ // If the type of the parent is specified, look it up on the child's type
+ // definition.
+ if (options.parentType) {
+ key = options.parentType.inverseFor(options.key).name;
+ DS.OneToManyChange.maintainInvariant( options, store, childRecord, key );
+ } else if (options.key) {
+ key = options.key;
+ } else {
+ Ember.assert("You must pass either a parentType or belongsToName option to OneToManyChange.forChildAndParent", false);
+ }
+
+ var change = DS.RelationshipChange._createChange({
+ parentRecord: parentRecord,
+ childRecord: childRecord,
+ firstRecord: childRecord,
+ secondRecord: parentRecord,
+ firstRecordKind: "belongsTo",
+ secondRecordKind: "hasMany",
+ store: store,
+ changeType: options.changeType,
+ firstRecordName: key
+ });
+
+ store.addRelationshipChangeFor(childRecord, key, parentRecord, change.getSecondRecordName(), change);
+
+
+ return change;
+};
+
+
+DS.OneToManyChange.maintainInvariant = function(options, store, childRecord, key){
+ if (options.changeType === "add" && childRecord) {
+ var oldParent = get(childRecord, key);
+ if (oldParent){
+ var correspondingChange = DS.OneToManyChange.createChange(childRecord, oldParent, store, {
+ parentType: options.parentType,
+ hasManyName: options.hasManyName,
+ changeType: "remove",
+ key: options.key
+ });
+ store.addRelationshipChangeFor(childRecord, key, options.parentRecord, correspondingChange.getSecondRecordName(), correspondingChange);
+ correspondingChange.sync();
+ }
+ }
+};
+
+/**
+ @class RelationshipChange
+ @namespace DS
+*/
+DS.RelationshipChange.prototype = {
+
+ getSecondRecordName: function() {
+ var name = this.secondRecordName, parent;
+
+ if (!name) {
+ parent = this.secondRecord;
+ if (!parent) { return; }
+
+ var childType = this.firstRecord.constructor;
+ var inverse = childType.inverseFor(this.firstRecordName);
+ this.secondRecordName = inverse.name;
+ }
+
+ return this.secondRecordName;
+ },
+
+ /**
+ Get the name of the relationship on the belongsTo side.
+
+ @method getFirstRecordName
+ @return {String}
+ */
+ getFirstRecordName: function() {
+ var name = this.firstRecordName;
+ return name;
+ },
+
+ /**
+ @method destroy
+ @private
+ */
+ destroy: function() {
+ var childRecord = this.childRecord,
+ belongsToName = this.getFirstRecordName(),
+ hasManyName = this.getSecondRecordName(),
+ store = this.store;
+
+ store.removeRelationshipChangeFor(childRecord, belongsToName, this.parentRecord, hasManyName, this.changeType);
+ },
+
+ getSecondRecord: function(){
+ return this.secondRecord;
+ },
+
+ /**
+ @method getFirstRecord
+ @private
+ */
+ getFirstRecord: function() {
+ return this.firstRecord;
+ },
+
+ coalesce: function(){
+ var relationshipPairs = this.store.relationshipChangePairsFor(this.firstRecord);
+ forEach(relationshipPairs, function(pair){
+ var addedChange = pair["add"];
+ var removedChange = pair["remove"];
+ if(addedChange && removedChange) {
+ addedChange.destroy();
+ removedChange.destroy();
+ }
+ });
+ }
+};
+
+DS.RelationshipChangeAdd.prototype = Ember.create(DS.RelationshipChange.create({}));
+DS.RelationshipChangeRemove.prototype = Ember.create(DS.RelationshipChange.create({}));
+
+// the object is a value, and not a promise
+function isValue(object) {
+ return typeof object === 'object' && (!object.then || typeof object.then !== 'function');
+}
+
+DS.RelationshipChangeAdd.prototype.changeType = "add";
+DS.RelationshipChangeAdd.prototype.sync = function() {
+ var secondRecordName = this.getSecondRecordName(),
+ firstRecordName = this.getFirstRecordName(),
+ firstRecord = this.getFirstRecord(),
+ secondRecord = this.getSecondRecord();
+
+ //Ember.assert("You specified a hasMany (" + hasManyName + ") on " + (!belongsToName && (newParent || oldParent || this.lastParent).constructor) + " but did not specify an inverse belongsTo on " + child.constructor, belongsToName);
+ //Ember.assert("You specified a belongsTo (" + belongsToName + ") on " + child.constructor + " but did not specify an inverse hasMany on " + (!hasManyName && (newParent || oldParent || this.lastParentRecord).constructor), hasManyName);
+
+ if (secondRecord instanceof DS.Model && firstRecord instanceof DS.Model) {
+ if(this.secondRecordKind === "belongsTo"){
+ secondRecord.suspendRelationshipObservers(function(){
+ set(secondRecord, secondRecordName, firstRecord);
+ });
+
+ }
+ else if(this.secondRecordKind === "hasMany"){
+ secondRecord.suspendRelationshipObservers(function(){
+ var relationship = get(secondRecord, secondRecordName);
+ if (isValue(relationship)) { relationship.addObject(firstRecord); }
+ });
+ }
+ }
+
+ if (firstRecord instanceof DS.Model && secondRecord instanceof DS.Model && get(firstRecord, firstRecordName) !== secondRecord) {
+ if(this.firstRecordKind === "belongsTo"){
+ firstRecord.suspendRelationshipObservers(function(){
+ set(firstRecord, firstRecordName, secondRecord);
+ });
+ }
+ else if(this.firstRecordKind === "hasMany"){
+ firstRecord.suspendRelationshipObservers(function(){
+ var relationship = get(firstRecord, firstRecordName);
+ if (isValue(relationship)) { relationship.addObject(secondRecord); }
+ });
+ }
+ }
+
+ this.coalesce();
+};
+
+DS.RelationshipChangeRemove.prototype.changeType = "remove";
+DS.RelationshipChangeRemove.prototype.sync = function() {
+ var secondRecordName = this.getSecondRecordName(),
+ firstRecordName = this.getFirstRecordName(),
+ firstRecord = this.getFirstRecord(),
+ secondRecord = this.getSecondRecord();
+
+ //Ember.assert("You specified a hasMany (" + hasManyName + ") on " + (!belongsToName && (newParent || oldParent || this.lastParent).constructor) + " but did not specify an inverse belongsTo on " + child.constructor, belongsToName);
+ //Ember.assert("You specified a belongsTo (" + belongsToName + ") on " + child.constructor + " but did not specify an inverse hasMany on " + (!hasManyName && (newParent || oldParent || this.lastParentRecord).constructor), hasManyName);
+
+ if (secondRecord instanceof DS.Model && firstRecord instanceof DS.Model) {
+ if(this.secondRecordKind === "belongsTo"){
+ secondRecord.suspendRelationshipObservers(function(){
+ set(secondRecord, secondRecordName, null);
+ });
+ }
+ else if(this.secondRecordKind === "hasMany"){
+ secondRecord.suspendRelationshipObservers(function(){
+ var relationship = get(secondRecord, secondRecordName);
+ if (isValue(relationship)) { relationship.removeObject(firstRecord); }
+ });
+ }
+ }
+
+ if (firstRecord instanceof DS.Model && get(firstRecord, firstRecordName)) {
+ if(this.firstRecordKind === "belongsTo"){
+ firstRecord.suspendRelationshipObservers(function(){
+ set(firstRecord, firstRecordName, null);
+ });
+ }
+ else if(this.firstRecordKind === "hasMany"){
+ firstRecord.suspendRelationshipObservers(function(){
+ var relationship = get(firstRecord, firstRecordName);
+ if (isValue(relationship)) { relationship.removeObject(secondRecord); }
+ });
+ }
+ }
+
+ this.coalesce();
+};
+
+})();
+
+
+
+(function() {
+/**
+ @module ember-data
+*/
+
+})();
+
+
+
+(function() {
+var get = Ember.get, set = Ember.set,
+ isNone = Ember.isNone;
+
+/**
+ @module ember-data
+*/
+
+function asyncBelongsTo(type, options, meta) {
+ return Ember.computed(function(key, value) {
+ var data = get(this, 'data'),
+ store = get(this, 'store'),
+ promiseLabel = "DS: Async belongsTo " + this + " : " + key;
+
+ if (arguments.length === 2) {
+ Ember.assert("You can only add a '" + type + "' record to this relationship", !value || value instanceof store.modelFor(type));
+ return value === undefined ? null : DS.PromiseObject.create({ promise: Ember.RSVP.resolve(value, promiseLabel) });
+ }
+
+ var link = data.links && data.links[key],
+ belongsTo = data[key];
+
+ if(!isNone(belongsTo)) {
+ var promise = store.fetchRecord(belongsTo) || Ember.RSVP.resolve(belongsTo, promiseLabel);
+ return DS.PromiseObject.create({ promise: promise});
+ } else if (link) {
+ var resolver = Ember.RSVP.defer("DS: Async belongsTo (link) " + this + " : " + key);
+ store.findBelongsTo(this, link, meta, resolver);
+ return DS.PromiseObject.create({ promise: resolver.promise });
+ } else {
+ return null;
+ }
+ }).property('data').meta(meta);
+}
+
+/**
+ `DS.belongsTo` is used to define One-To-One and One-To-Many
+ relationships on a [DS.Model](DS.Model.html).
+
+
+ `DS.belongsTo` takes an optional hash as a second parameter, currently
+ supported options are:
+
+ - `async`: A boolean value used to explicitly declare this to be an async relationship.
+ - `inverse`: A string used to identify the inverse property on a
+ related model in a One-To-Many relationship. See [Explicit Inverses](#toc_explicit-inverses)
+
+ #### One-To-One
+ To declare a one-to-one relationship between two models, use
+ `DS.belongsTo`:
+
+ ```javascript
+ App.User = DS.Model.extend({
+ profile: DS.belongsTo('profile')
+ });
+
+ App.Profile = DS.Model.extend({
+ user: DS.belongsTo('user')
+ });
+ ```
+
+ #### One-To-Many
+ To declare a one-to-many relationship between two models, use
+ `DS.belongsTo` in combination with `DS.hasMany`, like this:
+
+ ```javascript
+ App.Post = DS.Model.extend({
+ comments: DS.hasMany('comment')
+ });
+
+ App.Comment = DS.Model.extend({
+ post: DS.belongsTo('post')
+ });
+ ```
+
+ @namespace
+ @method belongsTo
+ @for DS
+ @param {String or DS.Model} type the model type of the relationship
+ @param {Object} options a hash of options
+ @return {Ember.computed} relationship
+*/
+DS.belongsTo = function(type, options) {
+ if (typeof type === 'object') {
+ options = type;
+ type = undefined;
+ } else {
+ Ember.assert("The first argument DS.belongsTo must be a model type or string, like DS.belongsTo(App.Person)", !!type && (typeof type === 'string' || DS.Model.detect(type)));
+ }
+
+ options = options || {};
+
+ var meta = { type: type, isRelationship: true, options: options, kind: 'belongsTo' };
+
+ if (options.async) {
+ return asyncBelongsTo(type, options, meta);
+ }
+
+ return Ember.computed(function(key, value) {
+ var data = get(this, 'data'),
+ store = get(this, 'store'), belongsTo, typeClass;
+
+ if (typeof type === 'string') {
+ typeClass = store.modelFor(type);
+ } else {
+ typeClass = type;
+ }
+
+ if (arguments.length === 2) {
+ Ember.assert("You can only add a '" + type + "' record to this relationship", !value || value instanceof typeClass);
+ return value === undefined ? null : value;
+ }
+
+ belongsTo = data[key];
+
+ if (isNone(belongsTo)) { return null; }
+
+ store.fetchRecord(belongsTo);
+
+ return belongsTo;
+ }).property('data').meta(meta);
+};
+
+/**
+ These observers observe all `belongsTo` relationships on the record. See
+ `relationships/ext` to see how these observers get their dependencies.
+
+ @class Model
+ @namespace DS
+*/
+DS.Model.reopen({
+
+ /**
+ @method belongsToWillChange
+ @private
+ @static
+ @param record
+ @param key
+ */
+ belongsToWillChange: Ember.beforeObserver(function(record, key) {
+ if (get(record, 'isLoaded')) {
+ var oldParent = get(record, key);
+
+ if (oldParent) {
+ var store = get(record, 'store'),
+ change = DS.RelationshipChange.createChange(record, oldParent, store, { key: key, kind: "belongsTo", changeType: "remove" });
+
+ change.sync();
+ this._changesToSync[key] = change;
+ }
+ }
+ }),
+
+ /**
+ @method belongsToDidChange
+ @private
+ @static
+ @param record
+ @param key
+ */
+ belongsToDidChange: Ember.immediateObserver(function(record, key) {
+ if (get(record, 'isLoaded')) {
+ var newParent = get(record, key);
+
+ if (newParent) {
+ var store = get(record, 'store'),
+ change = DS.RelationshipChange.createChange(record, newParent, store, { key: key, kind: "belongsTo", changeType: "add" });
+
+ change.sync();
+ }
+ }
+
+ delete this._changesToSync[key];
+ })
+});
+
+})();
+
+
+
+(function() {
+/**
+ @module ember-data
+*/
+
+var get = Ember.get, set = Ember.set, setProperties = Ember.setProperties;
+
+function asyncHasMany(type, options, meta) {
+ return Ember.computed(function(key, value) {
+ var relationship = this._relationships[key],
+ promiseLabel = "DS: Async hasMany " + this + " : " + key;
+
+ if (!relationship) {
+ var resolver = Ember.RSVP.defer(promiseLabel);
+ relationship = buildRelationship(this, key, options, function(store, data) {
+ var link = data.links && data.links[key];
+ var rel;
+ if (link) {
+ rel = store.findHasMany(this, link, meta, resolver);
+ } else {
+ rel = store.findMany(this, data[key], meta.type, resolver);
+ }
+ // cache the promise so we can use it
+ // when we come back and don't need to rebuild
+ // the relationship.
+ set(rel, 'promise', resolver.promise);
+ return rel;
+ });
+ }
+
+ var promise = relationship.get('promise').then(function() {
+ return relationship;
+ }, null, "DS: Async hasMany records received");
+
+ return DS.PromiseArray.create({ promise: promise });
+ }).property('data').meta(meta);
+}
+
+function buildRelationship(record, key, options, callback) {
+ var rels = record._relationships;
+
+ if (rels[key]) { return rels[key]; }
+
+ var data = get(record, 'data'),
+ store = get(record, 'store');
+
+ var relationship = rels[key] = callback.call(record, store, data);
+
+ return setProperties(relationship, {
+ owner: record, name: key, isPolymorphic: options.polymorphic
+ });
+}
+
+function hasRelationship(type, options) {
+ options = options || {};
+
+ var meta = { type: type, isRelationship: true, options: options, kind: 'hasMany' };
+
+ if (options.async) {
+ return asyncHasMany(type, options, meta);
+ }
+
+ return Ember.computed(function(key, value) {
+ return buildRelationship(this, key, options, function(store, data) {
+ var records = data[key];
+ Ember.assert("You looked up the '" + key + "' relationship on '" + this + "' but some of the associated records were not loaded. Either make sure they are all loaded together with the parent record, or specify that the relationship is async (`DS.hasMany({ async: true })`)", Ember.A(records).everyProperty('isEmpty', false));
+ return store.findMany(this, data[key], meta.type);
+ });
+ }).property('data').meta(meta);
+}
+
+/**
+ `DS.hasMany` is used to define One-To-Many and Many-To-Many
+ relationships on a [DS.Model](DS.Model.html).
+
+ `DS.hasMany` takes an optional hash as a second parameter, currently
+ supported options are:
+
+ - `async`: A boolean value used to explicitly declare this to be an async relationship.
+ - `inverse`: A string used to identify the inverse property on a related model.
+
+ #### One-To-Many
+ To declare a one-to-many relationship between two models, use
+ `DS.belongsTo` in combination with `DS.hasMany`, like this:
+
+ ```javascript
+ App.Post = DS.Model.extend({
+ comments: DS.hasMany('comment')
+ });
+
+ App.Comment = DS.Model.extend({
+ post: DS.belongsTo('post')
+ });
+ ```
+
+ #### Many-To-Many
+ To declare a many-to-many relationship between two models, use
+ `DS.hasMany`:
+
+ ```javascript
+ App.Post = DS.Model.extend({
+ tags: DS.hasMany('tag')
+ });
+
+ App.Tag = DS.Model.extend({
+ posts: DS.hasMany('post')
+ });
+ ```
+
+ #### Explicit Inverses
+
+ Ember Data will do its best to discover which relationships map to
+ one another. In the one-to-many code above, for example, Ember Data
+ can figure out that changing the `comments` relationship should update
+ the `post` relationship on the inverse because post is the only
+ relationship to that model.
+
+ However, sometimes you may have multiple `belongsTo`/`hasManys` for the
+ same type. You can specify which property on the related model is
+ the inverse using `DS.hasMany`'s `inverse` option:
+
+ ```javascript
+ var belongsTo = DS.belongsTo,
+ hasMany = DS.hasMany;
+
+ App.Comment = DS.Model.extend({
+ onePost: belongsTo('post'),
+ twoPost: belongsTo('post'),
+ redPost: belongsTo('post'),
+ bluePost: belongsTo('post')
+ });
+
+ App.Post = DS.Model.extend({
+ comments: hasMany('comment', {
+ inverse: 'redPost'
+ })
+ });
+ ```
+
+ You can also specify an inverse on a `belongsTo`, which works how
+ you'd expect.
+
+ @namespace
+ @method hasMany
+ @for DS
+ @param {String or DS.Model} type the model type of the relationship
+ @param {Object} options a hash of options
+ @return {Ember.computed} relationship
+*/
+DS.hasMany = function(type, options) {
+ if (typeof type === 'object') {
+ options = type;
+ type = undefined;
+ }
+ return hasRelationship(type, options);
+};
+
+})();
+
+
+
+(function() {
+var get = Ember.get, set = Ember.set;
+
+/**
+ @module ember-data
+*/
+
+/*
+ This file defines several extensions to the base `DS.Model` class that
+ add support for one-to-many relationships.
+*/
+
+/**
+ @class Model
+ @namespace DS
+*/
+DS.Model.reopen({
+
+ /**
+ This Ember.js hook allows an object to be notified when a property
+ is defined.
+
+ In this case, we use it to be notified when an Ember Data user defines a
+ belongs-to relationship. In that case, we need to set up observers for
+ each one, allowing us to track relationship changes and automatically
+ reflect changes in the inverse has-many array.
+
+ This hook passes the class being set up, as well as the key and value
+ being defined. So, for example, when the user does this:
+
+ ```javascript
+ DS.Model.extend({
+ parent: DS.belongsTo('user')
+ });
+ ```
+
+ This hook would be called with "parent" as the key and the computed
+ property returned by `DS.belongsTo` as the value.
+
+ @method didDefineProperty
+ @param proto
+ @param key
+ @param value
+ */
+ didDefineProperty: function(proto, key, value) {
+ // Check if the value being set is a computed property.
+ if (value instanceof Ember.Descriptor) {
+
+ // If it is, get the metadata for the relationship. This is
+ // populated by the `DS.belongsTo` helper when it is creating
+ // the computed property.
+ var meta = value.meta();
+
+ if (meta.isRelationship && meta.kind === 'belongsTo') {
+ Ember.addObserver(proto, key, null, 'belongsToDidChange');
+ Ember.addBeforeObserver(proto, key, null, 'belongsToWillChange');
+ }
+
+ meta.parentType = proto.constructor;
+ }
+ }
+});
+
+/*
+ These DS.Model extensions add class methods that provide relationship
+ introspection abilities about relationships.
+
+ A note about the computed properties contained here:
+
+ **These properties are effectively sealed once called for the first time.**
+ To avoid repeatedly doing expensive iteration over a model's fields, these
+ values are computed once and then cached for the remainder of the runtime of
+ your application.
+
+ If your application needs to modify a class after its initial definition
+ (for example, using `reopen()` to add additional attributes), make sure you
+ do it before using your model with the store, which uses these properties
+ extensively.
+*/
+
+DS.Model.reopenClass({
+ /**
+ For a given relationship name, returns the model type of the relationship.
+
+ For example, if you define a model like this:
+
+ ```javascript
+ App.Post = DS.Model.extend({
+ comments: DS.hasMany('comment')
+ });
+ ```
+
+ Calling `App.Post.typeForRelationship('comments')` will return `App.Comment`.
+
+ @method typeForRelationship
+ @static
+ @param {String} name the name of the relationship
+ @return {subclass of DS.Model} the type of the relationship, or undefined
+ */
+ typeForRelationship: function(name) {
+ var relationship = get(this, 'relationshipsByName').get(name);
+ return relationship && relationship.type;
+ },
+
+ inverseFor: function(name) {
+ var inverseType = this.typeForRelationship(name);
+
+ if (!inverseType) { return null; }
+
+ var options = this.metaForProperty(name).options;
+
+ if (options.inverse === null) { return null; }
+
+ var inverseName, inverseKind;
+
+ if (options.inverse) {
+ inverseName = options.inverse;
+ inverseKind = Ember.get(inverseType, 'relationshipsByName').get(inverseName).kind;
+ } else {
+ var possibleRelationships = findPossibleInverses(this, inverseType);
+
+ if (possibleRelationships.length === 0) { return null; }
+
+ Ember.assert("You defined the '" + name + "' relationship on " + this + ", but multiple possible inverse relationships of type " + this + " were found on " + inverseType + ". Look at http://emberjs.com/guides/models/defining-models/#toc_explicit-inverses for how to explicitly specify inverses", possibleRelationships.length === 1);
+
+ inverseName = possibleRelationships[0].name;
+ inverseKind = possibleRelationships[0].kind;
+ }
+
+ function findPossibleInverses(type, inverseType, possibleRelationships) {
+ possibleRelationships = possibleRelationships || [];
+
+ var relationshipMap = get(inverseType, 'relationships');
+ if (!relationshipMap) { return; }
+
+ var relationships = relationshipMap.get(type);
+ if (relationships) {
+ possibleRelationships.push.apply(possibleRelationships, relationshipMap.get(type));
+ }
+
+ if (type.superclass) {
+ findPossibleInverses(type.superclass, inverseType, possibleRelationships);
+ }
+
+ return possibleRelationships;
+ }
+
+ return {
+ type: inverseType,
+ name: inverseName,
+ kind: inverseKind
+ };
+ },
+
+ /**
+ The model's relationships as a map, keyed on the type of the
+ relationship. The value of each entry is an array containing a descriptor
+ for each relationship with that type, describing the name of the relationship
+ as well as the type.
+
+ For example, given the following model definition:
+
+ ```javascript
+ App.Blog = DS.Model.extend({
+ users: DS.hasMany('user'),
+ owner: DS.belongsTo('user'),
+ posts: DS.hasMany('post')
+ });
+ ```
+
+ This computed property would return a map describing these
+ relationships, like this:
+
+ ```javascript
+ var relationships = Ember.get(App.Blog, 'relationships');
+ relationships.get(App.User);
+ //=> [ { name: 'users', kind: 'hasMany' },
+ // { name: 'owner', kind: 'belongsTo' } ]
+ relationships.get(App.Post);
+ //=> [ { name: 'posts', kind: 'hasMany' } ]
+ ```
+
+ @property relationships
+ @static
+ @type Ember.Map
+ @readOnly
+ */
+ relationships: Ember.computed(function() {
+ var map = new Ember.MapWithDefault({
+ defaultValue: function() { return []; }
+ });
+
+ // Loop through each computed property on the class
+ this.eachComputedProperty(function(name, meta) {
+
+ // If the computed property is a relationship, add
+ // it to the map.
+ if (meta.isRelationship) {
+ if (typeof meta.type === 'string') {
+ meta.type = this.store.modelFor(meta.type);
+ }
+
+ var relationshipsForType = map.get(meta.type);
+
+ relationshipsForType.push({ name: name, kind: meta.kind });
+ }
+ });
+
+ return map;
+ }),
+
+ /**
+ A hash containing lists of the model's relationships, grouped
+ by the relationship kind. For example, given a model with this
+ definition:
+
+ ```javascript
+ App.Blog = DS.Model.extend({
+ users: DS.hasMany('user'),
+ owner: DS.belongsTo('user'),
+
+ posts: DS.hasMany('post')
+ });
+ ```
+
+ This property would contain the following:
+
+ ```javascript
+ var relationshipNames = Ember.get(App.Blog, 'relationshipNames');
+ relationshipNames.hasMany;
+ //=> ['users', 'posts']
+ relationshipNames.belongsTo;
+ //=> ['owner']
+ ```
+
+ @property relationshipNames
+ @static
+ @type Object
+ @readOnly
+ */
+ relationshipNames: Ember.computed(function() {
+ var names = { hasMany: [], belongsTo: [] };
+
+ this.eachComputedProperty(function(name, meta) {
+ if (meta.isRelationship) {
+ names[meta.kind].push(name);
+ }
+ });
+
+ return names;
+ }),
+
+ /**
+ An array of types directly related to a model. Each type will be
+ included once, regardless of the number of relationships it has with
+ the model.
+
+ For example, given a model with this definition:
+
+ ```javascript
+ App.Blog = DS.Model.extend({
+ users: DS.hasMany('user'),
+ owner: DS.belongsTo('user'),
+
+ posts: DS.hasMany('post')
+ });
+ ```
+
+ This property would contain the following:
+
+ ```javascript
+ var relatedTypes = Ember.get(App.Blog, 'relatedTypes');
+ //=> [ App.User, App.Post ]
+ ```
+
+ @property relatedTypes
+ @static
+ @type Ember.Array
+ @readOnly
+ */
+ relatedTypes: Ember.computed(function() {
+ var type,
+ types = Ember.A();
+
+ // Loop through each computed property on the class,
+ // and create an array of the unique types involved
+ // in relationships
+ this.eachComputedProperty(function(name, meta) {
+ if (meta.isRelationship) {
+ type = meta.type;
+
+ if (typeof type === 'string') {
+ type = get(this, type, false) || this.store.modelFor(type);
+ }
+
+ Ember.assert("You specified a hasMany (" + meta.type + ") on " + meta.parentType + " but " + meta.type + " was not found.", type);
+
+ if (!types.contains(type)) {
+ Ember.assert("Trying to sideload " + name + " on " + this.toString() + " but the type doesn't exist.", !!type);
+ types.push(type);
+ }
+ }
+ });
+
+ return types;
+ }),
+
+ /**
+ A map whose keys are the relationships of a model and whose values are
+ relationship descriptors.
+
+ For example, given a model with this
+ definition:
+
+ ```javascript
+ App.Blog = DS.Model.extend({
+ users: DS.hasMany('user'),
+ owner: DS.belongsTo('user'),
+
+ posts: DS.hasMany('post')
+ });
+ ```
+
+ This property would contain the following:
+
+ ```javascript
+ var relationshipsByName = Ember.get(App.Blog, 'relationshipsByName');
+ relationshipsByName.get('users');
+ //=> { key: 'users', kind: 'hasMany', type: App.User }
+ relationshipsByName.get('owner');
+ //=> { key: 'owner', kind: 'belongsTo', type: App.User }
+ ```
+
+ @property relationshipsByName
+ @static
+ @type Ember.Map
+ @readOnly
+ */
+ relationshipsByName: Ember.computed(function() {
+ var map = Ember.Map.create(), type;
+
+ this.eachComputedProperty(function(name, meta) {
+ if (meta.isRelationship) {
+ meta.key = name;
+ type = meta.type;
+
+ if (!type && meta.kind === 'hasMany') {
+ type = Ember.String.singularize(name);
+ } else if (!type) {
+ type = name;
+ }
+
+ if (typeof type === 'string') {
+ meta.type = this.store.modelFor(type);
+ }
+
+ map.set(name, meta);
+ }
+ });
+
+ return map;
+ }),
+
+ /**
+ A map whose keys are the fields of the model and whose values are strings
+ describing the kind of the field. A model's fields are the union of all of its
+ attributes and relationships.
+
+ For example:
+
+ ```javascript
+
+ App.Blog = DS.Model.extend({
+ users: DS.hasMany('user'),
+ owner: DS.belongsTo('user'),
+
+ posts: DS.hasMany('post'),
+
+ title: DS.attr('string')
+ });
+
+ var fields = Ember.get(App.Blog, 'fields');
+ fields.forEach(function(field, kind) {
+ console.log(field, kind);
+ });
+
+ // prints:
+ // users, hasMany
+ // owner, belongsTo
+ // posts, hasMany
+ // title, attribute
+ ```
+
+ @property fields
+ @static
+ @type Ember.Map
+ @readOnly
+ */
+ fields: Ember.computed(function() {
+ var map = Ember.Map.create();
+
+ this.eachComputedProperty(function(name, meta) {
+ if (meta.isRelationship) {
+ map.set(name, meta.kind);
+ } else if (meta.isAttribute) {
+ map.set(name, 'attribute');
+ }
+ });
+
+ return map;
+ }),
+
+ /**
+ Given a callback, iterates over each of the relationships in the model,
+ invoking the callback with the name of each relationship and its relationship
+ descriptor.
+
+ @method eachRelationship
+ @static
+ @param {Function} callback the callback to invoke
+ @param {any} binding the value to which the callback's `this` should be bound
+ */
+ eachRelationship: function(callback, binding) {
+ get(this, 'relationshipsByName').forEach(function(name, relationship) {
+ callback.call(binding, name, relationship);
+ });
+ },
+
+ /**
+ Given a callback, iterates over each of the types related to a model,
+ invoking the callback with the related type's class. Each type will be
+ returned just once, regardless of how many different relationships it has
+ with a model.
+
+ @method eachRelatedType
+ @static
+ @param {Function} callback the callback to invoke
+ @param {any} binding the value to which the callback's `this` should be bound
+ */
+ eachRelatedType: function(callback, binding) {
+ get(this, 'relatedTypes').forEach(function(type) {
+ callback.call(binding, type);
+ });
+ }
+});
+
+DS.Model.reopen({
+ /**
+ Given a callback, iterates over each of the relationships in the model,
+ invoking the callback with the name of each relationship and its relationship
+ descriptor.
+
+ @method eachRelationship
+ @param {Function} callback the callback to invoke
+ @param {any} binding the value to which the callback's `this` should be bound
+ */
+ eachRelationship: function(callback, binding) {
+ this.constructor.eachRelationship(callback, binding);
+ }
+});
+
+})();
+
+
+
+(function() {
+/**
+ @module ember-data
+*/
+
+})();
+
+
+
+(function() {
+/**
+ @module ember-data
+*/
+
+var get = Ember.get, set = Ember.set;
+var forEach = Ember.EnumerableUtils.forEach;
+
+/**
+ @class RecordArrayManager
+ @namespace DS
+ @private
+ @extends Ember.Object
+*/
+DS.RecordArrayManager = Ember.Object.extend({
+ init: function() {
+ this.filteredRecordArrays = Ember.MapWithDefault.create({
+ defaultValue: function() { return []; }
+ });
+
+ this.changedRecords = [];
+ },
+
+ recordDidChange: function(record) {
+ if (this.changedRecords.push(record) !== 1) { return; }
+
+ Ember.run.schedule('actions', this, this.updateRecordArrays);
+ },
+
+ recordArraysForRecord: function(record) {
+ record._recordArrays = record._recordArrays || Ember.OrderedSet.create();
+ return record._recordArrays;
+ },
+
+ /**
+ This method is invoked whenever data is loaded into the store by the
+ adapter or updated by the adapter, or when a record has changed.
+
+ It updates all record arrays that a record belongs to.
+
+ To avoid thrashing, it only runs at most once per run loop.
+
+ @method updateRecordArrays
+ @param {Class} type
+ @param {Number|String} clientId
+ */
+ updateRecordArrays: function() {
+ forEach(this.changedRecords, function(record) {
+ if (get(record, 'isDeleted')) {
+ this._recordWasDeleted(record);
+ } else {
+ this._recordWasChanged(record);
+ }
+ }, this);
+
+ this.changedRecords.length = 0;
+ },
+
+ _recordWasDeleted: function (record) {
+ var recordArrays = record._recordArrays;
+
+ if (!recordArrays) { return; }
+
+ forEach(recordArrays, function(array) {
+ array.removeRecord(record);
+ });
+ },
+
+ _recordWasChanged: function (record) {
+ var type = record.constructor,
+ recordArrays = this.filteredRecordArrays.get(type),
+ filter;
+
+ forEach(recordArrays, function(array) {
+ filter = get(array, 'filterFunction');
+ this.updateRecordArray(array, filter, type, record);
+ }, this);
+
+ // loop through all manyArrays containing an unloaded copy of this
+ // clientId and notify them that the record was loaded.
+ var manyArrays = record._loadingRecordArrays;
+
+ if (manyArrays) {
+ for (var i=0, l=manyArrays.length; i<l; i++) {
+ manyArrays[i].loadedRecord();
+ }
+
+ record._loadingRecordArrays = [];
+ }
+ },
+
+ /**
+ Update an individual filter.
+
+ @method updateRecordArray
+ @param {DS.FilteredRecordArray} array
+ @param {Function} filter
+ @param {Class} type
+ @param {Number|String} clientId
+ */
+ updateRecordArray: function(array, filter, type, record) {
+ var shouldBeInArray;
+
+ if (!filter) {
+ shouldBeInArray = true;
+ } else {
+ shouldBeInArray = filter(record);
+ }
+
+ var recordArrays = this.recordArraysForRecord(record);
+
+ if (shouldBeInArray) {
+ recordArrays.add(array);
+ array.addRecord(record);
+ } else if (!shouldBeInArray) {
+ recordArrays.remove(array);
+ array.removeRecord(record);
+ }
+ },
+
+ /**
+ This method is invoked if the `filterFunction` property is
+ changed on a `DS.FilteredRecordArray`.
+
+ It essentially re-runs the filter from scratch. This same
+ method is invoked when the filter is created in th first place.
+
+ @method updateFilter
+ @param array
+ @param type
+ @param filter
+ */
+ updateFilter: function(array, type, filter) {
+ var typeMap = this.store.typeMapFor(type),
+ records = typeMap.records, record;
+
+ for (var i=0, l=records.length; i<l; i++) {
+ record = records[i];
+
+ if (!get(record, 'isDeleted') && !get(record, 'isEmpty')) {
+ this.updateRecordArray(array, filter, type, record);
+ }
+ }
+ },
+
+ /**
+ Create a `DS.ManyArray` for a type and list of record references, and index
+ the `ManyArray` under each reference. This allows us to efficiently remove
+ records from `ManyArray`s when they are deleted.
+
+ @method createManyArray
+ @param {Class} type
+ @param {Array} references
+ @return {DS.ManyArray}
+ */
+ createManyArray: function(type, records) {
+ var manyArray = DS.ManyArray.create({
+ type: type,
+ content: records,
+ store: this.store
+ });
+
+ forEach(records, function(record) {
+ var arrays = this.recordArraysForRecord(record);
+ arrays.add(manyArray);
+ }, this);
+
+ return manyArray;
+ },
+
+ /**
+ Create a `DS.RecordArray` for a type and register it for updates.
+
+ @method createRecordArray
+ @param {Class} type
+ @return {DS.RecordArray}
+ */
+ createRecordArray: function(type) {
+ var array = DS.RecordArray.create({
+ type: type,
+ content: Ember.A(),
+ store: this.store,
+ isLoaded: true
+ });
+
+ this.registerFilteredRecordArray(array, type);
+
+ return array;
+ },
+
+ /**
+ Create a `DS.FilteredRecordArray` for a type and register it for updates.
+
+ @method createFilteredRecordArray
+ @param {Class} type
+ @param {Function} filter
+ @return {DS.FilteredRecordArray}
+ */
+ createFilteredRecordArray: function(type, filter) {
+ var array = DS.FilteredRecordArray.create({
+ type: type,
+ content: Ember.A(),
+ store: this.store,
+ manager: this,
+ filterFunction: filter
+ });
+
+ this.registerFilteredRecordArray(array, type, filter);
+
+ return array;
+ },
+
+ /**
+ Create a `DS.AdapterPopulatedRecordArray` for a type with given query.
+
+ @method createAdapterPopulatedRecordArray
+ @param {Class} type
+ @param {Object} query
+ @return {DS.AdapterPopulatedRecordArray}
+ */
+ createAdapterPopulatedRecordArray: function(type, query) {
+ return DS.AdapterPopulatedRecordArray.create({
+ type: type,
+ query: query,
+ content: Ember.A(),
+ store: this.store
+ });
+ },
+
+ /**
+ Register a RecordArray for a given type to be backed by
+ a filter function. This will cause the array to update
+ automatically when records of that type change attribute
+ values or states.
+
+ @method registerFilteredRecordArray
+ @param {DS.RecordArray} array
+ @param {Class} type
+ @param {Function} filter
+ */
+ registerFilteredRecordArray: function(array, type, filter) {
+ var recordArrays = this.filteredRecordArrays.get(type);
+ recordArrays.push(array);
+
+ this.updateFilter(array, type, filter);
+ },
+
+ // Internally, we maintain a map of all unloaded IDs requested by
+ // a ManyArray. As the adapter loads data into the store, the
+ // store notifies any interested ManyArrays. When the ManyArray's
+ // total number of loading records drops to zero, it becomes
+ // `isLoaded` and fires a `didLoad` event.
+ registerWaitingRecordArray: function(record, array) {
+ var loadingRecordArrays = record._loadingRecordArrays || [];
+ loadingRecordArrays.push(array);
+ record._loadingRecordArrays = loadingRecordArrays;
+ }
+});
+
+})();
+
+
+
+(function() {
+/**
+ @module ember-data
+*/
+
+var get = Ember.get, set = Ember.set;
+var map = Ember.ArrayPolyfills.map;
+
+var errorProps = ['description', 'fileName', 'lineNumber', 'message', 'name', 'number', 'stack'];
+
+/**
+ A `DS.InvalidError` is used by an adapter to signal the external API
+ was unable to process a request because the content was not
+ semantically correct or meaningful per the API. Usually this means a
+ record failed some form of server side validation. When a promise
+ from an adapter is rejected with a `DS.InvalidError` the record will
+ transition to the `invalid` state and the errors will be set to the
+ `errors` property on the record.
+
+ Example
+
+ ```javascript
+ App.ApplicationAdapter = DS.RESTAdapter.extend({
+ ajaxError: function(jqXHR) {
+ var error = this._super(jqXHR);
+
+ if (jqXHR && jqXHR.status === 422) {
+ var jsonErrors = Ember.$.parseJSON(jqXHR.responseText)["errors"];
+ return new DS.InvalidError(jsonErrors);
+ } else {
+ return error;
+ }
+ }
+ });
+ ```
+
+ @class InvalidError
+ @namespace DS
+*/
+DS.InvalidError = function(errors) {
+ var tmp = Error.prototype.constructor.call(this, "The backend rejected the commit because it was invalid: " + Ember.inspect(errors));
+ this.errors = errors;
+
+ for (var i=0, l=errorProps.length; i<l; i++) {
+ this[errorProps[i]] = tmp[errorProps[i]];
+ }
+};
+DS.InvalidError.prototype = Ember.create(Error.prototype);
+
+/**
+ An adapter is an object that receives requests from a store and
+ translates them into the appropriate action to take against your
+ persistence layer. The persistence layer is usually an HTTP API, but
+ may be anything, such as the browser's local storage. Typically the
+ adapter is not invoked directly instead its functionality is accessed
+ through the `store`.
+
+ ### Creating an Adapter
+
+ First, create a new subclass of `DS.Adapter`:
+
+ ```javascript
+ App.MyAdapter = DS.Adapter.extend({
+ // ...your code here
+ });
+ ```
+
+ To tell your store which adapter to use, set its `adapter` property:
+
+ ```javascript
+ App.store = DS.Store.create({
+ adapter: 'MyAdapter'
+ });
+ ```
+
+ `DS.Adapter` is an abstract base class that you should override in your
+ application to customize it for your backend. The minimum set of methods
+ that you should implement is:
+
+ * `find()`
+ * `createRecord()`
+ * `updateRecord()`
+ * `deleteRecord()`
+ * `findAll()`
+ * `findQuery()`
+
+ To improve the network performance of your application, you can optimize
+ your adapter by overriding these lower-level methods:
+
+ * `findMany()`
+
+
+ For an example implementation, see `DS.RESTAdapter`, the
+ included REST adapter.
+
+ @class Adapter
+ @namespace DS
+ @extends Ember.Object
+*/
+
+DS.Adapter = Ember.Object.extend({
+
+ /**
+ If you would like your adapter to use a custom serializer you can
+ set the `defaultSerializer` property to be the name of the custom
+ serializer.
+
+ Note the `defaultSerializer` serializer has a lower priority then
+ a model specific serializer (i.e. `PostSerializer`) or the
+ `application` serializer.
+
+ ```javascript
+ var DjangoAdapter = DS.Adapter.extend({
+ defaultSerializer: 'django'
+ });
+ ```
+
+ @property defaultSerializer
+ @type {String}
+ */
+
+ /**
+ The `find()` method is invoked when the store is asked for a record that
+ has not previously been loaded. In response to `find()` being called, you
+ should query your persistence layer for a record with the given ID. Once
+ found, you can asynchronously call the store's `push()` method to push
+ the record into the store.
+
+ Here is an example `find` implementation:
+
+ ```javascript
+ App.ApplicationAdapter = DS.Adapter.extend({
+ find: function(store, type, id) {
+ var url = [type, id].join('/');
+
+ return new Ember.RSVP.Promise(function(resolve, reject) {
+ jQuery.getJSON(url).then(function(data) {
+ Ember.run(null, resolve, data);
+ }, function(jqXHR) {
+ jqXHR.then = null; // tame jQuery's ill mannered promises
+ Ember.run(null, reject, jqXHR);
+ });
+ });
+ }
+ });
+ ```
+
+ @method find
+ @param {DS.Store} store
+ @param {subclass of DS.Model} type
+ @param {String} id
+ @return {Promise} promise
+ */
+ find: Ember.required(Function),
+
+ /**
+ The `findAll()` method is called when you call `find` on the store
+ without an ID (i.e. `store.find('post')`).
+
+ Example
+
+ ```javascript
+ App.ApplicationAdapter = DS.Adapter.extend({
+ findAll: function(store, type, sinceToken) {
+ var url = type;
+ var query = { since: sinceToken };
+ return new Ember.RSVP.Promise(function(resolve, reject) {
+ jQuery.getJSON(url, query).then(function(data) {
+ Ember.run(null, resolve, data);
+ }, function(jqXHR) {
+ jqXHR.then = null; // tame jQuery's ill mannered promises
+ Ember.run(null, reject, jqXHR);
+ });
+ });
+ }
+ });
+ ```
+
+ @private
+ @method findAll
+ @param {DS.Store} store
+ @param {subclass of DS.Model} type
+ @param {String} sinceToken
+ @return {Promise} promise
+ */
+ findAll: null,
+
+ /**
+ This method is called when you call `find` on the store with a
+ query object as the second parameter (i.e. `store.find('person', {
+ page: 1 })`).
+
+ Example
+
+ ```javascript
+ App.ApplicationAdapter = DS.Adapter.extend({
+ findQuery: function(store, type, query) {
+ var url = type;
+ return new Ember.RSVP.Promise(function(resolve, reject) {
+ jQuery.getJSON(url, query).then(function(data) {
+ Ember.run(null, resolve, data);
+ }, function(jqXHR) {
+ jqXHR.then = null; // tame jQuery's ill mannered promises
+ Ember.run(null, reject, jqXHR);
+ });
+ });
+ }
+ });
+ ```
+
+ @private
+ @method findQuery
+ @param {DS.Store} store
+ @param {subclass of DS.Model} type
+ @param {Object} query
+ @param {DS.AdapterPopulatedRecordArray} recordArray
+ @return {Promise} promise
+ */
+ findQuery: null,
+
+ /**
+ If the globally unique IDs for your records should be generated on the client,
+ implement the `generateIdForRecord()` method. This method will be invoked
+ each time you create a new record, and the value returned from it will be
+ assigned to the record's `primaryKey`.
+
+ Most traditional REST-like HTTP APIs will not use this method. Instead, the ID
+ of the record will be set by the server, and your adapter will update the store
+ with the new ID when it calls `didCreateRecord()`. Only implement this method if
+ you intend to generate record IDs on the client-side.
+
+ The `generateIdForRecord()` method will be invoked with the requesting store as
+ the first parameter and the newly created record as the second parameter:
+
+ ```javascript
+ generateIdForRecord: function(store, record) {
+ var uuid = App.generateUUIDWithStatisticallyLowOddsOfCollision();
+ return uuid;
+ }
+ ```
+
+ @method generateIdForRecord
+ @param {DS.Store} store
+ @param {DS.Model} record
+ @return {String|Number} id
+ */
+ generateIdForRecord: null,
+
+ /**
+ Proxies to the serializer's `serialize` method.
+
+ Example
+
+ ```javascript
+ App.ApplicationAdapter = DS.Adapter.extend({
+ createRecord: function(store, type, record) {
+ var data = this.serialize(record, { includeId: true });
+ var url = type;
+
+ // ...
+ }
+ });
+ ```
+
+ @method serialize
+ @param {DS.Model} record
+ @param {Object} options
+ @return {Object} serialized record
+ */
+ serialize: function(record, options) {
+ return get(record, 'store').serializerFor(record.constructor.typeKey).serialize(record, options);
+ },
+
+ /**
+ Implement this method in a subclass to handle the creation of
+ new records.
+
+ Serializes the record and send it to the server.
+
+ Example
+
+ ```javascript
+ App.ApplicationAdapter = DS.Adapter.extend({
+ createRecord: function(store, type, record) {
+ var data = this.serialize(record, { includeId: true });
+ var url = type;
+
+ return new Ember.RSVP.Promise(function(resolve, reject) {
+ jQuery.ajax({
+ type: 'POST',
+ url: url,
+ dataType: 'json',
+ data: data
+ }).then(function(data) {
+ Ember.run(null, resolve, data);
+ }, function(jqXHR) {
+ jqXHR.then = null; // tame jQuery's ill mannered promises
+ Ember.run(null, reject, jqXHR);
+ });
+ });
+ }
+ });
+ ```
+
+ @method createRecord
+ @param {DS.Store} store
+ @param {subclass of DS.Model} type the DS.Model class of the record
+ @param {DS.Model} record
+ @return {Promise} promise
+ */
+ createRecord: Ember.required(Function),
+
+ /**
+ Implement this method in a subclass to handle the updating of
+ a record.
+
+ Serializes the record update and send it to the server.
+
+ Example
+
+ ```javascript
+ App.ApplicationAdapter = DS.Adapter.extend({
+ updateRecord: function(store, type, record) {
+ var data = this.serialize(record, { includeId: true });
+ var id = record.get('id');
+ var url = [type, id].join('/');
+
+ return new Ember.RSVP.Promise(function(resolve, reject) {
+ jQuery.ajax({
+ type: 'PUT',
+ url: url,
+ dataType: 'json',
+ data: data
+ }).then(function(data) {
+ Ember.run(null, resolve, data);
+ }, function(jqXHR) {
+ jqXHR.then = null; // tame jQuery's ill mannered promises
+ Ember.run(null, reject, jqXHR);
+ });
+ });
+ }
+ });
+ ```
+
+ @method updateRecord
+ @param {DS.Store} store
+ @param {subclass of DS.Model} type the DS.Model class of the record
+ @param {DS.Model} record
+ @return {Promise} promise
+ */
+ updateRecord: Ember.required(Function),
+
+ /**
+ Implement this method in a subclass to handle the deletion of
+ a record.
+
+ Sends a delete request for the record to the server.
+
+ Example
+
+ ```javascript
+ App.ApplicationAdapter = DS.Adapter.extend({
+ deleteRecord: function(store, type, record) {
+ var data = this.serialize(record, { includeId: true });
+ var id = record.get('id');
+ var url = [type, id].join('/');
+
+ return new Ember.RSVP.Promise(function(resolve, reject) {
+ jQuery.ajax({
+ type: 'DELETE',
+ url: url,
+ dataType: 'json',
+ data: data
+ }).then(function(data) {
+ Ember.run(null, resolve, data);
+ }, function(jqXHR) {
+ jqXHR.then = null; // tame jQuery's ill mannered promises
+ Ember.run(null, reject, jqXHR);
+ });
+ });
+ }
+ });
+ ```
+
+ @method deleteRecord
+ @param {DS.Store} store
+ @param {subclass of DS.Model} type the DS.Model class of the record
+ @param {DS.Model} record
+ @return {Promise} promise
+ */
+ deleteRecord: Ember.required(Function),
+
+ /**
+ Find multiple records at once.
+
+ By default, it loops over the provided ids and calls `find` on each.
+ May be overwritten to improve performance and reduce the number of
+ server requests.
+
+ Example
+
+ ```javascript
+ App.ApplicationAdapter = DS.Adapter.extend({
+ findMany: function(store, type, ids) {
+ var url = type;
+ return new Ember.RSVP.Promise(function(resolve, reject) {
+ jQuery.getJSON(url, {ids: ids}).then(function(data) {
+ Ember.run(null, resolve, data);
+ }, function(jqXHR) {
+ jqXHR.then = null; // tame jQuery's ill mannered promises
+ Ember.run(null, reject, jqXHR);
+ });
+ });
+ }
+ });
+ ```
+
+ @method findMany
+ @param {DS.Store} store
+ @param {subclass of DS.Model} type the DS.Model class of the records
+ @param {Array} ids
+ @return {Promise} promise
+ */
+ findMany: function(store, type, ids) {
+ var promises = map.call(ids, function(id) {
+ return this.find(store, type, id);
+ }, this);
+
+ return Ember.RSVP.all(promises);
+ }
+});
+
+})();
+
+
+
+(function() {
+/**
+ @module ember-data
+*/
+
+var get = Ember.get, fmt = Ember.String.fmt,
+ indexOf = Ember.EnumerableUtils.indexOf;
+
+var counter = 0;
+
+/**
+ `DS.FixtureAdapter` is an adapter that loads records from memory.
+ Its primarily used for development and testing. You can also use
+ `DS.FixtureAdapter` while working on the API but are not ready to
+ integrate yet. It is a fully functioning adapter. All CRUD methods
+ are implemented. You can also implement query logic that a remote
+ system would do. Its possible to do develop your entire application
+ with `DS.FixtureAdapter`.
+
+ For information on how to use the `FixtureAdapter` in your
+ application please see the [FixtureAdapter
+ guide](/guides/models/the-fixture-adapter/).
+
+ @class FixtureAdapter
+ @namespace DS
+ @extends DS.Adapter
+*/
+DS.FixtureAdapter = DS.Adapter.extend({
+ // by default, fixtures are already in normalized form
+ serializer: null,
+
+ /**
+ If `simulateRemoteResponse` is `true` the `FixtureAdapter` will
+ wait a number of milliseconds before resolving promises with the
+ fixture values. The wait time can be configured via the `latency`
+ property.
+
+ @property simulateRemoteResponse
+ @type {Boolean}
+ @default true
+ */
+ simulateRemoteResponse: true,
+
+ /**
+ By default the `FixtureAdapter` will simulate a wait of the
+ `latency` milliseconds before resolving promises with the fixture
+ values. This behavior can be turned off via the
+ `simulateRemoteResponse` property.
+
+ @property latency
+ @type {Number}
+ @default 50
+ */
+ latency: 50,
+
+ /**
+ Implement this method in order to provide data associated with a type
+
+ @method fixturesForType
+ @param {Subclass of DS.Model} type
+ @return {Array}
+ */
+ fixturesForType: function(type) {
+ if (type.FIXTURES) {
+ var fixtures = Ember.A(type.FIXTURES);
+ return fixtures.map(function(fixture){
+ var fixtureIdType = typeof fixture.id;
+ if(fixtureIdType !== "number" && fixtureIdType !== "string"){
+ throw new Error(fmt('the id property must be defined as a number or string for fixture %@', [fixture]));
+ }
+ fixture.id = fixture.id + '';
+ return fixture;
+ });
+ }
+ return null;
+ },
+
+ /**
+ Implement this method in order to query fixtures data
+
+ @method queryFixtures
+ @param {Array} fixture
+ @param {Object} query
+ @param {Subclass of DS.Model} type
+ @return {Promise|Array}
+ */
+ queryFixtures: function(fixtures, query, type) {
+ Ember.assert('Not implemented: You must override the DS.FixtureAdapter::queryFixtures method to support querying the fixture store.');
+ },
+
+ /**
+ @method updateFixtures
+ @param {Subclass of DS.Model} type
+ @param {Array} fixture
+ */
+ updateFixtures: function(type, fixture) {
+ if(!type.FIXTURES) {
+ type.FIXTURES = [];
+ }
+
+ var fixtures = type.FIXTURES;
+
+ this.deleteLoadedFixture(type, fixture);
+
+ fixtures.push(fixture);
+ },
+
+ /**
+ Implement this method in order to provide json for CRUD methods
+
+ @method mockJSON
+ @param {Subclass of DS.Model} type
+ @param {DS.Model} record
+ */
+ mockJSON: function(store, type, record) {
+ return store.serializerFor(type).serialize(record, { includeId: true });
+ },
+
+ /**
+ @method generateIdForRecord
+ @param {DS.Store} store
+ @param {DS.Model} record
+ @return {String} id
+ */
+ generateIdForRecord: function(store) {
+ return "fixture-" + counter++;
+ },
+
+ /**
+ @method find
+ @param {DS.Store} store
+ @param {subclass of DS.Model} type
+ @param {String} id
+ @return {Promise} promise
+ */
+ find: function(store, type, id) {
+ var fixtures = this.fixturesForType(type),
+ fixture;
+
+ Ember.assert("Unable to find fixtures for model type "+type.toString(), fixtures);
+
+ if (fixtures) {
+ fixture = Ember.A(fixtures).findProperty('id', id);
+ }
+
+ if (fixture) {
+ return this.simulateRemoteCall(function() {
+ return fixture;
+ }, this);
+ }
+ },
+
+ /**
+ @method findMany
+ @param {DS.Store} store
+ @param {subclass of DS.Model} type
+ @param {Array} ids
+ @return {Promise} promise
+ */
+ findMany: function(store, type, ids) {
+ var fixtures = this.fixturesForType(type);
+
+ Ember.assert("Unable to find fixtures for model type "+type.toString(), fixtures);
+
+ if (fixtures) {
+ fixtures = fixtures.filter(function(item) {
+ return indexOf(ids, item.id) !== -1;
+ });
+ }
+
+ if (fixtures) {
+ return this.simulateRemoteCall(function() {
+ return fixtures;
+ }, this);
+ }
+ },
+
+ /**
+ @private
+ @method findAll
+ @param {DS.Store} store
+ @param {subclass of DS.Model} type
+ @param {String} sinceToken
+ @return {Promise} promise
+ */
+ findAll: function(store, type) {
+ var fixtures = this.fixturesForType(type);
+
+ Ember.assert("Unable to find fixtures for model type "+type.toString(), fixtures);
+
+ return this.simulateRemoteCall(function() {
+ return fixtures;
+ }, this);
+ },
+
+ /**
+ @private
+ @method findQuery
+ @param {DS.Store} store
+ @param {subclass of DS.Model} type
+ @param {Object} query
+ @param {DS.AdapterPopulatedRecordArray} recordArray
+ @return {Promise} promise
+ */
+ findQuery: function(store, type, query, array) {
+ var fixtures = this.fixturesForType(type);
+
+ Ember.assert("Unable to find fixtures for model type "+type.toString(), fixtures);
+
+ fixtures = this.queryFixtures(fixtures, query, type);
+
+ if (fixtures) {
+ return this.simulateRemoteCall(function() {
+ return fixtures;
+ }, this);
+ }
+ },
+
+ /**
+ @method createRecord
+ @param {DS.Store} store
+ @param {subclass of DS.Model} type
+ @param {DS.Model} record
+ @return {Promise} promise
+ */
+ createRecord: function(store, type, record) {
+ var fixture = this.mockJSON(store, type, record);
+
+ this.updateFixtures(type, fixture);
+
+ return this.simulateRemoteCall(function() {
+ return fixture;
+ }, this);
+ },
+
+ /**
+ @method updateRecord
+ @param {DS.Store} store
+ @param {subclass of DS.Model} type
+ @param {DS.Model} record
+ @return {Promise} promise
+ */
+ updateRecord: function(store, type, record) {
+ var fixture = this.mockJSON(store, type, record);
+
+ this.updateFixtures(type, fixture);
+
+ return this.simulateRemoteCall(function() {
+ return fixture;
+ }, this);
+ },
+
+ /**
+ @method deleteRecord
+ @param {DS.Store} store
+ @param {subclass of DS.Model} type
+ @param {DS.Model} record
+ @return {Promise} promise
+ */
+ deleteRecord: function(store, type, record) {
+ var fixture = this.mockJSON(store, type, record);
+
+ this.deleteLoadedFixture(type, fixture);
+
+ return this.simulateRemoteCall(function() {
+ // no payload in a deletion
+ return null;
+ });
+ },
+
+ /*
+ @method deleteLoadedFixture
+ @private
+ @param type
+ @param record
+ */
+ deleteLoadedFixture: function(type, record) {
+ var existingFixture = this.findExistingFixture(type, record);
+
+ if(existingFixture) {
+ var index = indexOf(type.FIXTURES, existingFixture);
+ type.FIXTURES.splice(index, 1);
+ return true;
+ }
+ },
+
+ /*
+ @method findExistingFixture
+ @private
+ @param type
+ @param record
+ */
+ findExistingFixture: function(type, record) {
+ var fixtures = this.fixturesForType(type);
+ var id = get(record, 'id');
+
+ return this.findFixtureById(fixtures, id);
+ },
+
+ /*
+ @method findFixtureById
+ @private
+ @param fixtures
+ @param id
+ */
+ findFixtureById: function(fixtures, id) {
+ return Ember.A(fixtures).find(function(r) {
+ if(''+get(r, 'id') === ''+id) {
+ return true;
+ } else {
+ return false;
+ }
+ });
+ },
+
+ /*
+ @method simulateRemoteCall
+ @private
+ @param callback
+ @param context
+ */
+ simulateRemoteCall: function(callback, context) {
+ var adapter = this;
+
+ return new Ember.RSVP.Promise(function(resolve) {
+ if (get(adapter, 'simulateRemoteResponse')) {
+ // Schedule with setTimeout
+ Ember.run.later(function() {
+ resolve(callback.call(context));
+ }, get(adapter, 'latency'));
+ } else {
+ // Asynchronous, but at the of the runloop with zero latency
+ Ember.run.schedule('actions', null, function() {
+ resolve(callback.call(context));
+ });
+ }
+ }, "DS: FixtureAdapter#simulateRemoteCall");
+ }
+});
+
+})();
+
+
+
+(function() {
+/**
+ @module ember-data
+*/
+
+var get = Ember.get, set = Ember.set;
+var forEach = Ember.ArrayPolyfills.forEach;
+var map = Ember.ArrayPolyfills.map;
+
+function coerceId(id) {
+ return id == null ? null : id+'';
+}
+
+/**
+ Normally, applications will use the `RESTSerializer` by implementing
+ the `normalize` method and individual normalizations under
+ `normalizeHash`.
+
+ This allows you to do whatever kind of munging you need, and is
+ especially useful if your server is inconsistent and you need to
+ do munging differently for many different kinds of responses.
+
+ See the `normalize` documentation for more information.
+
+ ## Across the Board Normalization
+
+ There are also a number of hooks that you might find useful to defined
+ across-the-board rules for your payload. These rules will be useful
+ if your server is consistent, or if you're building an adapter for
+ an infrastructure service, like Parse, and want to encode service
+ conventions.
+
+ For example, if all of your keys are underscored and all-caps, but
+ otherwise consistent with the names you use in your models, you
+ can implement across-the-board rules for how to convert an attribute
+ name in your model to a key in your JSON.
+
+ ```js
+ App.ApplicationSerializer = DS.RESTSerializer.extend({
+ keyForAttribute: function(attr) {
+ return Ember.String.underscore(attr).toUpperCase();
+ }
+ });
+ ```
+
+ You can also implement `keyForRelationship`, which takes the name
+ of the relationship as the first parameter, and the kind of
+ relationship (`hasMany` or `belongsTo`) as the second parameter.
+
+ @class RESTSerializer
+ @namespace DS
+ @extends DS.JSONSerializer
+*/
+DS.RESTSerializer = DS.JSONSerializer.extend({
+ /**
+ If you want to do normalizations specific to some part of the payload, you
+ can specify those under `normalizeHash`.
+
+ For example, given the following json where the the `IDs` under
+ `"comments"` are provided as `_id` instead of `id`.
+
+ ```javascript
+ {
+ "post": {
+ "id": 1,
+ "title": "Rails is omakase",
+ "comments": [ 1, 2 ]
+ },
+ "comments": [{
+ "_id": 1,
+ "body": "FIRST"
+ }, {
+ "_id": 2,
+ "body": "Rails is unagi"
+ }]
+ }
+ ```
+
+ You use `normalizeHash` to normalize just the comments:
+
+ ```javascript
+ App.PostSerializer = DS.RESTSerializer.extend({
+ normalizeHash: {
+ comments: function(hash) {
+ hash.id = hash._id;
+ delete hash._id;
+ return hash;
+ }
+ }
+ });
+ ```
+
+ The key under `normalizeHash` is usually just the original key
+ that was in the original payload. However, key names will be
+ impacted by any modifications done in the `normalizePayload`
+ method. The `DS.RESTSerializer`'s default implementation makes no
+ changes to the payload keys.
+
+ @property normalizeHash
+ @type {Object}
+ @default undefined
+ */
+
+ /**
+ Normalizes a part of the JSON payload returned by
+ the server. You should override this method, munge the hash
+ and call super if you have generic normalization to do.
+
+ It takes the type of the record that is being normalized
+ (as a DS.Model class), the property where the hash was
+ originally found, and the hash to normalize.
+
+ For example, if you have a payload that looks like this:
+
+ ```js
+ {
+ "post": {
+ "id": 1,
+ "title": "Rails is omakase",
+ "comments": [ 1, 2 ]
+ },
+ "comments": [{
+ "id": 1,
+ "body": "FIRST"
+ }, {
+ "id": 2,
+ "body": "Rails is unagi"
+ }]
+ }
+ ```
+
+ The `normalize` method will be called three times:
+
+ * With `App.Post`, `"posts"` and `{ id: 1, title: "Rails is omakase", ... }`
+ * With `App.Comment`, `"comments"` and `{ id: 1, body: "FIRST" }`
+ * With `App.Comment`, `"comments"` and `{ id: 2, body: "Rails is unagi" }`
+
+ You can use this method, for example, to normalize underscored keys to camelized
+ or other general-purpose normalizations.
+
+ If you want to do normalizations specific to some part of the payload, you
+ can specify those under `normalizeHash`.
+
+ For example, if the `IDs` under `"comments"` are provided as `_id` instead of
+ `id`, you can specify how to normalize just the comments:
+
+ ```js
+ App.PostSerializer = DS.RESTSerializer.extend({
+ normalizeHash: {
+ comments: function(hash) {
+ hash.id = hash._id;
+ delete hash._id;
+ return hash;
+ }
+ }
+ });
+ ```
+
+ The key under `normalizeHash` is just the original key that was in the original
+ payload.
+
+ @method normalize
+ @param {subclass of DS.Model} type
+ @param {Object} hash
+ @param {String} prop
+ @returns {Object}
+ */
+ normalize: function(type, hash, prop) {
+ this.normalizeId(hash);
+ this.normalizeAttributes(type, hash);
+ this.normalizeRelationships(type, hash);
+
+ this.normalizeUsingDeclaredMapping(type, hash);
+
+ if (this.normalizeHash && this.normalizeHash[prop]) {
+ this.normalizeHash[prop](hash);
+ }
+
+ return this._super(type, hash, prop);
+ },
+
+ /**
+ You can use this method to normalize all payloads, regardless of whether they
+ represent single records or an array.
+
+ For example, you might want to remove some extraneous data from the payload:
+
+ ```js
+ App.ApplicationSerializer = DS.RESTSerializer.extend({
+ normalizePayload: function(type, payload) {
+ delete payload.version;
+ delete payload.status;
+ return payload;
+ }
+ });
+ ```
+
+ @method normalizePayload
+ @param {subclass of DS.Model} type
+ @param {Object} hash
+ @returns {Object} the normalized payload
+ */
+ normalizePayload: function(type, payload) {
+ return payload;
+ },
+
+ /**
+ @method normalizeId
+ @private
+ */
+ normalizeId: function(hash) {
+ var primaryKey = get(this, 'primaryKey');
+
+ if (primaryKey === 'id') { return; }
+
+ hash.id = hash[primaryKey];
+ delete hash[primaryKey];
+ },
+
+ /**
+ @method normalizeUsingDeclaredMapping
+ @private
+ */
+ normalizeUsingDeclaredMapping: function(type, hash) {
+ var attrs = get(this, 'attrs'), payloadKey, key;
+
+ if (attrs) {
+ for (key in attrs) {
+ payloadKey = attrs[key];
+ if (payloadKey && payloadKey.key) {
+ payloadKey = payloadKey.key;
+ }
+ if (typeof payloadKey === 'string') {
+ hash[key] = hash[payloadKey];
+ delete hash[payloadKey];
+ }
+ }
+ }
+ },
+
+ /**
+ @method normalizeAttributes
+ @private
+ */
+ normalizeAttributes: function(type, hash) {
+ var payloadKey, key;
+
+ if (this.keyForAttribute) {
+ type.eachAttribute(function(key) {
+ payloadKey = this.keyForAttribute(key);
+ if (key === payloadKey) { return; }
+
+ hash[key] = hash[payloadKey];
+ delete hash[payloadKey];
+ }, this);
+ }
+ },
+
+ /**
+ @method normalizeRelationships
+ @private
+ */
+ normalizeRelationships: function(type, hash) {
+ var payloadKey, key;
+
+ if (this.keyForRelationship) {
+ type.eachRelationship(function(key, relationship) {
+ payloadKey = this.keyForRelationship(key, relationship.kind);
+ if (key === payloadKey) { return; }
+
+ hash[key] = hash[payloadKey];
+ delete hash[payloadKey];
+ }, this);
+ }
+ },
+
+ /**
+ Called when the server has returned a payload representing
+ a single record, such as in response to a `find` or `save`.
+
+ It is your opportunity to clean up the server's response into the normalized
+ form expected by Ember Data.
+
+ If you want, you can just restructure the top-level of your payload, and
+ do more fine-grained normalization in the `normalize` method.
+
+ For example, if you have a payload like this in response to a request for
+ post 1:
+
+ ```js
+ {
+ "id": 1,
+ "title": "Rails is omakase",
+
+ "_embedded": {
+ "comment": [{
+ "_id": 1,
+ "comment_title": "FIRST"
+ }, {
+ "_id": 2,
+ "comment_title": "Rails is unagi"
+ }]
+ }
+ }
+ ```
+
+ You could implement a serializer that looks like this to get your payload
+ into shape:
+
+ ```js
+ App.PostSerializer = DS.RESTSerializer.extend({
+ // First, restructure the top-level so it's organized by type
+ extractSingle: function(store, type, payload, id, requestType) {
+ var comments = payload._embedded.comment;
+ delete payload._embedded;
+
+ payload = { comments: comments, post: payload };
+ return this._super(store, type, payload, id, requestType);
+ },
+
+ normalizeHash: {
+ // Next, normalize individual comments, which (after `extract`)
+ // are now located under `comments`
+ comments: function(hash) {
+ hash.id = hash._id;
+ hash.title = hash.comment_title;
+ delete hash._id;
+ delete hash.comment_title;
+ return hash;
+ }
+ }
+ })
+ ```
+
+ When you call super from your own implementation of `extractSingle`, the
+ built-in implementation will find the primary record in your normalized
+ payload and push the remaining records into the store.
+
+ The primary record is the single hash found under `post` or the first
+ element of the `posts` array.
+
+ The primary record has special meaning when the record is being created
+ for the first time or updated (`createRecord` or `updateRecord`). In
+ particular, it will update the properties of the record that was saved.
+
+ @method extractSingle
+ @param {DS.Store} store
+ @param {subclass of DS.Model} type
+ @param {Object} payload
+ @param {String} id
+ @param {'find'|'createRecord'|'updateRecord'|'deleteRecord'} requestType
+ @returns {Object} the primary response to the original request
+ */
+ extractSingle: function(store, primaryType, payload, recordId, requestType) {
+ payload = this.normalizePayload(primaryType, payload);
+
+ var primaryTypeName = primaryType.typeKey,
+ primaryRecord;
+
+ for (var prop in payload) {
+ var typeName = this.typeForRoot(prop),
+ type = store.modelFor(typeName),
+ isPrimary = type.typeKey === primaryTypeName;
+
+ // legacy support for singular resources
+ if (isPrimary && Ember.typeOf(payload[prop]) !== "array" ) {
+ primaryRecord = this.normalize(primaryType, payload[prop], prop);
+ continue;
+ }
+
+ /*jshint loopfunc:true*/
+ forEach.call(payload[prop], function(hash) {
+ var typeName = this.typeForRoot(prop),
+ type = store.modelFor(typeName),
+ typeSerializer = store.serializerFor(type);
+
+ hash = typeSerializer.normalize(type, hash, prop);
+
+ var isFirstCreatedRecord = isPrimary && !recordId && !primaryRecord,
+ isUpdatedRecord = isPrimary && coerceId(hash.id) === recordId;
+
+ // find the primary record.
+ //
+ // It's either:
+ // * the record with the same ID as the original request
+ // * in the case of a newly created record that didn't have an ID, the first
+ // record in the Array
+ if (isFirstCreatedRecord || isUpdatedRecord) {
+ primaryRecord = hash;
+ } else {
+ store.push(typeName, hash);
+ }
+ }, this);
+ }
+
+ return primaryRecord;
+ },
+
+ /**
+ Called when the server has returned a payload representing
+ multiple records, such as in response to a `findAll` or `findQuery`.
+
+ It is your opportunity to clean up the server's response into the normalized
+ form expected by Ember Data.
+
+ If you want, you can just restructure the top-level of your payload, and
+ do more fine-grained normalization in the `normalize` method.
+
+ For example, if you have a payload like this in response to a request for
+ all posts:
+
+ ```js
+ {
+ "_embedded": {
+ "post": [{
+ "id": 1,
+ "title": "Rails is omakase"
+ }, {
+ "id": 2,
+ "title": "The Parley Letter"
+ }],
+ "comment": [{
+ "_id": 1,
+ "comment_title": "Rails is unagi"
+ "post_id": 1
+ }, {
+ "_id": 2,
+ "comment_title": "Don't tread on me",
+ "post_id": 2
+ }]
+ }
+ }
+ ```
+
+ You could implement a serializer that looks like this to get your payload
+ into shape:
+
+ ```js
+ App.PostSerializer = DS.RESTSerializer.extend({
+ // First, restructure the top-level so it's organized by type
+ // and the comments are listed under a post's `comments` key.
+ extractArray: function(store, type, payload, id, requestType) {
+ var posts = payload._embedded.post;
+ var comments = [];
+ var postCache = {};
+
+ posts.forEach(function(post) {
+ post.comments = [];
+ postCache[post.id] = post;
+ });
+
+ payload._embedded.comment.forEach(function(comment) {
+ comments.push(comment);
+ postCache[comment.post_id].comments.push(comment);
+ delete comment.post_id;
+ }
+
+ payload = { comments: comments, posts: payload };
+
+ return this._super(store, type, payload, id, requestType);
+ },
+
+ normalizeHash: {
+ // Next, normalize individual comments, which (after `extract`)
+ // are now located under `comments`
+ comments: function(hash) {
+ hash.id = hash._id;
+ hash.title = hash.comment_title;
+ delete hash._id;
+ delete hash.comment_title;
+ return hash;
+ }
+ }
+ })
+ ```
+
+ When you call super from your own implementation of `extractArray`, the
+ built-in implementation will find the primary array in your normalized
+ payload and push the remaining records into the store.
+
+ The primary array is the array found under `posts`.
+
+ The primary record has special meaning when responding to `findQuery`
+ or `findHasMany`. In particular, the primary array will become the
+ list of records in the record array that kicked off the request.
+
+ If your primary array contains secondary (embedded) records of the same type,
+ you cannot place these into the primary array `posts`. Instead, place the
+ secondary items into an underscore prefixed property `_posts`, which will
+ push these items into the store and will not affect the resulting query.
+
+ @method extractArray
+ @param {DS.Store} store
+ @param {subclass of DS.Model} type
+ @param {Object} payload
+ @param {'findAll'|'findMany'|'findHasMany'|'findQuery'} requestType
+ @returns {Array} The primary array that was returned in response
+ to the original query.
+ */
+ extractArray: function(store, primaryType, payload) {
+ payload = this.normalizePayload(primaryType, payload);
+
+ var primaryTypeName = primaryType.typeKey,
+ primaryArray;
+
+ for (var prop in payload) {
+ var typeKey = prop,
+ forcedSecondary = false;
+
+ if (prop.charAt(0) === '_') {
+ forcedSecondary = true;
+ typeKey = prop.substr(1);
+ }
+
+ var typeName = this.typeForRoot(typeKey),
+ type = store.modelFor(typeName),
+ typeSerializer = store.serializerFor(type),
+ isPrimary = (!forcedSecondary && (type.typeKey === primaryTypeName));
+
+ /*jshint loopfunc:true*/
+ var normalizedArray = map.call(payload[prop], function(hash) {
+ return typeSerializer.normalize(type, hash, prop);
+ }, this);
+
+ if (isPrimary) {
+ primaryArray = normalizedArray;
+ } else {
+ store.pushMany(typeName, normalizedArray);
+ }
+ }
+
+ return primaryArray;
+ },
+
+ /**
+ This method allows you to push a payload containing top-level
+ collections of records organized per type.
+
+ ```js
+ {
+ "posts": [{
+ "id": "1",
+ "title": "Rails is omakase",
+ "author", "1",
+ "comments": [ "1" ]
+ }],
+ "comments": [{
+ "id": "1",
+ "body": "FIRST"
+ }],
+ "users": [{
+ "id": "1",
+ "name": "@d2h"
+ }]
+ }
+ ```
+
+ It will first normalize the payload, so you can use this to push
+ in data streaming in from your server structured the same way
+ that fetches and saves are structured.
+
+ @method pushPayload
+ @param {DS.Store} store
+ @param {Object} payload
+ */
+ pushPayload: function(store, payload) {
+ payload = this.normalizePayload(null, payload);
+
+ for (var prop in payload) {
+ var typeName = this.typeForRoot(prop),
+ type = store.modelFor(typeName);
+
+ /*jshint loopfunc:true*/
+ var normalizedArray = map.call(Ember.makeArray(payload[prop]), function(hash) {
+ return this.normalize(type, hash, prop);
+ }, this);
+
+ store.pushMany(typeName, normalizedArray);
+ }
+ },
+
+ /**
+ You can use this method to normalize the JSON root keys returned
+ into the model type expected by your store.
+
+ For example, your server may return underscored root keys rather than
+ the expected camelcased versions.
+
+ ```js
+ App.ApplicationSerializer = DS.RESTSerializer.extend({
+ typeForRoot: function(root) {
+ var camelized = Ember.String.camelize(root);
+ return Ember.String.singularize(camelized);
+ }
+ });
+ ```
+
+ @method typeForRoot
+ @param {String} root
+ @returns {String} the model's typeKey
+ */
+ typeForRoot: function(root) {
+ return Ember.String.singularize(root);
+ },
+
+ // SERIALIZE
+
+ /**
+ Called when a record is saved in order to convert the
+ record into JSON.
+
+ By default, it creates a JSON object with a key for
+ each attribute and belongsTo relationship.
+
+ For example, consider this model:
+
+ ```js
+ App.Comment = DS.Model.extend({
+ title: DS.attr(),
+ body: DS.attr(),
+
+ author: DS.belongsTo('user')
+ });
+ ```
+
+ The default serialization would create a JSON object like:
+
+ ```js
+ {
+ "title": "Rails is unagi",
+ "body": "Rails? Omakase? O_O",
+ "author": 12
+ }
+ ```
+
+ By default, attributes are passed through as-is, unless
+ you specified an attribute type (`DS.attr('date')`). If
+ you specify a transform, the JavaScript value will be
+ serialized when inserted into the JSON hash.
+
+ By default, belongs-to relationships are converted into
+ IDs when inserted into the JSON hash.
+
+ ## IDs
+
+ `serialize` takes an options hash with a single option:
+ `includeId`. If this option is `true`, `serialize` will,
+ by default include the ID in the JSON object it builds.
+
+ The adapter passes in `includeId: true` when serializing
+ a record for `createRecord`, but not for `updateRecord`.
+
+ ## Customization
+
+ Your server may expect a different JSON format than the
+ built-in serialization format.
+
+ In that case, you can implement `serialize` yourself and
+ return a JSON hash of your choosing.
+
+ ```js
+ App.PostSerializer = DS.RESTSerializer.extend({
+ serialize: function(post, options) {
+ var json = {
+ POST_TTL: post.get('title'),
+ POST_BDY: post.get('body'),
+ POST_CMS: post.get('comments').mapProperty('id')
+ }
+
+ if (options.includeId) {
+ json.POST_ID_ = post.get('id');
+ }
+
+ return json;
+ }
+ });
+ ```
+
+ ## Customizing an App-Wide Serializer
+
+ If you want to define a serializer for your entire
+ application, you'll probably want to use `eachAttribute`
+ and `eachRelationship` on the record.
+
+ ```js
+ App.ApplicationSerializer = DS.RESTSerializer.extend({
+ serialize: function(record, options) {
+ var json = {};
+
+ record.eachAttribute(function(name) {
+ json[serverAttributeName(name)] = record.get(name);
+ })
+
+ record.eachRelationship(function(name, relationship) {
+ if (relationship.kind === 'hasMany') {
+ json[serverHasManyName(name)] = record.get(name).mapBy('id');
+ }
+ });
+
+ if (options.includeId) {
+ json.ID_ = record.get('id');
+ }
+
+ return json;
+ }
+ });
+
+ function serverAttributeName(attribute) {
+ return attribute.underscore().toUpperCase();
+ }
+
+ function serverHasManyName(name) {
+ return serverAttributeName(name.singularize()) + "_IDS";
+ }
+ ```
+
+ This serializer will generate JSON that looks like this:
+
+ ```js
+ {
+ "TITLE": "Rails is omakase",
+ "BODY": "Yep. Omakase.",
+ "COMMENT_IDS": [ 1, 2, 3 ]
+ }
+ ```
+
+ ## Tweaking the Default JSON
+
+ If you just want to do some small tweaks on the default JSON,
+ you can call super first and make the tweaks on the returned
+ JSON.
+
+ ```js
+ App.PostSerializer = DS.RESTSerializer.extend({
+ serialize: function(record, options) {
+ var json = this._super(record, options);
+
+ json.subject = json.title;
+ delete json.title;
+
+ return json;
+ }
+ });
+ ```
+
+ @method serialize
+ @param record
+ @param options
+ */
+ serialize: function(record, options) {
+ return this._super.apply(this, arguments);
+ },
+
+ /**
+ You can use this method to customize the root keys serialized into the JSON.
+ By default the REST Serializer sends camelized root keys.
+ For example, your server may expect underscored root objects.
+
+ ```js
+ App.ApplicationSerializer = DS.RESTSerializer.extend({
+ serializeIntoHash: function(data, type, record, options) {
+ var root = Ember.String.decamelize(type.typeKey);
+ data[root] = this.serialize(record, options);
+ }
+ });
+ ```
+
+ @method serializeIntoHash
+ @param {Object} hash
+ @param {subclass of DS.Model} type
+ @param {DS.Model} record
+ @param {Object} options
+ */
+ serializeIntoHash: function(hash, type, record, options) {
+ var root = Ember.String.camelize(type.typeKey);
+ hash[root] = this.serialize(record, options);
+ },
+
+ /**
+ You can use this method to customize how polymorphic objects are serialized.
+ By default the JSON Serializer creates the key by appending `Type` to
+ the attribute and value from the model's camelcased model name.
+
+ @method serializePolymorphicType
+ @param {DS.Model} record
+ @param {Object} json
+ @param {Object} relationship
+ */
+ serializePolymorphicType: function(record, json, relationship) {
+ var key = relationship.key,
+ belongsTo = get(record, key);
+ key = this.keyForAttribute ? this.keyForAttribute(key) : key;
+ json[key + "Type"] = Ember.String.camelize(belongsTo.constructor.typeKey);
+ }
+});
+
+})();
+
+
+
+(function() {
+/**
+ @module ember-data
+*/
+
+var get = Ember.get, set = Ember.set;
+var forEach = Ember.ArrayPolyfills.forEach;
+
+/**
+ The REST adapter allows your store to communicate with an HTTP server by
+ transmitting JSON via XHR. Most Ember.js apps that consume a JSON API
+ should use the REST adapter.
+
+ This adapter is designed around the idea that the JSON exchanged with
+ the server should be conventional.
+
+ ## JSON Structure
+
+ The REST adapter expects the JSON returned from your server to follow
+ these conventions.
+
+ ### Object Root
+
+ The JSON payload should be an object that contains the record inside a
+ root property. For example, in response to a `GET` request for
+ `/posts/1`, the JSON should look like this:
+
+ ```js
+ {
+ "post": {
+ "title": "I'm Running to Reform the W3C's Tag",
+ "author": "Yehuda Katz"
+ }
+ }
+ ```
+
+ ### Conventional Names
+
+ Attribute names in your JSON payload should be the camelCased versions of
+ the attributes in your Ember.js models.
+
+ For example, if you have a `Person` model:
+
+ ```js
+ App.Person = DS.Model.extend({
+ firstName: DS.attr('string'),
+ lastName: DS.attr('string'),
+ occupation: DS.attr('string')
+ });
+ ```
+
+ The JSON returned should look like this:
+
+ ```js
+ {
+ "person": {
+ "firstName": "Barack",
+ "lastName": "Obama",
+ "occupation": "President"
+ }
+ }
+ ```
+
+ ## Customization
+
+ ### Endpoint path customization
+
+ Endpoint paths can be prefixed with a `namespace` by setting the namespace
+ property on the adapter:
+
+ ```js
+ DS.RESTAdapter.reopen({
+ namespace: 'api/1'
+ });
+ ```
+ Requests for `App.Person` would now target `/api/1/people/1`.
+
+ ### Host customization
+
+ An adapter can target other hosts by setting the `host` property.
+
+ ```js
+ DS.RESTAdapter.reopen({
+ host: 'https://api.example.com'
+ });
+ ```
+
+ ### Headers customization
+
+ Some APIs require HTTP headers, e.g. to provide an API key. An array of
+ headers can be added to the adapter which are passed with every request:
+
+ ```js
+ DS.RESTAdapter.reopen({
+ headers: {
+ "API_KEY": "secret key",
+ "ANOTHER_HEADER": "Some header value"
+ }
+ });
+ ```
+
+ @class RESTAdapter
+ @constructor
+ @namespace DS
+ @extends DS.Adapter
+*/
+DS.RESTAdapter = DS.Adapter.extend({
+ defaultSerializer: '-rest',
+
+
+ /**
+ Endpoint paths can be prefixed with a `namespace` by setting the namespace
+ property on the adapter:
+
+ ```javascript
+ DS.RESTAdapter.reopen({
+ namespace: 'api/1'
+ });
+ ```
+
+ Requests for `App.Post` would now target `/api/1/post/`.
+
+ @property namespace
+ @type {String}
+ */
+
+ /**
+ An adapter can target other hosts by setting the `host` property.
+
+ ```javascript
+ DS.RESTAdapter.reopen({
+ host: 'https://api.example.com'
+ });
+ ```
+
+ Requests for `App.Post` would now target `https://api.example.com/post/`.
+
+ @property host
+ @type {String}
+ */
+
+ /**
+ Some APIs require HTTP headers, e.g. to provide an API key. An array of
+ headers can be added to the adapter which are passed with every request:
+
+ ```javascript
+ DS.RESTAdapter.reopen({
+ headers: {
+ "API_KEY": "secret key",
+ "ANOTHER_HEADER": "Some header value"
+ }
+ });
+ ```
+
+ @property headers
+ @type {Object}
+ */
+
+ /**
+ Called by the store in order to fetch the JSON for a given
+ type and ID.
+
+ The `find` method makes an Ajax request to a URL computed by `buildURL`, and returns a
+ promise for the resulting payload.
+
+ This method performs an HTTP `GET` request with the id provided as part of the query string.
+
+ @method find
+ @param {DS.Store} store
+ @param {subclass of DS.Model} type
+ @param {String} id
+ @returns {Promise} promise
+ */
+ find: function(store, type, id) {
+ return this.ajax(this.buildURL(type.typeKey, id), 'GET');
+ },
+
+ /**
+ Called by the store in order to fetch a JSON array for all
+ of the records for a given type.
+
+ The `findAll` method makes an Ajax (HTTP GET) request to a URL computed by `buildURL`, and returns a
+ promise for the resulting payload.
+
+ @private
+ @method findAll
+ @param {DS.Store} store
+ @param {subclass of DS.Model} type
+ @param {String} sinceToken
+ @returns {Promise} promise
+ */
+ findAll: function(store, type, sinceToken) {
+ var query;
+
+ if (sinceToken) {
+ query = { since: sinceToken };
+ }
+
+ return this.ajax(this.buildURL(type.typeKey), 'GET', { data: query });
+ },
+
+ /**
+ Called by the store in order to fetch a JSON array for
+ the records that match a particular query.
+
+ The `findQuery` method makes an Ajax (HTTP GET) request to a URL computed by `buildURL`, and returns a
+ promise for the resulting payload.
+
+ The `query` argument is a simple JavaScript object that will be passed directly
+ to the server as parameters.
+
+ @private
+ @method findQuery
+ @param {DS.Store} store
+ @param {subclass of DS.Model} type
+ @param {Object} query
+ @returns {Promise} promise
+ */
+ findQuery: function(store, type, query) {
+ return this.ajax(this.buildURL(type.typeKey), 'GET', { data: query });
+ },
+
+ /**
+ Called by the store in order to fetch a JSON array for
+ the unloaded records in a has-many relationship that were originally
+ specified as IDs.
+
+ For example, if the original payload looks like:
+
+ ```js
+ {
+ "id": 1,
+ "title": "Rails is omakase",
+ "comments": [ 1, 2, 3 ]
+ }
+ ```
+
+ The IDs will be passed as a URL-encoded Array of IDs, in this form:
+
+ ```
+ ids[]=1&ids[]=2&ids[]=3
+ ```
+
+ Many servers, such as Rails and PHP, will automatically convert this URL-encoded array
+ into an Array for you on the server-side. If you want to encode the
+ IDs, differently, just override this (one-line) method.
+
+ The `findMany` method makes an Ajax (HTTP GET) request to a URL computed by `buildURL`, and returns a
+ promise for the resulting payload.
+
+ @method findMany
+ @param {DS.Store} store
+ @param {subclass of DS.Model} type
+ @param {Array} ids
+ @returns {Promise} promise
+ */
+ findMany: function(store, type, ids) {
+ return this.ajax(this.buildURL(type.typeKey), 'GET', { data: { ids: ids } });
+ },
+
+ /**
+ Called by the store in order to fetch a JSON array for
+ the unloaded records in a has-many relationship that were originally
+ specified as a URL (inside of `links`).
+
+ For example, if your original payload looks like this:
+
+ ```js
+ {
+ "post": {
+ "id": 1,
+ "title": "Rails is omakase",
+ "links": { "comments": "/posts/1/comments" }
+ }
+ }
+ ```
+
+ This method will be called with the parent record and `/posts/1/comments`.
+
+ The `findHasMany` method will make an Ajax (HTTP GET) request to the originally specified URL.
+ If the URL is host-relative (starting with a single slash), the
+ request will use the host specified on the adapter (if any).
+
+ @method findHasMany
+ @param {DS.Store} store
+ @param {DS.Model} record
+ @param {String} url
+ @returns {Promise} promise
+ */
+ findHasMany: function(store, record, url) {
+ var host = get(this, 'host'),
+ id = get(record, 'id'),
+ type = record.constructor.typeKey;
+
+ if (host && url.charAt(0) === '/' && url.charAt(1) !== '/') {
+ url = host + url;
+ }
+
+ return this.ajax(this.urlPrefix(url, this.buildURL(type, id)), 'GET');
+ },
+
+ /**
+ Called by the store in order to fetch a JSON array for
+ the unloaded records in a belongs-to relationship that were originally
+ specified as a URL (inside of `links`).
+
+ For example, if your original payload looks like this:
+
+ ```js
+ {
+ "person": {
+ "id": 1,
+ "name": "Tom Dale",
+ "links": { "group": "/people/1/group" }
+ }
+ }
+ ```
+
+ This method will be called with the parent record and `/people/1/group`.
+
+ The `findBelongsTo` method will make an Ajax (HTTP GET) request to the originally specified URL.
+
+ @method findBelongsTo
+ @param {DS.Store} store
+ @param {DS.Model} record
+ @param {String} url
+ @returns {Promise} promise
+ */
+ findBelongsTo: function(store, record, url) {
+ var id = get(record, 'id'),
+ type = record.constructor.typeKey;
+
+ return this.ajax(this.urlPrefix(url, this.buildURL(type, id)), 'GET');
+ },
+
+ /**
+ Called by the store when a newly created record is
+ saved via the `save` method on a model record instance.
+
+ The `createRecord` method serializes the record and makes an Ajax (HTTP POST) request
+ to a URL computed by `buildURL`.
+
+ See `serialize` for information on how to customize the serialized form
+ of a record.
+
+ @method createRecord
+ @param {DS.Store} store
+ @param {subclass of DS.Model} type
+ @param {DS.Model} record
+ @returns {Promise} promise
+ */
+ createRecord: function(store, type, record) {
+ var data = {};
+ var serializer = store.serializerFor(type.typeKey);
+
+ serializer.serializeIntoHash(data, type, record, { includeId: true });
+
+ return this.ajax(this.buildURL(type.typeKey), "POST", { data: data });
+ },
+
+ /**
+ Called by the store when an existing record is saved
+ via the `save` method on a model record instance.
+
+ The `updateRecord` method serializes the record and makes an Ajax (HTTP PUT) request
+ to a URL computed by `buildURL`.
+
+ See `serialize` for information on how to customize the serialized form
+ of a record.
+
+ @method updateRecord
+ @param {DS.Store} store
+ @param {subclass of DS.Model} type
+ @param {DS.Model} record
+ @returns {Promise} promise
+ */
+ updateRecord: function(store, type, record) {
+ var data = {};
+ var serializer = store.serializerFor(type.typeKey);
+
+ serializer.serializeIntoHash(data, type, record);
+
+ var id = get(record, 'id');
+
+ return this.ajax(this.buildURL(type.typeKey, id), "PUT", { data: data });
+ },
+
+ /**
+ Called by the store when a record is deleted.
+
+ The `deleteRecord` method makes an Ajax (HTTP DELETE) request to a URL computed by `buildURL`.
+
+ @method deleteRecord
+ @param {DS.Store} store
+ @param {subclass of DS.Model} type
+ @param {DS.Model} record
+ @returns {Promise} promise
+ */
+ deleteRecord: function(store, type, record) {
+ var id = get(record, 'id');
+
+ return this.ajax(this.buildURL(type.typeKey, id), "DELETE");
+ },
+
+ /**
+ Builds a URL for a given type and optional ID.
+
+ By default, it pluralizes the type's name (for example, 'post'
+ becomes 'posts' and 'person' becomes 'people'). To override the
+ pluralization see [pathForType](#method_pathForType).
+
+ If an ID is specified, it adds the ID to the path generated
+ for the type, separated by a `/`.
+
+ @method buildURL
+ @param {String} type
+ @param {String} id
+ @returns {String} url
+ */
+ buildURL: function(type, id) {
+ var url = [],
+ host = get(this, 'host'),
+ prefix = this.urlPrefix();
+
+ if (type) { url.push(this.pathForType(type)); }
+ if (id) { url.push(id); }
+
+ if (prefix) { url.unshift(prefix); }
+
+ url = url.join('/');
+ if (!host && url) { url = '/' + url; }
+
+ return url;
+ },
+
+ /**
+ @method urlPrefix
+ @private
+ @param {String} path
+ @param {String} parentUrl
+ @return {String} urlPrefix
+ */
+ urlPrefix: function(path, parentURL) {
+ var host = get(this, 'host'),
+ namespace = get(this, 'namespace'),
+ url = [];
+
+ if (path) {
+ // Absolute path
+ if (path.charAt(0) === '/') {
+ if (host) {
+ path = path.slice(1);
+ url.push(host);
+ }
+ // Relative path
+ } else if (!/^http(s)?:\/\//.test(path)) {
+ url.push(parentURL);
+ }
+ } else {
+ if (host) { url.push(host); }
+ if (namespace) { url.push(namespace); }
+ }
+
+ if (path) {
+ url.push(path);
+ }
+
+ return url.join('/');
+ },
+
+ /**
+ Determines the pathname for a given type.
+
+ By default, it pluralizes the type's name (for example,
+ 'post' becomes 'posts' and 'person' becomes 'people').
+
+ ### Pathname customization
+
+ For example if you have an object LineItem with an
+ endpoint of "/line_items/".
+
+ ```js
+ DS.RESTAdapter.reopen({
+ pathForType: function(type) {
+ var decamelized = Ember.String.decamelize(type);
+ return Ember.String.pluralize(decamelized);
+ };
+ });
+ ```
+
+ @method pathForType
+ @param {String} type
+ @returns {String} path
+ **/
+ pathForType: function(type) {
+ var camelized = Ember.String.camelize(type);
+ return Ember.String.pluralize(camelized);
+ },
+
+ /**
+ Takes an ajax response, and returns a relevant error.
+
+ Returning a `DS.InvalidError` from this method will cause the
+ record to transition into the `invalid` state and make the
+ `errors` object available on the record.
+
+ ```javascript
+ App.ApplicationAdapter = DS.RESTAdapter.extend({
+ ajaxError: function(jqXHR) {
+ var error = this._super(jqXHR);
+
+ if (jqXHR && jqXHR.status === 422) {
+ var jsonErrors = Ember.$.parseJSON(jqXHR.responseText)["errors"];
+
+ return new DS.InvalidError(jsonErrors);
+ } else {
+ return error;
+ }
+ }
+ });
+ ```
+
+ Note: As a correctness optimization, the default implementation of
+ the `ajaxError` method strips out the `then` method from jquery's
+ ajax response (jqXHR). This is important because the jqXHR's
+ `then` method fulfills the promise with itself resulting in a
+ circular "thenable" chain which may cause problems for some
+ promise libraries.
+
+ @method ajaxError
+ @param {Object} jqXHR
+ @return {Object} jqXHR
+ */
+ ajaxError: function(jqXHR) {
+ if (jqXHR) {
+ jqXHR.then = null;
+ }
+
+ return jqXHR;
+ },
+
+ /**
+ Takes a URL, an HTTP method and a hash of data, and makes an
+ HTTP request.
+
+ When the server responds with a payload, Ember Data will call into `extractSingle`
+ or `extractArray` (depending on whether the original query was for one record or
+ many records).
+
+ By default, `ajax` method has the following behavior:
+
+ * It sets the response `dataType` to `"json"`
+ * If the HTTP method is not `"GET"`, it sets the `Content-Type` to be
+ `application/json; charset=utf-8`
+ * If the HTTP method is not `"GET"`, it stringifies the data passed in. The
+ data is the serialized record in the case of a save.
+ * Registers success and failure handlers.
+
+ @method ajax
+ @private
+ @param {String} url
+ @param {String} type The request type GET, POST, PUT, DELETE etc.
+ @param {Object} hash
+ @return {Promise} promise
+ */
+ ajax: function(url, type, hash) {
+ var adapter = this;
+
+ return new Ember.RSVP.Promise(function(resolve, reject) {
+ hash = adapter.ajaxOptions(url, type, hash);
+
+ hash.success = function(json) {
+ Ember.run(null, resolve, json);
+ };
+
+ hash.error = function(jqXHR, textStatus, errorThrown) {
+ Ember.run(null, reject, adapter.ajaxError(jqXHR));
+ };
+
+ Ember.$.ajax(hash);
+ }, "DS: RestAdapter#ajax " + type + " to " + url);
+ },
+
+ /**
+ @method ajaxOptions
+ @private
+ @param {String} url
+ @param {String} type The request type GET, POST, PUT, DELETE etc.
+ @param {Object} hash
+ @return {Object} hash
+ */
+ ajaxOptions: function(url, type, hash) {
+ hash = hash || {};
+ hash.url = url;
+ hash.type = type;
+ hash.dataType = 'json';
+ hash.context = this;
+
+ if (hash.data && type !== 'GET') {
+ hash.contentType = 'application/json; charset=utf-8';
+ hash.data = JSON.stringify(hash.data);
+ }
+
+ if (this.headers !== undefined) {
+ var headers = this.headers;
+ hash.beforeSend = function (xhr) {
+ forEach.call(Ember.keys(headers), function(key) {
+ xhr.setRequestHeader(key, headers[key]);
+ });
+ };
+ }
+
+
+ return hash;
+ }
+
+});
+
+})();
+
+
+
+(function() {
+/**
+ @module ember-data
+*/
+
+})();
+
+
+
+(function() {
+DS.Model.reopen({
+
+ /**
+ Provides info about the model for debugging purposes
+ by grouping the properties into more semantic groups.
+
+ Meant to be used by debugging tools such as the Chrome Ember Extension.
+
+ - Groups all attributes in "Attributes" group.
+ - Groups all belongsTo relationships in "Belongs To" group.
+ - Groups all hasMany relationships in "Has Many" group.
+ - Groups all flags in "Flags" group.
+ - Flags relationship CPs as expensive properties.
+
+ @method _debugInfo
+ @for DS.Model
+ @private
+ */
+ _debugInfo: function() {
+ var attributes = ['id'],
+ relationships = { belongsTo: [], hasMany: [] },
+ expensiveProperties = [];
+
+ this.eachAttribute(function(name, meta) {
+ attributes.push(name);
+ }, this);
+
+ this.eachRelationship(function(name, relationship) {
+ relationships[relationship.kind].push(name);
+ expensiveProperties.push(name);
+ });
+
+ var groups = [
+ {
+ name: 'Attributes',
+ properties: attributes,
+ expand: true
+ },
+ {
+ name: 'Belongs To',
+ properties: relationships.belongsTo,
+ expand: true
+ },
+ {
+ name: 'Has Many',
+ properties: relationships.hasMany,
+ expand: true
+ },
+ {
+ name: 'Flags',
+ properties: ['isLoaded', 'isDirty', 'isSaving', 'isDeleted', 'isError', 'isNew', 'isValid']
+ }
+ ];
+
+ return {
+ propertyInfo: {
+ // include all other mixins / properties (not just the grouped ones)
+ includeOtherProperties: true,
+ groups: groups,
+ // don't pre-calculate unless cached
+ expensiveProperties: expensiveProperties
+ }
+ };
+ }
+
+});
+
+})();
+
+
+
+(function() {
+/**
+ @module ember-data
+*/
+
+})();
+
+
+
+(function() {
+/**
+ Ember Data
+
+ @module ember-data
+ @main ember-data
+*/
+
+})();
+
+(function() {
+Ember.String.pluralize = function(word) {
+ return Ember.Inflector.inflector.pluralize(word);
+};
+
+Ember.String.singularize = function(word) {
+ return Ember.Inflector.inflector.singularize(word);
+};
+
+})();
+
+
+
+(function() {
+var BLANK_REGEX = /^\s*$/;
+
+function loadUncountable(rules, uncountable) {
+ for (var i = 0, length = uncountable.length; i < length; i++) {
+ rules.uncountable[uncountable[i].toLowerCase()] = true;
+ }
+}
+
+function loadIrregular(rules, irregularPairs) {
+ var pair;
+
+ for (var i = 0, length = irregularPairs.length; i < length; i++) {
+ pair = irregularPairs[i];
+
+ rules.irregular[pair[0].toLowerCase()] = pair[1];
+ rules.irregularInverse[pair[1].toLowerCase()] = pair[0];
+ }
+}
+
+/**
+ Inflector.Ember provides a mechanism for supplying inflection rules for your
+ application. Ember includes a default set of inflection rules, and provides an
+ API for providing additional rules.
+
+ Examples:
+
+ Creating an inflector with no rules.
+
+ ```js
+ var inflector = new Ember.Inflector();
+ ```
+
+ Creating an inflector with the default ember ruleset.
+
+ ```js
+ var inflector = new Ember.Inflector(Ember.Inflector.defaultRules);
+
+ inflector.pluralize('cow') //=> 'kine'
+ inflector.singularize('kine') //=> 'cow'
+ ```
+
+ Creating an inflector and adding rules later.
+
+ ```javascript
+ var inflector = Ember.Inflector.inflector;
+
+ inflector.pluralize('advice') // => 'advices'
+ inflector.uncountable('advice');
+ inflector.pluralize('advice') // => 'advice'
+
+ inflector.pluralize('formula') // => 'formulas'
+ inflector.irregular('formula', 'formulae');
+ inflector.pluralize('formula') // => 'formulae'
+
+ // you would not need to add these as they are the default rules
+ inflector.plural(/$/, 's');
+ inflector.singular(/s$/i, '');
+ ```
+
+ Creating an inflector with a nondefault ruleset.
+
+ ```javascript
+ var rules = {
+ plurals: [ /$/, 's' ],
+ singular: [ /\s$/, '' ],
+ irregularPairs: [
+ [ 'cow', 'kine' ]
+ ],
+ uncountable: [ 'fish' ]
+ };
+
+ var inflector = new Ember.Inflector(rules);
+ ```
+
+ @class Inflector
+ @namespace Ember
+*/
+function Inflector(ruleSet) {
+ ruleSet = ruleSet || {};
+ ruleSet.uncountable = ruleSet.uncountable || {};
+ ruleSet.irregularPairs = ruleSet.irregularPairs || {};
+
+ var rules = this.rules = {
+ plurals: ruleSet.plurals || [],
+ singular: ruleSet.singular || [],
+ irregular: {},
+ irregularInverse: {},
+ uncountable: {}
+ };
+
+ loadUncountable(rules, ruleSet.uncountable);
+ loadIrregular(rules, ruleSet.irregularPairs);
+}
+
+Inflector.prototype = {
+ /**
+ @method plural
+ @param {RegExp} regex
+ @param {String} string
+ */
+ plural: function(regex, string) {
+ this.rules.plurals.push([regex, string.toLowerCase()]);
+ },
+
+ /**
+ @method singular
+ @param {RegExp} regex
+ @param {String} string
+ */
+ singular: function(regex, string) {
+ this.rules.singular.push([regex, string.toLowerCase()]);
+ },
+
+ /**
+ @method uncountable
+ @param {String} regex
+ */
+ uncountable: function(string) {
+ loadUncountable(this.rules, [string.toLowerCase()]);
+ },
+
+ /**
+ @method irregular
+ @param {String} singular
+ @param {String} plural
+ */
+ irregular: function (singular, plural) {
+ loadIrregular(this.rules, [[singular, plural]]);
+ },
+
+ /**
+ @method pluralize
+ @param {String} word
+ */
+ pluralize: function(word) {
+ return this.inflect(word, this.rules.plurals, this.rules.irregular);
+ },
+
+ /**
+ @method singularize
+ @param {String} word
+ */
+ singularize: function(word) {
+ return this.inflect(word, this.rules.singular, this.rules.irregularInverse);
+ },
+
+ /**
+ @protected
+
+ @method inflect
+ @param {String} word
+ @param {Object} typeRules
+ @param {Object} irregular
+ */
+ inflect: function(word, typeRules, irregular) {
+ var inflection, substitution, result, lowercase, isBlank,
+ isUncountable, isIrregular, isIrregularInverse, rule;
+
+ isBlank = BLANK_REGEX.test(word);
+
+ if (isBlank) {
+ return word;
+ }
+
+ lowercase = word.toLowerCase();
+
+ isUncountable = this.rules.uncountable[lowercase];
+
+ if (isUncountable) {
+ return word;
+ }
+
+ isIrregular = irregular && irregular[lowercase];
+
+ if (isIrregular) {
+ return isIrregular;
+ }
+
+ for (var i = typeRules.length, min = 0; i > min; i--) {
+ inflection = typeRules[i-1];
+ rule = inflection[0];
+
+ if (rule.test(word)) {
+ break;
+ }
+ }
+
+ inflection = inflection || [];
+
+ rule = inflection[0];
+ substitution = inflection[1];
+
+ result = word.replace(rule, substitution);
+
+ return result;
+ }
+};
+
+Ember.Inflector = Inflector;
+
+})();
+
+
+
+(function() {
+Ember.Inflector.defaultRules = {
+ plurals: [
+ [/$/, 's'],
+ [/s$/i, 's'],
+ [/^(ax|test)is$/i, '$1es'],
+ [/(octop|vir)us$/i, '$1i'],
+ [/(octop|vir)i$/i, '$1i'],
+ [/(alias|status)$/i, '$1es'],
+ [/(bu)s$/i, '$1ses'],
+ [/(buffal|tomat)o$/i, '$1oes'],
+ [/([ti])um$/i, '$1a'],
+ [/([ti])a$/i, '$1a'],
+ [/sis$/i, 'ses'],
+ [/(?:([^f])fe|([lr])f)$/i, '$1$2ves'],
+ [/(hive)$/i, '$1s'],
+ [/([^aeiouy]|qu)y$/i, '$1ies'],
+ [/(x|ch|ss|sh)$/i, '$1es'],
+ [/(matr|vert|ind)(?:ix|ex)$/i, '$1ices'],
+ [/^(m|l)ouse$/i, '$1ice'],
+ [/^(m|l)ice$/i, '$1ice'],
+ [/^(ox)$/i, '$1en'],
+ [/^(oxen)$/i, '$1'],
+ [/(quiz)$/i, '$1zes']
+ ],
+
+ singular: [
+ [/s$/i, ''],
+ [/(ss)$/i, '$1'],
+ [/(n)ews$/i, '$1ews'],
+ [/([ti])a$/i, '$1um'],
+ [/((a)naly|(b)a|(d)iagno|(p)arenthe|(p)rogno|(s)ynop|(t)he)(sis|ses)$/i, '$1sis'],
+ [/(^analy)(sis|ses)$/i, '$1sis'],
+ [/([^f])ves$/i, '$1fe'],
+ [/(hive)s$/i, '$1'],
+ [/(tive)s$/i, '$1'],
+ [/([lr])ves$/i, '$1f'],
+ [/([^aeiouy]|qu)ies$/i, '$1y'],
+ [/(s)eries$/i, '$1eries'],
+ [/(m)ovies$/i, '$1ovie'],
+ [/(x|ch|ss|sh)es$/i, '$1'],
+ [/^(m|l)ice$/i, '$1ouse'],
+ [/(bus)(es)?$/i, '$1'],
+ [/(o)es$/i, '$1'],
+ [/(shoe)s$/i, '$1'],
+ [/(cris|test)(is|es)$/i, '$1is'],
+ [/^(a)x[ie]s$/i, '$1xis'],
+ [/(octop|vir)(us|i)$/i, '$1us'],
+ [/(alias|status)(es)?$/i, '$1'],
+ [/^(ox)en/i, '$1'],
+ [/(vert|ind)ices$/i, '$1ex'],
+ [/(matr)ices$/i, '$1ix'],
+ [/(quiz)zes$/i, '$1'],
+ [/(database)s$/i, '$1']
+ ],
+
+ irregularPairs: [
+ ['person', 'people'],
+ ['man', 'men'],
+ ['child', 'children'],
+ ['sex', 'sexes'],
+ ['move', 'moves'],
+ ['cow', 'kine'],
+ ['zombie', 'zombies']
+ ],
+
+ uncountable: [
+ 'equipment',
+ 'information',
+ 'rice',
+ 'money',
+ 'species',
+ 'series',
+ 'fish',
+ 'sheep',
+ 'jeans',
+ 'police'
+ ]
+};
+
+})();
+
+
+
+(function() {
+if (Ember.EXTEND_PROTOTYPES === true || Ember.EXTEND_PROTOTYPES.String) {
+ /**
+ See {{#crossLink "Ember.String/pluralize"}}{{/crossLink}}
+
+ @method pluralize
+ @for String
+ */
+ String.prototype.pluralize = function() {
+ return Ember.String.pluralize(this);
+ };
+
+ /**
+ See {{#crossLink "Ember.String/singularize"}}{{/crossLink}}
+
+ @method singularize
+ @for String
+ */
+ String.prototype.singularize = function() {
+ return Ember.String.singularize(this);
+ };
+}
+
+})();
+
+
+
+(function() {
+Ember.Inflector.inflector = new Ember.Inflector(Ember.Inflector.defaultRules);
+
+})();
+
+
+
+(function() {
+
+})();
+
+(function() {
+/**
+ @module ember-data
+*/
+
+var get = Ember.get,
+ forEach = Ember.EnumerableUtils.forEach,
+ camelize = Ember.String.camelize,
+ capitalize = Ember.String.capitalize,
+ decamelize = Ember.String.decamelize,
+ singularize = Ember.String.singularize,
+ underscore = Ember.String.underscore;
+
+DS.ActiveModelSerializer = DS.RESTSerializer.extend({
+ // SERIALIZE
+
+ /**
+ Converts camelcased attributes to underscored when serializing.
+
+ @method keyForAttribute
+ @param {String} attribute
+ @returns String
+ */
+ keyForAttribute: function(attr) {
+ return decamelize(attr);
+ },
+
+ /**
+ Underscores relationship names and appends "_id" or "_ids" when serializing
+ relationship keys.
+
+ @method keyForRelationship
+ @param {String} key
+ @param {String} kind
+ @returns String
+ */
+ keyForRelationship: function(key, kind) {
+ key = decamelize(key);
+ if (kind === "belongsTo") {
+ return key + "_id";
+ } else if (kind === "hasMany") {
+ return singularize(key) + "_ids";
+ } else {
+ return key;
+ }
+ },
+
+ /**
+ Does not serialize hasMany relationships by default.
+ */
+ serializeHasMany: Ember.K,
+
+ /**
+ Underscores the JSON root keys when serializing.
+
+ @method serializeIntoHash
+ @param {Object} hash
+ @param {subclass of DS.Model} type
+ @param {DS.Model} record
+ @param {Object} options
+ */
+ serializeIntoHash: function(data, type, record, options) {
+ var root = underscore(decamelize(type.typeKey));
+ data[root] = this.serialize(record, options);
+ },
+
+ /**
+ Serializes a polymorphic type as a fully capitalized model name.
+
+ @method serializePolymorphicType
+ @param {DS.Model} record
+ @param {Object} json
+ @param relationship
+ */
+ serializePolymorphicType: function(record, json, relationship) {
+ var key = relationship.key,
+ belongsTo = get(record, key);
+ key = this.keyForAttribute(key);
+ json[key + "_type"] = capitalize(camelize(belongsTo.constructor.typeKey));
+ },
+
+ // EXTRACT
+
+ /**
+ Extracts the model typeKey from underscored root objects.
+
+ @method typeForRoot
+ @param {String} root
+ @returns String the model's typeKey
+ */
+ typeForRoot: function(root) {
+ var camelized = camelize(root);
+ return singularize(camelized);
+ },
+
+ /**
+ Add extra step to `DS.RESTSerializer.normalize` so links are
+ normalized.
+
+ If your payload looks like this
+
+ ```js
+ {
+ "post": {
+ "id": 1,
+ "title": "Rails is omakase",
+ "links": { "flagged_comments": "api/comments/flagged" }
+ }
+ }
+ ```
+ The normalized version would look like this
+
+ ```js
+ {
+ "post": {
+ "id": 1,
+ "title": "Rails is omakase",
+ "links": { "flaggedComments": "api/comments/flagged" }
+ }
+ }
+ ```
+
+ @method normalize
+ @param {subclass of DS.Model} type
+ @param {Object} hash
+ @param {String} prop
+ @returns Object
+ */
+
+ normalize: function(type, hash, prop) {
+ this.normalizeLinks(hash);
+
+ return this._super(type, hash, prop);
+ },
+
+ /**
+ Convert `snake_cased` links to `camelCase`
+
+ @method normalizeLinks
+ @param {Object} hash
+ */
+
+ normalizeLinks: function(data){
+ if (data.links) {
+ var links = data.links;
+
+ for (var link in links) {
+ var camelizedLink = camelize(link);
+
+ if (camelizedLink !== link) {
+ links[camelizedLink] = links[link];
+ delete links[link];
+ }
+ }
+ }
+ },
+
+ /**
+ Normalize the polymorphic type from the JSON.
+
+ Normalize:
+ ```js
+ {
+ id: "1"
+ minion: { type: "evil_minion", id: "12"}
+ }
+ ```
+
+ To:
+ ```js
+ {
+ id: "1"
+ minion: { type: "evilMinion", id: "12"}
+ }
+ ```
+
+ @method normalizeRelationships
+ @private
+ */
+ normalizeRelationships: function(type, hash) {
+ var payloadKey, payload;
+
+ if (this.keyForRelationship) {
+ type.eachRelationship(function(key, relationship) {
+ if (relationship.options.polymorphic) {
+ payloadKey = this.keyForAttribute(key);
+ payload = hash[payloadKey];
+ if (payload && payload.type) {
+ payload.type = this.typeForRoot(payload.type);
+ } else if (payload && relationship.kind === "hasMany") {
+ var self = this;
+ forEach(payload, function(single) {
+ single.type = self.typeForRoot(single.type);
+ });
+ }
+ } else {
+ payloadKey = this.keyForRelationship(key, relationship.kind);
+ payload = hash[payloadKey];
+ }
+
+ hash[key] = payload;
+
+ if (key !== payloadKey) {
+ delete hash[payloadKey];
+ }
+ }, this);
+ }
+ }
+});
+
+})();
+
+
+
+(function() {
+var get = Ember.get;
+var forEach = Ember.EnumerableUtils.forEach;
+
+/**
+ The EmbeddedRecordsMixin allows you to add embedded record support to your
+ serializers.
+ To set up embedded records, you include the mixin into the serializer and then
+ define your embedded relations.
+
+ ```js
+ App.PostSerializer = DS.ActiveModelSerializer.extend(DS.EmbeddedRecordsMixin, {
+ attrs: {
+ comments: {embedded: 'always'}
+ }
+ })
+ ```
+
+ Currently only `{embedded: 'always'}` records are supported.
+
+ @class EmbeddedRecordsMixin
+ @namespace DS
+*/
+DS.EmbeddedRecordsMixin = Ember.Mixin.create({
+
+ /**
+ Serialize has-may relationship when it is configured as embedded objects.
+
+ @method serializeHasMany
+ */
+ serializeHasMany: function(record, json, relationship) {
+ var key = relationship.key,
+ attrs = get(this, 'attrs'),
+ embed = attrs && attrs[key] && attrs[key].embedded === 'always';
+
+ if (embed) {
+ json[this.keyForAttribute(key)] = get(record, key).map(function(relation) {
+ var data = relation.serialize(),
+ primaryKey = get(this, 'primaryKey');
+
+ data[primaryKey] = get(relation, primaryKey);
+
+ return data;
+ }, this);
+ }
+ },
+
+ /**
+ Extract embedded objects out of the payload for a single object
+ and add them as sideloaded objects instead.
+
+ @method extractSingle
+ */
+ extractSingle: function(store, primaryType, payload, recordId, requestType) {
+ var root = this.keyForAttribute(primaryType.typeKey),
+ partial = payload[root];
+
+ updatePayloadWithEmbedded(store, this, primaryType, partial, payload);
+
+ return this._super(store, primaryType, payload, recordId, requestType);
+ },
+
+ /**
+ Extract embedded objects out of a standard payload
+ and add them as sideloaded objects instead.
+
+ @method extractArray
+ */
+ extractArray: function(store, type, payload) {
+ var root = this.keyForAttribute(type.typeKey),
+ partials = payload[Ember.String.pluralize(root)];
+
+ forEach(partials, function(partial) {
+ updatePayloadWithEmbedded(store, this, type, partial, payload);
+ }, this);
+
+ return this._super(store, type, payload);
+ }
+});
+
+function updatePayloadWithEmbedded(store, serializer, type, partial, payload) {
+ var attrs = get(serializer, 'attrs');
+
+ if (!attrs) {
+ return;
+ }
+
+ type.eachRelationship(function(key, relationship) {
+ var expandedKey, embeddedTypeKey, attribute, ids,
+ config = attrs[key],
+ serializer = store.serializerFor(relationship.type.typeKey),
+ primaryKey = get(serializer, "primaryKey");
+
+ if (relationship.kind !== "hasMany") {
+ return;
+ }
+
+ if (config && (config.embedded === 'always' || config.embedded === 'load')) {
+ // underscore forces the embedded records to be side loaded.
+ // it is needed when main type === relationship.type
+ embeddedTypeKey = '_' + Ember.String.pluralize(relationship.type.typeKey);
+ expandedKey = this.keyForRelationship(key, relationship.kind);
+ attribute = this.keyForAttribute(key);
+ ids = [];
+
+ if (!partial[attribute]) {
+ return;
+ }
+
+ payload[embeddedTypeKey] = payload[embeddedTypeKey] || [];
+
+ forEach(partial[attribute], function(data) {
+ var embeddedType = store.modelFor(relationship.type.typeKey);
+ updatePayloadWithEmbedded(store, serializer, embeddedType, data, payload);
+ ids.push(data[primaryKey]);
+ payload[embeddedTypeKey].push(data);
+ });
+
+ partial[expandedKey] = ids;
+ delete partial[attribute];
+ }
+ }, serializer);
+}
+})();
+
+
+
+(function() {
+/**
+ @module ember-data
+*/
+
+var forEach = Ember.EnumerableUtils.forEach;
+var decamelize = Ember.String.decamelize,
+ underscore = Ember.String.underscore,
+ pluralize = Ember.String.pluralize;
+
+/**
+ The ActiveModelAdapter is a subclass of the RESTAdapter designed to integrate
+ with a JSON API that uses an underscored naming convention instead of camelcasing.
+ It has been designed to work out of the box with the
+ [active_model_serializers](http://github.com/rails-api/active_model_serializers)
+ Ruby gem.
+
+ This adapter extends the DS.RESTAdapter by making consistent use of the camelization,
+ decamelization and pluralization methods to normalize the serialized JSON into a
+ format that is compatible with a conventional Rails backend and Ember Data.
+
+ ## JSON Structure
+
+ The ActiveModelAdapter expects the JSON returned from your server to follow
+ the REST adapter conventions substituting underscored keys for camelcased ones.
+
+ ### Conventional Names
+
+ Attribute names in your JSON payload should be the underscored versions of
+ the attributes in your Ember.js models.
+
+ For example, if you have a `Person` model:
+
+ ```js
+ App.FamousPerson = DS.Model.extend({
+ firstName: DS.attr('string'),
+ lastName: DS.attr('string'),
+ occupation: DS.attr('string')
+ });
+ ```
+
+ The JSON returned should look like this:
+
+ ```js
+ {
+ "famous_person": {
+ "first_name": "Barack",
+ "last_name": "Obama",
+ "occupation": "President"
+ }
+ }
+ ```
+
+ @class ActiveModelAdapter
+ @constructor
+ @namespace DS
+ @extends DS.Adapter
+**/
+
+DS.ActiveModelAdapter = DS.RESTAdapter.extend({
+ defaultSerializer: '-active-model',
+ /**
+ The ActiveModelAdapter overrides the `pathForType` method to build
+ underscored URLs by decamelizing and pluralizing the object type name.
+
+ ```js
+ this.pathForType("famousPerson");
+ //=> "famous_people"
+ ```
+
+ @method pathForType
+ @param {String} type
+ @returns String
+ */
+ pathForType: function(type) {
+ var decamelized = decamelize(type);
+ var underscored = underscore(decamelized);
+ return pluralize(underscored);
+ },
+
+ /**
+ The ActiveModelAdapter overrides the `ajaxError` method
+ to return a DS.InvalidError for all 422 Unprocessable Entity
+ responses.
+
+ A 422 HTTP response from the server generally implies that the request
+ was well formed but the API was unable to process it because the
+ content was not semantically correct or meaningful per the API.
+
+ For more information on 422 HTTP Error code see 11.2 WebDAV RFC 4918
+ https://tools.ietf.org/html/rfc4918#section-11.2
+
+ @method ajaxError
+ @param jqXHR
+ @returns error
+ */
+ ajaxError: function(jqXHR) {
+ var error = this._super(jqXHR);
+
+ if (jqXHR && jqXHR.status === 422) {
+ var response = Ember.$.parseJSON(jqXHR.responseText),
+ errors = {};
+
+ if (response.errors !== undefined) {
+ var jsonErrors = response.errors;
+
+ forEach(Ember.keys(jsonErrors), function(key) {
+ errors[Ember.String.camelize(key)] = jsonErrors[key];
+ });
+ }
+
+ return new DS.InvalidError(errors);
+ } else {
+ return error;
+ }
+ }
+});
+
+})();
+
+
+
+(function() {
+
+})();
+
+
+
+(function() {
+Ember.onLoad('Ember.Application', function(Application) {
+ Application.initializer({
+ name: "activeModelAdapter",
+
+ initialize: function(container, application) {
+ var proxy = new DS.ContainerProxy(container);
+ proxy.registerDeprecations([
+ {deprecated: 'serializer:_ams', valid: 'serializer:-active-model'},
+ {deprecated: 'adapter:_ams', valid: 'adapter:-active-model'}
+ ]);
+
+ application.register('serializer:-active-model', DS.ActiveModelSerializer);
+ application.register('adapter:-active-model', DS.ActiveModelAdapter);
+ }
+ });
+});
+
+})();
+
+
+
+(function() {
+
+})();
+
+
+})();
</ins></span></pre></div>
<a id="trunkPerformanceTestsDoYouEvenBenchresourcestodomvcarchitectureexamplesemberjsbower_componentsemberlocalstorageadapterlocalstorage_adapterjs"></a>
<div class="modfile"><h4>Modified: trunk/PerformanceTests/DoYouEvenBench/resources/todomvc/architecture-examples/emberjs/bower_components/ember-localstorage-adapter/localstorage_adapter.js (163428 => 163429)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/PerformanceTests/DoYouEvenBench/resources/todomvc/architecture-examples/emberjs/bower_components/ember-localstorage-adapter/localstorage_adapter.js        2014-02-05 05:32:21 UTC (rev 163428)
+++ trunk/PerformanceTests/DoYouEvenBench/resources/todomvc/architecture-examples/emberjs/bower_components/ember-localstorage-adapter/localstorage_adapter.js        2014-02-05 05:36:04 UTC (rev 163429)
</span><span class="lines">@@ -1,66 +1,31 @@
</span><del>-DS.LSSerializer = DS.JSONSerializer.extend({
</del><ins>+/*global Ember*/
+/*global DS*/
+'use strict';
</ins><span class="cx">
</span><del>- addBelongsTo: function(data, record, key, association) {
- data[key] = record.get(key + '.id');
- },
-
- addHasMany: function(data, record, key, association) {
- data[key] = record.get(key).map(function(record) {
- return record.get('id');
- });
- },
-
- // extract expects a root key, we don't want to save all these keys to
- // localStorage so we generate the root keys here
- extract: function(loader, json, type, record) {
- this._super(loader, this.rootJSON(json, type), type, record);
- },
-
- extractMany: function(loader, json, type, records) {
- this._super(loader, this.rootJSON(json, type, 'pluralize'), type, records);
- },
-
- rootJSON: function(json, type, pluralize) {
- var root = this.rootForType(type);
- if (pluralize == 'pluralize') { root = this.pluralize(root); }
- var rootedJSON = {};
- rootedJSON[root] = json;
- return rootedJSON;
- }
-
-});
-
</del><span class="cx"> DS.LSAdapter = DS.Adapter.extend(Ember.Evented, {
</span><span class="cx">
</span><del>- init: function() {
- this._loadData();
- },
</del><ins>+ init: function () {
+ this._loadData();
+ },
</ins><span class="cx">
</span><del>- generateIdForRecord: function() {
- return Math.random().toString(32).slice(2).substr(0,5);
- },
</del><ins>+ generateIdForRecord: function () {
+ return Math.random().toString(32).slice(2).substr(0, 5);
+ },
</ins><span class="cx">
</span><del>- serializer: DS.LSSerializer.create(),
</del><ins>+ find: function (store, type, id) {
+ var namespace = this._namespaceForType(type);
+ return Ember.RSVP.resolve(Ember.copy(namespace.records[id]));
+ },
</ins><span class="cx">
</span><del>- find: function(store, type, id) {
- var namespace = this._namespaceForType(type);
- this._async(function(){
- var copy = Ember.copy(namespace.records[id]);
- this.didFindRecord(store, type, copy, id);
- });
- },
</del><ins>+ findMany: function (store, type, ids) {
+ var namespace = this._namespaceForType(type);
+ var results = [];
+ for (var i = 0; i < ids.length; i++) {
+ results.push(Ember.copy(namespace.records[ids[i]]));
+ }
+ return Ember.RSVP.resolve(results);
+ },
</ins><span class="cx">
</span><del>- findMany: function(store, type, ids) {
- var namespace = this._namespaceForType(type);
- this._async(function(){
- var results = [];
- for (var i = 0; i < ids.length; i++) {
- results.push(Ember.copy(namespace.records[ids[i]]));
- }
- this.didFindMany(store, type, results);
- });
- },
-
</del><span class="cx"> // Supports queries that look like this:
</span><span class="cx"> //
</span><span class="cx"> // {
</span><span class="lines">@@ -75,141 +40,87 @@
</span><span class="cx"> // match records with "complete: true" and the name "foo" or "bar"
</span><span class="cx"> //
</span><span class="cx"> // { complete: true, name: /foo|bar/ }
</span><del>- findQuery: function(store, type, query, recordArray) {
- var namespace = this._namespaceForType(type);
- this._async(function() {
- var results = this.query(namespace.records, query);
- this.didFindQuery(store, type, results, recordArray);
- });
- },
</del><ins>+ findQuery: function (store, type, query, recordArray) {
+ var namespace = this._namespaceForType(type);
+ var results = this.query(namespace.records, query);
+ return Ember.RSVP.resolve(results);
+ },
</ins><span class="cx">
</span><del>- query: function(records, query) {
- var results = [];
- var id, record, property, test, push;
- for (id in records) {
- record = records[id];
- for (property in query) {
- test = query[property];
- push = false;
- if (Object.prototype.toString.call(test) == '[object RegExp]') {
- push = test.test(record[property]);
- } else {
- push = record[property] === test;
</del><ins>+ query: function (records, query) {
+ var results = [];
+ var id, record, property, test, push;
+ for (id in records) {
+ record = records[id];
+ for (property in query) {
+ test = query[property];
+ push = false;
+ if (Object.prototype.toString.call(test) === '[object RegExp]') {
+ push = test.test(record[property]);
+ } else {
+ push = record[property] === test;
+ }
+ }
+ if (push) {
+ results.push(record);
+ }
</ins><span class="cx"> }
</span><del>- }
- if (push) {
- results.push(record);
- }
- }
- return results;
- },
</del><ins>+ return results;
+ },
</ins><span class="cx">
</span><del>- findAll: function(store, type) {
- var namespace = this._namespaceForType(type);
- this._async(function() {
- var results = [];
- for (var id in namespace.records) {
- results.push(Ember.copy(namespace.records[id]));
- }
- this.didFindAll(store, type, results);
- });
- },
</del><ins>+ findAll: function (store, type) {
+ var namespace = this._namespaceForType(type);
+ var results = [];
+ for (var id in namespace.records) {
+ results.push(Ember.copy(namespace.records[id]));
+ }
+ return Ember.RSVP.resolve(results);
+ },
</ins><span class="cx">
</span><del>- createRecords: function(store, type, records) {
- var namespace = this._namespaceForType(type);
- records.forEach(function(record) {
- this._addRecordToNamespace(namespace, record);
- }, this);
- this._async(function() {
- this._didSaveRecords(store, type, records);
- });
- },
</del><ins>+ createRecord: function (store, type, record) {
+ var namespace = this._namespaceForType(type);
+ this._addRecordToNamespace(namespace, record);
+ this._saveData();
+ return Ember.RSVP.resolve();
+ },
</ins><span class="cx">
</span><del>- updateRecords: function(store, type, records) {
- var namespace = this._namespaceForType(type);
- this._async(function() {
- records.forEach(function(record) {
</del><ins>+ updateRecord: function (store, type, record) {
+ var namespace = this._namespaceForType(type);
</ins><span class="cx"> var id = record.get('id');
</span><del>- namespace.records[id] = record.serialize({includeId:true});
- }, this);
- this._didSaveRecords(store, type, records);
- });
- },
</del><ins>+ namespace.records[id] = record.toJSON({ includeId: true });
+ this._saveData();
+ return Ember.RSVP.resolve();
+ },
</ins><span class="cx">
</span><del>- deleteRecords: function(store, type, records) {
- var namespace = this._namespaceForType(type);
- this._async(function() {
- records.forEach(function(record) {
</del><ins>+ deleteRecord: function (store, type, record) {
+ var namespace = this._namespaceForType(type);
</ins><span class="cx"> var id = record.get('id');
</span><span class="cx"> delete namespace.records[id];
</span><del>- });
- this._didSaveRecords(store, type, records);
- });
</del><ins>+ this._saveData();
+ return Ember.RSVP.resolve();
+ },
</ins><span class="cx">
</span><del>- },
-
- dirtyRecordsForHasManyChange: function(dirtySet, parent, relationship) {
- dirtySet.add(parent);
- },
-
- dirtyRecordsForBelongsToChange: function(dirtySet, child, relationship) {
- dirtySet.add(child);
- },
-
</del><span class="cx"> // private
</span><span class="cx">
</span><del>- _getNamespace: function() {
- return this.namespace || 'DS.LSAdapter';
- },
</del><ins>+ _getNamespace: function () {
+ return this.namespace || 'DS.LSAdapter';
+ },
</ins><span class="cx">
</span><del>- _loadData: function() {
- var storage = localStorage.getItem(this._getNamespace());
- this._data = storage ? JSON.parse(storage) : {};
- },
</del><ins>+ _loadData: function () {
+ this._data = {};
+ },
</ins><span class="cx">
</span><del>- _didSaveRecords: function(store, type, records) {
- var success = this._saveData();
- if (success) {
- store.didSaveRecords(records);
- } else {
- records.forEach(function(record) {
- store.recordWasError(record);
- });
- this.trigger('QUOTA_EXCEEDED_ERR', records);
- }
- },
</del><ins>+ _saveData: function () {
+ },
</ins><span class="cx">
</span><del>- _saveData: function() {
- try {
- localStorage.setItem(this._getNamespace(), JSON.stringify(this._data));
- return true;
- } catch(error) {
- if (error.name == 'QUOTA_EXCEEDED_ERR') {
- return false;
- } else {
- throw new Error(error);
- }
- }
- },
</del><ins>+ _namespaceForType: function (type) {
+ var namespace = type.url || type.toString();
+ return this._data[namespace] || (
+ this._data[namespace] = {records: {}}
+ );
+ },
</ins><span class="cx">
</span><del>- _namespaceForType: function(type) {
- var namespace = type.url || type.toString();
- return this._data[namespace] || (
- this._data[namespace] = {records: {}}
- );
- },
-
- _addRecordToNamespace: function(namespace, record) {
- var data = record.serialize({includeId: true});
- namespace.records[data.id] = data;
- },
-
- _async: function(callback) {
- var _this = this;
- setTimeout(function(){
- Ember.run(_this, callback);
- }, 1);
- }
-
</del><ins>+ _addRecordToNamespace: function (namespace, record) {
+ var data = record.serialize({includeId: true});
+ namespace.records[data.id] = data;
+ }
</ins><span class="cx"> });
</span><del>-
</del></span></pre></div>
<a id="trunkPerformanceTestsDoYouEvenBenchresourcestodomvcarchitectureexamplesemberjsbower_componentshandlebarshandlebarsjs"></a>
<div class="modfile"><h4>Modified: trunk/PerformanceTests/DoYouEvenBench/resources/todomvc/architecture-examples/emberjs/bower_components/handlebars/handlebars.js (163428 => 163429)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/PerformanceTests/DoYouEvenBench/resources/todomvc/architecture-examples/emberjs/bower_components/handlebars/handlebars.js        2014-02-05 05:32:21 UTC (rev 163428)
+++ trunk/PerformanceTests/DoYouEvenBench/resources/todomvc/architecture-examples/emberjs/bower_components/handlebars/handlebars.js        2014-02-05 05:36:04 UTC (rev 163429)
</span><span class="lines">@@ -1,5 +1,7 @@
</span><del>-/*
</del><ins>+/*!
</ins><span class="cx">
</span><ins>+ handlebars v1.3.0
+
</ins><span class="cx"> Copyright (C) 2011 by Yehuda Katz
</span><span class="cx">
</span><span class="cx"> Permission is hereby granted, free of charge, to any person obtaining a copy
</span><span class="lines">@@ -20,846 +22,1269 @@
</span><span class="cx"> OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
</span><span class="cx"> THE SOFTWARE.
</span><span class="cx">
</span><ins>+@license
</ins><span class="cx"> */
</span><ins>+/* 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;
+ }
</ins><span class="cx">
</span><del>-// lib/handlebars/base.js
</del><ins>+ SafeString.prototype.toString = function() {
+ return "" + this.string;
+ };
</ins><span class="cx">
</span><del>-/*jshint eqnull:true*/
-this.Handlebars = {};
</del><ins>+ __exports__ = SafeString;
+ return __exports__;
+})();
</ins><span class="cx">
</span><del>-(function(Handlebars) {
</del><ins>+// handlebars/utils.js
+var __module3__ = (function(__dependency1__) {
+ "use strict";
+ var __exports__ = {};
+ /*jshint -W004 */
+ var SafeString = __dependency1__;
</ins><span class="cx">
</span><del>-Handlebars.VERSION = "1.0.0-rc.3";
-Handlebars.COMPILER_REVISION = 2;
</del><ins>+ var escape = {
+ "&": "&amp;",
+ "<": "&lt;",
+ ">": "&gt;",
+ '"': "&quot;",
+ "'": "&#x27;",
+ "`": "&#x60;"
+ };
</ins><span class="cx">
</span><del>-Handlebars.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'
-};
</del><ins>+ var badChars = /[&<>"'`]/g;
+ var possible = /[&<>"'`]/;
</ins><span class="cx">
</span><del>-Handlebars.helpers = {};
-Handlebars.partials = {};
</del><ins>+ function escapeChar(chr) {
+ return escape[chr] || "&amp;";
+ }
</ins><span class="cx">
</span><del>-Handlebars.registerHelper = function(name, fn, inverse) {
- if(inverse) { fn.not = inverse; }
- this.helpers[name] = fn;
-};
</del><ins>+ function extend(obj, value) {
+ for(var key in value) {
+ if(Object.prototype.hasOwnProperty.call(value, key)) {
+ obj[key] = value[key];
+ }
+ }
+ }
</ins><span class="cx">
</span><del>-Handlebars.registerPartial = function(name, str) {
- this.partials[name] = str;
-};
</del><ins>+ __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;
</ins><span class="cx">
</span><del>-Handlebars.registerHelper('helperMissing', function(arg) {
- if(arguments.length === 2) {
- return undefined;
- } else {
- throw new Error("Could not find property '" + arg + "'");
</del><ins>+ 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);
</ins><span class="cx"> }
</span><del>-});
</del><span class="cx">
</span><del>-var toString = Object.prototype.toString, functionType = "[object Function]";
</del><ins>+ __exports__.escapeExpression = escapeExpression;function isEmpty(value) {
+ if (!value && value !== 0) {
+ return true;
+ } else if (isArray(value) && value.length === 0) {
+ return true;
+ } else {
+ return false;
+ }
+ }
</ins><span class="cx">
</span><del>-Handlebars.registerHelper('blockHelperMissing', function(context, options) {
- var inverse = options.inverse || function() {}, fn = options.fn;
</del><ins>+ __exports__.isEmpty = isEmpty;
+ return __exports__;
+})(__module4__);
</ins><span class="cx">
</span><ins>+// handlebars/exception.js
+var __module5__ = (function() {
+ "use strict";
+ var __exports__;
</ins><span class="cx">
</span><del>- var ret = "";
- var type = toString.call(context);
</del><ins>+ var errorProps = ['description', 'fileName', 'lineNumber', 'message', 'name', 'number', 'stack'];
</ins><span class="cx">
</span><del>- if(type === functionType) { context = context.call(this); }
</del><ins>+ function Exception(message, node) {
+ var line;
+ if (node && node.firstLine) {
+ line = node.firstLine;
</ins><span class="cx">
</span><del>- if(context === true) {
- return fn(this);
- } else if(context === false || context == null) {
- return inverse(this);
- } else if(type === "[object Array]") {
- if(context.length > 0) {
- return Handlebars.helpers.each(context, options);
- } else {
- return inverse(this);
</del><ins>+ message += ' - ' + line + ':' + node.firstColumn;
</ins><span class="cx"> }
</span><del>- } else {
- return fn(context);
</del><ins>+
+ 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;
+ }
</ins><span class="cx"> }
</span><del>-});
</del><span class="cx">
</span><del>-Handlebars.K = function() {};
</del><ins>+ Exception.prototype = new Error();
</ins><span class="cx">
</span><del>-Handlebars.createFrame = Object.create || function(object) {
- Handlebars.K.prototype = object;
- var obj = new Handlebars.K();
- Handlebars.K.prototype = null;
- return obj;
-};
</del><ins>+ __exports__ = Exception;
+ return __exports__;
+})();
</ins><span class="cx">
</span><del>-Handlebars.logger = {
- DEBUG: 0, INFO: 1, WARN: 2, ERROR: 3, level: 3,
</del><ins>+// handlebars/base.js
+var __module2__ = (function(__dependency1__, __dependency2__) {
+ "use strict";
+ var __exports__ = {};
+ var Utils = __dependency1__;
+ var Exception = __dependency2__;
</ins><span class="cx">
</span><del>- methodMap: {0: 'debug', 1: 'info', 2: 'warn', 3: 'error'},
</del><ins>+ 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]';
</ins><span class="cx">
</span><del>- // can be overridden in the host environment
- log: function(level, obj) {
- if (Handlebars.logger.level <= level) {
- var method = Handlebars.logger.methodMap[level];
- if (typeof console !== 'undefined' && console[method]) {
- console[method].call(console, obj);
</del><ins>+ 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;
</ins><span class="cx"> }
</span><ins>+ },
+
+ registerPartial: function(name, str) {
+ if (toString.call(name) === objectType) {
+ Utils.extend(this.partials, name);
+ } else {
+ this.partials[name] = str;
+ }
</ins><span class="cx"> }
</span><del>- }
-};
</del><ins>+ };
</ins><span class="cx">
</span><del>-Handlebars.log = function(level, obj) { Handlebars.logger.log(level, obj); };
</del><ins>+ function registerDefaultHelpers(instance) {
+ instance.registerHelper('helperMissing', function(arg) {
+ if(arguments.length === 2) {
+ return undefined;
+ } else {
+ throw new Exception("Missing helper: '" + arg + "'");
+ }
+ });
</ins><span class="cx">
</span><del>-Handlebars.registerHelper('each', function(context, options) {
- var fn = options.fn, inverse = options.inverse;
- var i = 0, ret = "", data;
</del><ins>+ instance.registerHelper('blockHelperMissing', function(context, options) {
+ var inverse = options.inverse || function() {}, fn = options.fn;
</ins><span class="cx">
</span><del>- if (options.data) {
- data = Handlebars.createFrame(options.data);
- }
</del><ins>+ if (isFunction(context)) { context = context.call(this); }
</ins><span class="cx">
</span><del>- if(context && typeof context === 'object') {
- if(context instanceof Array){
- for(var j = context.length; i<j; i++) {
- if (data) { data.index = i; }
- ret = ret + fn(context[i], { data: data });
</del><ins>+ 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);
</ins><span class="cx"> }
</span><del>- } else {
- for(var key in context) {
- if(context.hasOwnProperty(key)) {
- if(data) { data.key = key; }
- ret = ret + fn(context[key], {data: data});
- i++;
</del><ins>+ });
+
+ 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<j; i++) {
+ if (data) {
+ data.index = i;
+ data.first = (i === 0);
+ data.last = (i === (context.length-1));
+ }
+ ret = ret + fn(context[i], { data: data });
+ }
+ } else {
+ for(var key in context) {
+ if(context.hasOwnProperty(key)) {
+ if(data) {
+ data.key = key;
+ data.index = i;
+ data.first = (i === 0);
+ }
+ ret = ret + fn(context[key], {data: data});
+ i++;
+ }
+ }
</ins><span class="cx"> }
</span><span class="cx"> }
</span><del>- }
- }
</del><span class="cx">
</span><del>- if(i === 0){
- ret = inverse(this);
- }
</del><ins>+ if(i === 0){
+ ret = inverse(this);
+ }
</ins><span class="cx">
</span><del>- return ret;
-});
</del><ins>+ return ret;
+ });
</ins><span class="cx">
</span><del>-Handlebars.registerHelper('if', function(context, options) {
- var type = toString.call(context);
- if(type === functionType) { context = context.call(this); }
</del><ins>+ instance.registerHelper('if', function(conditional, options) {
+ if (isFunction(conditional)) { conditional = conditional.call(this); }
</ins><span class="cx">
</span><del>- if(!context || Handlebars.Utils.isEmpty(context)) {
- return options.inverse(this);
- } else {
- return options.fn(this);
</del><ins>+ // Default behavior is to render the positive path if the value is truthy and not empty.
+ // The `includeZero` option may be set to treat the condtional as purely not empty based on the
+ // behavior of isEmpty. Effectively this determines if 0 is handled by the positive path or negative.
+ if ((!options.hash.includeZero && !conditional) || Utils.isEmpty(conditional)) {
+ return options.inverse(this);
+ } else {
+ return options.fn(this);
+ }
+ });
+
+ instance.registerHelper('unless', function(conditional, options) {
+ return instance.helpers['if'].call(this, conditional, {fn: options.inverse, inverse: options.fn, hash: options.hash});
+ });
+
+ instance.registerHelper('with', function(context, options) {
+ if (isFunction(context)) { context = context.call(this); }
+
+ if (!Utils.isEmpty(context)) return options.fn(context);
+ });
+
+ instance.registerHelper('log', function(context, options) {
+ var level = options.data && options.data.level != null ? parseInt(options.data.level, 10) : 1;
+ instance.log(level, context);
+ });
</ins><span class="cx"> }
</span><del>-});
</del><span class="cx">
</span><del>-Handlebars.registerHelper('unless', function(context, options) {
- var fn = options.fn, inverse = options.inverse;
- options.fn = inverse;
- options.inverse = fn;
</del><ins>+ var logger = {
+ methodMap: { 0: 'debug', 1: 'info', 2: 'warn', 3: 'error' },
</ins><span class="cx">
</span><del>- return Handlebars.helpers['if'].call(this, context, options);
-});
</del><ins>+ // State enum
+ DEBUG: 0,
+ INFO: 1,
+ WARN: 2,
+ ERROR: 3,
+ level: 3,
</ins><span class="cx">
</span><del>-Handlebars.registerHelper('with', function(context, options) {
- return options.fn(context);
-});
</del><ins>+ // can be overridden in the host environment
+ log: function(level, obj) {
+ if (logger.level <= level) {
+ var method = logger.methodMap[level];
+ if (typeof console !== 'undefined' && console[method]) {
+ console[method].call(console, obj);
+ }
+ }
+ }
+ };
+ __exports__.logger = logger;
+ function log(level, obj) { logger.log(level, obj); }
</ins><span class="cx">
</span><del>-Handlebars.registerHelper('log', function(context, options) {
- var level = options.data && options.data.level != null ? parseInt(options.data.level, 10) : 1;
- Handlebars.log(level, context);
-});
</del><ins>+ __exports__.log = log;var createFrame = function(object) {
+ var obj = {};
+ Utils.extend(obj, object);
+ return obj;
+ };
+ __exports__.createFrame = createFrame;
+ return __exports__;
+})(__module3__, __module5__);
</ins><span class="cx">
</span><del>-}(this.Handlebars));
-;
-// lib/handlebars/compiler/parser.js
-/* Jison generated parser */
-var handlebars = (function(){
-var parser = {trace: function trace() { },
-yy: {},
-symbols_: {"error":2,"root":3,"program":4,"EOF":5,"simpleInverse":6,"statements":7,"statement":8,"openInverse":9,"closeBlock":10,"openBlock":11,"mustache":12,"partial":13,"CONTENT":14,"COMMENT":15,"OPEN_BLOCK":16,"inMustache":17,"CLOSE":18,"OPEN_INVERSE":19,"OPEN_ENDBLOCK":20,"path":21,"OPEN":22,"OPEN_UNESCAPED":23,"OPEN_PARTIAL":24,"partialName":25,"params":26,"hash":27,"DATA":28,"param":29,"STRING":30,"INTEGER":31,"BOOLEAN":32,"hashSegments":33,"hashSegment":34,"ID":35,"EQUALS":36,"PARTIAL_NAME":37,"pathSegments":38,"SEP":39,"$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:"OPEN_PARTIAL",28:"DATA",30:"STRING",31:"INTEGER",32:"BOOLEAN",35:"ID",36:"EQUALS",37:"PARTIAL_NAME",39:"SEP"},
-productions_: [0,[3,2],[4,2],[4,3],[4,2],[4,1],[4,1],[4,0],[7,1],[7,2],[8,3],[8,3],[8,1],[8,1],[8,1],[8,1],[11,3],[9,3],[10,3],[12,3],[12,3],[13,3],[13,4],[6,2],[17,3],[17,2],[17,2],[17,1],[17,1],[26,2],[26,1],[29,1],[29,1],[29,1],[29,1],[29,1],[27,1],[33,2],[33,1],[34,3],[34,3],[34,3],[34,3],[34,3],[25,1],[21,1],[38,3],[38,1]],
-performAction: function anonymous(yytext,yyleng,yylineno,yy,yystate,$$,_$) {
</del><ins>+// handlebars/runtime.js
+var __module6__ = (function(__dependency1__, __dependency2__, __dependency3__) {
+ "use strict";
+ var __exports__ = {};
+ var Utils = __dependency1__;
+ var Exception = __dependency2__;
+ var COMPILER_REVISION = __dependency3__.COMPILER_REVISION;
+ var REVISION_CHANGES = __dependency3__.REVISION_CHANGES;
</ins><span class="cx">
</span><del>-var $0 = $$.length - 1;
-switch (yystate) {
-case 1: return $$[$0-1];
-break;
-case 2: this.$ = new yy.ProgramNode([], $$[$0]);
-break;
-case 3: this.$ = new yy.ProgramNode($$[$0-2], $$[$0]);
-break;
-case 4: this.$ = new yy.ProgramNode($$[$0-1], []);
-break;
-case 5: this.$ = new yy.ProgramNode($$[$0]);
-break;
-case 6: this.$ = new yy.ProgramNode([], []);
-break;
-case 7: this.$ = new yy.ProgramNode([]);
-break;
-case 8: this.$ = [$$[$0]];
-break;
-case 9: $$[$0-1].push($$[$0]); this.$ = $$[$0-1];
-break;
-case 10: this.$ = new yy.BlockNode($$[$0-2], $$[$0-1].inverse, $$[$0-1], $$[$0]);
-break;
-case 11: this.$ = new yy.BlockNode($$[$0-2], $$[$0-1], $$[$0-1].inverse, $$[$0]);
-break;
-case 12: this.$ = $$[$0];
-break;
-case 13: this.$ = $$[$0];
-break;
-case 14: this.$ = new yy.ContentNode($$[$0]);
-break;
-case 15: this.$ = new yy.CommentNode($$[$0]);
-break;
-case 16: this.$ = new yy.MustacheNode($$[$0-1][0], $$[$0-1][1]);
-break;
-case 17: this.$ = new yy.MustacheNode($$[$0-1][0], $$[$0-1][1]);
-break;
-case 18: this.$ = $$[$0-1];
-break;
-case 19: this.$ = new yy.MustacheNode($$[$0-1][0], $$[$0-1][1]);
-break;
-case 20: this.$ = new yy.MustacheNode($$[$0-1][0], $$[$0-1][1], true);
-break;
-case 21: this.$ = new yy.PartialNode($$[$0-1]);
-break;
-case 22: this.$ = new yy.PartialNode($$[$0-2], $$[$0-1]);
-break;
-case 23:
-break;
-case 24: this.$ = [[$$[$0-2]].concat($$[$0-1]), $$[$0]];
-break;
-case 25: this.$ = [[$$[$0-1]].concat($$[$0]), null];
-break;
-case 26: this.$ = [[$$[$0-1]], $$[$0]];
-break;
-case 27: this.$ = [[$$[$0]], null];
-break;
-case 28: this.$ = [[new yy.DataNode($$[$0])], null];
-break;
-case 29: $$[$0-1].push($$[$0]); this.$ = $$[$0-1];
-break;
-case 30: this.$ = [$$[$0]];
-break;
-case 31: this.$ = $$[$0];
-break;
-case 32: this.$ = new yy.StringNode($$[$0]);
-break;
-case 33: this.$ = new yy.IntegerNode($$[$0]);
-break;
-case 34: this.$ = new yy.BooleanNode($$[$0]);
-break;
-case 35: this.$ = new yy.DataNode($$[$0]);
-break;
-case 36: this.$ = new yy.HashNode($$[$0]);
-break;
-case 37: $$[$0-1].push($$[$0]); this.$ = $$[$0-1];
-break;
-case 38: this.$ = [$$[$0]];
-break;
-case 39: this.$ = [$$[$0-2], $$[$0]];
-break;
-case 40: this.$ = [$$[$0-2], new yy.StringNode($$[$0])];
-break;
-case 41: this.$ = [$$[$0-2], new yy.IntegerNode($$[$0])];
-break;
-case 42: this.$ = [$$[$0-2], new yy.BooleanNode($$[$0])];
-break;
-case 43: this.$ = [$$[$0-2], new yy.DataNode($$[$0])];
-break;
-case 44: this.$ = new yy.PartialNameNode($$[$0]);
-break;
-case 45: this.$ = new yy.IdNode($$[$0]);
-break;
-case 46: $$[$0-2].push($$[$0]); this.$ = $$[$0-2];
-break;
-case 47: this.$ = [$$[$0]];
-break;
-}
-},
-table: [{3:1,4:2,5:[2,7],6:3,7:4,8:6,9:7,11:8,12:9,13:10,14:[1,11],15:[1,12],16:[1,13],19:[1,5],22:[1,14],23:[1,15],24:[1,16]},{1:[3]},{5:[1,17]},{5:[2,6],7:18,8:6,9:7,11:8,12:9,13:10,14:[1,11],15:[1,12],16:[1,13],19:[1,19],20:[2,6],22:[1,14],23:[1,15],24:[1,16]},{5:[2,5],6:20,8:21,9:7,11:8,12:9,13:10,14:[1,11],15:[1,12],16:[1,13],19:[1,5],20:[2,5],22:[1,14],23:[1,15],24:[1,16]},{17:23,18:[1,22],21:24,28:[1,25],35:[1,27],38:26},{5:[2,8],14:[2,8],15:[2,8],16:[2,8],19:[2,8],20:[2,8],22:[2,8],23:[2,8],24:[2,8]},{4:28,6:3,7:4,8:6,9:7,11:8,12:9,13:10,14:[1,11],15:[1,12],16:[1,13],19:[1,5],20:[2,7],22:[1,14],23:[1,15],24:[1,16]},{4:29,6:3,7:4,8:6,9:7,11:8,12:9,13:10,14:[1,11],15:[1,12],16:[1,13],19:[1,5],20:[2,7],22:[1,14],23:[1,15],24:[1,16]},{5:[2,12],14:[2,12],15:[2,12],16:[2,12],19:[2,12],20:[2,12],22:[2,12],23:[2,12],24:[2,12]},{5:[2,13],14:[2,13],15:[2,13],16:[2,13],19:[2,13],20:[2,13],22:[2,13],23:[2,13],24:[2,13]},{5:[2,14],14:[2,14],15:[2,14],16:[2,14],19:[2,14],20:[2,14],22:[2,14],23:[2,14],24:[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],24:[2,15]},{17:30,21:24,28:[1,25],35:[1,27],38:26},{17:31,21:24,28:[1,25],35:[1,27],38:26},{17:32,21:24,28:[1,25],35:[1,27],38:26},{25:33,37:[1,34]},{1:[2,1]},{5:[2,2],8:21,9:7,11:8,12:9,13:10,14:[1,11],15:[1,12],16:[1,13],19:[1,19],20:[2,2],22:[1,14],23:[1,15],24:[1,16]},{17:23,21:24,28:[1,25],35:[1,27],38:26},{5:[2,4],7:35,8:6,9:7,11:8,12:9,13:10,14:[1,11],15:[1,12],16:[1,13],19:[1,19],20:[2,4],22:[1,14],23:[1,15],24:[1,16]},{5:[2,9],14:[2,9],15:[2,9],16:[2,9],19:[2,9],20:[2,9],22:[2,9],23:[2,9],24:[2,9]},{5:[2,23],14:[2,23],15:[2,23],16:[2,23],19:[2,23],20:[2,23],22:[2,23],23:[2,23],24:[2,23]},{18:[1,36]},{18:[2,27],21:41,26:37,27:38,28:[1,45],29:39,30:[1,42],31:[1,43],32:[1,44],33:40,34:46,35:[1,47],38:26},{18:[2,28]},{18:[2,45],28:[2,45],30:[2,45],31:[2,45],32:[2,45],35:[2,45],39:[1,48]},{18:[2,47],28:[2,47],30:[2,47],31:[2,47],32:[2,47],35:[2,47],39:[2,47]},{10:49,20:[1,50]},{10:51,20:[1,50]},{18:[1,52]},{18:[1,53]},{18:[1,54]},{18:[1,55],21:56,35:[1,27],38:26},{18:[2,44],35:[2,44]},{5:[2,3],8:21,9:7,11:8,12:9,13:10,14:[1,11],15:[1,12],16:[1,13],19:[1,19],20:[2,3],22:[1,14],23:[1,15],24:[1,16]},{14:[2,17],15:[2,17],16:[2,17],19:[2,17],20:[2,17],22:[2,17],23:[2,17],24:[2,17]},{18:[2,25],21:41,27:57,28:[1,45],29:58,30:[1,42],31:[1,43],32:[1,44],33:40,34:46,35:[1,47],38:26},{18:[2,26]},{18:[2,30],28:[2,30],30:[2,30],31:[2,30],32:[2,30],35:[2,30]},{18:[2,36],34:59,35:[1,60]},{18:[2,31],28:[2,31],30:[2,31],31:[2,31],32:[2,31],35:[2,31]},{18:[2,32],28:[2,32],30:[2,32],31:[2,32],32:[2,32],35:[2,32]},{18:[2,33],28:[2,33],30:[2,33],31:[2,33],32:[2,33],35:[2,33]},{18:[2,34],28:[2,34],30:[2,34],31:[2,34],32:[2,34],35:[2,34]},{18:[2,35],28:[2,35],30:[2,35],31:[2,35],32:[2,35],35:[2,35]},{18:[2,38],35:[2,38]},{18:[2,47],28:[2,47],30:[2,47],31:[2,47],32:[2,47],35:[2,47],36:[1,61],39:[2,47]},{35:[1,62]},{5:[2,10],14:[2,10],15:[2,10],16:[2,10],19:[2,10],20:[2,10],22:[2,10],23:[2,10],24:[2,10]},{21:63,35:[1,27],38:26},{5:[2,11],14:[2,11],15:[2,11],16:[2,11],19:[2,11],20:[2,11],22:[2,11],23:[2,11],24:[2,11]},{14:[2,16],15:[2,16],16:[2,16],19:[2,16],20:[2,16],22:[2,16],23:[2,16],24:[2,16]},{5:[2,19],14:[2,19],15:[2,19],16:[2,19],19:[2,19],20:[2,19],22:[2,19],23:[2,19],24:[2,19]},{5:[2,20],14:[2,20],15:[2,20],16:[2,20],19:[2,20],20:[2,20],22:[2,20],23:[2,20],24:[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],24:[2,21]},{18:[1,64]},{18:[2,24]},{18:[2,29],28:[2,29],30:[2,29],31:[2,29],32:[2,29],35:[2,29]},{18:[2,37],35:[2,37]},{36:[1,61]},{21:65,28:[1,69],30:[1,66],31:[1,67],32:[1,68],35:[1,27],38:26},{18:[2,46],28:[2,46],30:[2,46],31:[2,46],32:[2,46],35:[2,46],39:[2,46]},{18:[1,70]},{5:[2,22],14:[2,22],15:[2,22],16:[2,22],19:[2,22],20:[2,22],22:[2,22],23:[2,22],24:[2,22]},{18:[2,39],35:[2,39]},{18:[2,40],35:[2,40]},{18:[2,41],35:[2,41]},{18:[2,42],35:[2,42]},{18:[2,43],35:[2,43]},{5:[2,18],14:[2,18],15:[2,18],16:[2,18],19:[2,18],20:[2,18],22:[2,18],23:[2,18],24:[2,18]}],
-defaultActions: {17:[2,1],25:[2,28],38:[2,26],57:[2,24]},
-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;
</del><ins>+ function checkRevision(compilerInfo) {
+ var compilerRevision = compilerInfo && compilerInfo[0] || 1,
+ currentRevision = COMPILER_REVISION;
+
+ if (compilerRevision !== currentRevision) {
+ if (compilerRevision < currentRevision) {
+ var runtimeVersions = REVISION_CHANGES[currentRevision],
+ compilerVersions = REVISION_CHANGES[compilerRevision];
+ throw new Exception("Template was precompiled with an older version of Handlebars than the current runtime. "+
+ "Please update your precompiler to a newer version ("+runtimeVersions+") or downgrade your runtime to an older version ("+compilerVersions+").");
+ } else {
+ // Use the embedded version info since the runtime doesn't know about this revision yet
+ throw new Exception("Template was precompiled with a newer version of Handlebars than the current runtime. "+
+ "Please update your runtime to a newer version ("+compilerInfo[1]+").");
+ }
</ins><span class="cx"> }
</span><del>- function lex() {
- var token;
- token = self.lexer.lex() || 1;
- if (typeof token !== "number") {
- token = self.symbols_[token] || token;
- }
- return token;
</del><ins>+ }
+
+ __exports__.checkRevision = checkRevision;// TODO: Remove this line and break up compilePartial
+
+ function template(templateSpec, env) {
+ if (!env) {
+ throw new Exception("No environment passed to template");
</ins><span class="cx"> }
</span><del>- 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];
</del><ins>+
+ // Note: Using env.VM references rather than local var references throughout this section to allow
+ // for external users to override these as psuedo-supported APIs.
+ var invokePartialWrapper = function(partial, name, context, helpers, partials, data) {
+ var result = env.VM.invokePartial.apply(this, arguments);
+ if (result != null) { return result; }
+
+ if (env.compile) {
+ var options = { helpers: helpers, partials: partials, data: data };
+ partials[name] = env.compile(partial, { data: data !== undefined }, env);
+ return partials[name](context, options);
+ } else {
+ throw new Exception("The partial " + name + " could not be compiled when running in runtime-only mode");
+ }
+ };
+
+ // Just add water
+ var container = {
+ escapeExpression: Utils.escapeExpression,
+ invokePartial: invokePartialWrapper,
+ programs: [],
+ program: function(i, fn, data) {
+ var programWrapper = this.programs[i];
+ if(data) {
+ programWrapper = program(i, fn, data);
+ } else if (!programWrapper) {
+ programWrapper = this.programs[i] = program(i, fn);
</ins><span class="cx"> }
</span><del>- 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});
- }
</del><ins>+ return programWrapper;
+ },
+ merge: function(param, common) {
+ var ret = param || common;
+
+ if (param && common && (param !== common)) {
+ ret = {};
+ Utils.extend(ret, common);
+ Utils.extend(ret, param);
</ins><span class="cx"> }
</span><del>- 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;
- }
</del><ins>+ return ret;
+ },
+ programWithDepth: env.VM.programWithDepth,
+ noop: env.VM.noop,
+ compilerInfo: null
+ };
+
+ return function(context, options) {
+ options = options || {};
+ var namespace = options.partial ? options : env,
+ helpers,
+ partials;
+
+ if (!options.partial) {
+ helpers = options.helpers;
+ partials = options.partials;
+ }
+ var result = templateSpec.call(
+ container,
+ namespace, context,
+ helpers,
+ partials,
+ options.data);
+
+ if (!options.partial) {
+ env.VM.checkRevision(container.compilerInfo);
+ }
+
+ return result;
+ };
+ }
+
+ __exports__.template = template;function programWithDepth(i, fn, data /*, $depth */) {
+ var args = Array.prototype.slice.call(arguments, 3);
+
+ var prog = function(context, options) {
+ options = options || {};
+
+ return fn.apply(this, [context, options.data || data].concat(args));
+ };
+ prog.program = i;
+ prog.depth = args.length;
+ return prog;
+ }
+
+ __exports__.programWithDepth = programWithDepth;function program(i, fn, data) {
+ var prog = function(context, options) {
+ options = options || {};
+
+ return fn(context, options.data || data);
+ };
+ prog.program = i;
+ prog.depth = 0;
+ return prog;
+ }
+
+ __exports__.program = program;function invokePartial(partial, name, context, helpers, partials, data) {
+ var options = { partial: true, helpers: helpers, partials: partials, data: data };
+
+ if(partial === undefined) {
+ throw new Exception("The partial " + name + " could not be found");
+ } else if(partial instanceof Function) {
+ return partial(context, options);
</ins><span class="cx"> }
</span><del>- return true;
-}
-};
-/* 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);
</del><ins>+ }
+
+ __exports__.invokePartial = invokePartial;function noop() { return ""; }
+
+ __exports__.noop = noop;
+ return __exports__;
+})(__module3__, __module5__, __module2__);
+
+// handlebars.runtime.js
+var __module1__ = (function(__dependency1__, __dependency2__, __dependency3__, __dependency4__, __dependency5__) {
+ "use strict";
+ var __exports__;
+ /*globals Handlebars: true */
+ var base = __dependency1__;
+
+ // Each of these augment the Handlebars object. No need to setup here.
+ // (This is done to easily share code between commonjs and browse envs)
+ var SafeString = __dependency2__;
+ var Exception = __dependency3__;
+ var Utils = __dependency4__;
+ var runtime = __dependency5__;
+
+ // For compatibility and usage outside of module systems, make the Handlebars object a namespace
+ var create = function() {
+ var hb = new base.HandlebarsEnvironment();
+
+ Utils.extend(hb, base);
+ hb.SafeString = SafeString;
+ hb.Exception = Exception;
+ hb.Utils = Utils;
+
+ hb.VM = runtime;
+ hb.template = function(spec) {
+ return runtime.template(spec, hb);
+ };
+
+ return hb;
+ };
+
+ var Handlebars = create();
+ Handlebars.create = create;
+
+ __exports__ = Handlebars;
+ return __exports__;
+})(__module2__, __module4__, __module5__, __module3__, __module6__);
+
+// handlebars/compiler/ast.js
+var __module7__ = (function(__dependency1__) {
+ "use strict";
+ var __exports__;
+ var Exception = __dependency1__;
+
+ function LocationInfo(locInfo){
+ locInfo = locInfo || {};
+ this.firstLine = locInfo.first_line;
+ this.firstColumn = locInfo.first_column;
+ this.lastColumn = locInfo.last_column;
+ this.lastLine = locInfo.last_line;
+ }
+
+ var AST = {
+ ProgramNode: function(statements, inverseStrip, inverse, locInfo) {
+ var inverseLocationInfo, firstInverseNode;
+ if (arguments.length === 3) {
+ locInfo = inverse;
+ inverse = null;
+ } else if (arguments.length === 2) {
+ locInfo = inverseStrip;
+ inverseStrip = null;
+ }
+
+ LocationInfo.call(this, locInfo);
+ this.type = "program";
+ this.statements = statements;
+ this.strip = {};
+
+ if(inverse) {
+ firstInverseNode = inverse[0];
+ if (firstInverseNode) {
+ inverseLocationInfo = {
+ first_line: firstInverseNode.firstLine,
+ last_line: firstInverseNode.lastLine,
+ last_column: firstInverseNode.lastColumn,
+ first_column: firstInverseNode.firstColumn
+ };
+ this.inverse = new AST.ProgramNode(inverse, inverseStrip, inverseLocationInfo);
</ins><span class="cx"> } else {
</span><del>- throw new Error(str);
</del><ins>+ this.inverse = new AST.ProgramNode(inverse, inverseStrip);
</ins><span class="cx"> }
</span><ins>+ this.strip.right = inverseStrip.left;
+ } else if (inverseStrip) {
+ this.strip.left = inverseStrip.right;
+ }
</ins><span class="cx"> },
</span><del>-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]++;
</del><span class="cx">
</span><del>- this._input = this._input.slice(1);
- return ch;
</del><ins>+ MustacheNode: function(rawParams, hash, open, strip, locInfo) {
+ LocationInfo.call(this, locInfo);
+ this.type = "mustache";
+ this.strip = strip;
+
+ // Open may be a string parsed from the parser or a passed boolean flag
+ if (open != null && open.charAt) {
+ // Must use charAt to support IE pre-10
+ var escapeFlag = open.charAt(3) || open.charAt(2);
+ this.escaped = escapeFlag !== '{' && escapeFlag !== '&';
+ } else {
+ this.escaped = !!open;
+ }
+
+ if (rawParams instanceof AST.SexprNode) {
+ this.sexpr = rawParams;
+ } else {
+ // Support old AST API
+ this.sexpr = new AST.SexprNode(rawParams, hash);
+ }
+
+ this.sexpr.isRoot = true;
+
+ // Support old AST API that stored this info in MustacheNode
+ this.id = this.sexpr.id;
+ this.params = this.sexpr.params;
+ this.hash = this.sexpr.hash;
+ this.eligibleHelper = this.sexpr.eligibleHelper;
+ this.isHelper = this.sexpr.isHelper;
</ins><span class="cx"> },
</span><del>-unput:function (ch) {
- var len = ch.length;
- var lines = ch.split(/(?:\r\n?|\n)/g);
</del><span class="cx">
</span><del>- 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);
</del><ins>+ SexprNode: function(rawParams, hash, locInfo) {
+ LocationInfo.call(this, locInfo);
</ins><span class="cx">
</span><del>- if (lines.length-1) this.yylineno -= lines.length-1;
- var r = this.yylloc.range;
</del><ins>+ this.type = "sexpr";
+ this.hash = hash;
</ins><span class="cx">
</span><del>- 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
- };
</del><ins>+ var id = this.id = rawParams[0];
+ var params = this.params = rawParams.slice(1);
</ins><span class="cx">
</span><del>- if (this.options.ranges) {
- this.yylloc.range = [r[0], r[0] + this.yyleng - len];
- }
- return this;
</del><ins>+ // a mustache is an eligible helper if:
+ // * its id is simple (a single part, not `this` or `..`)
+ var eligibleHelper = this.eligibleHelper = id.isSimple;
+
+ // a mustache is definitely a helper if:
+ // * it is an eligible helper, and
+ // * it has at least one parameter or hash segment
+ this.isHelper = eligibleHelper && (params.length || hash);
+
+ // if a mustache is an eligible helper but not a definite
+ // helper, it is ambiguous, and will be resolved in a later
+ // pass or at runtime.
</ins><span class="cx"> },
</span><del>-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;
</del><span class="cx">
</span><del>- 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});
- }
</del><ins>+ PartialNode: function(partialName, context, strip, locInfo) {
+ LocationInfo.call(this, locInfo);
+ this.type = "partial";
+ this.partialName = partialName;
+ this.context = context;
+ this.strip = strip;
</ins><span class="cx"> },
</span><del>-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) {
</del><span class="cx">
</span><del>-var YYSTATE=YY_START
-switch($avoiding_name_collisions) {
-case 0:
- if(yy_.yytext.slice(-1) !== "\\") this.begin("mu");
- if(yy_.yytext.slice(-1) === "\\") yy_.yytext = yy_.yytext.substr(0,yy_.yyleng-1), this.begin("emu");
- if(yy_.yytext) return 14;
-
-break;
-case 1: return 14;
-break;
-case 2:
- if(yy_.yytext.slice(-1) !== "\\") this.popState();
- if(yy_.yytext.slice(-1) === "\\") yy_.yytext = yy_.yytext.substr(0,yy_.yyleng-1);
- return 14;
-
-break;
-case 3: yy_.yytext = yy_.yytext.substr(0, yy_.yyleng-4); this.popState(); return 15;
-break;
-case 4: this.begin("par"); return 24;
-break;
-case 5: return 16;
-break;
-case 6: return 20;
-break;
-case 7: return 19;
-break;
-case 8: return 19;
-break;
-case 9: return 23;
-break;
-case 10: return 23;
-break;
-case 11: this.popState(); this.begin('com');
-break;
-case 12: yy_.yytext = yy_.yytext.substr(3,yy_.yyleng-5); this.popState(); return 15;
-break;
-case 13: return 22;
-break;
-case 14: return 36;
-break;
-case 15: return 35;
-break;
-case 16: return 35;
-break;
-case 17: return 39;
-break;
-case 18: /*ignore whitespace*/
-break;
-case 19: this.popState(); return 18;
-break;
-case 20: this.popState(); return 18;
-break;
-case 21: yy_.yytext = yy_.yytext.substr(1,yy_.yyleng-2).replace(/\\"/g,'"'); return 30;
-break;
-case 22: yy_.yytext = yy_.yytext.substr(1,yy_.yyleng-2).replace(/\\'/g,"'"); return 30;
-break;
-case 23: yy_.yytext = yy_.yytext.substr(1); return 28;
-break;
-case 24: return 32;
-break;
-case 25: return 32;
-break;
-case 26: return 31;
-break;
-case 27: return 35;
-break;
-case 28: yy_.yytext = yy_.yytext.substr(1, yy_.yyleng-2); return 35;
-break;
-case 29: return 'INVALID';
-break;
-case 30: /*ignore whitespace*/
-break;
-case 31: this.popState(); return 37;
-break;
-case 32: return 5;
-break;
-}
-};
-lexer.rules = [/^(?:[^\x00]*?(?=(\{\{)))/,/^(?:[^\x00]+)/,/^(?:[^\x00]{2,}?(?=(\{\{|$)))/,/^(?:[\s\S]*?--\}\})/,/^(?:\{\{>)/,/^(?:\{\{#)/,/^(?:\{\{\/)/,/^(?:\{\{\^)/,/^(?:\{\{\s*else\b)/,/^(?:\{\{\{)/,/^(?:\{\{&)/,/^(?:\{\{!--)/,/^(?:\{\{![\s\S]*?\}\})/,/^(?:\{\{)/,/^(?:=)/,/^(?:\.(?=[} ]))/,/^(?:\.\.)/,/^(?:[\/.])/,/^(?:\s+)/,/^(?:\}\}\})/,/^(?:\}\})/,/^(?:"(\\["]|[^"])*")/,/^(?:'(\\[']|[^'])*')/,/^(?:@[a-zA-Z]+)/,/^(?:true(?=[}\s]))/,/^(?:false(?=[}\s]))/,/^(?:[0-9]+(?=[}\s]))/,/^(?:[a-zA-Z0-9_$-]+(?=[=}\s\/.]))/,/^(?:\[[^\]]*\])/,/^(?:.)/,/^(?:\s+)/,/^(?:[a-zA-Z0-9_$-/]+)/,/^(?:$)/];
-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,32],"inclusive":false},"emu":{"rules":[2],"inclusive":false},"com":{"rules":[3],"inclusive":false},"par":{"rules":[30,31],"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;
-})();;
-// lib/handlebars/compiler/base.js
-Handlebars.Parser = handlebars;
</del><ins>+ BlockNode: function(mustache, program, inverse, close, locInfo) {
+ LocationInfo.call(this, locInfo);
</ins><span class="cx">
</span><del>-Handlebars.parse = function(input) {
</del><ins>+ if(mustache.sexpr.id.original !== close.path.original) {
+ throw new Exception(mustache.sexpr.id.original + " doesn't match " + close.path.original, this);
+ }
</ins><span class="cx">
</span><del>- // Just return if an already-compile AST was passed in.
- if(input.constructor === Handlebars.AST.ProgramNode) { return input; }
</del><ins>+ this.type = 'block';
+ this.mustache = mustache;
+ this.program = program;
+ this.inverse = inverse;
</ins><span class="cx">
</span><del>- Handlebars.Parser.yy = Handlebars.AST;
- return Handlebars.Parser.parse(input);
-};
</del><ins>+ this.strip = {
+ left: mustache.strip.left,
+ right: close.strip.right
+ };
</ins><span class="cx">
</span><del>-Handlebars.print = function(ast) {
- return new Handlebars.PrintVisitor().accept(ast);
-};;
-// lib/handlebars/compiler/ast.js
-(function() {
</del><ins>+ (program || inverse).strip.left = mustache.strip.right;
+ (inverse || program).strip.right = close.strip.left;
</ins><span class="cx">
</span><del>- Handlebars.AST = {};
</del><ins>+ if (inverse && !program) {
+ this.isInverse = true;
+ }
+ },
</ins><span class="cx">
</span><del>- Handlebars.AST.ProgramNode = function(statements, inverse) {
- this.type = "program";
- this.statements = statements;
- if(inverse) { this.inverse = new Handlebars.AST.ProgramNode(inverse); }
- };
</del><ins>+ ContentNode: function(string, locInfo) {
+ LocationInfo.call(this, locInfo);
+ this.type = "content";
+ this.string = string;
+ },
</ins><span class="cx">
</span><del>- Handlebars.AST.MustacheNode = function(rawParams, hash, unescaped) {
- this.type = "mustache";
- this.escaped = !unescaped;
- this.hash = hash;
</del><ins>+ HashNode: function(pairs, locInfo) {
+ LocationInfo.call(this, locInfo);
+ this.type = "hash";
+ this.pairs = pairs;
+ },
</ins><span class="cx">
</span><del>- var id = this.id = rawParams[0];
- var params = this.params = rawParams.slice(1);
</del><ins>+ IdNode: function(parts, locInfo) {
+ LocationInfo.call(this, locInfo);
+ this.type = "ID";
</ins><span class="cx">
</span><del>- // a mustache is an eligible helper if:
- // * its id is simple (a single part, not `this` or `..`)
- var eligibleHelper = this.eligibleHelper = id.isSimple;
</del><ins>+ var original = "",
+ dig = [],
+ depth = 0;
</ins><span class="cx">
</span><del>- // a mustache is definitely a helper if:
- // * it is an eligible helper, and
- // * it has at least one parameter or hash segment
- this.isHelper = eligibleHelper && (params.length || hash);
</del><ins>+ for(var i=0,l=parts.length; i<l; i++) {
+ var part = parts[i].part;
+ original += (parts[i].separator || '') + part;
</ins><span class="cx">
</span><del>- // if a mustache is an eligible helper but not a definite
- // helper, it is ambiguous, and will be resolved in a later
- // pass or at runtime.
- };
</del><ins>+ if (part === ".." || part === "." || part === "this") {
+ if (dig.length > 0) {
+ throw new Exception("Invalid path: " + original, this);
+ } else if (part === "..") {
+ depth++;
+ } else {
+ this.isScoped = true;
+ }
+ } else {
+ dig.push(part);
+ }
+ }
</ins><span class="cx">
</span><del>- Handlebars.AST.PartialNode = function(partialName, context) {
- this.type = "partial";
- this.partialName = partialName;
- this.context = context;
- };
</del><ins>+ this.original = original;
+ this.parts = dig;
+ this.string = dig.join('.');
+ this.depth = depth;
</ins><span class="cx">
</span><del>- var verifyMatch = function(open, close) {
- if(open.original !== close.original) {
- throw new Handlebars.Exception(open.original + " doesn't match " + close.original);
- }
- };
</del><ins>+ // 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;
</ins><span class="cx">
</span><del>- Handlebars.AST.BlockNode = function(mustache, program, inverse, close) {
- verifyMatch(mustache.id, close);
- this.type = "block";
- this.mustache = mustache;
- this.program = program;
- this.inverse = inverse;
</del><ins>+ this.stringModeValue = this.string;
+ },
</ins><span class="cx">
</span><del>- if (this.inverse && !this.program) {
- this.isInverse = true;
- }
- };
</del><ins>+ PartialNameNode: function(name, locInfo) {
+ LocationInfo.call(this, locInfo);
+ this.type = "PARTIAL_NAME";
+ this.name = name.original;
+ },
</ins><span class="cx">
</span><del>- Handlebars.AST.ContentNode = function(string) {
- this.type = "content";
- this.string = string;
- };
</del><ins>+ DataNode: function(id, locInfo) {
+ LocationInfo.call(this, locInfo);
+ this.type = "DATA";
+ this.id = id;
+ },
</ins><span class="cx">
</span><del>- Handlebars.AST.HashNode = function(pairs) {
- this.type = "hash";
- this.pairs = pairs;
- };
</del><ins>+ StringNode: function(string, locInfo) {
+ LocationInfo.call(this, locInfo);
+ this.type = "STRING";
+ this.original =
+ this.string =
+ this.stringModeValue = string;
+ },
</ins><span class="cx">
</span><del>- Handlebars.AST.IdNode = function(parts) {
- this.type = "ID";
- this.original = parts.join(".");
</del><ins>+ IntegerNode: function(integer, locInfo) {
+ LocationInfo.call(this, locInfo);
+ this.type = "INTEGER";
+ this.original =
+ this.integer = integer;
+ this.stringModeValue = Number(integer);
+ },
</ins><span class="cx">
</span><del>- var dig = [], depth = 0;
</del><ins>+ BooleanNode: function(bool, locInfo) {
+ LocationInfo.call(this, locInfo);
+ this.type = "BOOLEAN";
+ this.bool = bool;
+ this.stringModeValue = bool === "true";
+ },
</ins><span class="cx">
</span><del>- for(var i=0,l=parts.length; i<l; i++) {
- var part = parts[i];
-
- if (part === ".." || part === "." || part === "this") {
- if (dig.length > 0) { throw new Handlebars.Exception("Invalid path: " + this.original); }
- else if (part === "..") { depth++; }
- else { this.isScoped = true; }
- }
- else { dig.push(part); }
</del><ins>+ CommentNode: function(comment, locInfo) {
+ LocationInfo.call(this, locInfo);
+ this.type = "comment";
+ this.comment = comment;
</ins><span class="cx"> }
</span><ins>+ };
</ins><span class="cx">
</span><del>- this.parts = dig;
- this.string = dig.join('.');
- this.depth = depth;
</del><ins>+ // 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__);
</ins><span class="cx">
</span><del>- // 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;
</del><ins>+// 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,$$,_$) {
</ins><span class="cx">
</span><del>- this.stringModeValue = this.string;
</del><ins>+ 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;
+ }
</ins><span class="cx"> };
</span><span class="cx">
</span><del>- Handlebars.AST.PartialNameNode = function(name) {
- this.type = "PARTIAL_NAME";
- this.name = name;
- };
</del><span class="cx">
</span><del>- Handlebars.AST.DataNode = function(id) {
- this.type = "DATA";
- this.id = id;
- };
</del><ins>+ function stripFlags(open, close) {
+ return {
+ left: open.charAt(2) === '~',
+ right: close.charAt(0) === '~' || close.charAt(1) === '~'
+ };
+ }
</ins><span class="cx">
</span><del>- Handlebars.AST.StringNode = function(string) {
- this.type = "STRING";
- this.string = string;
- this.stringModeValue = string;
- };
</del><ins>+ /* 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]++;
</ins><span class="cx">
</span><del>- Handlebars.AST.IntegerNode = function(integer) {
- this.type = "INTEGER";
- this.integer = integer;
- this.stringModeValue = Number(integer);
- };
</del><ins>+ this._input = this._input.slice(1);
+ return ch;
+ },
+ unput:function (ch) {
+ var len = ch.length;
+ var lines = ch.split(/(?:\r\n?|\n)/g);
</ins><span class="cx">
</span><del>- Handlebars.AST.BooleanNode = function(bool) {
- this.type = "BOOLEAN";
- this.bool = bool;
- this.stringModeValue = bool === "true";
- };
</del><ins>+ 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);
</ins><span class="cx">
</span><del>- Handlebars.AST.CommentNode = function(comment) {
- this.type = "comment";
- this.comment = comment;
- };
</del><ins>+ if (lines.length-1) this.yylineno -= lines.length-1;
+ var r = this.yylloc.range;
</ins><span class="cx">
</span><del>-})();;
-// lib/handlebars/utils.js
</del><ins>+ 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
+ };
</ins><span class="cx">
</span><del>-var errorProps = ['description', 'fileName', 'lineNumber', 'message', 'name', 'number', 'stack'];
</del><ins>+ 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;
</ins><span class="cx">
</span><del>-Handlebars.Exception = function(message) {
- var tmp = Error.prototype.constructor.apply(this, arguments);
</del><ins>+ 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) {
</ins><span class="cx">
</span><del>- // 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]];
</del><ins>+
+ function strip(start, end) {
+ return yy_.yytext = yy_.yytext.substr(start, yy_.yyleng-end);
</ins><span class="cx"> }
</span><del>-};
-Handlebars.Exception.prototype = new Error();
</del><span class="cx">
</span><del>-// Build out our basic SafeString type
-Handlebars.SafeString = function(string) {
- this.string = string;
-};
-Handlebars.SafeString.prototype.toString = function() {
- return this.string.toString();
-};
</del><span class="cx">
</span><del>-(function() {
- var escape = {
- "&": "&amp;",
- "<": "&lt;",
- ">": "&gt;",
- '"': "&quot;",
- "'": "&#x27;",
- "`": "&#x60;"
</del><ins>+ 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;
+ }
</ins><span class="cx"> };
</span><ins>+ 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__;
+})();
</ins><span class="cx">
</span><del>- var badChars = /[&<>"'`]/g;
- var possible = /[&<>"'`]/;
</del><ins>+// handlebars/compiler/base.js
+var __module8__ = (function(__dependency1__, __dependency2__) {
+ "use strict";
+ var __exports__ = {};
+ var parser = __dependency1__;
+ var AST = __dependency2__;
</ins><span class="cx">
</span><del>- var escapeChar = function(chr) {
- return escape[chr] || "&amp;";
- };
</del><ins>+ __exports__.parser = parser;
</ins><span class="cx">
</span><del>- Handlebars.Utils = {
- escapeExpression: function(string) {
- // don't escape SafeStrings, since they're already safe
- if (string instanceof Handlebars.SafeString) {
- return string.toString();
- } else if (string == null || string === false) {
- return "";
- }
</del><ins>+ function parse(input) {
+ // Just return if an already-compile AST was passed in.
+ if(input.constructor === AST.ProgramNode) { return input; }
</ins><span class="cx">
</span><del>- if(!possible.test(string)) { return string; }
- return string.replace(badChars, escapeChar);
- },
</del><ins>+ parser.yy = AST;
+ return parser.parse(input);
+ }
</ins><span class="cx">
</span><del>- isEmpty: function(value) {
- if (!value && value !== 0) {
- return true;
- } else if(Object.prototype.toString.call(value) === "[object Array]" && value.length === 0) {
- return true;
- } else {
- return false;
- }
- }
- };
-})();;
-// lib/handlebars/compiler/compiler.js
</del><ins>+ __exports__.parse = parse;
+ return __exports__;
+})(__module9__, __module7__);
</ins><span class="cx">
</span><del>-/*jshint eqnull:true*/
-Handlebars.Compiler = function() {};
-Handlebars.JavaScriptCompiler = function() {};
</del><ins>+// handlebars/compiler/compiler.js
+var __module10__ = (function(__dependency1__) {
+ "use strict";
+ var __exports__ = {};
+ var Exception = __dependency1__;
</ins><span class="cx">
</span><del>-(function(Compiler, JavaScriptCompiler) {
- // the foundHelper register will disambiguate helper lookup from finding a
</del><ins>+ function Compiler() {}
+
+ __exports__.Compiler = Compiler;// the foundHelper register will disambiguate helper lookup from finding a
</ins><span class="cx"> // function in a context. This is necessary for mustache compatibility, which
</span><span class="cx"> // requires that context functions in blocks are evaluated by blockHelperMissing,
</span><span class="cx"> // and then proceed as if the resulting value was provided to blockHelperMissing.
</span><span class="lines">@@ -890,6 +1315,7 @@
</span><span class="cx">
</span><span class="cx"> return out.join("\n");
</span><span class="cx"> },
</span><ins>+
</ins><span class="cx"> equals: function(other) {
</span><span class="cx"> var len = this.opcodes.length;
</span><span class="cx"> if (other.opcodes.length !== len) {
</span><span class="lines">@@ -908,12 +1334,24 @@
</span><span class="cx"> }
</span><span class="cx"> }
</span><span class="cx"> }
</span><ins>+
+ len = this.children.length;
+ if (other.children.length !== len) {
+ return false;
+ }
+ for (i = 0; i < len; i++) {
+ if (!this.children[i].equals(other.children[i])) {
+ return false;
+ }
+ }
+
</ins><span class="cx"> return true;
</span><span class="cx"> },
</span><span class="cx">
</span><span class="cx"> guid: 0,
</span><span class="cx">
</span><span class="cx"> compile: function(program, options) {
</span><ins>+ this.opcodes = [];
</ins><span class="cx"> this.children = [];
</span><span class="cx"> this.depths = {list: []};
</span><span class="cx"> this.options = options;
</span><span class="lines">@@ -935,20 +1373,30 @@
</span><span class="cx"> }
</span><span class="cx"> }
</span><span class="cx">
</span><del>- return this.program(program);
</del><ins>+ return this.accept(program);
</ins><span class="cx"> },
</span><span class="cx">
</span><span class="cx"> accept: function(node) {
</span><del>- return this[node.type](node);
</del><ins>+ var strip = node.strip || {},
+ ret;
+ if (strip.left) {
+ this.opcode('strip');
+ }
+
+ ret = this[node.type](node);
+
+ if (strip.right) {
+ this.opcode('strip');
+ }
+
+ return ret;
</ins><span class="cx"> },
</span><span class="cx">
</span><span class="cx"> program: function(program) {
</span><del>- var statements = program.statements, statement;
- this.opcodes = [];
</del><ins>+ var statements = program.statements;
</ins><span class="cx">
</span><span class="cx"> for(var i=0, l=statements.length; i<l; i++) {
</span><del>- statement = statements[i];
- this[statement.type](statement);
</del><ins>+ this.accept(statements[i]);
</ins><span class="cx"> }
</span><span class="cx"> this.isSimple = l === 1;
</span><span class="cx">
</span><span class="lines">@@ -990,12 +1438,13 @@
</span><span class="cx"> inverse = this.compileProgram(inverse);
</span><span class="cx"> }
</span><span class="cx">
</span><del>- var type = this.classifyMustache(mustache);
</del><ins>+ var sexpr = mustache.sexpr;
+ var type = this.classifySexpr(sexpr);
</ins><span class="cx">
</span><span class="cx"> if (type === "helper") {
</span><del>- this.helperMustache(mustache, program, inverse);
</del><ins>+ this.helperSexpr(sexpr, program, inverse);
</ins><span class="cx"> } else if (type === "simple") {
</span><del>- this.simpleMustache(mustache);
</del><ins>+ this.simpleSexpr(sexpr);
</ins><span class="cx">
</span><span class="cx"> // now that the simple mustache is resolved, we need to
</span><span class="cx"> // evaluate it by executing `blockHelperMissing`
</span><span class="lines">@@ -1004,7 +1453,7 @@
</span><span class="cx"> this.opcode('emptyHash');
</span><span class="cx"> this.opcode('blockValue');
</span><span class="cx"> } else {
</span><del>- this.ambiguousMustache(mustache, program, inverse);
</del><ins>+ this.ambiguousSexpr(sexpr, program, inverse);
</ins><span class="cx">
</span><span class="cx"> // now that the simple mustache is resolved, we need to
</span><span class="cx"> // evaluate it by executing `blockHelperMissing`
</span><span class="lines">@@ -1027,7 +1476,17 @@
</span><span class="cx"> val = pair[1];
</span><span class="cx">
</span><span class="cx"> if (this.options.stringParams) {
</span><ins>+ if(val.depth) {
+ this.addDepth(val.depth);
+ }
+ this.opcode('getContext', val.depth || 0);
</ins><span class="cx"> this.opcode('pushStringParam', val.stringModeValue, val.type);
</span><ins>+
+ if (val.type === 'sexpr') {
+ // Subexpressions get evaluated and passed in
+ // in string params mode.
+ this.sexpr(val);
+ }
</ins><span class="cx"> } else {
</span><span class="cx"> this.accept(val);
</span><span class="cx"> }
</span><span class="lines">@@ -1056,26 +1515,17 @@
</span><span class="cx"> },
</span><span class="cx">
</span><span class="cx"> mustache: function(mustache) {
</span><del>- var options = this.options;
- var type = this.classifyMustache(mustache);
</del><ins>+ this.sexpr(mustache.sexpr);
</ins><span class="cx">
</span><del>- if (type === "simple") {
- this.simpleMustache(mustache);
- } else if (type === "helper") {
- this.helperMustache(mustache);
- } else {
- this.ambiguousMustache(mustache);
- }
-
- if(mustache.escaped && !options.noEscape) {
</del><ins>+ if(mustache.escaped && !this.options.noEscape) {
</ins><span class="cx"> this.opcode('appendEscaped');
</span><span class="cx"> } else {
</span><span class="cx"> this.opcode('append');
</span><span class="cx"> }
</span><span class="cx"> },
</span><span class="cx">
</span><del>- ambiguousMustache: function(mustache, program, inverse) {
- var id = mustache.id,
</del><ins>+ ambiguousSexpr: function(sexpr, program, inverse) {
+ var id = sexpr.id,
</ins><span class="cx"> name = id.parts[0],
</span><span class="cx"> isBlock = program != null || inverse != null;
</span><span class="cx">
</span><span class="lines">@@ -1087,8 +1537,8 @@
</span><span class="cx"> this.opcode('invokeAmbiguous', name, isBlock);
</span><span class="cx"> },
</span><span class="cx">
</span><del>- simpleMustache: function(mustache) {
- var id = mustache.id;
</del><ins>+ simpleSexpr: function(sexpr) {
+ var id = sexpr.id;
</ins><span class="cx">
</span><span class="cx"> if (id.type === 'DATA') {
</span><span class="cx"> this.DATA(id);
</span><span class="lines">@@ -1104,19 +1554,31 @@
</span><span class="cx"> this.opcode('resolvePossibleLambda');
</span><span class="cx"> },
</span><span class="cx">
</span><del>- helperMustache: function(mustache, program, inverse) {
- var params = this.setupFullMustacheParams(mustache, program, inverse),
- name = mustache.id.parts[0];
</del><ins>+ helperSexpr: function(sexpr, program, inverse) {
+ var params = this.setupFullMustacheParams(sexpr, program, inverse),
+ name = sexpr.id.parts[0];
</ins><span class="cx">
</span><span class="cx"> if (this.options.knownHelpers[name]) {
</span><span class="cx"> this.opcode('invokeKnownHelper', params.length, name);
</span><del>- } else if (this.knownHelpersOnly) {
- throw new Error("You specified knownHelpersOnly, but used the unknown helper " + name);
</del><ins>+ } else if (this.options.knownHelpersOnly) {
+ throw new Exception("You specified knownHelpersOnly, but used the unknown helper " + name, sexpr);
</ins><span class="cx"> } else {
</span><del>- this.opcode('invokeHelper', params.length, name);
</del><ins>+ this.opcode('invokeHelper', params.length, name, sexpr.isRoot);
</ins><span class="cx"> }
</span><span class="cx"> },
</span><span class="cx">
</span><ins>+ sexpr: function(sexpr) {
+ var type = this.classifySexpr(sexpr);
+
+ if (type === "simple") {
+ this.simpleSexpr(sexpr);
+ } else if (type === "helper") {
+ this.helperSexpr(sexpr);
+ } else {
+ this.ambiguousSexpr(sexpr);
+ }
+ },
+
</ins><span class="cx"> ID: function(id) {
</span><span class="cx"> this.addDepth(id.depth);
</span><span class="cx"> this.opcode('getContext', id.depth);
</span><span class="lines">@@ -1135,7 +1597,15 @@
</span><span class="cx">
</span><span class="cx"> DATA: function(data) {
</span><span class="cx"> this.options.data = true;
</span><del>- this.opcode('lookupData', data.id);
</del><ins>+ if (data.id.isScoped || data.id.depth) {
+ throw new Exception('Scoped data references are not supported: ' + data.original, data);
+ }
+
+ this.opcode('lookupData');
+ var parts = data.id.parts;
+ for(var i=0, l=parts.length; i<l; i++) {
+ this.opcode('lookup', parts[i]);
+ }
</ins><span class="cx"> },
</span><span class="cx">
</span><span class="cx"> STRING: function(string) {
</span><span class="lines">@@ -1162,7 +1632,6 @@
</span><span class="cx"> },
</span><span class="cx">
</span><span class="cx"> addDepth: function(depth) {
</span><del>- if(isNaN(depth)) { throw new Error("EWOT"); }
</del><span class="cx"> if(depth === 0) { return; }
</span><span class="cx">
</span><span class="cx"> if(!this.depths[depth]) {
</span><span class="lines">@@ -1171,14 +1640,14 @@
</span><span class="cx"> }
</span><span class="cx"> },
</span><span class="cx">
</span><del>- classifyMustache: function(mustache) {
- var isHelper = mustache.isHelper;
- var isEligible = mustache.eligibleHelper;
</del><ins>+ classifySexpr: function(sexpr) {
+ var isHelper = sexpr.isHelper;
+ var isEligible = sexpr.eligibleHelper;
</ins><span class="cx"> var options = this.options;
</span><span class="cx">
</span><span class="cx"> // if ambiguous, we can possibly resolve the ambiguity now
</span><span class="cx"> if (isEligible && !isHelper) {
</span><del>- var name = mustache.id.parts[0];
</del><ins>+ var name = sexpr.id.parts[0];
</ins><span class="cx">
</span><span class="cx"> if (options.knownHelpers[name]) {
</span><span class="cx"> isHelper = true;
</span><span class="lines">@@ -1205,35 +1674,27 @@
</span><span class="cx">
</span><span class="cx"> this.opcode('getContext', param.depth || 0);
</span><span class="cx"> this.opcode('pushStringParam', param.stringModeValue, param.type);
</span><ins>+
+ if (param.type === 'sexpr') {
+ // Subexpressions get evaluated and passed in
+ // in string params mode.
+ this.sexpr(param);
+ }
</ins><span class="cx"> } else {
</span><span class="cx"> this[param.type](param);
</span><span class="cx"> }
</span><span class="cx"> }
</span><span class="cx"> },
</span><span class="cx">
</span><del>- setupMustacheParams: function(mustache) {
- var params = mustache.params;
</del><ins>+ setupFullMustacheParams: function(sexpr, program, inverse) {
+ var params = sexpr.params;
</ins><span class="cx"> this.pushParams(params);
</span><span class="cx">
</span><del>- if(mustache.hash) {
- this.hash(mustache.hash);
- } else {
- this.opcode('emptyHash');
- }
-
- return params;
- },
-
- // this will replace setupMustacheParams when we're done
- setupFullMustacheParams: function(mustache, program, inverse) {
- var params = mustache.params;
- this.pushParams(params);
-
</del><span class="cx"> this.opcode('pushProgram', program);
</span><span class="cx"> this.opcode('pushProgram', inverse);
</span><span class="cx">
</span><del>- if(mustache.hash) {
- this.hash(mustache.hash);
</del><ins>+ if (sexpr.hash) {
+ this.hash(sexpr.hash);
</ins><span class="cx"> } else {
</span><span class="cx"> this.opcode('emptyHash');
</span><span class="cx"> }
</span><span class="lines">@@ -1242,24 +1703,101 @@
</span><span class="cx"> }
</span><span class="cx"> };
</span><span class="cx">
</span><del>- var Literal = function(value) {
</del><ins>+ function precompile(input, options, env) {
+ if (input == null || (typeof input !== 'string' && input.constructor !== env.AST.ProgramNode)) {
+ throw new Exception("You must pass a string or Handlebars AST to Handlebars.precompile. You passed " + input);
+ }
+
+ options = options || {};
+ if (!('data' in options)) {
+ options.data = true;
+ }
+
+ var ast = env.parse(input);
+ var environment = new env.Compiler().compile(ast, options);
+ return new env.JavaScriptCompiler().compile(environment, options);
+ }
+
+ __exports__.precompile = precompile;function compile(input, options, env) {
+ if (input == null || (typeof input !== 'string' && input.constructor !== env.AST.ProgramNode)) {
+ throw new Exception("You must pass a string or Handlebars AST to Handlebars.compile. You passed " + input);
+ }
+
+ options = options || {};
+
+ if (!('data' in options)) {
+ options.data = true;
+ }
+
+ var compiled;
+
+ function compileInput() {
+ var ast = env.parse(input);
+ var environment = new env.Compiler().compile(ast, options);
+ var templateSpec = new env.JavaScriptCompiler().compile(environment, options, undefined, true);
+ return env.template(templateSpec);
+ }
+
+ // Template is only compiled on first use and cached after that point.
+ return function(context, options) {
+ if (!compiled) {
+ compiled = compileInput();
+ }
+ return compiled.call(this, context, options);
+ };
+ }
+
+ __exports__.compile = compile;
+ return __exports__;
+})(__module5__);
+
+// handlebars/compiler/javascript-compiler.js
+var __module11__ = (function(__dependency1__, __dependency2__) {
+ "use strict";
+ var __exports__;
+ var COMPILER_REVISION = __dependency1__.COMPILER_REVISION;
+ var REVISION_CHANGES = __dependency1__.REVISION_CHANGES;
+ var log = __dependency1__.log;
+ var Exception = __dependency2__;
+
+ function Literal(value) {
</ins><span class="cx"> this.value = value;
</span><del>- };
</del><ins>+ }
</ins><span class="cx">
</span><ins>+ function JavaScriptCompiler() {}
+
</ins><span class="cx"> JavaScriptCompiler.prototype = {
</span><span class="cx"> // PUBLIC API: You can override these methods in a subclass to provide
</span><span class="cx"> // alternative compiled forms for name lookup and buffering semantics
</span><span class="cx"> nameLookup: function(parent, name /* , type*/) {
</span><ins>+ var wrap,
+ ret;
+ if (parent.indexOf('depth') === 0) {
+ wrap = true;
+ }
+
</ins><span class="cx"> if (/^[0-9]+$/.test(name)) {
</span><del>- return parent + "[" + name + "]";
</del><ins>+ ret = parent + "[" + name + "]";
</ins><span class="cx"> } else if (JavaScriptCompiler.isValidJavaScriptVariableName(name)) {
</span><del>- return parent + "." + name;
</del><ins>+ ret = parent + "." + name;
</ins><span class="cx"> }
</span><span class="cx"> else {
</span><del>- return parent + "['" + name + "']";
</del><ins>+ ret = parent + "['" + name + "']";
</ins><span class="cx"> }
</span><ins>+
+ if (wrap) {
+ return '(' + parent + ' && ' + ret + ')';
+ } else {
+ return ret;
+ }
</ins><span class="cx"> },
</span><span class="cx">
</span><ins>+ compilerInfo: function() {
+ var revision = COMPILER_REVISION,
+ versions = REVISION_CHANGES[revision];
+ return "this.compilerInfo = ["+revision+",'"+versions+"'];\n";
+ },
+
</ins><span class="cx"> appendToBuffer: function(string) {
</span><span class="cx"> if (this.environment.isSimple) {
</span><span class="cx"> return "return " + string + ";";
</span><span class="lines">@@ -1283,7 +1821,7 @@
</span><span class="cx"> this.environment = environment;
</span><span class="cx"> this.options = options || {};
</span><span class="cx">
</span><del>- Handlebars.log(Handlebars.logger.DEBUG, this.environment.disassemble() + "\n\n");
</del><ins>+ log('debug', this.environment.disassemble() + "\n\n");
</ins><span class="cx">
</span><span class="cx"> this.name = this.environment.name;
</span><span class="cx"> this.isChild = !!context;
</span><span class="lines">@@ -1298,6 +1836,7 @@
</span><span class="cx"> this.stackSlot = 0;
</span><span class="cx"> this.stackVars = [];
</span><span class="cx"> this.registers = { list: [] };
</span><ins>+ this.hashes = [];
</ins><span class="cx"> this.compileStack = [];
</span><span class="cx"> this.inlineStack = [];
</span><span class="cx">
</span><span class="lines">@@ -1307,7 +1846,7 @@
</span><span class="cx">
</span><span class="cx"> this.i = 0;
</span><span class="cx">
</span><del>- for(l=opcodes.length; this.i<l; this.i++) {
</del><ins>+ for(var l=opcodes.length; this.i<l; this.i++) {
</ins><span class="cx"> opcode = opcodes[this.i];
</span><span class="cx">
</span><span class="cx"> if(opcode.opcode === 'DECLARE') {
</span><span class="lines">@@ -1315,18 +1854,21 @@
</span><span class="cx"> } else {
</span><span class="cx"> this[opcode.opcode].apply(this, opcode.args);
</span><span class="cx"> }
</span><ins>+
+ // Reset the stripNext flag if it was not set by this operation.
+ if (opcode.opcode !== this.stripNext) {
+ this.stripNext = false;
+ }
</ins><span class="cx"> }
</span><span class="cx">
</span><del>- return this.createFunctionContext(asObject);
- },
</del><ins>+ // Flush any trailing content that might be pending.
+ this.pushSource('');
</ins><span class="cx">
</span><del>- nextOpcode: function() {
- var opcodes = this.environment.opcodes;
- return opcodes[this.i + 1];
- },
</del><ins>+ if (this.stackSlot || this.inlineStack.length || this.compileStack.length) {
+ throw new Exception('Compile completed with content left on stack');
+ }
</ins><span class="cx">
</span><del>- eat: function() {
- this.i = this.i + 1;
</del><ins>+ return this.createFunctionContext(asObject);
</ins><span class="cx"> },
</span><span class="cx">
</span><span class="cx"> preamble: function() {
</span><span class="lines">@@ -1334,8 +1876,9 @@
</span><span class="cx">
</span><span class="cx"> if (!this.isChild) {
</span><span class="cx"> var namespace = this.namespace;
</span><del>- var copies = "helpers = helpers || " + namespace + ".helpers;";
- if (this.environment.usePartial) { copies = copies + " partials = partials || " + namespace + ".partials;"; }
</del><ins>+
+ var copies = "helpers = this.merge(helpers, " + namespace + ".helpers);";
+ if (this.environment.usePartial) { copies = copies + " partials = this.merge(partials, " + namespace + ".partials);"; }
</ins><span class="cx"> if (this.options.data) { copies = copies + " data = data || {};"; }
</span><span class="cx"> out.push(copies);
</span><span class="cx"> } else {
</span><span class="lines">@@ -1364,7 +1907,9 @@
</span><span class="cx"> // Generate minimizer alias mappings
</span><span class="cx"> if (!this.isChild) {
</span><span class="cx"> for (var alias in this.context.aliases) {
</span><del>- this.source[1] = this.source[1] + ', ' + alias + '=' + this.context.aliases[alias];
</del><ins>+ if (this.context.aliases.hasOwnProperty(alias)) {
+ this.source[1] = this.source[1] + ', ' + alias + '=' + this.context.aliases[alias];
+ }
</ins><span class="cx"> }
</span><span class="cx"> }
</span><span class="cx">
</span><span class="lines">@@ -1378,7 +1923,7 @@
</span><span class="cx"> }
</span><span class="cx">
</span><span class="cx"> if (!this.environment.isSimple) {
</span><del>- this.source.push("return buffer;");
</del><ins>+ this.pushSource("return buffer;");
</ins><span class="cx"> }
</span><span class="cx">
</span><span class="cx"> var params = this.isChild ? ["depth0", "data"] : ["Handlebars", "depth0", "helpers", "partials", "data"];
</span><span class="lines">@@ -1391,9 +1936,7 @@
</span><span class="cx"> var source = this.mergeSource();
</span><span class="cx">
</span><span class="cx"> if (!this.isChild) {
</span><del>- var revision = Handlebars.COMPILER_REVISION,
- versions = Handlebars.REVISION_CHANGES[revision];
- source = "this.compilerInfo = ["+revision+",'"+versions+"'];\n"+source;
</del><ins>+ source = this.compilerInfo()+source;
</ins><span class="cx"> }
</span><span class="cx">
</span><span class="cx"> if (asObject) {
</span><span class="lines">@@ -1402,7 +1945,7 @@
</span><span class="cx"> return Function.apply(this, params);
</span><span class="cx"> } else {
</span><span class="cx"> var functionSource = 'function ' + (this.name || '') + '(' + params.join(',') + ') {\n ' + source + '}';
</span><del>- Handlebars.log(Handlebars.logger.DEBUG, functionSource + "\n\n");
</del><ins>+ log('debug', functionSource + "\n\n");
</ins><span class="cx"> return functionSource;
</span><span class="cx"> }
</span><span class="cx"> },
</span><span class="lines">@@ -1466,10 +2009,7 @@
</span><span class="cx"> var current = this.topStack();
</span><span class="cx"> params.splice(1, 0, current);
</span><span class="cx">
</span><del>- // Use the options value generated from the invocation
- params[params.length-1] = 'options';
-
- this.source.push("if (!" + this.lastHelper + ") { " + current + " = blockHelperMissing.call(" + params.join(", ") + "); }");
</del><ins>+ this.pushSource("if (!" + this.lastHelper + ") { " + current + " = blockHelperMissing.call(" + params.join(", ") + "); }");
</ins><span class="cx"> },
</span><span class="cx">
</span><span class="cx"> // [appendContent]
</span><span class="lines">@@ -1479,9 +2019,30 @@
</span><span class="cx"> //
</span><span class="cx"> // Appends the string value of `content` to the current buffer
</span><span class="cx"> appendContent: function(content) {
</span><del>- this.source.push(this.appendToBuffer(this.quotedString(content)));
</del><ins>+ if (this.pendingContent) {
+ content = this.pendingContent + content;
+ }
+ if (this.stripNext) {
+ content = content.replace(/^\s+/, '');
+ }
+
+ this.pendingContent = content;
</ins><span class="cx"> },
</span><span class="cx">
</span><ins>+ // [strip]
+ //
+ // On stack, before: ...
+ // On stack, after: ...
+ //
+ // Removes any trailing whitespace from the prior content node and flags
+ // the next operation for stripping if it is a content node.
+ strip: function() {
+ if (this.pendingContent) {
+ this.pendingContent = this.pendingContent.replace(/\s+$/, '');
+ }
+ this.stripNext = 'strip';
+ },
+
</ins><span class="cx"> // [append]
</span><span class="cx"> //
</span><span class="cx"> // On stack, before: value, ...
</span><span class="lines">@@ -1496,9 +2057,9 @@
</span><span class="cx"> // when we examine local
</span><span class="cx"> this.flushInline();
</span><span class="cx"> var local = this.popStack();
</span><del>- this.source.push("if(" + local + " || " + local + " === 0) { " + this.appendToBuffer(local) + " }");
</del><ins>+ this.pushSource("if(" + local + " || " + local + " === 0) { " + this.appendToBuffer(local) + " }");
</ins><span class="cx"> if (this.environment.isSimple) {
</span><del>- this.source.push("else { " + this.appendToBuffer("''") + " }");
</del><ins>+ this.pushSource("else { " + this.appendToBuffer("''") + " }");
</ins><span class="cx"> }
</span><span class="cx"> },
</span><span class="cx">
</span><span class="lines">@@ -1511,7 +2072,7 @@
</span><span class="cx"> appendEscaped: function() {
</span><span class="cx"> this.context.aliases.escapeExpression = 'this.escapeExpression';
</span><span class="cx">
</span><del>- this.source.push(this.appendToBuffer("escapeExpression(" + this.popStack() + ")"));
</del><ins>+ this.pushSource(this.appendToBuffer("escapeExpression(" + this.popStack() + ")"));
</ins><span class="cx"> },
</span><span class="cx">
</span><span class="cx"> // [getContext]
</span><span class="lines">@@ -1579,11 +2140,11 @@
</span><span class="cx"> // [lookupData]
</span><span class="cx"> //
</span><span class="cx"> // On stack, before: ...
</span><del>- // On stack, after: data[id], ...
</del><ins>+ // On stack, after: data, ...
</ins><span class="cx"> //
</span><del>- // Push the result of looking up `id` on the current data
- lookupData: function(id) {
- this.push(this.nameLookup('data', id, 'data'));
</del><ins>+ // Push the data lookup operator
+ lookupData: function() {
+ this.pushStackLiteral('data');
</ins><span class="cx"> },
</span><span class="cx">
</span><span class="cx"> // [pushStringParam]
</span><span class="lines">@@ -1599,10 +2160,14 @@
</span><span class="cx">
</span><span class="cx"> this.pushString(type);
</span><span class="cx">
</span><del>- if (typeof string === 'string') {
- this.pushString(string);
- } else {
- this.pushStackLiteral(string);
</del><ins>+ // If it's a subexpression, the string result
+ // will be pushed after this opcode.
+ if (type !== 'sexpr') {
+ if (typeof string === 'string') {
+ this.pushString(string);
+ } else {
+ this.pushStackLiteral(string);
+ }
</ins><span class="cx"> }
</span><span class="cx"> },
</span><span class="cx">
</span><span class="lines">@@ -1610,19 +2175,25 @@
</span><span class="cx"> this.pushStackLiteral('{}');
</span><span class="cx">
</span><span class="cx"> if (this.options.stringParams) {
</span><del>- this.register('hashTypes', '{}');
</del><ins>+ this.push('{}'); // hashContexts
+ this.push('{}'); // hashTypes
</ins><span class="cx"> }
</span><span class="cx"> },
</span><span class="cx"> pushHash: function() {
</span><del>- this.hash = {values: [], types: []};
</del><ins>+ if (this.hash) {
+ this.hashes.push(this.hash);
+ }
+ this.hash = {values: [], types: [], contexts: []};
</ins><span class="cx"> },
</span><span class="cx"> popHash: function() {
</span><span class="cx"> var hash = this.hash;
</span><del>- this.hash = undefined;
</del><ins>+ this.hash = this.hashes.pop();
</ins><span class="cx">
</span><span class="cx"> if (this.options.stringParams) {
</span><del>- this.register('hashTypes', '{' + hash.types.join(',') + '}');
</del><ins>+ this.push('{' + hash.contexts.join(',') + '}');
+ this.push('{' + hash.types.join(',') + '}');
</ins><span class="cx"> }
</span><ins>+
</ins><span class="cx"> this.push('{\n ' + hash.values.join(',\n ') + '\n }');
</span><span class="cx"> },
</span><span class="cx">
</span><span class="lines">@@ -1684,17 +2255,31 @@
</span><span class="cx"> // and pushes the helper's return value onto the stack.
</span><span class="cx"> //
</span><span class="cx"> // If the helper is not found, `helperMissing` is called.
</span><del>- invokeHelper: function(paramSize, name) {
</del><ins>+ invokeHelper: function(paramSize, name, isRoot) {
</ins><span class="cx"> this.context.aliases.helperMissing = 'helpers.helperMissing';
</span><ins>+ this.useRegister('helper');
</ins><span class="cx">
</span><span class="cx"> var helper = this.lastHelper = this.setupHelper(paramSize, name, true);
</span><ins>+ var nonHelper = this.nameLookup('depth' + this.lastContext, name, 'context');
</ins><span class="cx">
</span><del>- this.push(helper.name);
- this.replaceStack(function(name) {
- return name + ' ? ' + name + '.call(' +
- helper.callParams + ") " + ": helperMissing.call(" +
- helper.helperMissingParams + ")";
- });
</del><ins>+ var lookup = 'helper = ' + helper.name + ' || ' + nonHelper;
+ if (helper.paramsInit) {
+ lookup += ',' + helper.paramsInit;
+ }
+
+ this.push(
+ '('
+ + lookup
+ + ',helper '
+ + '? helper.call(' + helper.callParams + ') '
+ + ': helperMissing.call(' + helper.helperMissingParams + '))');
+
+ // Always flush subexpressions. This is both to prevent the compounding size issue that
+ // occurs when the code has to be duplicated for inlining and also to prevent errors
+ // due to the incorrect options object being passed due to the shared register.
+ if (!isRoot) {
+ this.flushInline();
+ }
</ins><span class="cx"> },
</span><span class="cx">
</span><span class="cx"> // [invokeKnownHelper]
</span><span class="lines">@@ -1723,8 +2308,9 @@
</span><span class="cx"> // `knownHelpersOnly` flags at compile-time.
</span><span class="cx"> invokeAmbiguous: function(name, helperCall) {
</span><span class="cx"> this.context.aliases.functionType = '"function"';
</span><ins>+ this.useRegister('helper');
</ins><span class="cx">
</span><del>- this.pushStackLiteral('{}'); // Hash value
</del><ins>+ this.emptyHash();
</ins><span class="cx"> var helper = this.setupHelper(0, name, helperCall);
</span><span class="cx">
</span><span class="cx"> var helperName = this.lastHelper = this.nameLookup('helpers', name, 'helper');
</span><span class="lines">@@ -1732,8 +2318,11 @@
</span><span class="cx"> var nonHelper = this.nameLookup('depth' + this.lastContext, name, 'context');
</span><span class="cx"> var nextStack = this.nextStack();
</span><span class="cx">
</span><del>- this.source.push('if (' + nextStack + ' = ' + helperName + ') { ' + nextStack + ' = ' + nextStack + '.call(' + helper.callParams + '); }');
- this.source.push('else { ' + nextStack + ' = ' + nonHelper + '; ' + nextStack + ' = typeof ' + nextStack + ' === functionType ? ' + nextStack + '.apply(depth0) : ' + nextStack + '; }');
</del><ins>+ if (helper.paramsInit) {
+ this.pushSource(helper.paramsInit);
+ }
+ this.pushSource('if (helper = ' + helperName + ') { ' + nextStack + ' = helper.call(' + helper.callParams + '); }');
+ this.pushSource('else { helper = ' + nonHelper + '; ' + nextStack + ' = typeof helper === functionType ? helper.call(' + helper.callParams + ') : helper; }');
</ins><span class="cx"> },
</span><span class="cx">
</span><span class="cx"> // [invokePartial]
</span><span class="lines">@@ -1763,14 +2352,18 @@
</span><span class="cx"> // and pushes the hash back onto the stack.
</span><span class="cx"> assignToHash: function(key) {
</span><span class="cx"> var value = this.popStack(),
</span><ins>+ context,
</ins><span class="cx"> type;
</span><span class="cx">
</span><span class="cx"> if (this.options.stringParams) {
</span><span class="cx"> type = this.popStack();
</span><del>- this.popStack();
</del><ins>+ context = this.popStack();
</ins><span class="cx"> }
</span><span class="cx">
</span><span class="cx"> var hash = this.hash;
</span><ins>+ if (context) {
+ hash.contexts.push("'" + key + "': " + context);
+ }
</ins><span class="cx"> if (type) {
</span><span class="cx"> hash.types.push("'" + key + "': " + type);
</span><span class="cx"> }
</span><span class="lines">@@ -1831,17 +2424,12 @@
</span><span class="cx"> else { programParams.push("depth" + (depth - 1)); }
</span><span class="cx"> }
</span><span class="cx">
</span><del>- if(depths.length === 0) {
- return "self.program(" + programParams.join(", ") + ")";
- } else {
- programParams.shift();
- return "self.programWithDepth(" + programParams.join(", ") + ")";
- }
</del><ins>+ return (depths.length === 0 ? "self.program(" : "self.programWithDepth(") + programParams.join(", ") + ")";
</ins><span class="cx"> },
</span><span class="cx">
</span><span class="cx"> register: function(name, val) {
</span><span class="cx"> this.useRegister(name);
</span><del>- this.source.push(name + " = " + val + ";");
</del><ins>+ this.pushSource(name + " = " + val + ";");
</ins><span class="cx"> },
</span><span class="cx">
</span><span class="cx"> useRegister: function(name) {
</span><span class="lines">@@ -1855,12 +2443,23 @@
</span><span class="cx"> return this.push(new Literal(item));
</span><span class="cx"> },
</span><span class="cx">
</span><ins>+ pushSource: function(source) {
+ if (this.pendingContent) {
+ this.source.push(this.appendToBuffer(this.quotedString(this.pendingContent)));
+ this.pendingContent = undefined;
+ }
+
+ if (source) {
+ this.source.push(source);
+ }
+ },
+
</ins><span class="cx"> pushStack: function(item) {
</span><span class="cx"> this.flushInline();
</span><span class="cx">
</span><span class="cx"> var stack = this.incrStack();
</span><span class="cx"> if (item) {
</span><del>- this.source.push(stack + " = " + item + ";");
</del><ins>+ this.pushSource(stack + " = " + item + ";");
</ins><span class="cx"> }
</span><span class="cx"> this.compileStack.push(stack);
</span><span class="cx"> return stack;
</span><span class="lines">@@ -1869,7 +2468,9 @@
</span><span class="cx"> replaceStack: function(callback) {
</span><span class="cx"> var prefix = '',
</span><span class="cx"> inline = this.isInline(),
</span><del>- stack;
</del><ins>+ stack,
+ createdStack,
+ usedLiteral;
</ins><span class="cx">
</span><span class="cx"> // If we are currently inline then we want to merge the inline statement into the
</span><span class="cx"> // replacement statement via ','
</span><span class="lines">@@ -1879,9 +2480,11 @@
</span><span class="cx"> if (top instanceof Literal) {
</span><span class="cx"> // Literals do not need to be inlined
</span><span class="cx"> stack = top.value;
</span><ins>+ usedLiteral = true;
</ins><span class="cx"> } else {
</span><span class="cx"> // Get or create the current stack name for use by the inline
</span><del>- var name = this.stackSlot ? this.topStackName() : this.incrStack();
</del><ins>+ createdStack = !this.stackSlot;
+ var name = !createdStack ? this.topStackName() : this.incrStack();
</ins><span class="cx">
</span><span class="cx"> prefix = '(' + this.push(name) + ' = ' + top + '),';
</span><span class="cx"> stack = this.topStack();
</span><span class="lines">@@ -1893,9 +2496,12 @@
</span><span class="cx"> var item = callback.call(this, stack);
</span><span class="cx">
</span><span class="cx"> if (inline) {
</span><del>- if (this.inlineStack.length || this.compileStack.length) {
</del><ins>+ if (!usedLiteral) {
</ins><span class="cx"> this.popStack();
</span><span class="cx"> }
</span><ins>+ if (createdStack) {
+ this.stackSlot--;
+ }
</ins><span class="cx"> this.push('(' + prefix + item + ')');
</span><span class="cx"> } else {
</span><span class="cx"> // Prevent modification of the context depth variable. Through replaceStack
</span><span class="lines">@@ -1903,7 +2509,7 @@
</span><span class="cx"> stack = this.nextStack();
</span><span class="cx"> }
</span><span class="cx">
</span><del>- this.source.push(stack + " = (" + prefix + item + ");");
</del><ins>+ this.pushSource(stack + " = (" + prefix + item + ");");
</ins><span class="cx"> }
</span><span class="cx"> return stack;
</span><span class="cx"> },
</span><span class="lines">@@ -1946,6 +2552,9 @@
</span><span class="cx"> return item.value;
</span><span class="cx"> } else {
</span><span class="cx"> if (!inline) {
</span><ins>+ if (!this.stackSlot) {
+ throw new Exception('Invalid stack pop');
+ }
</ins><span class="cx"> this.stackSlot--;
</span><span class="cx"> }
</span><span class="cx"> return item;
</span><span class="lines">@@ -1968,29 +2577,35 @@
</span><span class="cx"> .replace(/\\/g, '\\\\')
</span><span class="cx"> .replace(/"/g, '\\"')
</span><span class="cx"> .replace(/\n/g, '\\n')
</span><del>- .replace(/\r/g, '\\r') + '"';
</del><ins>+ .replace(/\r/g, '\\r')
+ .replace(/\u2028/g, '\\u2028') // Per Ecma-262 7.3 + 7.8.4
+ .replace(/\u2029/g, '\\u2029') + '"';
</ins><span class="cx"> },
</span><span class="cx">
</span><span class="cx"> setupHelper: function(paramSize, name, missingParams) {
</span><del>- var params = [];
- this.setupParams(paramSize, params, missingParams);
</del><ins>+ var params = [],
+ paramsInit = this.setupParams(paramSize, params, missingParams);
</ins><span class="cx"> var foundHelper = this.nameLookup('helpers', name, 'helper');
</span><span class="cx">
</span><span class="cx"> return {
</span><span class="cx"> params: params,
</span><ins>+ paramsInit: paramsInit,
</ins><span class="cx"> name: foundHelper,
</span><span class="cx"> callParams: ["depth0"].concat(params).join(", "),
</span><span class="cx"> helperMissingParams: missingParams && ["depth0", this.quotedString(name)].concat(params).join(", ")
</span><span class="cx"> };
</span><span class="cx"> },
</span><span class="cx">
</span><del>- // the params and contexts arguments are passed in arrays
- // to fill in
- setupParams: function(paramSize, params, useRegister) {
</del><ins>+ setupOptions: function(paramSize, params) {
</ins><span class="cx"> var options = [], contexts = [], types = [], param, inverse, program;
</span><span class="cx">
</span><span class="cx"> options.push("hash:" + this.popStack());
</span><span class="cx">
</span><ins>+ if (this.options.stringParams) {
+ options.push("hashTypes:" + this.popStack());
+ options.push("hashContexts:" + this.popStack());
+ }
+
</ins><span class="cx"> inverse = this.popStack();
</span><span class="cx"> program = this.popStack();
</span><span class="cx">
</span><span class="lines">@@ -2003,7 +2618,7 @@
</span><span class="cx"> }
</span><span class="cx">
</span><span class="cx"> if (!inverse) {
</span><del>- this.context.aliases.self = "this";
</del><ins>+ this.context.aliases.self = "this";
</ins><span class="cx"> inverse = "self.noop";
</span><span class="cx"> }
</span><span class="cx">
</span><span class="lines">@@ -2024,21 +2639,28 @@
</span><span class="cx"> if (this.options.stringParams) {
</span><span class="cx"> options.push("contexts:[" + contexts.join(",") + "]");
</span><span class="cx"> options.push("types:[" + types.join(",") + "]");
</span><del>- options.push("hashTypes:hashTypes");
</del><span class="cx"> }
</span><span class="cx">
</span><span class="cx"> if(this.options.data) {
</span><span class="cx"> options.push("data:data");
</span><span class="cx"> }
</span><span class="cx">
</span><del>- options = "{" + options.join(",") + "}";
</del><ins>+ return options;
+ },
+
+ // the params and contexts arguments are passed in arrays
+ // to fill in
+ setupParams: function(paramSize, params, useRegister) {
+ var options = '{' + this.setupOptions(paramSize, params).join(',') + '}';
+
</ins><span class="cx"> if (useRegister) {
</span><del>- this.register('options', options);
</del><ins>+ this.useRegister('options');
</ins><span class="cx"> params.push('options');
</span><ins>+ return 'options=' + options;
</ins><span class="cx"> } else {
</span><span class="cx"> params.push(options);
</span><ins>+ return '';
</ins><span class="cx"> }
</span><del>- return params.join(", ");
</del><span class="cx"> }
</span><span class="cx"> };
</span><span class="cx">
</span><span class="lines">@@ -2067,135 +2689,58 @@
</span><span class="cx"> }
</span><span class="cx">
</span><span class="cx"> JavaScriptCompiler.isValidJavaScriptVariableName = function(name) {
</span><del>- if(!JavaScriptCompiler.RESERVED_WORDS[name] && /^[a-zA-Z_$][0-9a-zA-Z_$]+$/.test(name)) {
</del><ins>+ if(!JavaScriptCompiler.RESERVED_WORDS[name] && /^[a-zA-Z_$][0-9a-zA-Z_$]*$/.test(name)) {
</ins><span class="cx"> return true;
</span><span class="cx"> }
</span><span class="cx"> return false;
</span><span class="cx"> };
</span><span class="cx">
</span><del>-})(Handlebars.Compiler, Handlebars.JavaScriptCompiler);
</del><ins>+ __exports__ = JavaScriptCompiler;
+ return __exports__;
+})(__module2__, __module5__);
</ins><span class="cx">
</span><del>-Handlebars.precompile = function(input, options) {
- if (!input || (typeof input !== 'string' && input.constructor !== Handlebars.AST.ProgramNode)) {
- throw new Handlebars.Exception("You must pass a string or Handlebars AST to Handlebars.compile. You passed " + input);
- }
</del><ins>+// handlebars.js
+var __module0__ = (function(__dependency1__, __dependency2__, __dependency3__, __dependency4__, __dependency5__) {
+ "use strict";
+ var __exports__;
+ /*globals Handlebars: true */
+ var Handlebars = __dependency1__;
</ins><span class="cx">
</span><del>- options = options || {};
- if (!('data' in options)) {
- options.data = true;
- }
- var ast = Handlebars.parse(input);
- var environment = new Handlebars.Compiler().compile(ast, options);
- return new Handlebars.JavaScriptCompiler().compile(environment, options);
-};
</del><ins>+ // Compiler imports
+ var AST = __dependency2__;
+ var Parser = __dependency3__.parser;
+ var parse = __dependency3__.parse;
+ var Compiler = __dependency4__.Compiler;
+ var compile = __dependency4__.compile;
+ var precompile = __dependency4__.precompile;
+ var JavaScriptCompiler = __dependency5__;
</ins><span class="cx">
</span><del>-Handlebars.compile = function(input, options) {
- if (!input || (typeof input !== 'string' && input.constructor !== Handlebars.AST.ProgramNode)) {
- throw new Handlebars.Exception("You must pass a string or Handlebars AST to Handlebars.compile. You passed " + input);
- }
</del><ins>+ var _create = Handlebars.create;
+ var create = function() {
+ var hb = _create();
</ins><span class="cx">
</span><del>- options = options || {};
- if (!('data' in options)) {
- options.data = true;
- }
- var compiled;
- function compile() {
- var ast = Handlebars.parse(input);
- var environment = new Handlebars.Compiler().compile(ast, options);
- var templateSpec = new Handlebars.JavaScriptCompiler().compile(environment, options, undefined, true);
- return Handlebars.template(templateSpec);
- }
-
- // Template is only compiled on first use and cached after that point.
- return function(context, options) {
- if (!compiled) {
- compiled = compile();
- }
- return compiled.call(this, context, options);
- };
-};
-;
-// lib/handlebars/runtime.js
-Handlebars.VM = {
- template: function(templateSpec) {
- // Just add water
- var container = {
- escapeExpression: Handlebars.Utils.escapeExpression,
- invokePartial: Handlebars.VM.invokePartial,
- programs: [],
- program: function(i, fn, data) {
- var programWrapper = this.programs[i];
- if(data) {
- return Handlebars.VM.program(fn, data);
- } else if(programWrapper) {
- return programWrapper;
- } else {
- programWrapper = this.programs[i] = Handlebars.VM.program(fn);
- return programWrapper;
- }
- },
- programWithDepth: Handlebars.VM.programWithDepth,
- noop: Handlebars.VM.noop,
- compilerInfo: null
</del><ins>+ hb.compile = function(input, options) {
+ return compile(input, options, hb);
</ins><span class="cx"> };
</span><del>-
- return function(context, options) {
- options = options || {};
- var result = templateSpec.call(container, Handlebars, context, options.helpers, options.partials, options.data);
-
- var compilerInfo = container.compilerInfo || [],
- compilerRevision = compilerInfo[0] || 1,
- currentRevision = Handlebars.COMPILER_REVISION;
-
- if (compilerRevision !== currentRevision) {
- if (compilerRevision < currentRevision) {
- var runtimeVersions = Handlebars.REVISION_CHANGES[currentRevision],
- compilerVersions = Handlebars.REVISION_CHANGES[compilerRevision];
- throw "Template was precompiled with an older version of Handlebars than the current runtime. "+
- "Please update your precompiler to a newer version ("+runtimeVersions+") or downgrade your runtime to an older version ("+compilerVersions+").";
- } else {
- // Use the embedded version info since the runtime doesn't know about this revision yet
- throw "Template was precompiled with a newer version of Handlebars than the current runtime. "+
- "Please update your runtime to a newer version ("+compilerInfo[1]+").";
- }
- }
-
- return result;
</del><ins>+ hb.precompile = function (input, options) {
+ return precompile(input, options, hb);
</ins><span class="cx"> };
</span><del>- },
</del><span class="cx">
</span><del>- programWithDepth: function(fn, data, $depth) {
- var args = Array.prototype.slice.call(arguments, 2);
</del><ins>+ hb.AST = AST;
+ hb.Compiler = Compiler;
+ hb.JavaScriptCompiler = JavaScriptCompiler;
+ hb.Parser = Parser;
+ hb.parse = parse;
</ins><span class="cx">
</span><del>- return function(context, options) {
- options = options || {};
</del><ins>+ return hb;
+ };
</ins><span class="cx">
</span><del>- return fn.apply(this, [context, options.data || data].concat(args));
- };
- },
- program: function(fn, data) {
- return function(context, options) {
- options = options || {};
</del><ins>+ Handlebars = create();
+ Handlebars.create = create;
</ins><span class="cx">
</span><del>- return fn(context, options.data || data);
- };
- },
- noop: function() { return ""; },
- invokePartial: function(partial, name, context, helpers, partials, data) {
- var options = { helpers: helpers, partials: partials, data: data };
</del><ins>+ __exports__ = Handlebars;
+ return __exports__;
+})(__module1__, __module7__, __module8__, __module10__, __module11__);
</ins><span class="cx">
</span><del>- if(partial === undefined) {
- throw new Handlebars.Exception("The partial " + name + " could not be found");
- } else if(partial instanceof Function) {
- return partial(context, options);
- } else if (!Handlebars.compile) {
- throw new Handlebars.Exception("The partial " + name + " could not be compiled when running in runtime-only mode");
- } else {
- partials[name] = Handlebars.compile(partial, {data: data !== undefined});
- return partials[name](context, options);
- }
- }
-};
-
-Handlebars.template = Handlebars.VM.template;
-;
</del><ins>+ return __module0__;
+})();
</ins></span></pre></div>
<a id="trunkPerformanceTestsDoYouEvenBenchresourcestodomvcarchitectureexamplesemberjsbower_componentsjqueryjqueryjs"></a>
<div class="modfile"><h4>Modified: trunk/PerformanceTests/DoYouEvenBench/resources/todomvc/architecture-examples/emberjs/bower_components/jquery/jquery.js (163428 => 163429)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/PerformanceTests/DoYouEvenBench/resources/todomvc/architecture-examples/emberjs/bower_components/jquery/jquery.js        2014-02-05 05:32:21 UTC (rev 163428)
+++ trunk/PerformanceTests/DoYouEvenBench/resources/todomvc/architecture-examples/emberjs/bower_components/jquery/jquery.js        2014-02-05 05:36:04 UTC (rev 163429)
</span><span class="lines">@@ -1,795 +1,493 @@
</span><span class="cx"> /*!
</span><del>- * jQuery JavaScript Library v1.9.1
</del><ins>+ * jQuery JavaScript Library v2.1.0
</ins><span class="cx"> * http://jquery.com/
</span><span class="cx"> *
</span><span class="cx"> * Includes Sizzle.js
</span><span class="cx"> * http://sizzlejs.com/
</span><span class="cx"> *
</span><del>- * Copyright 2005, 2012 jQuery Foundation, Inc. and other contributors
</del><ins>+ * Copyright 2005, 2014 jQuery Foundation, Inc. and other contributors
</ins><span class="cx"> * Released under the MIT license
</span><span class="cx"> * http://jquery.org/license
</span><span class="cx"> *
</span><del>- * Date: 2013-2-4
</del><ins>+ * Date: 2014-01-23T21:10Z
</ins><span class="cx"> */
</span><del>-(function( window, undefined ) {
</del><span class="cx">
</span><ins>+(function( global, factory ) {
+
+ if ( typeof module === "object" && typeof module.exports === "object" ) {
+ // For CommonJS and CommonJS-like environments where a proper window is present,
+ // execute the factory and get jQuery
+ // For environments that do not inherently posses a window with a document
+ // (such as Node.js), expose a jQuery-making factory as module.exports
+ // This accentuates the need for the creation of a real window
+ // e.g. var jQuery = require("jquery")(window);
+ // See ticket #14549 for more info
+ module.exports = global.document ?
+ factory( global, true ) :
+ function( w ) {
+ if ( !w.document ) {
+ throw new Error( "jQuery requires a window with a document" );
+ }
+ return factory( w );
+ };
+ } else {
+ factory( global );
+ }
+
+// Pass this if window is not defined yet
+}(typeof window !== "undefined" ? window : this, function( window, noGlobal ) {
+
</ins><span class="cx"> // Can't do this because several apps including ASP.NET trace
</span><span class="cx"> // the stack via arguments.caller.callee and Firefox dies if
</span><span class="cx"> // you try to trace through "use strict" call chains. (#13335)
</span><span class="cx"> // Support: Firefox 18+
</span><del>-//"use strict";
-var
- // The deferred used on DOM ready
- readyList,
</del><ins>+//
</ins><span class="cx">
</span><del>- // A central reference to the root jQuery(document)
- rootjQuery,
</del><ins>+var arr = [];
</ins><span class="cx">
</span><del>- // Support: IE<9
- // For `typeof node.method` instead of `node.method !== undefined`
- core_strundefined = typeof undefined,
</del><ins>+var slice = arr.slice;
</ins><span class="cx">
</span><del>- // Use the correct document accordingly with window argument (sandbox)
- document = window.document,
- location = window.location,
</del><ins>+var concat = arr.concat;
</ins><span class="cx">
</span><del>- // Map over jQuery in case of overwrite
- _jQuery = window.jQuery,
</del><ins>+var push = arr.push;
</ins><span class="cx">
</span><del>- // Map over the $ in case of overwrite
- _$ = window.$,
</del><ins>+var indexOf = arr.indexOf;
</ins><span class="cx">
</span><del>- // [[Class]] -> type pairs
- class2type = {},
</del><ins>+var class2type = {};
</ins><span class="cx">
</span><del>- // List of deleted data cache ids, so we can reuse them
- core_deletedIds = [],
</del><ins>+var toString = class2type.toString;
</ins><span class="cx">
</span><del>- core_version = "1.9.1",
</del><ins>+var hasOwn = class2type.hasOwnProperty;
</ins><span class="cx">
</span><del>- // Save a reference to some core methods
- core_concat = core_deletedIds.concat,
- core_push = core_deletedIds.push,
- core_slice = core_deletedIds.slice,
- core_indexOf = core_deletedIds.indexOf,
- core_toString = class2type.toString,
- core_hasOwn = class2type.hasOwnProperty,
- core_trim = core_version.trim,
</del><ins>+var trim = "".trim;
</ins><span class="cx">
</span><del>- // Define a local copy of jQuery
- jQuery = function( selector, context ) {
- // The jQuery object is actually just the init constructor 'enhanced'
- return new jQuery.fn.init( selector, context, rootjQuery );
- },
</del><ins>+var support = {};
</ins><span class="cx">
</span><del>- // Used for matching numbers
- core_pnum = /[+-]?(?:\d*\.|)\d+(?:[eE][+-]?\d+|)/.source,
</del><span class="cx">
</span><del>- // Used for splitting on whitespace
- core_rnotwhite = /\S+/g,
</del><span class="cx">
</span><del>- // Make sure we trim BOM and NBSP (here's looking at you, Safari 5.0 and IE)
- rtrim = /^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g,
</del><ins>+var
+ // Use the correct document accordingly with window argument (sandbox)
+ document = window.document,
</ins><span class="cx">
</span><del>- // A simple way to check for HTML strings
- // Prioritize #id over <tag> to avoid XSS via location.hash (#9521)
- // Strict HTML recognition (#11290: must start with <)
- rquickExpr = /^(?:(<[\w\W]+>)[^>]*|#([\w-]*))$/,
</del><ins>+ version = "2.1.0",
</ins><span class="cx">
</span><del>- // Match a standalone tag
- rsingleTag = /^<(\w+)\s*\/?>(?:<\/\1>|)$/,
</del><ins>+ // Define a local copy of jQuery
+ jQuery = function( selector, context ) {
+ // The jQuery object is actually just the init constructor 'enhanced'
+ // Need init if jQuery is called (just allow error to be thrown if not included)
+ return new jQuery.fn.init( selector, context );
+ },
</ins><span class="cx">
</span><del>- // JSON RegExp
- rvalidchars = /^[\],:{}\s]*$/,
- rvalidbraces = /(?:^|:|,)(?:\s*\[)+/g,
- rvalidescape = /\\(?:["\\\/bfnrt]|u[\da-fA-F]{4})/g,
- rvalidtokens = /"[^"\\\r\n]*"|true|false|null|-?(?:\d+\.|)\d+(?:[eE][+-]?\d+|)/g,
-
</del><span class="cx"> // Matches dashed string for camelizing
</span><span class="cx"> rmsPrefix = /^-ms-/,
</span><span class="cx"> rdashAlpha = /-([\da-z])/gi,
</span><span class="cx">
</span><span class="cx"> // Used by jQuery.camelCase as callback to replace()
</span><span class="cx"> fcamelCase = function( all, letter ) {
</span><del>- return letter.toUpperCase();
- },
-
- // The ready event handler
- completed = function( event ) {
-
- // readyState === "complete" is good enough for us to call the dom ready in oldIE
- if ( document.addEventListener || event.type === "load" || document.readyState === "complete" ) {
- detach();
- jQuery.ready();
- }
- },
- // Clean-up method for dom ready events
- detach = function() {
- if ( document.addEventListener ) {
- document.removeEventListener( "DOMContentLoaded", completed, false );
- window.removeEventListener( "load", completed, false );
-
- } else {
- document.detachEvent( "onreadystatechange", completed );
- window.detachEvent( "onload", completed );
- }
</del><ins>+ return letter.toUpperCase();
</ins><span class="cx"> };
</span><span class="cx">
</span><span class="cx"> jQuery.fn = jQuery.prototype = {
</span><span class="cx"> // The current version of jQuery being used
</span><del>- jquery: core_version,
</del><ins>+ jquery: version,
</ins><span class="cx">
</span><span class="cx"> constructor: jQuery,
</span><del>- init: function( selector, context, rootjQuery ) {
- var match, elem;
</del><span class="cx">
</span><del>- // HANDLE: $(""), $(null), $(undefined), $(false)
- if ( !selector ) {
- return this;
- }
-
- // Handle HTML strings
- if ( typeof selector === "string" ) {
- if ( selector.charAt(0) === "<" && selector.charAt( selector.length - 1 ) === ">" && selector.length >= 3 ) {
- // Assume that strings that start and end with <> are HTML and skip the regex check
- match = [ null, selector, null ];
-
- } else {
- match = rquickExpr.exec( selector );
- }
-
- // Match html or make sure no context is specified for #id
- if ( match && (match[1] || !context) ) {
-
- // HANDLE: $(html) -> $(array)
- if ( match[1] ) {
- context = context instanceof jQuery ? context[0] : context;
-
- // scripts is true for back-compat
- jQuery.merge( this, jQuery.parseHTML(
- match[1],
- context && context.nodeType ? context.ownerDocument || context : document,
- true
- ) );
-
- // HANDLE: $(html, props)
- if ( rsingleTag.test( match[1] ) && jQuery.isPlainObject( context ) ) {
- for ( match in context ) {
- // Properties of context are called as methods if possible
- if ( jQuery.isFunction( this[ match ] ) ) {
- this[ match ]( context[ match ] );
-
- // ...and otherwise set as attributes
- } else {
- this.attr( match, context[ match ] );
- }
- }
- }
-
- return this;
-
- // HANDLE: $(#id)
- } else {
- elem = document.getElementById( match[2] );
-
- // Check parentNode to catch when Blackberry 4.6 returns
- // nodes that are no longer in the document #6963
- if ( elem && elem.parentNode ) {
- // Handle the case where IE and Opera return items
- // by name instead of ID
- if ( elem.id !== match[2] ) {
- return rootjQuery.find( selector );
- }
-
- // Otherwise, we inject the element directly into the jQuery object
- this.length = 1;
- this[0] = elem;
- }
-
- this.context = document;
- this.selector = selector;
- return this;
- }
-
- // HANDLE: $(expr, $(...))
- } else if ( !context || context.jquery ) {
- return ( context || rootjQuery ).find( selector );
-
- // HANDLE: $(expr, context)
- // (which is just equivalent to: $(context).find(expr)
- } else {
- return this.constructor( context ).find( selector );
- }
-
- // HANDLE: $(DOMElement)
- } else if ( selector.nodeType ) {
- this.context = this[0] = selector;
- this.length = 1;
- return this;
-
- // HANDLE: $(function)
- // Shortcut for document ready
- } else if ( jQuery.isFunction( selector ) ) {
- return rootjQuery.ready( selector );
- }
-
- if ( selector.selector !== undefined ) {
- this.selector = selector.selector;
- this.context = selector.context;
- }
-
- return jQuery.makeArray( selector, this );
- },
-
</del><span class="cx"> // Start with an empty selector
</span><span class="cx"> selector: "",
</span><span class="cx">
</span><span class="cx"> // The default length of a jQuery object is 0
</span><span class="cx"> length: 0,
</span><span class="cx">
</span><del>- // The number of elements contained in the matched element set
- size: function() {
- return this.length;
- },
-
</del><span class="cx"> toArray: function() {
</span><del>- return core_slice.call( this );
</del><ins>+ return slice.call( this );
</ins><span class="cx"> },
</span><span class="cx">
</span><span class="cx"> // Get the Nth element in the matched element set OR
</span><span class="cx"> // Get the whole matched element set as a clean array
</span><span class="cx"> get: function( num ) {
</span><del>- return num == null ?
</del><ins>+ return num != null ?
</ins><span class="cx">
</span><del>- // Return a 'clean' array
- this.toArray() :
</del><ins>+ // Return a 'clean' array
+ ( num < 0 ? this[ num + this.length ] : this[ num ] ) :
</ins><span class="cx">
</span><del>- // Return just the object
- ( num < 0 ? this[ this.length + num ] : this[ num ] );
</del><ins>+ // Return just the object
+ slice.call( this );
</ins><span class="cx"> },
</span><span class="cx">
</span><span class="cx"> // Take an array of elements and push it onto the stack
</span><span class="cx"> // (returning the new matched element set)
</span><span class="cx"> pushStack: function( elems ) {
</span><span class="cx">
</span><del>- // Build a new jQuery matched element set
- var ret = jQuery.merge( this.constructor(), elems );
</del><ins>+ // Build a new jQuery matched element set
+ var ret = jQuery.merge( this.constructor(), elems );
</ins><span class="cx">
</span><del>- // Add the old object onto the stack (as a reference)
- ret.prevObject = this;
- ret.context = this.context;
</del><ins>+ // Add the old object onto the stack (as a reference)
+ ret.prevObject = this;
+ ret.context = this.context;
</ins><span class="cx">
</span><del>- // Return the newly-formed element set
- return ret;
</del><ins>+ // Return the newly-formed element set
+ return ret;
</ins><span class="cx"> },
</span><span class="cx">
</span><span class="cx"> // Execute a callback for every element in the matched set.
</span><span class="cx"> // (You can seed the arguments with an array of args, but this is
</span><span class="cx"> // only used internally.)
</span><span class="cx"> each: function( callback, args ) {
</span><del>- return jQuery.each( this, callback, args );
</del><ins>+ return jQuery.each( this, callback, args );
</ins><span class="cx"> },
</span><span class="cx">
</span><del>- ready: function( fn ) {
- // Add the callback
- jQuery.ready.promise().done( fn );
-
- return this;
</del><ins>+ map: function( callback ) {
+ return this.pushStack( jQuery.map(this, function( elem, i ) {
+ return callback.call( elem, i, elem );
+ }));
</ins><span class="cx"> },
</span><span class="cx">
</span><span class="cx"> slice: function() {
</span><del>- return this.pushStack( core_slice.apply( this, arguments ) );
</del><ins>+ return this.pushStack( slice.apply( this, arguments ) );
</ins><span class="cx"> },
</span><span class="cx">
</span><span class="cx"> first: function() {
</span><del>- return this.eq( 0 );
</del><ins>+ return this.eq( 0 );
</ins><span class="cx"> },
</span><span class="cx">
</span><span class="cx"> last: function() {
</span><del>- return this.eq( -1 );
</del><ins>+ return this.eq( -1 );
</ins><span class="cx"> },
</span><span class="cx">
</span><span class="cx"> eq: function( i ) {
</span><del>- var len = this.length,
- j = +i + ( i < 0 ? len : 0 );
- return this.pushStack( j >= 0 && j < len ? [ this[j] ] : [] );
</del><ins>+ var len = this.length,
+ j = +i + ( i < 0 ? len : 0 );
+ return this.pushStack( j >= 0 && j < len ? [ this[j] ] : [] );
</ins><span class="cx"> },
</span><span class="cx">
</span><del>- map: function( callback ) {
- return this.pushStack( jQuery.map(this, function( elem, i ) {
- return callback.call( elem, i, elem );
- }));
- },
-
</del><span class="cx"> end: function() {
</span><del>- return this.prevObject || this.constructor(null);
</del><ins>+ return this.prevObject || this.constructor(null);
</ins><span class="cx"> },
</span><span class="cx">
</span><span class="cx"> // For internal use only.
</span><span class="cx"> // Behaves like an Array's method, not like a jQuery method.
</span><del>- push: core_push,
- sort: [].sort,
- splice: [].splice
</del><ins>+ push: push,
+ sort: arr.sort,
+ splice: arr.splice
</ins><span class="cx"> };
</span><span class="cx">
</span><del>-// Give the init function the jQuery prototype for later instantiation
-jQuery.fn.init.prototype = jQuery.fn;
-
</del><span class="cx"> jQuery.extend = jQuery.fn.extend = function() {
</span><del>- var src, copyIsArray, copy, name, options, clone,
- target = arguments[0] || {},
- i = 1,
- length = arguments.length,
- deep = false;
</del><ins>+ var options, name, src, copy, copyIsArray, clone,
+ target = arguments[0] || {},
+ i = 1,
+ length = arguments.length,
+ deep = false;
</ins><span class="cx">
</span><span class="cx"> // Handle a deep copy situation
</span><span class="cx"> if ( typeof target === "boolean" ) {
</span><del>- deep = target;
- target = arguments[1] || {};
- // skip the boolean and the target
- i = 2;
</del><ins>+ deep = target;
+
+ // skip the boolean and the target
+ target = arguments[ i ] || {};
+ i++;
</ins><span class="cx"> }
</span><span class="cx">
</span><span class="cx"> // Handle case when target is a string or something (possible in deep copy)
</span><span class="cx"> if ( typeof target !== "object" && !jQuery.isFunction(target) ) {
</span><del>- target = {};
</del><ins>+ target = {};
</ins><span class="cx"> }
</span><span class="cx">
</span><span class="cx"> // extend jQuery itself if only one argument is passed
</span><del>- if ( length === i ) {
- target = this;
- --i;
</del><ins>+ if ( i === length ) {
+ target = this;
+ i--;
</ins><span class="cx"> }
</span><span class="cx">
</span><span class="cx"> for ( ; i < length; i++ ) {
</span><del>- // Only deal with non-null/undefined values
- if ( (options = arguments[ i ]) != null ) {
- // Extend the base object
- for ( name in options ) {
- src = target[ name ];
- copy = options[ name ];
</del><ins>+ // Only deal with non-null/undefined values
+ if ( (options = arguments[ i ]) != null ) {
+ // Extend the base object
+ for ( name in options ) {
+ src = target[ name ];
+ copy = options[ name ];
</ins><span class="cx">
</span><del>- // Prevent never-ending loop
- if ( target === copy ) {
- continue;
- }
</del><ins>+ // Prevent never-ending loop
+ if ( target === copy ) {
+ continue;
+ }
</ins><span class="cx">
</span><del>- // Recurse if we're merging plain objects or arrays
- if ( deep && copy && ( jQuery.isPlainObject(copy) || (copyIsArray = jQuery.isArray(copy)) ) ) {
- if ( copyIsArray ) {
- copyIsArray = false;
- clone = src && jQuery.isArray(src) ? src : [];
</del><ins>+ // Recurse if we're merging plain objects or arrays
+ if ( deep && copy && ( jQuery.isPlainObject(copy) || (copyIsArray = jQuery.isArray(copy)) ) ) {
+ if ( copyIsArray ) {
+ copyIsArray = false;
+ clone = src && jQuery.isArray(src) ? src : [];
</ins><span class="cx">
</span><del>- } else {
- clone = src && jQuery.isPlainObject(src) ? src : {};
- }
</del><ins>+ } else {
+ clone = src && jQuery.isPlainObject(src) ? src : {};
+ }
</ins><span class="cx">
</span><del>- // Never move original objects, clone them
- target[ name ] = jQuery.extend( deep, clone, copy );
</del><ins>+ // Never move original objects, clone them
+ target[ name ] = jQuery.extend( deep, clone, copy );
</ins><span class="cx">
</span><del>- // Don't bring in undefined values
- } else if ( copy !== undefined ) {
- target[ name ] = copy;
</del><ins>+ // Don't bring in undefined values
+ } else if ( copy !== undefined ) {
+ target[ name ] = copy;
+ }
+ }
+ }
</ins><span class="cx"> }
</span><del>- }
- }
- }
</del><span class="cx">
</span><span class="cx"> // Return the modified object
</span><span class="cx"> return target;
</span><span class="cx"> };
</span><span class="cx">
</span><span class="cx"> jQuery.extend({
</span><del>- noConflict: function( deep ) {
- if ( window.$ === jQuery ) {
- window.$ = _$;
- }
</del><ins>+ // Unique for each copy of jQuery on the page
+ expando: "jQuery" + ( version + Math.random() ).replace( /\D/g, "" ),
</ins><span class="cx">
</span><del>- if ( deep && window.jQuery === jQuery ) {
- window.jQuery = _jQuery;
- }
</del><ins>+ // Assume jQuery is ready without the ready module
+ isReady: true,
</ins><span class="cx">
</span><del>- return jQuery;
</del><ins>+ error: function( msg ) {
+ throw new Error( msg );
</ins><span class="cx"> },
</span><span class="cx">
</span><del>- // Is the DOM ready to be used? Set to true once it occurs.
- isReady: false,
</del><ins>+ noop: function() {},
</ins><span class="cx">
</span><del>- // A counter to track how many items to wait for before
- // the ready event fires. See #6781
- readyWait: 1,
-
- // Hold (or release) the ready event
- holdReady: function( hold ) {
- if ( hold ) {
- jQuery.readyWait++;
- } else {
- jQuery.ready( true );
- }
- },
-
- // Handle when the DOM is ready
- ready: function( wait ) {
-
- // Abort if there are pending holds or we're already ready
- if ( wait === true ? --jQuery.readyWait : jQuery.isReady ) {
- return;
- }
-
- // Make sure body exists, at least, in case IE gets a little overzealous (ticket #5443).
- if ( !document.body ) {
- return setTimeout( jQuery.ready );
- }
-
- // Remember that the DOM is ready
- jQuery.isReady = true;
-
- // If a normal DOM Ready event fired, decrement, and wait if need be
- if ( wait !== true && --jQuery.readyWait > 0 ) {
- return;
- }
-
- // If there are functions bound, to execute
- readyList.resolveWith( document, [ jQuery ] );
-
- // Trigger any bound ready events
- if ( jQuery.fn.trigger ) {
- jQuery( document ).trigger("ready").off("ready");
- }
- },
-
</del><span class="cx"> // See test/unit/core.js for details concerning isFunction.
</span><span class="cx"> // Since version 1.3, DOM methods and functions like alert
</span><span class="cx"> // aren't supported. They return false on IE (#2968).
</span><span class="cx"> isFunction: function( obj ) {
</span><del>- return jQuery.type(obj) === "function";
</del><ins>+ return jQuery.type(obj) === "function";
</ins><span class="cx"> },
</span><span class="cx">
</span><del>- isArray: Array.isArray || function( obj ) {
- return jQuery.type(obj) === "array";
- },
</del><ins>+ isArray: Array.isArray,
</ins><span class="cx">
</span><span class="cx"> isWindow: function( obj ) {
</span><del>- return obj != null && obj == obj.window;
</del><ins>+ return obj != null && obj === obj.window;
</ins><span class="cx"> },
</span><span class="cx">
</span><span class="cx"> isNumeric: function( obj ) {
</span><del>- return !isNaN( parseFloat(obj) ) && isFinite( obj );
</del><ins>+ // parseFloat NaNs numeric-cast false positives (null|true|false|"")
+ // ...but misinterprets leading-number strings, particularly hex literals ("0x...")
+ // subtraction forces infinities to NaN
+ return obj - parseFloat( obj ) >= 0;
</ins><span class="cx"> },
</span><span class="cx">
</span><del>- type: function( obj ) {
- if ( obj == null ) {
- return String( obj );
- }
- return typeof obj === "object" || typeof obj === "function" ?
- class2type[ core_toString.call(obj) ] || "object" :
- typeof obj;
- },
-
</del><span class="cx"> isPlainObject: function( obj ) {
</span><del>- // Must be an Object.
- // Because of IE, we also have to check the presence of the constructor property.
- // Make sure that DOM nodes and window objects don't pass through, as well
- if ( !obj || jQuery.type(obj) !== "object" || obj.nodeType || jQuery.isWindow( obj ) ) {
- return false;
- }
</del><ins>+ // Not plain objects:
+ // - Any object or value whose internal [[Class]] property is not "[object Object]"
+ // - DOM nodes
+ // - window
+ if ( jQuery.type( obj ) !== "object" || obj.nodeType || jQuery.isWindow( obj ) ) {
+ return false;
+ }
</ins><span class="cx">
</span><del>- try {
- // Not own constructor property must be Object
- if ( obj.constructor &&
- !core_hasOwn.call(obj, "constructor") &&
- !core_hasOwn.call(obj.constructor.prototype, "isPrototypeOf") ) {
- return false;
- }
- } catch ( e ) {
- // IE8,9 Will throw exceptions on certain host objects #9897
- return false;
- }
</del><ins>+ // Support: Firefox <20
+ // The try/catch suppresses exceptions thrown when attempting to access
+ // the "constructor" property of certain host objects, ie. |window.location|
+ // https://bugzilla.mozilla.org/show_bug.cgi?id=814622
+ try {
+ if ( obj.constructor &&
+ !hasOwn.call( obj.constructor.prototype, "isPrototypeOf" ) ) {
+ return false;
+ }
+ } catch ( e ) {
+ return false;
+ }
</ins><span class="cx">
</span><del>- // Own properties are enumerated firstly, so to speed up,
- // if last one is own, then all properties are own.
-
- var key;
- for ( key in obj ) {}
-
- return key === undefined || core_hasOwn.call( obj, key );
</del><ins>+ // If the function hasn't returned already, we're confident that
+ // |obj| is a plain object, created by {} or constructed with new Object
+ return true;
</ins><span class="cx"> },
</span><span class="cx">
</span><span class="cx"> isEmptyObject: function( obj ) {
</span><del>- var name;
- for ( name in obj ) {
- return false;
- }
- return true;
</del><ins>+ var name;
+ for ( name in obj ) {
+ return false;
+ }
+ return true;
</ins><span class="cx"> },
</span><span class="cx">
</span><del>- error: function( msg ) {
- throw new Error( msg );
</del><ins>+ type: function( obj ) {
+ if ( obj == null ) {
+ return obj + "";
+ }
+ // Support: Android < 4.0, iOS < 6 (functionish RegExp)
+ return typeof obj === "object" || typeof obj === "function" ?
+ class2type[ toString.call(obj) ] || "object" :
+ typeof obj;
</ins><span class="cx"> },
</span><span class="cx">
</span><del>- // data: string of html
- // context (optional): If specified, the fragment will be created in this context, defaults to document
- // keepScripts (optional): If true, will include scripts passed in the html string
- parseHTML: function( data, context, keepScripts ) {
- if ( !data || typeof data !== "string" ) {
- return null;
- }
- if ( typeof context === "boolean" ) {
- keepScripts = context;
- context = false;
- }
- context = context || document;
</del><ins>+ // Evaluates a script in a global context
+ globalEval: function( code ) {
+ var script,
+ indirect = eval;
</ins><span class="cx">
</span><del>- var parsed = rsingleTag.exec( data ),
- scripts = !keepScripts && [];
</del><ins>+ code = jQuery.trim( code );
</ins><span class="cx">
</span><del>- // Single tag
- if ( parsed ) {
- return [ context.createElement( parsed[1] ) ];
- }
-
- parsed = jQuery.buildFragment( [ data ], context, scripts );
- if ( scripts ) {
- jQuery( scripts ).remove();
- }
- return jQuery.merge( [], parsed.childNodes );
</del><ins>+ if ( code ) {
+ // If the code includes a valid, prologue position
+ // strict mode pragma, execute code by injecting a
+ // script tag into the document.
+ if ( code.indexOf("use strict") === 1 ) {
+ script = document.createElement("script");
+ script.text = code;
+ document.head.appendChild( script ).parentNode.removeChild( script );
+ } else {
+ // Otherwise, avoid the DOM node creation, insertion
+ // and removal by using an indirect global eval
+ indirect( code );
+ }
+ }
</ins><span class="cx"> },
</span><span class="cx">
</span><del>- parseJSON: function( data ) {
- // Attempt to parse using the native JSON parser first
- if ( window.JSON && window.JSON.parse ) {
- return window.JSON.parse( data );
- }
-
- if ( data === null ) {
- return data;
- }
-
- if ( typeof data === "string" ) {
-
- // Make sure leading/trailing whitespace is removed (IE can't handle it)
- data = jQuery.trim( data );
-
- if ( data ) {
- // Make sure the incoming data is actual JSON
- // Logic borrowed from http://json.org/json2.js
- if ( rvalidchars.test( data.replace( rvalidescape, "@" )
- .replace( rvalidtokens, "]" )
- .replace( rvalidbraces, "")) ) {
-
- return ( new Function( "return " + data ) )();
- }
- }
- }
-
- jQuery.error( "Invalid JSON: " + data );
- },
-
- // Cross-browser xml parsing
- parseXML: function( data ) {
- var xml, tmp;
- if ( !data || typeof data !== "string" ) {
- return null;
- }
- try {
- if ( window.DOMParser ) { // Standard
- tmp = new DOMParser();
- xml = tmp.parseFromString( data , "text/xml" );
- } else { // IE
- xml = new ActiveXObject( "Microsoft.XMLDOM" );
- xml.async = "false";
- xml.loadXML( data );
- }
- } catch( e ) {
- xml = undefined;
- }
- if ( !xml || !xml.documentElement || xml.getElementsByTagName( "parsererror" ).length ) {
- jQuery.error( "Invalid XML: " + data );
- }
- return xml;
- },
-
- noop: function() {},
-
- // Evaluates a script in a global context
- // Workarounds based on findings by Jim Driscoll
- // http://weblogs.java.net/blog/driscoll/archive/2009/09/08/eval-javascript-global-context
- globalEval: function( data ) {
- if ( data && jQuery.trim( data ) ) {
- // We use execScript on Internet Explorer
- // We use an anonymous function so that context is window
- // rather than jQuery in Firefox
- ( window.execScript || function( data ) {
- window[ "eval" ].call( window, data );
- } )( data );
- }
- },
-
</del><span class="cx"> // Convert dashed to camelCase; used by the css and data modules
</span><span class="cx"> // Microsoft forgot to hump their vendor prefix (#9572)
</span><span class="cx"> camelCase: function( string ) {
</span><del>- return string.replace( rmsPrefix, "ms-" ).replace( rdashAlpha, fcamelCase );
</del><ins>+ return string.replace( rmsPrefix, "ms-" ).replace( rdashAlpha, fcamelCase );
</ins><span class="cx"> },
</span><span class="cx">
</span><span class="cx"> nodeName: function( elem, name ) {
</span><del>- return elem.nodeName && elem.nodeName.toLowerCase() === name.toLowerCase();
</del><ins>+ return elem.nodeName && elem.nodeName.toLowerCase() === name.toLowerCase();
</ins><span class="cx"> },
</span><span class="cx">
</span><span class="cx"> // args is for internal usage only
</span><span class="cx"> each: function( obj, callback, args ) {
</span><del>- var value,
- i = 0,
- length = obj.length,
- isArray = isArraylike( obj );
</del><ins>+ var value,
+ i = 0,
+ length = obj.length,
+ isArray = isArraylike( obj );
</ins><span class="cx">
</span><del>- if ( args ) {
- if ( isArray ) {
- for ( ; i < length; i++ ) {
- value = callback.apply( obj[ i ], args );
</del><ins>+ if ( args ) {
+ if ( isArray ) {
+ for ( ; i < length; i++ ) {
+ value = callback.apply( obj[ i ], args );
</ins><span class="cx">
</span><del>- if ( value === false ) {
- break;
- }
- }
- } else {
- for ( i in obj ) {
- value = callback.apply( obj[ i ], args );
</del><ins>+ if ( value === false ) {
+ break;
+ }
+ }
+ } else {
+ for ( i in obj ) {
+ value = callback.apply( obj[ i ], args );
</ins><span class="cx">
</span><del>- if ( value === false ) {
- break;
- }
- }
- }
</del><ins>+ if ( value === false ) {
+ break;
+ }
+ }
+ }
</ins><span class="cx">
</span><del>- // A special, fast, case for the most common use of each
- } else {
- if ( isArray ) {
- for ( ; i < length; i++ ) {
- value = callback.call( obj[ i ], i, obj[ i ] );
</del><ins>+ // A special, fast, case for the most common use of each
+ } else {
+ if ( isArray ) {
+ for ( ; i < length; i++ ) {
+ value = callback.call( obj[ i ], i, obj[ i ] );
</ins><span class="cx">
</span><del>- if ( value === false ) {
- break;
- }
- }
- } else {
- for ( i in obj ) {
- value = callback.call( obj[ i ], i, obj[ i ] );
</del><ins>+ if ( value === false ) {
+ break;
+ }
+ }
+ } else {
+ for ( i in obj ) {
+ value = callback.call( obj[ i ], i, obj[ i ] );
</ins><span class="cx">
</span><del>- if ( value === false ) {
- break;
- }
- }
- }
- }
</del><ins>+ if ( value === false ) {
+ break;
+ }
+ }
+ }
+ }
</ins><span class="cx">
</span><del>- return obj;
</del><ins>+ return obj;
</ins><span class="cx"> },
</span><span class="cx">
</span><del>- // Use native String.trim function wherever possible
- trim: core_trim && !core_trim.call("\uFEFF\xA0") ?
- function( text ) {
- return text == null ?
- "" :
- core_trim.call( text );
- } :
-
- // Otherwise use our own trimming functionality
- function( text ) {
- return text == null ?
- "" :
- ( text + "" ).replace( rtrim, "" );
</del><ins>+ trim: function( text ) {
+ return text == null ? "" : trim.call( text );
</ins><span class="cx"> },
</span><span class="cx">
</span><span class="cx"> // results is for internal usage only
</span><span class="cx"> makeArray: function( arr, results ) {
</span><del>- var ret = results || [];
</del><ins>+ var ret = results || [];
</ins><span class="cx">
</span><del>- if ( arr != null ) {
- if ( isArraylike( Object(arr) ) ) {
- jQuery.merge( ret,
- typeof arr === "string" ?
- [ arr ] : arr
- );
- } else {
- core_push.call( ret, arr );
- }
- }
</del><ins>+ if ( arr != null ) {
+ if ( isArraylike( Object(arr) ) ) {
+ jQuery.merge( ret,
+ typeof arr === "string" ?
+ [ arr ] : arr
+ );
+ } else {
+ push.call( ret, arr );
+ }
+ }
</ins><span class="cx">
</span><del>- return ret;
</del><ins>+ return ret;
</ins><span class="cx"> },
</span><span class="cx">
</span><span class="cx"> inArray: function( elem, arr, i ) {
</span><del>- var len;
-
- if ( arr ) {
- if ( core_indexOf ) {
- return core_indexOf.call( arr, elem, i );
- }
-
- len = arr.length;
- i = i ? i < 0 ? Math.max( 0, len + i ) : i : 0;
-
- for ( ; i < len; i++ ) {
- // Skip accessing in sparse arrays
- if ( i in arr && arr[ i ] === elem ) {
- return i;
- }
- }
- }
-
- return -1;
</del><ins>+ return arr == null ? -1 : indexOf.call( arr, elem, i );
</ins><span class="cx"> },
</span><span class="cx">
</span><span class="cx"> merge: function( first, second ) {
</span><del>- var l = second.length,
- i = first.length,
- j = 0;
</del><ins>+ var len = +second.length,
+ j = 0,
+ i = first.length;
</ins><span class="cx">
</span><del>- if ( typeof l === "number" ) {
- for ( ; j < l; j++ ) {
- first[ i++ ] = second[ j ];
- }
- } else {
- while ( second[j] !== undefined ) {
- first[ i++ ] = second[ j++ ];
- }
- }
</del><ins>+ for ( ; j < len; j++ ) {
+ first[ i++ ] = second[ j ];
+ }
</ins><span class="cx">
</span><del>- first.length = i;
</del><ins>+ first.length = i;
</ins><span class="cx">
</span><del>- return first;
</del><ins>+ return first;
</ins><span class="cx"> },
</span><span class="cx">
</span><del>- grep: function( elems, callback, inv ) {
- var retVal,
- ret = [],
- i = 0,
- length = elems.length;
- inv = !!inv;
</del><ins>+ grep: function( elems, callback, invert ) {
+ var callbackInverse,
+ matches = [],
+ i = 0,
+ length = elems.length,
+ callbackExpect = !invert;
</ins><span class="cx">
</span><del>- // Go through the array, only saving the items
- // that pass the validator function
- for ( ; i < length; i++ ) {
- retVal = !!callback( elems[ i ], i );
- if ( inv !== retVal ) {
- ret.push( elems[ i ] );
- }
- }
</del><ins>+ // Go through the array, only saving the items
+ // that pass the validator function
+ for ( ; i < length; i++ ) {
+ callbackInverse = !callback( elems[ i ], i );
+ if ( callbackInverse !== callbackExpect ) {
+ matches.push( elems[ i ] );
+ }
+ }
</ins><span class="cx">
</span><del>- return ret;
</del><ins>+ return matches;
</ins><span class="cx"> },
</span><span class="cx">
</span><span class="cx"> // arg is for internal usage only
</span><span class="cx"> map: function( elems, callback, arg ) {
</span><del>- var value,
- i = 0,
- length = elems.length,
- isArray = isArraylike( elems ),
- ret = [];
</del><ins>+ var value,
+ i = 0,
+ length = elems.length,
+ isArray = isArraylike( elems ),
+ ret = [];
</ins><span class="cx">
</span><del>- // Go through the array, translating each of the items to their
- if ( isArray ) {
- for ( ; i < length; i++ ) {
- value = callback( elems[ i ], i, arg );
</del><ins>+ // Go through the array, translating each of the items to their new values
+ if ( isArray ) {
+ for ( ; i < length; i++ ) {
+ value = callback( elems[ i ], i, arg );
</ins><span class="cx">
</span><del>- if ( value != null ) {
- ret[ ret.length ] = value;
- }
- }
</del><ins>+ if ( value != null ) {
+ ret.push( value );
+ }
+ }
</ins><span class="cx">
</span><del>- // Go through every key on the object,
- } else {
- for ( i in elems ) {
- value = callback( elems[ i ], i, arg );
</del><ins>+ // Go through every key on the object,
+ } else {
+ for ( i in elems ) {
+ value = callback( elems[ i ], i, arg );
</ins><span class="cx">
</span><del>- if ( value != null ) {
- ret[ ret.length ] = value;
- }
- }
- }
</del><ins>+ if ( value != null ) {
+ ret.push( value );
+ }
+ }
+ }
</ins><span class="cx">
</span><del>- // Flatten any nested arrays
- return core_concat.apply( [], ret );
</del><ins>+ // Flatten any nested arrays
+ return concat.apply( [], ret );
</ins><span class="cx"> },
</span><span class="cx">
</span><span class="cx"> // A global GUID counter for objects
</span><span class="lines">@@ -798,6634 +496,6975 @@
</span><span class="cx"> // Bind a function to a context, optionally partially applying any
</span><span class="cx"> // arguments.
</span><span class="cx"> proxy: function( fn, context ) {
</span><del>- var args, proxy, tmp;
</del><ins>+ var tmp, args, proxy;
</ins><span class="cx">
</span><del>- if ( typeof context === "string" ) {
- tmp = fn[ context ];
- context = fn;
- fn = tmp;
- }
</del><ins>+ if ( typeof context === "string" ) {
+ tmp = fn[ context ];
+ context = fn;
+ fn = tmp;
+ }
</ins><span class="cx">
</span><del>- // Quick check to determine if target is callable, in the spec
- // this throws a TypeError, but we will just return undefined.
- if ( !jQuery.isFunction( fn ) ) {
- return undefined;
- }
</del><ins>+ // Quick check to determine if target is callable, in the spec
+ // this throws a TypeError, but we will just return undefined.
+ if ( !jQuery.isFunction( fn ) ) {
+ return undefined;
+ }
</ins><span class="cx">
</span><del>- // Simulated bind
- args = core_slice.call( arguments, 2 );
- proxy = function() {
- return fn.apply( context || this, args.concat( core_slice.call( arguments ) ) );
- };
</del><ins>+ // Simulated bind
+ args = slice.call( arguments, 2 );
+ proxy = function() {
+ return fn.apply( context || this, args.concat( slice.call( arguments ) ) );
+ };
</ins><span class="cx">
</span><del>- // Set the guid of unique handler to the same of original handler, so it can be removed
- proxy.guid = fn.guid = fn.guid || jQuery.guid++;
</del><ins>+ // Set the guid of unique handler to the same of original handler, so it can be removed
+ proxy.guid = fn.guid = fn.guid || jQuery.guid++;
</ins><span class="cx">
</span><del>- return proxy;
</del><ins>+ return proxy;
</ins><span class="cx"> },
</span><span class="cx">
</span><del>- // Multifunctional method to get and set values of a collection
- // The value/s can optionally be executed if it's a function
- access: function( elems, fn, key, value, chainable, emptyGet, raw ) {
- var i = 0,
- length = elems.length,
- bulk = key == null;
</del><ins>+ now: Date.now,
</ins><span class="cx">
</span><del>- // Sets many values
- if ( jQuery.type( key ) === "object" ) {
- chainable = true;
- for ( i in key ) {
- jQuery.access( elems, fn, i, key[i], true, emptyGet, raw );
- }
</del><ins>+ // jQuery.support is not used in Core but other projects attach their
+ // properties to it so it needs to exist.
+ support: support
+});
</ins><span class="cx">
</span><del>- // Sets one value
- } else if ( value !== undefined ) {
- chainable = true;
</del><ins>+// Populate the class2type map
+jQuery.each("Boolean Number String Function Array Date RegExp Object Error".split(" "), function(i, name) {
+ class2type[ "[object " + name + "]" ] = name.toLowerCase();
+});
</ins><span class="cx">
</span><del>- if ( !jQuery.isFunction( value ) ) {
- raw = true;
- }
</del><ins>+function isArraylike( obj ) {
+ var length = obj.length,
+ type = jQuery.type( obj );
</ins><span class="cx">
</span><del>- if ( bulk ) {
- // Bulk operations run against the entire set
- if ( raw ) {
- fn.call( elems, value );
- fn = null;
-
- // ...except when executing function values
- } else {
- bulk = fn;
- fn = function( elem, key, value ) {
- return bulk.call( jQuery( elem ), value );
- };
</del><ins>+ if ( type === "function" || jQuery.isWindow( obj ) ) {
+ return false;
</ins><span class="cx"> }
</span><del>- }
</del><span class="cx">
</span><del>- if ( fn ) {
- for ( ; i < length; i++ ) {
- fn( elems[i], key, raw ? value : value.call( elems[i], i, fn( elems[i], key ) ) );
</del><ins>+ if ( obj.nodeType === 1 && length ) {
+ return true;
</ins><span class="cx"> }
</span><del>- }
- }
</del><span class="cx">
</span><del>- return chainable ?
- elems :
</del><ins>+ return type === "array" || length === 0 ||
+ typeof length === "number" && length > 0 && ( length - 1 ) in obj;
+}
+var Sizzle =
+/*!
+ * Sizzle CSS Selector Engine v1.10.16
+ * http://sizzlejs.com/
+ *
+ * Copyright 2013 jQuery Foundation, Inc. and other contributors
+ * Released under the MIT license
+ * http://jquery.org/license
+ *
+ * Date: 2014-01-13
+ */
+(function( window ) {
</ins><span class="cx">
</span><del>- // Gets
- bulk ?
- fn.call( elems ) :
- length ? fn( elems[0], key ) : emptyGet;
</del><ins>+var i,
+ support,
+ Expr,
+ getText,
+ isXML,
+ compile,
+ outermostContext,
+ sortInput,
+ hasDuplicate,
+
+ // Local document vars
+ setDocument,
+ document,
+ docElem,
+ documentIsHTML,
+ rbuggyQSA,
+ rbuggyMatches,
+ matches,
+ contains,
+
+ // Instance-specific data
+ expando = "sizzle" + -(new Date()),
+ preferredDoc = window.document,
+ dirruns = 0,
+ done = 0,
+ classCache = createCache(),
+ tokenCache = createCache(),
+ compilerCache = createCache(),
+ sortOrder = function( a, b ) {
+ if ( a === b ) {
+ hasDuplicate = true;
+ }
+ return 0;
</ins><span class="cx"> },
</span><span class="cx">
</span><del>- now: function() {
- return ( new Date() ).getTime();
- }
-});
</del><ins>+ // General-purpose constants
+ strundefined = typeof undefined,
+ MAX_NEGATIVE = 1 << 31,
</ins><span class="cx">
</span><del>-jQuery.ready.promise = function( obj ) {
- if ( !readyList ) {
</del><ins>+ // Instance methods
+ hasOwn = ({}).hasOwnProperty,
+ arr = [],
+ pop = arr.pop,
+ push_native = arr.push,
+ push = arr.push,
+ slice = arr.slice,
+ // Use a stripped-down indexOf if we can't use a native one
+ indexOf = arr.indexOf || function( elem ) {
+ var i = 0,
+ len = this.length;
+ for ( ; i < len; i++ ) {
+ if ( this[i] === elem ) {
+ return i;
+ }
+ }
+ return -1;
+ },
</ins><span class="cx">
</span><del>- readyList = jQuery.Deferred();
</del><ins>+ booleans = "checked|selected|async|autofocus|autoplay|controls|defer|disabled|hidden|ismap|loop|multiple|open|readonly|required|scoped",
</ins><span class="cx">
</span><del>- // Catch cases where $(document).ready() is called after the browser event has already occurred.
- // we once tried to use readyState "interactive" here, but it caused issues like the one
- // discovered by ChrisS here: http://bugs.jquery.com/ticket/12282#comment:15
- if ( document.readyState === "complete" ) {
- // Handle it asynchronously to allow scripts the opportunity to delay ready
- setTimeout( jQuery.ready );
</del><ins>+ // Regular expressions
</ins><span class="cx">
</span><del>- // Standards-based browsers support DOMContentLoaded
- } else if ( document.addEventListener ) {
- // Use the handy event callback
- document.addEventListener( "DOMContentLoaded", completed, false );
</del><ins>+ // Whitespace characters http://www.w3.org/TR/css3-selectors/#whitespace
+ whitespace = "[\\x20\\t\\r\\n\\f]",
+ // http://www.w3.org/TR/css3-syntax/#characters
+ characterEncoding = "(?:\\\\.|[\\w-]|[^\\x00-\\xa0])+",
</ins><span class="cx">
</span><del>- // A fallback to window.onload, that will always work
- window.addEventListener( "load", completed, false );
</del><ins>+ // Loosely modeled on CSS identifier characters
+ // An unquoted value should be a CSS identifier http://www.w3.org/TR/css3-selectors/#attribute-selectors
+ // Proper syntax: http://www.w3.org/TR/CSS21/syndata.html#value-def-identifier
+ identifier = characterEncoding.replace( "w", "w#" ),
</ins><span class="cx">
</span><del>- // If IE event model is used
- } else {
- // Ensure firing before onload, maybe late but safe also for iframes
- document.attachEvent( "onreadystatechange", completed );
</del><ins>+ // Acceptable operators http://www.w3.org/TR/selectors/#attribute-selectors
+ attributes = "\\[" + whitespace + "*(" + characterEncoding + ")" + whitespace +
+ "*(?:([*^$|!~]?=)" + whitespace + "*(?:(['\"])((?:\\\\.|[^\\\\])*?)\\3|(" + identifier + ")|)|)" + whitespace + "*\\]",
</ins><span class="cx">
</span><del>- // A fallback to window.onload, that will always work
- window.attachEvent( "onload", completed );
</del><ins>+ // Prefer arguments quoted,
+ // then not containing pseudos/brackets,
+ // then attribute selectors/non-parenthetical expressions,
+ // then anything else
+ // These preferences are here to reduce the number of selectors
+ // needing tokenize in the PSEUDO preFilter
+ pseudos = ":(" + characterEncoding + ")(?:\\(((['\"])((?:\\\\.|[^\\\\])*?)\\3|((?:\\\\.|[^\\\\()[\\]]|" + attributes.replace( 3, 8 ) + ")*)|.*)\\)|)",
</ins><span class="cx">
</span><del>- // If IE and not a frame
- // continually check to see if the document is ready
- var top = false;
</del><ins>+ // Leading and non-escaped trailing whitespace, capturing some non-whitespace characters preceding the latter
+ rtrim = new RegExp( "^" + whitespace + "+|((?:^|[^\\\\])(?:\\\\.)*)" + whitespace + "+$", "g" ),
</ins><span class="cx">
</span><del>- try {
- top = window.frameElement == null && document.documentElement;
- } catch(e) {}
</del><ins>+ rcomma = new RegExp( "^" + whitespace + "*," + whitespace + "*" ),
+ rcombinators = new RegExp( "^" + whitespace + "*([>+~]|" + whitespace + ")" + whitespace + "*" ),
</ins><span class="cx">
</span><del>- if ( top && top.doScroll ) {
- (function doScrollCheck() {
- if ( !jQuery.isReady ) {
</del><ins>+ rattributeQuotes = new RegExp( "=" + whitespace + "*([^\\]'\"]*?)" + whitespace + "*\\]", "g" ),
</ins><span class="cx">
</span><del>- try {
- // Use the trick by Diego Perini
- // http://javascript.nwbox.com/IEContentLoaded/
- top.doScroll("left");
- } catch(e) {
- return setTimeout( doScrollCheck, 50 );
- }
</del><ins>+ rpseudo = new RegExp( pseudos ),
+ ridentifier = new RegExp( "^" + identifier + "$" ),
</ins><span class="cx">
</span><del>- // detach all dom ready events
- detach();
</del><ins>+ matchExpr = {
+ "ID": new RegExp( "^#(" + characterEncoding + ")" ),
+ "CLASS": new RegExp( "^\\.(" + characterEncoding + ")" ),
+ "TAG": new RegExp( "^(" + characterEncoding.replace( "w", "w*" ) + ")" ),
+ "ATTR": new RegExp( "^" + attributes ),
+ "PSEUDO": new RegExp( "^" + pseudos ),
+ "CHILD": new RegExp( "^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\(" + whitespace +
+ "*(even|odd|(([+-]|)(\\d*)n|)" + whitespace + "*(?:([+-]|)" + whitespace +
+ "*(\\d+)|))" + whitespace + "*\\)|)", "i" ),
+ "bool": new RegExp( "^(?:" + booleans + ")$", "i" ),
+ // For use in libraries implementing .is()
+ // We use this for POS matching in `select`
+ "needsContext": new RegExp( "^" + whitespace + "*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\(" +
+ whitespace + "*((?:-\\d)?\\d*)" + whitespace + "*\\)|)(?=[^-]|$)", "i" )
+ },
</ins><span class="cx">
</span><del>- // and execute any waiting functions
- jQuery.ready();
- }
- })();
- }
- }
- }
- return readyList.promise( obj );
-};
</del><ins>+ rinputs = /^(?:input|select|textarea|button)$/i,
+ rheader = /^h\d$/i,
</ins><span class="cx">
</span><del>-// Populate the class2type map
-jQuery.each("Boolean Number String Function Array Date RegExp Object Error".split(" "), function(i, name) {
- class2type[ "[object " + name + "]" ] = name.toLowerCase();
-});
</del><ins>+ rnative = /^[^{]+\{\s*\[native \w/,
</ins><span class="cx">
</span><del>-function isArraylike( obj ) {
- var length = obj.length,
- type = jQuery.type( obj );
</del><ins>+ // Easily-parseable/retrievable ID or TAG or CLASS selectors
+ rquickExpr = /^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/,
</ins><span class="cx">
</span><del>- if ( jQuery.isWindow( obj ) ) {
- return false;
- }
</del><ins>+ rsibling = /[+~]/,
+ rescape = /'|\\/g,
</ins><span class="cx">
</span><del>- if ( obj.nodeType === 1 && length ) {
- return true;
- }
</del><ins>+ // CSS escapes http://www.w3.org/TR/CSS21/syndata.html#escaped-characters
+ runescape = new RegExp( "\\\\([\\da-f]{1,6}" + whitespace + "?|(" + whitespace + ")|.)", "ig" ),
+ funescape = function( _, escaped, escapedWhitespace ) {
+ var high = "0x" + escaped - 0x10000;
+ // NaN means non-codepoint
+ // Support: Firefox
+ // Workaround erroneous numeric interpretation of +"0x"
+ return high !== high || escapedWhitespace ?
+ escaped :
+ high < 0 ?
+ // BMP codepoint
+ String.fromCharCode( high + 0x10000 ) :
+ // Supplemental Plane codepoint (surrogate pair)
+ String.fromCharCode( high >> 10 | 0xD800, high & 0x3FF | 0xDC00 );
+ };
</ins><span class="cx">
</span><del>- return type === "array" || type !== "function" &&
- ( length === 0 ||
- typeof length === "number" && length > 0 && ( length - 1 ) in obj );
-}
</del><ins>+// Optimize for push.apply( _, NodeList )
+try {
+ push.apply(
+ (arr = slice.call( preferredDoc.childNodes )),
+ preferredDoc.childNodes
+ );
+ // Support: Android<4.0
+ // Detect silently failing push.apply
+ arr[ preferredDoc.childNodes.length ].nodeType;
+} catch ( e ) {
+ push = { apply: arr.length ?
</ins><span class="cx">
</span><del>-// All jQuery objects should point back to these
-rootjQuery = jQuery(document);
-// String to Object options format cache
-var optionsCache = {};
</del><ins>+ // Leverage slice if possible
+ function( target, els ) {
+ push_native.apply( target, slice.call(els) );
+ } :
</ins><span class="cx">
</span><del>-// Convert String-formatted options into Object-formatted ones and store in cache
-function createOptions( options ) {
- var object = optionsCache[ options ] = {};
- jQuery.each( options.match( core_rnotwhite ) || [], function( _, flag ) {
- object[ flag ] = true;
- });
- return object;
</del><ins>+ // Support: IE<9
+ // Otherwise append directly
+ function( target, els ) {
+ var j = target.length,
+ i = 0;
+ // Can't trust NodeList.length
+ while ( (target[j++] = els[i++]) ) {}
+ target.length = j - 1;
+ }
+ };
</ins><span class="cx"> }
</span><span class="cx">
</span><del>-/*
- * Create a callback list using the following parameters:
- *
- * options: an optional list of space-separated options that will change how
- * the callback list behaves or a more traditional option object
- *
- * By default a callback list will act like an event callback list and can be
- * "fired" multiple times.
- *
- * Possible options:
- *
- * once: will ensure the callback list can only be fired once (like a Deferred)
- *
- * memory: will keep track of previous values and will call any callback added
- * after the list has been fired right away with the latest "memorized"
- * values (like a Deferred)
- *
- * unique: will ensure a callback can only be added once (no duplicate in the list)
- *
- * stopOnFalse: interrupt callings when a callback returns false
- *
- */
-jQuery.Callbacks = function( options ) {
</del><ins>+function Sizzle( selector, context, results, seed ) {
+ var match, elem, m, nodeType,
+ // QSA vars
+ i, groups, old, nid, newContext, newSelector;
</ins><span class="cx">
</span><del>- // Convert options from String-formatted to Object-formatted if needed
- // (we check in cache first)
- options = typeof options === "string" ?
- ( optionsCache[ options ] || createOptions( options ) ) :
- jQuery.extend( {}, options );
-
- var // Flag to know if list is currently firing
- firing,
- // Last fire value (for non-forgettable lists)
- memory,
- // Flag to know if list was already fired
- fired,
- // End of the loop when firing
- firingLength,
- // Index of currently firing callback (modified by remove if needed)
- firingIndex,
- // First callback to fire (used internally by add and fireWith)
- firingStart,
- // Actual callback list
- list = [],
- // Stack of fire calls for repeatable lists
- stack = !options.once && [],
- // Fire callbacks
- fire = function( data ) {
- memory = options.memory && data;
- fired = true;
- firingIndex = firingStart || 0;
- firingStart = 0;
- firingLength = list.length;
- firing = true;
- for ( ; list && firingIndex < firingLength; firingIndex++ ) {
- if ( list[ firingIndex ].apply( data[ 0 ], data[ 1 ] ) === false && options.stopOnFalse ) {
- memory = false; // To prevent further calls using add
- break;
</del><ins>+ if ( ( context ? context.ownerDocument || context : preferredDoc ) !== document ) {
+ setDocument( context );
</ins><span class="cx"> }
</span><del>- }
- firing = false;
- if ( list ) {
- if ( stack ) {
- if ( stack.length ) {
- fire( stack.shift() );
- }
- } else if ( memory ) {
- list = [];
- } else {
- self.disable();
- }
- }
- },
- // Actual Callbacks object
- self = {
- // Add a callback or a collection of callbacks to the list
- add: function() {
- if ( list ) {
- // First, we save the current length
- var start = list.length;
- (function add( args ) {
- jQuery.each( args, function( _, arg ) {
- var type = jQuery.type( arg );
- if ( type === "function" ) {
- if ( !options.unique || !self.has( arg ) ) {
- list.push( arg );
- }
- } else if ( arg && arg.length && type !== "string" ) {
- // Inspect recursively
- add( arg );
- }
- });
- })( arguments );
- // Do we need to add the callbacks to the
- // current firing batch?
- if ( firing ) {
- firingLength = list.length;
- // With memory, if we're not firing then
- // we should call right away
- } else if ( memory ) {
- firingStart = start;
- fire( memory );
- }
- }
- return this;
- },
- // Remove a callback from the list
- remove: function() {
- if ( list ) {
- jQuery.each( arguments, function( _, arg ) {
- var index;
- while( ( index = jQuery.inArray( arg, list, index ) ) > -1 ) {
- list.splice( index, 1 );
- // Handle firing indexes
- if ( firing ) {
- if ( index <= firingLength ) {
- firingLength--;
- }
- if ( index <= firingIndex ) {
- firingIndex--;
- }
- }
- }
- });
- }
- return this;
- },
- // Check if a given callback is in the list.
- // If no argument is given, return whether or not list has callbacks attached.
- has: function( fn ) {
- return fn ? jQuery.inArray( fn, list ) > -1 : !!( list && list.length );
- },
- // Remove all callbacks from the list
- empty: function() {
- list = [];
- return this;
- },
- // Have the list do nothing anymore
- disable: function() {
- list = stack = memory = undefined;
- return this;
- },
- // Is it disabled?
- disabled: function() {
- return !list;
- },
- // Lock the list in its current state
- lock: function() {
- stack = undefined;
- if ( !memory ) {
- self.disable();
- }
- return this;
- },
- // Is it locked?
- locked: function() {
- return !stack;
- },
- // Call all callbacks with the given context and arguments
- fireWith: function( context, args ) {
- args = args || [];
- args = [ context, args.slice ? args.slice() : args ];
- if ( list && ( !fired || stack ) ) {
- if ( firing ) {
- stack.push( args );
- } else {
- fire( args );
- }
- }
- return this;
- },
- // Call all the callbacks with the given arguments
- fire: function() {
- self.fireWith( this, arguments );
- return this;
- },
- // To know if the callbacks have already been called at least once
- fired: function() {
- return !!fired;
- }
- };
</del><span class="cx">
</span><del>- return self;
-};
-jQuery.extend({
</del><ins>+ context = context || document;
+ results = results || [];
</ins><span class="cx">
</span><del>- Deferred: function( func ) {
- var tuples = [
- // action, add listener, listener list, final state
- [ "resolve", "done", jQuery.Callbacks("once memory"), "resolved" ],
- [ "reject", "fail", jQuery.Callbacks("once memory"), "rejected" ],
- [ "notify", "progress", jQuery.Callbacks("memory") ]
- ],
- state = "pending",
- promise = {
- state: function() {
- return state;
- },
- always: function() {
- deferred.done( arguments ).fail( arguments );
- return this;
- },
- then: function( /* fnDone, fnFail, fnProgress */ ) {
- var fns = arguments;
- return jQuery.Deferred(function( newDefer ) {
- jQuery.each( tuples, function( i, tuple ) {
- var action = tuple[ 0 ],
- fn = jQuery.isFunction( fns[ i ] ) && fns[ i ];
- // deferred[ done | fail | progress ] for forwarding actions to newDefer
- deferred[ tuple[1] ](function() {
- var returned = fn && fn.apply( this, arguments );
- if ( returned && jQuery.isFunction( returned.promise ) ) {
- returned.promise()
- .done( newDefer.resolve )
- .fail( newDefer.reject )
- .progress( newDefer.notify );
- } else {
- newDefer[ action + "With" ]( this === promise ? newDefer.promise() : this, fn ? [ returned ] : arguments );
</del><ins>+ if ( !selector || typeof selector !== "string" ) {
+ return results;
</ins><span class="cx"> }
</span><del>- });
- });
- fns = null;
- }).promise();
- },
- // Get a promise for this deferred
- // If obj is provided, the promise aspect is added to the object
- promise: function( obj ) {
- return obj != null ? jQuery.extend( obj, promise ) : promise;
</del><ins>+
+ if ( (nodeType = context.nodeType) !== 1 && nodeType !== 9 ) {
+ return [];
</ins><span class="cx"> }
</span><del>- },
- deferred = {};
</del><span class="cx">
</span><del>- // Keep pipe for back-compat
- promise.pipe = promise.then;
</del><ins>+ if ( documentIsHTML && !seed ) {
</ins><span class="cx">
</span><del>- // Add list-specific methods
- jQuery.each( tuples, function( i, tuple ) {
- var list = tuple[ 2 ],
- stateString = tuple[ 3 ];
</del><ins>+ // Shortcuts
+ if ( (match = rquickExpr.exec( selector )) ) {
+ // Speed-up: Sizzle("#ID")
+ if ( (m = match[1]) ) {
+ if ( nodeType === 9 ) {
+ elem = context.getElementById( m );
+ // Check parentNode to catch when Blackberry 4.6 returns
+ // nodes that are no longer in the document (jQuery #6963)
+ if ( elem && elem.parentNode ) {
+ // Handle the case where IE, Opera, and Webkit return items
+ // by name instead of ID
+ if ( elem.id === m ) {
+ results.push( elem );
+ return results;
+ }
+ } else {
+ return results;
+ }
+ } else {
+ // Context is not a document
+ if ( context.ownerDocument && (elem = context.ownerDocument.getElementById( m )) &&
+ contains( context, elem ) && elem.id === m ) {
+ results.push( elem );
+ return results;
+ }
+ }
</ins><span class="cx">
</span><del>- // promise[ done | fail | progress ] = list.add
- promise[ tuple[1] ] = list.add;
</del><ins>+ // Speed-up: Sizzle("TAG")
+ } else if ( match[2] ) {
+ push.apply( results, context.getElementsByTagName( selector ) );
+ return results;
</ins><span class="cx">
</span><del>- // Handle state
- if ( stateString ) {
- list.add(function() {
- // state = [ resolved | rejected ]
- state = stateString;
</del><ins>+ // Speed-up: Sizzle(".CLASS")
+ } else if ( (m = match[3]) && support.getElementsByClassName && context.getElementsByClassName ) {
+ push.apply( results, context.getElementsByClassName( m ) );
+ return results;
+ }
+ }
</ins><span class="cx">
</span><del>- // [ reject_list | resolve_list ].disable; progress_list.lock
- }, tuples[ i ^ 1 ][ 2 ].disable, tuples[ 2 ][ 2 ].lock );
- }
</del><ins>+ // QSA path
+ if ( support.qsa && (!rbuggyQSA || !rbuggyQSA.test( selector )) ) {
+ nid = old = expando;
+ newContext = context;
+ newSelector = nodeType === 9 && selector;
</ins><span class="cx">
</span><del>- // deferred[ resolve | reject | notify ]
- deferred[ tuple[0] ] = function() {
- deferred[ tuple[0] + "With" ]( this === deferred ? promise : this, arguments );
- return this;
- };
- deferred[ tuple[0] + "With" ] = list.fireWith;
- });
</del><ins>+ // qSA works strangely on Element-rooted queries
+ // We can work around this by specifying an extra ID on the root
+ // and working up from there (Thanks to Andrew Dupont for the technique)
+ // IE 8 doesn't work on object elements
+ if ( nodeType === 1 && context.nodeName.toLowerCase() !== "object" ) {
+ groups = tokenize( selector );
</ins><span class="cx">
</span><del>- // Make the deferred a promise
- promise.promise( deferred );
</del><ins>+ if ( (old = context.getAttribute("id")) ) {
+ nid = old.replace( rescape, "\\$&" );
+ } else {
+ context.setAttribute( "id", nid );
+ }
+ nid = "[id='" + nid + "'] ";
</ins><span class="cx">
</span><del>- // Call given func if any
- if ( func ) {
- func.call( deferred, deferred );
</del><ins>+ i = groups.length;
+ while ( i-- ) {
+ groups[i] = nid + toSelector( groups[i] );
+ }
+ newContext = rsibling.test( selector ) && testContext( context.parentNode ) || context;
+ newSelector = groups.join(",");
+ }
+
+ if ( newSelector ) {
+ try {
+ push.apply( results,
+ newContext.querySelectorAll( newSelector )
+ );
+ return results;
+ } catch(qsaError) {
+ } finally {
+ if ( !old ) {
+ context.removeAttribute("id");
+ }
+ }
+ }
+ }
</ins><span class="cx"> }
</span><span class="cx">
</span><del>- // All done!
- return deferred;
- },
</del><ins>+ // All others
+ return select( selector.replace( rtrim, "$1" ), context, results, seed );
+}
</ins><span class="cx">
</span><del>- // Deferred helper
- when: function( subordinate /* , ..., subordinateN */ ) {
- var i = 0,
- resolveValues = core_slice.call( arguments ),
- length = resolveValues.length,
</del><ins>+/**
+ * Create key-value caches of limited size
+ * @returns {Function(string, Object)} Returns the Object data after storing it on itself with
+ * property name the (space-suffixed) string and (if the cache is larger than Expr.cacheLength)
+ * deleting the oldest entry
+ */
+function createCache() {
+ var keys = [];
</ins><span class="cx">
</span><del>- // the count of uncompleted subordinates
- remaining = length !== 1 || ( subordinate && jQuery.isFunction( subordinate.promise ) ) ? length : 0,
</del><ins>+ function cache( key, value ) {
+ // Use (key + " ") to avoid collision with native prototype properties (see Issue #157)
+ if ( keys.push( key + " " ) > Expr.cacheLength ) {
+ // Only keep the most recent entries
+ delete cache[ keys.shift() ];
+ }
+ return (cache[ key + " " ] = value);
+ }
+ return cache;
+}
</ins><span class="cx">
</span><del>- // the master Deferred. If resolveValues consist of only a single Deferred, just use that.
- deferred = remaining === 1 ? subordinate : jQuery.Deferred(),
</del><ins>+/**
+ * Mark a function for special use by Sizzle
+ * @param {Function} fn The function to mark
+ */
+function markFunction( fn ) {
+ fn[ expando ] = true;
+ return fn;
+}
</ins><span class="cx">
</span><del>- // Update function for both resolve and progress values
- updateFunc = function( i, contexts, values ) {
- return function( value ) {
- contexts[ i ] = this;
- values[ i ] = arguments.length > 1 ? core_slice.call( arguments ) : value;
- if( values === progressValues ) {
- deferred.notifyWith( contexts, values );
- } else if ( !( --remaining ) ) {
- deferred.resolveWith( contexts, values );
</del><ins>+/**
+ * Support testing using an element
+ * @param {Function} fn Passed the created div and expects a boolean result
+ */
+function assert( fn ) {
+ var div = document.createElement("div");
+
+ try {
+ return !!fn( div );
+ } catch (e) {
+ return false;
+ } finally {
+ // Remove from its parent by default
+ if ( div.parentNode ) {
+ div.parentNode.removeChild( div );
+ }
+ // release memory in IE
+ div = null;
</ins><span class="cx"> }
</span><del>- };
- },
</del><ins>+}
</ins><span class="cx">
</span><del>- progressValues, progressContexts, resolveContexts;
</del><ins>+/**
+ * Adds the same handler for all of the specified attrs
+ * @param {String} attrs Pipe-separated list of attributes
+ * @param {Function} handler The method that will be applied
+ */
+function addHandle( attrs, handler ) {
+ var arr = attrs.split("|"),
+ i = attrs.length;
</ins><span class="cx">
</span><del>- // add listeners to Deferred subordinates; treat others as resolved
- if ( length > 1 ) {
- progressValues = new Array( length );
- progressContexts = new Array( length );
- resolveContexts = new Array( length );
- for ( ; i < length; i++ ) {
- if ( resolveValues[ i ] && jQuery.isFunction( resolveValues[ i ].promise ) ) {
- resolveValues[ i ].promise()
- .done( updateFunc( i, resolveContexts, resolveValues ) )
- .fail( deferred.reject )
- .progress( updateFunc( i, progressContexts, progressValues ) );
- } else {
- --remaining;
</del><ins>+ while ( i-- ) {
+ Expr.attrHandle[ arr[i] ] = handler;
</ins><span class="cx"> }
</span><del>- }
- }
</del><ins>+}
</ins><span class="cx">
</span><del>- // if we're not waiting on anything, resolve the master
- if ( !remaining ) {
- deferred.resolveWith( resolveContexts, resolveValues );
</del><ins>+/**
+ * Checks document order of two siblings
+ * @param {Element} a
+ * @param {Element} b
+ * @returns {Number} Returns less than 0 if a precedes b, greater than 0 if a follows b
+ */
+function siblingCheck( a, b ) {
+ var cur = b && a,
+ diff = cur && a.nodeType === 1 && b.nodeType === 1 &&
+ ( ~b.sourceIndex || MAX_NEGATIVE ) -
+ ( ~a.sourceIndex || MAX_NEGATIVE );
+
+ // Use IE sourceIndex if available on both nodes
+ if ( diff ) {
+ return diff;
</ins><span class="cx"> }
</span><span class="cx">
</span><del>- return deferred.promise();
</del><ins>+ // Check if b follows a
+ if ( cur ) {
+ while ( (cur = cur.nextSibling) ) {
+ if ( cur === b ) {
+ return -1;
+ }
+ }
</ins><span class="cx"> }
</span><del>-});
-jQuery.support = (function() {
</del><span class="cx">
</span><del>- var support, all, a,
- input, select, fragment,
- opt, eventName, isSupported, i,
- div = document.createElement("div");
</del><ins>+ return a ? 1 : -1;
+}
</ins><span class="cx">
</span><del>- // Setup
- div.setAttribute( "className", "t" );
- div.innerHTML = " <link/><table></table><a href='/a'>a</a><input type='checkbox'/>";
</del><ins>+/**
+ * Returns a function to use in pseudos for input types
+ * @param {String} type
+ */
+function createInputPseudo( type ) {
+ return function( elem ) {
+ var name = elem.nodeName.toLowerCase();
+ return name === "input" && elem.type === type;
+ };
+}
</ins><span class="cx">
</span><del>- // Support tests won't run in some limited or non-browser environments
- all = div.getElementsByTagName("*");
- a = div.getElementsByTagName("a")[ 0 ];
- if ( !all || !a || !all.length ) {
- return {};
- }
</del><ins>+/**
+ * Returns a function to use in pseudos for buttons
+ * @param {String} type
+ */
+function createButtonPseudo( type ) {
+ return function( elem ) {
+ var name = elem.nodeName.toLowerCase();
+ return (name === "input" || name === "button") && elem.type === type;
+ };
+}
</ins><span class="cx">
</span><del>- // First batch of tests
- select = document.createElement("select");
- opt = select.appendChild( document.createElement("option") );
- input = div.getElementsByTagName("input")[ 0 ];
</del><ins>+/**
+ * Returns a function to use in pseudos for positionals
+ * @param {Function} fn
+ */
+function createPositionalPseudo( fn ) {
+ return markFunction(function( argument ) {
+ argument = +argument;
+ return markFunction(function( seed, matches ) {
+ var j,
+ matchIndexes = fn( [], seed.length, argument ),
+ i = matchIndexes.length;
</ins><span class="cx">
</span><del>- a.style.cssText = "top:1px;float:left;opacity:.5";
- support = {
- // Test setAttribute on camelCase class. If it works, we need attrFixes when doing get/setAttribute (ie6/7)
- getSetAttribute: div.className !== "t",
</del><ins>+ // Match elements found at the specified indexes
+ while ( i-- ) {
+ if ( seed[ (j = matchIndexes[i]) ] ) {
+ seed[j] = !(matches[j] = seed[j]);
+ }
+ }
+ });
+ });
+}
</ins><span class="cx">
</span><del>- // IE strips leading whitespace when .innerHTML is used
- leadingWhitespace: div.firstChild.nodeType === 3,
</del><ins>+/**
+ * Checks a node for validity as a Sizzle context
+ * @param {Element|Object=} context
+ * @returns {Element|Object|Boolean} The input node if acceptable, otherwise a falsy value
+ */
+function testContext( context ) {
+ return context && typeof context.getElementsByTagName !== strundefined && context;
+}
</ins><span class="cx">
</span><del>- // Make sure that tbody elements aren't automatically inserted
- // IE will insert them into empty tables
- tbody: !div.getElementsByTagName("tbody").length,
</del><ins>+// Expose support vars for convenience
+support = Sizzle.support = {};
</ins><span class="cx">
</span><del>- // Make sure that link elements get serialized correctly by innerHTML
- // This requires a wrapper element in IE
- htmlSerialize: !!div.getElementsByTagName("link").length,
</del><ins>+/**
+ * Detects XML nodes
+ * @param {Element|Object} elem An element or a document
+ * @returns {Boolean} True iff elem is a non-HTML XML node
+ */
+isXML = Sizzle.isXML = function( elem ) {
+ // documentElement is verified for cases where it doesn't yet exist
+ // (such as loading iframes in IE - #4833)
+ var documentElement = elem && (elem.ownerDocument || elem).documentElement;
+ return documentElement ? documentElement.nodeName !== "HTML" : false;
+};
</ins><span class="cx">
</span><del>- // Get the style information from getAttribute
- // (IE uses .cssText instead)
- style: /top/.test( a.getAttribute("style") ),
</del><ins>+/**
+ * Sets document-related variables once based on the current document
+ * @param {Element|Object} [doc] An element or document object to use to set the document
+ * @returns {Object} Returns the current document
+ */
+setDocument = Sizzle.setDocument = function( node ) {
+ var hasCompare,
+ doc = node ? node.ownerDocument || node : preferredDoc,
+ parent = doc.defaultView;
</ins><span class="cx">
</span><del>- // Make sure that URLs aren't manipulated
- // (IE normalizes it by default)
- hrefNormalized: a.getAttribute("href") === "/a",
</del><ins>+ // If no document and documentElement is available, return
+ if ( doc === document || doc.nodeType !== 9 || !doc.documentElement ) {
+ return document;
+ }
</ins><span class="cx">
</span><del>- // Make sure that element opacity exists
- // (IE uses filter instead)
- // Use a regex to work around a WebKit issue. See #5145
- opacity: /^0.5/.test( a.style.opacity ),
</del><ins>+ // Set our document
+ document = doc;
+ docElem = doc.documentElement;
</ins><span class="cx">
</span><del>- // Verify style float existence
- // (IE uses styleFloat instead of cssFloat)
- cssFloat: !!a.style.cssFloat,
</del><ins>+ // Support tests
+ documentIsHTML = !isXML( doc );
</ins><span class="cx">
</span><del>- // Check the default checkbox/radio value ("" on WebKit; "on" elsewhere)
- checkOn: !!input.value,
</del><ins>+ // Support: IE>8
+ // If iframe document is assigned to "document" variable and if iframe has been reloaded,
+ // IE will throw "permission denied" error when accessing "document" variable, see jQuery #13936
+ // IE6-8 do not support the defaultView property so parent will be undefined
+ if ( parent && parent !== parent.top ) {
+ // IE11 does not have attachEvent, so all must suffer
+ if ( parent.addEventListener ) {
+ parent.addEventListener( "unload", function() {
+ setDocument();
+ }, false );
+ } else if ( parent.attachEvent ) {
+ parent.attachEvent( "onunload", function() {
+ setDocument();
+ });
+ }
+ }
</ins><span class="cx">
</span><del>- // Make sure that a selected-by-default option has a working selected property.
- // (WebKit defaults to false instead of true, IE too, if it's in an optgroup)
- optSelected: opt.selected,
</del><ins>+ /* Attributes
+ ---------------------------------------------------------------------- */
</ins><span class="cx">
</span><del>- // Tests for enctype support on a form (#6743)
- enctype: !!document.createElement("form").enctype,
</del><ins>+ // Support: IE<8
+ // Verify that getAttribute really returns attributes and not properties (excepting IE8 booleans)
+ support.attributes = assert(function( div ) {
+ div.className = "i";
+ return !div.getAttribute("className");
+ });
</ins><span class="cx">
</span><del>- // Makes sure cloning an html5 element does not cause problems
- // Where outerHTML is undefined, this still works
- html5Clone: document.createElement("nav").cloneNode( true ).outerHTML !== "<:nav></:nav>",
</del><ins>+ /* getElement(s)By*
+ ---------------------------------------------------------------------- */
</ins><span class="cx">
</span><del>- // jQuery.support.boxModel DEPRECATED in 1.8 since we don't support Quirks Mode
- boxModel: document.compatMode === "CSS1Compat",
</del><ins>+ // Check if getElementsByTagName("*") returns only elements
+ support.getElementsByTagName = assert(function( div ) {
+ div.appendChild( doc.createComment("") );
+ return !div.getElementsByTagName("*").length;
+ });
</ins><span class="cx">
</span><del>- // Will be defined later
- deleteExpando: true,
- noCloneEvent: true,
- inlineBlockNeedsLayout: false,
- shrinkWrapBlocks: false,
- reliableMarginRight: true,
- boxSizingReliable: true,
- pixelPosition: false
- };
</del><ins>+ // Check if getElementsByClassName can be trusted
+ support.getElementsByClassName = rnative.test( doc.getElementsByClassName ) && assert(function( div ) {
+ div.innerHTML = "<div class='a'></div><div class='a i'></div>";
</ins><span class="cx">
</span><del>- // Make sure checked status is properly cloned
- input.checked = true;
- support.noCloneChecked = input.cloneNode( true ).checked;
</del><ins>+ // Support: Safari<4
+ // Catch class over-caching
+ div.firstChild.className = "i";
+ // Support: Opera<10
+ // Catch gEBCN failure to find non-leading classes
+ return div.getElementsByClassName("i").length === 2;
+ });
</ins><span class="cx">
</span><del>- // Make sure that the options inside disabled selects aren't marked as disabled
- // (WebKit marks them as disabled)
- select.disabled = true;
- support.optDisabled = !opt.disabled;
</del><ins>+ // Support: IE<10
+ // Check if getElementById returns elements by name
+ // The broken getElementById methods don't pick up programatically-set names,
+ // so use a roundabout getElementsByName test
+ support.getById = assert(function( div ) {
+ docElem.appendChild( div ).id = expando;
+ return !doc.getElementsByName || !doc.getElementsByName( expando ).length;
+ });
</ins><span class="cx">
</span><del>- // Support: IE<9
- try {
- delete div.test;
- } catch( e ) {
- support.deleteExpando = false;
</del><ins>+ // ID find and filter
+ if ( support.getById ) {
+ Expr.find["ID"] = function( id, context ) {
+ if ( typeof context.getElementById !== strundefined && documentIsHTML ) {
+ var m = context.getElementById( id );
+ // Check parentNode to catch when Blackberry 4.6 returns
+ // nodes that are no longer in the document #6963
+ return m && m.parentNode ? [m] : [];
+ }
+ };
+ Expr.filter["ID"] = function( id ) {
+ var attrId = id.replace( runescape, funescape );
+ return function( elem ) {
+ return elem.getAttribute("id") === attrId;
+ };
+ };
+ } else {
+ // Support: IE6/7
+ // getElementById is not reliable as a find shortcut
+ delete Expr.find["ID"];
+
+ Expr.filter["ID"] = function( id ) {
+ var attrId = id.replace( runescape, funescape );
+ return function( elem ) {
+ var node = typeof elem.getAttributeNode !== strundefined && elem.getAttributeNode("id");
+ return node && node.value === attrId;
+ };
+ };
</ins><span class="cx"> }
</span><span class="cx">
</span><del>- // Check if we can trust getAttribute("value")
- input = document.createElement("input");
- input.setAttribute( "value", "" );
- support.input = input.getAttribute( "value" ) === "";
</del><ins>+ // Tag
+ Expr.find["TAG"] = support.getElementsByTagName ?
+ function( tag, context ) {
+ if ( typeof context.getElementsByTagName !== strundefined ) {
+ return context.getElementsByTagName( tag );
+ }
+ } :
+ function( tag, context ) {
+ var elem,
+ tmp = [],
+ i = 0,
+ results = context.getElementsByTagName( tag );
</ins><span class="cx">
</span><del>- // Check if an input maintains its value after becoming a radio
- input.value = "t";
- input.setAttribute( "type", "radio" );
- support.radioValue = input.value === "t";
</del><ins>+ // Filter out possible comments
+ if ( tag === "*" ) {
+ while ( (elem = results[i++]) ) {
+ if ( elem.nodeType === 1 ) {
+ tmp.push( elem );
+ }
+ }
</ins><span class="cx">
</span><del>- // #11217 - WebKit loses check when the name is after the checked attribute
- input.setAttribute( "checked", "t" );
- input.setAttribute( "name", "t" );
</del><ins>+ return tmp;
+ }
+ return results;
+ };
</ins><span class="cx">
</span><del>- fragment = document.createDocumentFragment();
- fragment.appendChild( input );
</del><ins>+ // Class
+ Expr.find["CLASS"] = support.getElementsByClassName && function( className, context ) {
+ if ( typeof context.getElementsByClassName !== strundefined && documentIsHTML ) {
+ return context.getElementsByClassName( className );
+ }
+ };
</ins><span class="cx">
</span><del>- // Check if a disconnected checkbox will retain its checked
- // value of true after appended to the DOM (IE6/7)
- support.appendChecked = input.checked;
</del><ins>+ /* QSA/matchesSelector
+ ---------------------------------------------------------------------- */
</ins><span class="cx">
</span><del>- // WebKit doesn't clone checked state correctly in fragments
- support.checkClone = fragment.cloneNode( true ).cloneNode( true ).lastChild.checked;
</del><ins>+ // QSA and matchesSelector support
</ins><span class="cx">
</span><del>- // Support: IE<9
- // Opera does not clone events (and typeof div.attachEvent === undefined).
- // IE9-10 clones events bound via attachEvent, but they don't trigger with .click()
- if ( div.attachEvent ) {
- div.attachEvent( "onclick", function() {
- support.noCloneEvent = false;
- });
</del><ins>+ // matchesSelector(:active) reports false when true (IE9/Opera 11.5)
+ rbuggyMatches = [];
</ins><span class="cx">
</span><del>- div.cloneNode( true ).click();
- }
</del><ins>+ // qSa(:focus) reports false when true (Chrome 21)
+ // We allow this because of a bug in IE8/9 that throws an error
+ // whenever `document.activeElement` is accessed on an iframe
+ // So, we allow :focus to pass through QSA all the time to avoid the IE error
+ // See http://bugs.jquery.com/ticket/13378
+ rbuggyQSA = [];
</ins><span class="cx">
</span><del>- // Support: IE<9 (lack submit/change bubble), Firefox 17+ (lack focusin event)
- // Beware of CSP restrictions (https://developer.mozilla.org/en/Security/CSP), test/csp.php
- for ( i in { submit: true, change: true, focusin: true }) {
- div.setAttribute( eventName = "on" + i, "t" );
</del><ins>+ if ( (support.qsa = rnative.test( doc.querySelectorAll )) ) {
+ // Build QSA regex
+ // Regex strategy adopted from Diego Perini
+ assert(function( div ) {
+ // Select is set to empty string on purpose
+ // This is to test IE's treatment of not explicitly
+ // setting a boolean content attribute,
+ // since its presence should be enough
+ // http://bugs.jquery.com/ticket/12359
+ div.innerHTML = "<select t=''><option selected=''></option></select>";
</ins><span class="cx">
</span><del>- support[ i + "Bubbles" ] = eventName in window || div.attributes[ eventName ].expando === false;
- }
</del><ins>+ // Support: IE8, Opera 10-12
+ // Nothing should be selected when empty strings follow ^= or $= or *=
+ if ( div.querySelectorAll("[t^='']").length ) {
+ rbuggyQSA.push( "[*^$]=" + whitespace + "*(?:''|\"\")" );
+ }
</ins><span class="cx">
</span><del>- div.style.backgroundClip = "content-box";
- div.cloneNode( true ).style.backgroundClip = "";
- support.clearCloneStyle = div.style.backgroundClip === "content-box";
</del><ins>+ // Support: IE8
+ // Boolean attributes and "value" are not treated correctly
+ if ( !div.querySelectorAll("[selected]").length ) {
+ rbuggyQSA.push( "\\[" + whitespace + "*(?:value|" + booleans + ")" );
+ }
</ins><span class="cx">
</span><del>- // Run tests that need a body at doc ready
- jQuery(function() {
- var container, marginDiv, tds,
- divReset = "padding:0;margin:0;border:0;display:block;box-sizing:content-box;-moz-box-sizing:content-box;-webkit-box-sizing:content-box;",
- body = document.getElementsByTagName("body")[0];
</del><ins>+ // Webkit/Opera - :checked should return selected option elements
+ // http://www.w3.org/TR/2011/REC-css3-selectors-20110929/#checked
+ // IE8 throws error here and will not see later tests
+ if ( !div.querySelectorAll(":checked").length ) {
+ rbuggyQSA.push(":checked");
+ }
+ });
</ins><span class="cx">
</span><del>- if ( !body ) {
- // Return for frameset docs that don't have a body
- return;
- }
</del><ins>+ assert(function( div ) {
+ // Support: Windows 8 Native Apps
+ // The type and name attributes are restricted during .innerHTML assignment
+ var input = doc.createElement("input");
+ input.setAttribute( "type", "hidden" );
+ div.appendChild( input ).setAttribute( "name", "D" );
</ins><span class="cx">
</span><del>- container = document.createElement("div");
- container.style.cssText = "border:0;width:0;height:0;position:absolute;top:0;left:-9999px;margin-top:1px";
</del><ins>+ // Support: IE8
+ // Enforce case-sensitivity of name attribute
+ if ( div.querySelectorAll("[name=d]").length ) {
+ rbuggyQSA.push( "name" + whitespace + "*[*^$|!~]?=" );
+ }
</ins><span class="cx">
</span><del>- body.appendChild( container ).appendChild( div );
</del><ins>+ // FF 3.5 - :enabled/:disabled and hidden elements (hidden elements are still enabled)
+ // IE8 throws error here and will not see later tests
+ if ( !div.querySelectorAll(":enabled").length ) {
+ rbuggyQSA.push( ":enabled", ":disabled" );
+ }
</ins><span class="cx">
</span><del>- // Support: IE8
- // Check if table cells still have offsetWidth/Height when they are set
- // to display:none and there are still other visible table cells in a
- // table row; if so, offsetWidth/Height are not reliable for use when
- // determining if an element has been hidden directly using
- // display:none (it is still safe to use offsets if a parent element is
- // hidden; don safety goggles and see bug #4512 for more information).
- div.innerHTML = "<table><tr><td></td><td>t</td></tr></table>";
- tds = div.getElementsByTagName("td");
- tds[ 0 ].style.cssText = "padding:0;margin:0;border:0;display:none";
- isSupported = ( tds[ 0 ].offsetHeight === 0 );
</del><ins>+ // Opera 10-11 does not throw on post-comma invalid pseudos
+ div.querySelectorAll("*,:x");
+ rbuggyQSA.push(",.*:");
+ });
+ }
</ins><span class="cx">
</span><del>- tds[ 0 ].style.display = "";
- tds[ 1 ].style.display = "none";
</del><ins>+ if ( (support.matchesSelector = rnative.test( (matches = docElem.webkitMatchesSelector ||
+ docElem.mozMatchesSelector ||
+ docElem.oMatchesSelector ||
+ docElem.msMatchesSelector) )) ) {
</ins><span class="cx">
</span><del>- // Support: IE8
- // Check if empty table cells still have offsetWidth/Height
- support.reliableHiddenOffsets = isSupported && ( tds[ 0 ].offsetHeight === 0 );
</del><ins>+ assert(function( div ) {
+ // Check to see if it's possible to do matchesSelector
+ // on a disconnected node (IE 9)
+ support.disconnectedMatch = matches.call( div, "div" );
</ins><span class="cx">
</span><del>- // Check box-sizing and margin behavior
- div.innerHTML = "";
- div.style.cssText = "box-sizing:border-box;-moz-box-sizing:border-box;-webkit-box-sizing:border-box;padding:1px;border:1px;display:block;width:4px;margin-top:1%;position:absolute;top:1%;";
- support.boxSizing = ( div.offsetWidth === 4 );
- support.doesNotIncludeMarginInBodyOffset = ( body.offsetTop !== 1 );
</del><ins>+ // This should fail with an exception
+ // Gecko does not error, returns false instead
+ matches.call( div, "[s!='']:x" );
+ rbuggyMatches.push( "!=", pseudos );
+ });
+ }
</ins><span class="cx">
</span><del>- // Use window.getComputedStyle because jsdom on node.js will break without it.
- if ( window.getComputedStyle ) {
- support.pixelPosition = ( window.getComputedStyle( div, null ) || {} ).top !== "1%";
- support.boxSizingReliable = ( window.getComputedStyle( div, null ) || { width: "4px" } ).width === "4px";
</del><ins>+ rbuggyQSA = rbuggyQSA.length && new RegExp( rbuggyQSA.join("|") );
+ rbuggyMatches = rbuggyMatches.length && new RegExp( rbuggyMatches.join("|") );
</ins><span class="cx">
</span><del>- // Check if div with explicit width and no margin-right incorrectly
- // gets computed margin-right based on width of container. (#3333)
- // Fails in WebKit before Feb 2011 nightlies
- // WebKit Bug 13343 - getComputedStyle returns wrong value for margin-right
- marginDiv = div.appendChild( document.createElement("div") );
- marginDiv.style.cssText = div.style.cssText = divReset;
- marginDiv.style.marginRight = marginDiv.style.width = "0";
- div.style.width = "1px";
</del><ins>+ /* Contains
+ ---------------------------------------------------------------------- */
+ hasCompare = rnative.test( docElem.compareDocumentPosition );
</ins><span class="cx">
</span><del>- support.reliableMarginRight =
- !parseFloat( ( window.getComputedStyle( marginDiv, null ) || {} ).marginRight );
- }
</del><ins>+ // Element contains another
+ // Purposefully does not implement inclusive descendent
+ // As in, an element does not contain itself
+ contains = hasCompare || rnative.test( docElem.contains ) ?
+ function( a, b ) {
+ var adown = a.nodeType === 9 ? a.documentElement : a,
+ bup = b && b.parentNode;
+ return a === bup || !!( bup && bup.nodeType === 1 && (
+ adown.contains ?
+ adown.contains( bup ) :
+ a.compareDocumentPosition && a.compareDocumentPosition( bup ) & 16
+ ));
+ } :
+ function( a, b ) {
+ if ( b ) {
+ while ( (b = b.parentNode) ) {
+ if ( b === a ) {
+ return true;
+ }
+ }
+ }
+ return false;
+ };
</ins><span class="cx">
</span><del>- if ( typeof div.style.zoom !== core_strundefined ) {
- // Support: IE<8
- // Check if natively block-level elements act like inline-block
- // elements when setting their display to 'inline' and giving
- // them layout
- div.innerHTML = "";
- div.style.cssText = divReset + "width:1px;padding:1px;display:inline;zoom:1";
- support.inlineBlockNeedsLayout = ( div.offsetWidth === 3 );
</del><ins>+ /* Sorting
+ ---------------------------------------------------------------------- */
</ins><span class="cx">
</span><del>- // Support: IE6
- // Check if elements with layout shrink-wrap their children
- div.style.display = "block";
- div.innerHTML = "<div></div>";
- div.firstChild.style.width = "5px";
- support.shrinkWrapBlocks = ( div.offsetWidth !== 3 );
</del><ins>+ // Document order sorting
+ sortOrder = hasCompare ?
+ function( a, b ) {
</ins><span class="cx">
</span><del>- if ( support.inlineBlockNeedsLayout ) {
- // Prevent IE 6 from affecting layout for positioned elements #11048
- // Prevent IE from shrinking the body in IE 7 mode #12869
- // Support: IE<8
- body.style.zoom = 1;
- }
- }
</del><ins>+ // Flag for duplicate removal
+ if ( a === b ) {
+ hasDuplicate = true;
+ return 0;
+ }
</ins><span class="cx">
</span><del>- body.removeChild( container );
</del><ins>+ // Sort on method existence if only one input has compareDocumentPosition
+ var compare = !a.compareDocumentPosition - !b.compareDocumentPosition;
+ if ( compare ) {
+ return compare;
+ }
</ins><span class="cx">
</span><del>- // Null elements to avoid leaks in IE
- container = div = tds = marginDiv = null;
- });
</del><ins>+ // Calculate position if both inputs belong to the same document
+ compare = ( a.ownerDocument || a ) === ( b.ownerDocument || b ) ?
+ a.compareDocumentPosition( b ) :
</ins><span class="cx">
</span><del>- // Null elements to avoid leaks in IE
- all = select = fragment = opt = a = input = null;
</del><ins>+ // Otherwise we know they are disconnected
+ 1;
</ins><span class="cx">
</span><del>- return support;
-})();
</del><ins>+ // Disconnected nodes
+ if ( compare & 1 ||
+ (!support.sortDetached && b.compareDocumentPosition( a ) === compare) ) {
</ins><span class="cx">
</span><del>-var rbrace = /(?:\{[\s\S]*\}|\[[\s\S]*\])$/,
- rmultiDash = /([A-Z])/g;
</del><ins>+ // Choose the first element that is related to our preferred document
+ if ( a === doc || a.ownerDocument === preferredDoc && contains(preferredDoc, a) ) {
+ return -1;
+ }
+ if ( b === doc || b.ownerDocument === preferredDoc && contains(preferredDoc, b) ) {
+ return 1;
+ }
</ins><span class="cx">
</span><del>-function internalData( elem, name, data, pvt /* Internal Use Only */ ){
- if ( !jQuery.acceptData( elem ) ) {
- return;
- }
</del><ins>+ // Maintain original order
+ return sortInput ?
+ ( indexOf.call( sortInput, a ) - indexOf.call( sortInput, b ) ) :
+ 0;
+ }
</ins><span class="cx">
</span><del>- var thisCache, ret,
- internalKey = jQuery.expando,
- getByName = typeof name === "string",
</del><ins>+ return compare & 4 ? -1 : 1;
+ } :
+ function( a, b ) {
+ // Exit early if the nodes are identical
+ if ( a === b ) {
+ hasDuplicate = true;
+ return 0;
+ }
</ins><span class="cx">
</span><del>- // We have to handle DOM nodes and JS objects differently because IE6-7
- // can't GC object references properly across the DOM-JS boundary
- isNode = elem.nodeType,
</del><ins>+ var cur,
+ i = 0,
+ aup = a.parentNode,
+ bup = b.parentNode,
+ ap = [ a ],
+ bp = [ b ];
</ins><span class="cx">
</span><del>- // Only DOM nodes need the global jQuery cache; JS object data is
- // attached directly to the object so GC can occur automatically
- cache = isNode ? jQuery.cache : elem,
</del><ins>+ // Parentless nodes are either documents or disconnected
+ if ( !aup || !bup ) {
+ return a === doc ? -1 :
+ b === doc ? 1 :
+ aup ? -1 :
+ bup ? 1 :
+ sortInput ?
+ ( indexOf.call( sortInput, a ) - indexOf.call( sortInput, b ) ) :
+ 0;
</ins><span class="cx">
</span><del>- // Only defining an ID for JS objects if its cache already exists allows
- // the code to shortcut on the same path as a DOM node with no cache
- id = isNode ? elem[ internalKey ] : elem[ internalKey ] && internalKey;
</del><ins>+ // If the nodes are siblings, we can do a quick check
+ } else if ( aup === bup ) {
+ return siblingCheck( a, b );
+ }
</ins><span class="cx">
</span><del>- // Avoid doing any more work than we need to when trying to get data on an
- // object that has no data at all
- if ( (!id || !cache[id] || (!pvt && !cache[id].data)) && getByName && data === undefined ) {
- return;
- }
</del><ins>+ // Otherwise we need full lists of their ancestors for comparison
+ cur = a;
+ while ( (cur = cur.parentNode) ) {
+ ap.unshift( cur );
+ }
+ cur = b;
+ while ( (cur = cur.parentNode) ) {
+ bp.unshift( cur );
+ }
</ins><span class="cx">
</span><del>- if ( !id ) {
- // Only DOM nodes need a new unique ID for each element since their data
- // ends up in the global cache
- if ( isNode ) {
- elem[ internalKey ] = id = core_deletedIds.pop() || jQuery.guid++;
- } else {
- id = internalKey;
- }
- }
</del><ins>+ // Walk down the tree looking for a discrepancy
+ while ( ap[i] === bp[i] ) {
+ i++;
+ }
</ins><span class="cx">
</span><del>- if ( !cache[ id ] ) {
- cache[ id ] = {};
</del><ins>+ return i ?
+ // Do a sibling check if the nodes have a common ancestor
+ siblingCheck( ap[i], bp[i] ) :
</ins><span class="cx">
</span><del>- // Avoids exposing jQuery metadata on plain JS objects when the object
- // is serialized using JSON.stringify
- if ( !isNode ) {
- cache[ id ].toJSON = jQuery.noop;
- }
- }
</del><ins>+ // Otherwise nodes in our document sort first
+ ap[i] === preferredDoc ? -1 :
+ bp[i] === preferredDoc ? 1 :
+ 0;
+ };
</ins><span class="cx">
</span><del>- // An object can be passed to jQuery.data instead of a key/value pair; this gets
- // shallow copied over onto the existing cache
- if ( typeof name === "object" || typeof name === "function" ) {
- if ( pvt ) {
- cache[ id ] = jQuery.extend( cache[ id ], name );
- } else {
- cache[ id ].data = jQuery.extend( cache[ id ].data, name );
- }
- }
</del><ins>+ return doc;
+};
</ins><span class="cx">
</span><del>- thisCache = cache[ id ];
</del><ins>+Sizzle.matches = function( expr, elements ) {
+ return Sizzle( expr, null, null, elements );
+};
</ins><span class="cx">
</span><del>- // jQuery data() is stored in a separate object inside the object's internal data
- // cache in order to avoid key collisions between internal data and user-defined
- // data.
- if ( !pvt ) {
- if ( !thisCache.data ) {
- thisCache.data = {};
</del><ins>+Sizzle.matchesSelector = function( elem, expr ) {
+ // Set document vars if needed
+ if ( ( elem.ownerDocument || elem ) !== document ) {
+ setDocument( elem );
</ins><span class="cx"> }
</span><span class="cx">
</span><del>- thisCache = thisCache.data;
- }
</del><ins>+ // Make sure that attribute selectors are quoted
+ expr = expr.replace( rattributeQuotes, "='$1']" );
</ins><span class="cx">
</span><del>- if ( data !== undefined ) {
- thisCache[ jQuery.camelCase( name ) ] = data;
- }
</del><ins>+ if ( support.matchesSelector && documentIsHTML &&
+ ( !rbuggyMatches || !rbuggyMatches.test( expr ) ) &&
+ ( !rbuggyQSA || !rbuggyQSA.test( expr ) ) ) {
</ins><span class="cx">
</span><del>- // Check for both converted-to-camel and non-converted data property names
- // If a data property was specified
- if ( getByName ) {
</del><ins>+ try {
+ var ret = matches.call( elem, expr );
</ins><span class="cx">
</span><del>- // First Try to find as-is property data
- ret = thisCache[ name ];
-
- // Test for null|undefined property data
- if ( ret == null ) {
-
- // Try to find the camelCased property
- ret = thisCache[ jQuery.camelCase( name ) ];
</del><ins>+ // IE 9's matchesSelector returns false on disconnected nodes
+ if ( ret || support.disconnectedMatch ||
+ // As well, disconnected nodes are said to be in a document
+ // fragment in IE 9
+ elem.document && elem.document.nodeType !== 11 ) {
+ return ret;
+ }
+ } catch(e) {}
</ins><span class="cx"> }
</span><del>- } else {
- ret = thisCache;
- }
</del><span class="cx">
</span><del>- return ret;
-}
</del><ins>+ return Sizzle( expr, document, null, [elem] ).length > 0;
+};
</ins><span class="cx">
</span><del>-function internalRemoveData( elem, name, pvt ) {
- if ( !jQuery.acceptData( elem ) ) {
- return;
</del><ins>+Sizzle.contains = function( context, elem ) {
+ // Set document vars if needed
+ if ( ( context.ownerDocument || context ) !== document ) {
+ setDocument( context );
</ins><span class="cx"> }
</span><ins>+ return contains( context, elem );
+};
</ins><span class="cx">
</span><del>- var i, l, thisCache,
- isNode = elem.nodeType,
-
- // See jQuery.data for more information
- cache = isNode ? jQuery.cache : elem,
- id = isNode ? elem[ jQuery.expando ] : jQuery.expando;
-
- // If there is already no cache entry for this object, there is no
- // purpose in continuing
- if ( !cache[ id ] ) {
- return;
</del><ins>+Sizzle.attr = function( elem, name ) {
+ // Set document vars if needed
+ if ( ( elem.ownerDocument || elem ) !== document ) {
+ setDocument( elem );
</ins><span class="cx"> }
</span><span class="cx">
</span><del>- if ( name ) {
</del><ins>+ var fn = Expr.attrHandle[ name.toLowerCase() ],
+ // Don't get fooled by Object.prototype properties (jQuery #13807)
+ val = fn && hasOwn.call( Expr.attrHandle, name.toLowerCase() ) ?
+ fn( elem, name, !documentIsHTML ) :
+ undefined;
</ins><span class="cx">
</span><del>- thisCache = pvt ? cache[ id ] : cache[ id ].data;
</del><ins>+ return val !== undefined ?
+ val :
+ support.attributes || !documentIsHTML ?
+ elem.getAttribute( name ) :
+ (val = elem.getAttributeNode(name)) && val.specified ?
+ val.value :
+ null;
+};
</ins><span class="cx">
</span><del>- if ( thisCache ) {
</del><ins>+Sizzle.error = function( msg ) {
+ throw new Error( "Syntax error, unrecognized expression: " + msg );
+};
</ins><span class="cx">
</span><del>- // Support array or space separated string names for data keys
- if ( !jQuery.isArray( name ) ) {
</del><ins>+/**
+ * Document sorting and removing duplicates
+ * @param {ArrayLike} results
+ */
+Sizzle.uniqueSort = function( results ) {
+ var elem,
+ duplicates = [],
+ j = 0,
+ i = 0;
</ins><span class="cx">
</span><del>- // try the string as a key before any manipulation
- if ( name in thisCache ) {
- name = [ name ];
- } else {
</del><ins>+ // Unless we *know* we can detect duplicates, assume their presence
+ hasDuplicate = !support.detectDuplicates;
+ sortInput = !support.sortStable && results.slice( 0 );
+ results.sort( sortOrder );
</ins><span class="cx">
</span><del>- // split the camel cased version by spaces unless a key with the spaces exists
- name = jQuery.camelCase( name );
- if ( name in thisCache ) {
- name = [ name ];
- } else {
- name = name.split(" ");
</del><ins>+ if ( hasDuplicate ) {
+ while ( (elem = results[i++]) ) {
+ if ( elem === results[ i ] ) {
+ j = duplicates.push( i );
+ }
+ }
+ while ( j-- ) {
+ results.splice( duplicates[ j ], 1 );
+ }
</ins><span class="cx"> }
</span><del>- }
- } else {
- // If "name" is an array of keys...
- // When data is initially created, via ("key", "val") signature,
- // keys will be converted to camelCase.
- // Since there is no way to tell _how_ a key was added, remove
- // both plain key and camelCase key. #12786
- // This will only penalize the array argument path.
- name = name.concat( jQuery.map( name, jQuery.camelCase ) );
- }
</del><span class="cx">
</span><del>- for ( i = 0, l = name.length; i < l; i++ ) {
- delete thisCache[ name[i] ];
- }
</del><ins>+ // Clear input after sorting to release objects
+ // See https://github.com/jquery/sizzle/pull/225
+ sortInput = null;
</ins><span class="cx">
</span><del>- // If there is no data left in the cache, we want to continue
- // and let the cache object itself get destroyed
- if ( !( pvt ? isEmptyDataObject : jQuery.isEmptyObject )( thisCache ) ) {
- return;
- }
- }
- }
</del><ins>+ return results;
+};
</ins><span class="cx">
</span><del>- // See jQuery.data for more information
- if ( !pvt ) {
- delete cache[ id ].data;
</del><ins>+/**
+ * Utility function for retrieving the text value of an array of DOM nodes
+ * @param {Array|Element} elem
+ */
+getText = Sizzle.getText = function( elem ) {
+ var node,
+ ret = "",
+ i = 0,
+ nodeType = elem.nodeType;
</ins><span class="cx">
</span><del>- // Don't destroy the parent cache unless the internal data object
- // had been the only thing left in it
- if ( !isEmptyDataObject( cache[ id ] ) ) {
- return;
</del><ins>+ if ( !nodeType ) {
+ // If no nodeType, this is expected to be an array
+ while ( (node = elem[i++]) ) {
+ // Do not traverse comment nodes
+ ret += getText( node );
+ }
+ } else if ( nodeType === 1 || nodeType === 9 || nodeType === 11 ) {
+ // Use textContent for elements
+ // innerText usage removed for consistency of new lines (jQuery #11153)
+ if ( typeof elem.textContent === "string" ) {
+ return elem.textContent;
+ } else {
+ // Traverse its children
+ for ( elem = elem.firstChild; elem; elem = elem.nextSibling ) {
+ ret += getText( elem );
+ }
+ }
+ } else if ( nodeType === 3 || nodeType === 4 ) {
+ return elem.nodeValue;
</ins><span class="cx"> }
</span><del>- }
</del><ins>+ // Do not include comment or processing instruction nodes
</ins><span class="cx">
</span><del>- // Destroy the cache
- if ( isNode ) {
- jQuery.cleanData( [ elem ], true );
</del><ins>+ return ret;
+};
</ins><span class="cx">
</span><del>- // Use delete when supported for expandos or `cache` is not a window per isWindow (#10080)
- } else if ( jQuery.support.deleteExpando || cache != cache.window ) {
- delete cache[ id ];
</del><ins>+Expr = Sizzle.selectors = {
</ins><span class="cx">
</span><del>- // When all else fails, null
- } else {
- cache[ id ] = null;
- }
-}
</del><ins>+ // Can be adjusted by the user
+ cacheLength: 50,
</ins><span class="cx">
</span><del>-jQuery.extend({
- cache: {},
</del><ins>+ createPseudo: markFunction,
</ins><span class="cx">
</span><del>- // Unique for each copy of jQuery on the page
- // Non-digits removed to match rinlinejQuery
- expando: "jQuery" + ( core_version + Math.random() ).replace( /\D/g, "" ),
</del><ins>+ match: matchExpr,
</ins><span class="cx">
</span><del>- // The following elements throw uncatchable exceptions if you
- // attempt to add expando properties to them.
- noData: {
- "embed": true,
- // Ban all objects except for Flash (which handle expandos)
- "object": "clsid:D27CDB6E-AE6D-11cf-96B8-444553540000",
- "applet": true
- },
</del><ins>+ attrHandle: {},
</ins><span class="cx">
</span><del>- hasData: function( elem ) {
- elem = elem.nodeType ? jQuery.cache[ elem[jQuery.expando] ] : elem[ jQuery.expando ];
- return !!elem && !isEmptyDataObject( elem );
- },
</del><ins>+ find: {},
</ins><span class="cx">
</span><del>- data: function( elem, name, data ) {
- return internalData( elem, name, data );
</del><ins>+ relative: {
+ ">": { dir: "parentNode", first: true },
+ " ": { dir: "parentNode" },
+ "+": { dir: "previousSibling", first: true },
+ "~": { dir: "previousSibling" }
</ins><span class="cx"> },
</span><span class="cx">
</span><del>- removeData: function( elem, name ) {
- return internalRemoveData( elem, name );
- },
</del><ins>+ preFilter: {
+ "ATTR": function( match ) {
+ match[1] = match[1].replace( runescape, funescape );
</ins><span class="cx">
</span><del>- // For internal use only.
- _data: function( elem, name, data ) {
- return internalData( elem, name, data, true );
- },
</del><ins>+ // Move the given value to match[3] whether quoted or unquoted
+ match[3] = ( match[4] || match[5] || "" ).replace( runescape, funescape );
</ins><span class="cx">
</span><del>- _removeData: function( elem, name ) {
- return internalRemoveData( elem, name, true );
- },
</del><ins>+ if ( match[2] === "~=" ) {
+ match[3] = " " + match[3] + " ";
+ }
</ins><span class="cx">
</span><del>- // A method for determining if a DOM node can handle the data expando
- acceptData: function( elem ) {
- // Do not set data on non-element because it will not be cleared (#8335).
- if ( elem.nodeType && elem.nodeType !== 1 && elem.nodeType !== 9 ) {
- return false;
- }
</del><ins>+ return match.slice( 0, 4 );
+ },
</ins><span class="cx">
</span><del>- var noData = elem.nodeName && jQuery.noData[ elem.nodeName.toLowerCase() ];
</del><ins>+ "CHILD": function( match ) {
+ /* matches from matchExpr["CHILD"]
+ 1 type (only|nth|...)
+ 2 what (child|of-type)
+ 3 argument (even|odd|\d*|\d*n([+-]\d+)?|...)
+ 4 xn-component of xn+y argument ([+-]?\d*n|)
+ 5 sign of xn-component
+ 6 x of xn-component
+ 7 sign of y-component
+ 8 y of y-component
+ */
+ match[1] = match[1].toLowerCase();
</ins><span class="cx">
</span><del>- // nodes accept data unless otherwise specified; rejection can be conditional
- return !noData || noData !== true && elem.getAttribute("classid") === noData;
- }
-});
</del><ins>+ if ( match[1].slice( 0, 3 ) === "nth" ) {
+ // nth-* requires argument
+ if ( !match[3] ) {
+ Sizzle.error( match[0] );
+ }
</ins><span class="cx">
</span><del>-jQuery.fn.extend({
- data: function( key, value ) {
- var attrs, name,
- elem = this[0],
- i = 0,
- data = null;
</del><ins>+ // numeric x and y parameters for Expr.filter.CHILD
+ // remember that false/true cast respectively to 0/1
+ match[4] = +( match[4] ? match[5] + (match[6] || 1) : 2 * ( match[3] === "even" || match[3] === "odd" ) );
+ match[5] = +( ( match[7] + match[8] ) || match[3] === "odd" );
</ins><span class="cx">
</span><del>- // Gets all values
- if ( key === undefined ) {
- if ( this.length ) {
- data = jQuery.data( elem );
</del><ins>+ // other types prohibit arguments
+ } else if ( match[3] ) {
+ Sizzle.error( match[0] );
+ }
</ins><span class="cx">
</span><del>- if ( elem.nodeType === 1 && !jQuery._data( elem, "parsedAttrs" ) ) {
- attrs = elem.attributes;
- for ( ; i < attrs.length; i++ ) {
- name = attrs[i].name;
</del><ins>+ return match;
+ },
</ins><span class="cx">
</span><del>- if ( !name.indexOf( "data-" ) ) {
- name = jQuery.camelCase( name.slice(5) );
</del><ins>+ "PSEUDO": function( match ) {
+ var excess,
+ unquoted = !match[5] && match[2];
</ins><span class="cx">
</span><del>- dataAttr( elem, name, data[ name ] );
- }
- }
- jQuery._data( elem, "parsedAttrs", true );
- }
- }
</del><ins>+ if ( matchExpr["CHILD"].test( match[0] ) ) {
+ return null;
+ }
</ins><span class="cx">
</span><del>- return data;
- }
</del><ins>+ // Accept quoted arguments as-is
+ if ( match[3] && match[4] !== undefined ) {
+ match[2] = match[4];
</ins><span class="cx">
</span><del>- // Sets multiple values
- if ( typeof key === "object" ) {
- return this.each(function() {
- jQuery.data( this, key );
- });
- }
</del><ins>+ // Strip excess characters from unquoted arguments
+ } else if ( unquoted && rpseudo.test( unquoted ) &&
+ // Get excess from tokenize (recursively)
+ (excess = tokenize( unquoted, true )) &&
+ // advance to the next closing parenthesis
+ (excess = unquoted.indexOf( ")", unquoted.length - excess ) - unquoted.length) ) {
</ins><span class="cx">
</span><del>- return jQuery.access( this, function( value ) {
</del><ins>+ // excess is a negative index
+ match[0] = match[0].slice( 0, excess );
+ match[2] = unquoted.slice( 0, excess );
+ }
</ins><span class="cx">
</span><del>- if ( value === undefined ) {
- // Try to fetch any internally stored data first
- return elem ? dataAttr( elem, key, jQuery.data( elem, key ) ) : null;
- }
-
- this.each(function() {
- jQuery.data( this, key, value );
- });
- }, null, value, arguments.length > 1, null, true );
</del><ins>+ // Return only captures needed by the pseudo filter method (type and argument)
+ return match.slice( 0, 3 );
+ }
</ins><span class="cx"> },
</span><span class="cx">
</span><del>- removeData: function( key ) {
- return this.each(function() {
- jQuery.removeData( this, key );
- });
- }
-});
</del><ins>+ filter: {
</ins><span class="cx">
</span><del>-function dataAttr( elem, key, data ) {
- // If nothing was found internally, try to fetch any
- // data from the HTML5 data-* attribute
- if ( data === undefined && elem.nodeType === 1 ) {
</del><ins>+ "TAG": function( nodeNameSelector ) {
+ var nodeName = nodeNameSelector.replace( runescape, funescape ).toLowerCase();
+ return nodeNameSelector === "*" ?
+ function() { return true; } :
+ function( elem ) {
+ return elem.nodeName && elem.nodeName.toLowerCase() === nodeName;
+ };
+ },
</ins><span class="cx">
</span><del>- var name = "data-" + key.replace( rmultiDash, "-$1" ).toLowerCase();
</del><ins>+ "CLASS": function( className ) {
+ var pattern = classCache[ className + " " ];
</ins><span class="cx">
</span><del>- data = elem.getAttribute( name );
</del><ins>+ return pattern ||
+ (pattern = new RegExp( "(^|" + whitespace + ")" + className + "(" + whitespace + "|$)" )) &&
+ classCache( className, function( elem ) {
+ return pattern.test( typeof elem.className === "string" && elem.className || typeof elem.getAttribute !== strundefined && elem.getAttribute("class") || "" );
+ });
+ },
</ins><span class="cx">
</span><del>- if ( typeof data === "string" ) {
- try {
- data = data === "true" ? true :
- data === "false" ? false :
- data === "null" ? null :
- // Only convert to a number if it doesn't change the string
- +data + "" === data ? +data :
- rbrace.test( data ) ? jQuery.parseJSON( data ) :
- data;
- } catch( e ) {}
</del><ins>+ "ATTR": function( name, operator, check ) {
+ return function( elem ) {
+ var result = Sizzle.attr( elem, name );
</ins><span class="cx">
</span><del>- // Make sure we set the data so it isn't changed later
- jQuery.data( elem, key, data );
</del><ins>+ if ( result == null ) {
+ return operator === "!=";
+ }
+ if ( !operator ) {
+ return true;
+ }
</ins><span class="cx">
</span><del>- } else {
- data = undefined;
- }
- }
</del><ins>+ result += "";
</ins><span class="cx">
</span><del>- return data;
-}
</del><ins>+ return operator === "=" ? result === check :
+ operator === "!=" ? result !== check :
+ operator === "^=" ? check && result.indexOf( check ) === 0 :
+ operator === "*=" ? check && result.indexOf( check ) > -1 :
+ operator === "$=" ? check && result.slice( -check.length ) === check :
+ operator === "~=" ? ( " " + result + " " ).indexOf( check ) > -1 :
+ operator === "|=" ? result === check || result.slice( 0, check.length + 1 ) === check + "-" :
+ false;
+ };
+ },
</ins><span class="cx">
</span><del>-// checks a cache object for emptiness
-function isEmptyDataObject( obj ) {
- var name;
- for ( name in obj ) {
</del><ins>+ "CHILD": function( type, what, argument, first, last ) {
+ var simple = type.slice( 0, 3 ) !== "nth",
+ forward = type.slice( -4 ) !== "last",
+ ofType = what === "of-type";
</ins><span class="cx">
</span><del>- // if the public data object is empty, the private is still empty
- if ( name === "data" && jQuery.isEmptyObject( obj[name] ) ) {
- continue;
- }
- if ( name !== "toJSON" ) {
- return false;
- }
- }
</del><ins>+ return first === 1 && last === 0 ?
</ins><span class="cx">
</span><del>- return true;
-}
-jQuery.extend({
- queue: function( elem, type, data ) {
- var queue;
</del><ins>+ // Shortcut for :nth-*(n)
+ function( elem ) {
+ return !!elem.parentNode;
+ } :
</ins><span class="cx">
</span><del>- if ( elem ) {
- type = ( type || "fx" ) + "queue";
- queue = jQuery._data( elem, type );
</del><ins>+ function( elem, context, xml ) {
+ var cache, outerCache, node, diff, nodeIndex, start,
+ dir = simple !== forward ? "nextSibling" : "previousSibling",
+ parent = elem.parentNode,
+ name = ofType && elem.nodeName.toLowerCase(),
+ useCache = !xml && !ofType;
</ins><span class="cx">
</span><del>- // Speed up dequeue by getting out quickly if this is just a lookup
- if ( data ) {
- if ( !queue || jQuery.isArray(data) ) {
- queue = jQuery._data( elem, type, jQuery.makeArray(data) );
- } else {
- queue.push( data );
- }
- }
- return queue || [];
- }
- },
</del><ins>+ if ( parent ) {
</ins><span class="cx">
</span><del>- dequeue: function( elem, type ) {
- type = type || "fx";
</del><ins>+ // :(first|last|only)-(child|of-type)
+ if ( simple ) {
+ while ( dir ) {
+ node = elem;
+ while ( (node = node[ dir ]) ) {
+ if ( ofType ? node.nodeName.toLowerCase() === name : node.nodeType === 1 ) {
+ return false;
+ }
+ }
+ // Reverse direction for :only-* (if we haven't yet done so)
+ start = dir = type === "only" && !start && "nextSibling";
+ }
+ return true;
+ }
</ins><span class="cx">
</span><del>- var queue = jQuery.queue( elem, type ),
- startLength = queue.length,
- fn = queue.shift(),
- hooks = jQuery._queueHooks( elem, type ),
- next = function() {
- jQuery.dequeue( elem, type );
- };
</del><ins>+ start = [ forward ? parent.firstChild : parent.lastChild ];
</ins><span class="cx">
</span><del>- // If the fx queue is dequeued, always remove the progress sentinel
- if ( fn === "inprogress" ) {
- fn = queue.shift();
- startLength--;
- }
</del><ins>+ // non-xml :nth-child(...) stores cache data on `parent`
+ if ( forward && useCache ) {
+ // Seek `elem` from a previously-cached index
+ outerCache = parent[ expando ] || (parent[ expando ] = {});
+ cache = outerCache[ type ] || [];
+ nodeIndex = cache[0] === dirruns && cache[1];
+ diff = cache[0] === dirruns && cache[2];
+ node = nodeIndex && parent.childNodes[ nodeIndex ];
</ins><span class="cx">
</span><del>- hooks.cur = fn;
- if ( fn ) {
</del><ins>+ while ( (node = ++nodeIndex && node && node[ dir ] ||
</ins><span class="cx">
</span><del>- // Add a progress sentinel to prevent the fx queue from being
- // automatically dequeued
- if ( type === "fx" ) {
- queue.unshift( "inprogress" );
- }
</del><ins>+ // Fallback to seeking `elem` from the start
+ (diff = nodeIndex = 0) || start.pop()) ) {
</ins><span class="cx">
</span><del>- // clear up the last queue stop function
- delete hooks.stop;
- fn.call( elem, next, hooks );
- }
</del><ins>+ // When found, cache indexes on `parent` and break
+ if ( node.nodeType === 1 && ++diff && node === elem ) {
+ outerCache[ type ] = [ dirruns, nodeIndex, diff ];
+ break;
+ }
+ }
</ins><span class="cx">
</span><del>- if ( !startLength && hooks ) {
- hooks.empty.fire();
- }
- },
</del><ins>+ // Use previously-cached element index if available
+ } else if ( useCache && (cache = (elem[ expando ] || (elem[ expando ] = {}))[ type ]) && cache[0] === dirruns ) {
+ diff = cache[1];
</ins><span class="cx">
</span><del>- // not intended for public consumption - generates a queueHooks object, or returns the current one
- _queueHooks: function( elem, type ) {
- var key = type + "queueHooks";
- return jQuery._data( elem, key ) || jQuery._data( elem, key, {
- empty: jQuery.Callbacks("once memory").add(function() {
- jQuery._removeData( elem, type + "queue" );
- jQuery._removeData( elem, key );
- })
- });
- }
-});
</del><ins>+ // xml :nth-child(...) or :nth-last-child(...) or :nth(-last)?-of-type(...)
+ } else {
+ // Use the same loop as above to seek `elem` from the start
+ while ( (node = ++nodeIndex && node && node[ dir ] ||
+ (diff = nodeIndex = 0) || start.pop()) ) {
</ins><span class="cx">
</span><del>-jQuery.fn.extend({
- queue: function( type, data ) {
- var setter = 2;
</del><ins>+ if ( ( ofType ? node.nodeName.toLowerCase() === name : node.nodeType === 1 ) && ++diff ) {
+ // Cache the index of each encountered element
+ if ( useCache ) {
+ (node[ expando ] || (node[ expando ] = {}))[ type ] = [ dirruns, diff ];
+ }
</ins><span class="cx">
</span><del>- if ( typeof type !== "string" ) {
- data = type;
- type = "fx";
- setter--;
- }
</del><ins>+ if ( node === elem ) {
+ break;
+ }
+ }
+ }
+ }
</ins><span class="cx">
</span><del>- if ( arguments.length < setter ) {
- return jQuery.queue( this[0], type );
- }
</del><ins>+ // Incorporate the offset, then check against cycle size
+ diff -= last;
+ return diff === first || ( diff % first === 0 && diff / first >= 0 );
+ }
+ };
+ },
</ins><span class="cx">
</span><del>- return data === undefined ?
- this :
- this.each(function() {
- var queue = jQuery.queue( this, type, data );
</del><ins>+ "PSEUDO": function( pseudo, argument ) {
+ // pseudo-class names are case-insensitive
+ // http://www.w3.org/TR/selectors/#pseudo-classes
+ // Prioritize by case sensitivity in case custom pseudos are added with uppercase letters
+ // Remember that setFilters inherits from pseudos
+ var args,
+ fn = Expr.pseudos[ pseudo ] || Expr.setFilters[ pseudo.toLowerCase() ] ||
+ Sizzle.error( "unsupported pseudo: " + pseudo );
</ins><span class="cx">
</span><del>- // ensure a hooks for this queue
- jQuery._queueHooks( this, type );
</del><ins>+ // The user may use createPseudo to indicate that
+ // arguments are needed to create the filter function
+ // just as Sizzle does
+ if ( fn[ expando ] ) {
+ return fn( argument );
+ }
</ins><span class="cx">
</span><del>- if ( type === "fx" && queue[0] !== "inprogress" ) {
- jQuery.dequeue( this, type );
- }
- });
- },
- dequeue: function( type ) {
- return this.each(function() {
- jQuery.dequeue( this, type );
- });
- },
- // Based off of the plugin by Clint Helfers, with permission.
- // http://blindsignals.com/index.php/2009/07/jquery-delay/
- delay: function( time, type ) {
- time = jQuery.fx ? jQuery.fx.speeds[ time ] || time : time;
- type = type || "fx";
</del><ins>+ // But maintain support for old signatures
+ if ( fn.length > 1 ) {
+ args = [ pseudo, pseudo, "", argument ];
+ return Expr.setFilters.hasOwnProperty( pseudo.toLowerCase() ) ?
+ markFunction(function( seed, matches ) {
+ var idx,
+ matched = fn( seed, argument ),
+ i = matched.length;
+ while ( i-- ) {
+ idx = indexOf.call( seed, matched[i] );
+ seed[ idx ] = !( matches[ idx ] = matched[i] );
+ }
+ }) :
+ function( elem ) {
+ return fn( elem, 0, args );
+ };
+ }
</ins><span class="cx">
</span><del>- return this.queue( type, function( next, hooks ) {
- var timeout = setTimeout( next, time );
- hooks.stop = function() {
- clearTimeout( timeout );
- };
- });
</del><ins>+ return fn;
+ }
</ins><span class="cx"> },
</span><del>- clearQueue: function( type ) {
- return this.queue( type || "fx", [] );
- },
- // Get a promise resolved when queues of a certain type
- // are emptied (fx is the type by default)
- promise: function( type, obj ) {
- var tmp,
- count = 1,
- defer = jQuery.Deferred(),
- elements = this,
- i = this.length,
- resolve = function() {
- if ( !( --count ) ) {
- defer.resolveWith( elements, [ elements ] );
- }
- };
</del><span class="cx">
</span><del>- if ( typeof type !== "string" ) {
- obj = type;
- type = undefined;
- }
- type = type || "fx";
</del><ins>+ pseudos: {
+ // Potentially complex pseudos
+ "not": markFunction(function( selector ) {
+ // Trim the selector passed to compile
+ // to avoid treating leading and trailing
+ // spaces as combinators
+ var input = [],
+ results = [],
+ matcher = compile( selector.replace( rtrim, "$1" ) );
</ins><span class="cx">
</span><del>- while( i-- ) {
- tmp = jQuery._data( elements[ i ], type + "queueHooks" );
- if ( tmp && tmp.empty ) {
- count++;
- tmp.empty.add( resolve );
- }
- }
- resolve();
- return defer.promise( obj );
- }
-});
-var nodeHook, boolHook,
- rclass = /[\t\r\n]/g,
- rreturn = /\r/g,
- rfocusable = /^(?:input|select|textarea|button|object)$/i,
- rclickable = /^(?:a|area)$/i,
- rboolean = /^(?:checked|selected|autofocus|autoplay|async|controls|defer|disabled|hidden|loop|multiple|open|readonly|required|scoped)$/i,
- ruseDefault = /^(?:checked|selected)$/i,
- getSetAttribute = jQuery.support.getSetAttribute,
- getSetInput = jQuery.support.input;
</del><ins>+ return matcher[ expando ] ?
+ markFunction(function( seed, matches, context, xml ) {
+ var elem,
+ unmatched = matcher( seed, null, xml, [] ),
+ i = seed.length;
</ins><span class="cx">
</span><del>-jQuery.fn.extend({
- attr: function( name, value ) {
- return jQuery.access( this, jQuery.attr, name, value, arguments.length > 1 );
- },
</del><ins>+ // Match elements unmatched by `matcher`
+ while ( i-- ) {
+ if ( (elem = unmatched[i]) ) {
+ seed[i] = !(matches[i] = elem);
+ }
+ }
+ }) :
+ function( elem, context, xml ) {
+ input[0] = elem;
+ matcher( input, null, xml, results );
+ return !results.pop();
+ };
+ }),
</ins><span class="cx">
</span><del>- removeAttr: function( name ) {
- return this.each(function() {
- jQuery.removeAttr( this, name );
- });
- },
</del><ins>+ "has": markFunction(function( selector ) {
+ return function( elem ) {
+ return Sizzle( selector, elem ).length > 0;
+ };
+ }),
</ins><span class="cx">
</span><del>- prop: function( name, value ) {
- return jQuery.access( this, jQuery.prop, name, value, arguments.length > 1 );
- },
</del><ins>+ "contains": markFunction(function( text ) {
+ return function( elem ) {
+ return ( elem.textContent || elem.innerText || getText( elem ) ).indexOf( text ) > -1;
+ };
+ }),
</ins><span class="cx">
</span><del>- removeProp: function( name ) {
- name = jQuery.propFix[ name ] || name;
- return this.each(function() {
- // try/catch handles cases where IE balks (such as removing a property on window)
- try {
- this[ name ] = undefined;
- delete this[ name ];
- } catch( e ) {}
- });
- },
</del><ins>+ // "Whether an element is represented by a :lang() selector
+ // is based solely on the element's language value
+ // being equal to the identifier C,
+ // or beginning with the identifier C immediately followed by "-".
+ // The matching of C against the element's language value is performed case-insensitively.
+ // The identifier C does not have to be a valid language name."
+ // http://www.w3.org/TR/selectors/#lang-pseudo
+ "lang": markFunction( function( lang ) {
+ // lang value must be a valid identifier
+ if ( !ridentifier.test(lang || "") ) {
+ Sizzle.error( "unsupported lang: " + lang );
+ }
+ lang = lang.replace( runescape, funescape ).toLowerCase();
+ return function( elem ) {
+ var elemLang;
+ do {
+ if ( (elemLang = documentIsHTML ?
+ elem.lang :
+ elem.getAttribute("xml:lang") || elem.getAttribute("lang")) ) {
</ins><span class="cx">
</span><del>- addClass: function( value ) {
- var classes, elem, cur, clazz, j,
- i = 0,
- len = this.length,
- proceed = typeof value === "string" && value;
</del><ins>+ elemLang = elemLang.toLowerCase();
+ return elemLang === lang || elemLang.indexOf( lang + "-" ) === 0;
+ }
+ } while ( (elem = elem.parentNode) && elem.nodeType === 1 );
+ return false;
+ };
+ }),
</ins><span class="cx">
</span><del>- if ( jQuery.isFunction( value ) ) {
- return this.each(function( j ) {
- jQuery( this ).addClass( value.call( this, j, this.className ) );
- });
- }
</del><ins>+ // Miscellaneous
+ "target": function( elem ) {
+ var hash = window.location && window.location.hash;
+ return hash && hash.slice( 1 ) === elem.id;
+ },
</ins><span class="cx">
</span><del>- if ( proceed ) {
- // The disjunction here is for better compressibility (see removeClass)
- classes = ( value || "" ).match( core_rnotwhite ) || [];
</del><ins>+ "root": function( elem ) {
+ return elem === docElem;
+ },
</ins><span class="cx">
</span><del>- for ( ; i < len; i++ ) {
- elem = this[ i ];
- cur = elem.nodeType === 1 && ( elem.className ?
- ( " " + elem.className + " " ).replace( rclass, " " ) :
- " "
- );
</del><ins>+ "focus": function( elem ) {
+ return elem === document.activeElement && (!document.hasFocus || document.hasFocus()) && !!(elem.type || elem.href || ~elem.tabIndex);
+ },
</ins><span class="cx">
</span><del>- if ( cur ) {
- j = 0;
- while ( (clazz = classes[j++]) ) {
- if ( cur.indexOf( " " + clazz + " " ) < 0 ) {
- cur += clazz + " ";
- }
- }
- elem.className = jQuery.trim( cur );
</del><ins>+ // Boolean properties
+ "enabled": function( elem ) {
+ return elem.disabled === false;
+ },
</ins><span class="cx">
</span><del>- }
- }
- }
</del><ins>+ "disabled": function( elem ) {
+ return elem.disabled === true;
+ },
</ins><span class="cx">
</span><del>- return this;
- },
</del><ins>+ "checked": function( elem ) {
+ // In CSS3, :checked should return both checked and selected elements
+ // http://www.w3.org/TR/2011/REC-css3-selectors-20110929/#checked
+ var nodeName = elem.nodeName.toLowerCase();
+ return (nodeName === "input" && !!elem.checked) || (nodeName === "option" && !!elem.selected);
+ },
</ins><span class="cx">
</span><del>- removeClass: function( value ) {
- var classes, elem, cur, clazz, j,
- i = 0,
- len = this.length,
- proceed = arguments.length === 0 || typeof value === "string" && value;
</del><ins>+ "selected": function( elem ) {
+ // Accessing this property makes selected-by-default
+ // options in Safari work properly
+ if ( elem.parentNode ) {
+ elem.parentNode.selectedIndex;
+ }
</ins><span class="cx">
</span><del>- if ( jQuery.isFunction( value ) ) {
- return this.each(function( j ) {
- jQuery( this ).removeClass( value.call( this, j, this.className ) );
- });
- }
- if ( proceed ) {
- classes = ( value || "" ).match( core_rnotwhite ) || [];
</del><ins>+ return elem.selected === true;
+ },
</ins><span class="cx">
</span><del>- for ( ; i < len; i++ ) {
- elem = this[ i ];
- // This expression is here for better compressibility (see addClass)
- cur = elem.nodeType === 1 && ( elem.className ?
- ( " " + elem.className + " " ).replace( rclass, " " ) :
- ""
- );
</del><ins>+ // Contents
+ "empty": function( elem ) {
+ // http://www.w3.org/TR/selectors/#empty-pseudo
+ // :empty is negated by element (1) or content nodes (text: 3; cdata: 4; entity ref: 5),
+ // but not by others (comment: 8; processing instruction: 7; etc.)
+ // nodeType < 6 works because attributes (2) do not appear as children
+ for ( elem = elem.firstChild; elem; elem = elem.nextSibling ) {
+ if ( elem.nodeType < 6 ) {
+ return false;
+ }
+ }
+ return true;
+ },
</ins><span class="cx">
</span><del>- if ( cur ) {
- j = 0;
- while ( (clazz = classes[j++]) ) {
- // Remove *all* instances
- while ( cur.indexOf( " " + clazz + " " ) >= 0 ) {
- cur = cur.replace( " " + clazz + " ", " " );
- }
- }
- elem.className = value ? jQuery.trim( cur ) : "";
- }
- }
- }
</del><ins>+ "parent": function( elem ) {
+ return !Expr.pseudos["empty"]( elem );
+ },
</ins><span class="cx">
</span><del>- return this;
- },
</del><ins>+ // Element/input types
+ "header": function( elem ) {
+ return rheader.test( elem.nodeName );
+ },
</ins><span class="cx">
</span><del>- toggleClass: function( value, stateVal ) {
- var type = typeof value,
- isBool = typeof stateVal === "boolean";
</del><ins>+ "input": function( elem ) {
+ return rinputs.test( elem.nodeName );
+ },
</ins><span class="cx">
</span><del>- if ( jQuery.isFunction( value ) ) {
- return this.each(function( i ) {
- jQuery( this ).toggleClass( value.call(this, i, this.className, stateVal), stateVal );
- });
- }
</del><ins>+ "button": function( elem ) {
+ var name = elem.nodeName.toLowerCase();
+ return name === "input" && elem.type === "button" || name === "button";
+ },
</ins><span class="cx">
</span><del>- return this.each(function() {
- if ( type === "string" ) {
- // toggle individual class names
- var className,
- i = 0,
- self = jQuery( this ),
- state = stateVal,
- classNames = value.match( core_rnotwhite ) || [];
</del><ins>+ "text": function( elem ) {
+ var attr;
+ return elem.nodeName.toLowerCase() === "input" &&
+ elem.type === "text" &&
</ins><span class="cx">
</span><del>- while ( (className = classNames[ i++ ]) ) {
- // check each className given, space separated list
- state = isBool ? state : !self.hasClass( className );
- self[ state ? "addClass" : "removeClass" ]( className );
- }
</del><ins>+ // Support: IE<8
+ // New HTML5 attribute values (e.g., "search") appear with elem.type === "text"
+ ( (attr = elem.getAttribute("type")) == null || attr.toLowerCase() === "text" );
+ },
</ins><span class="cx">
</span><del>- // Toggle whole class name
- } else if ( type === core_strundefined || type === "boolean" ) {
- if ( this.className ) {
- // store className if set
- jQuery._data( this, "__className__", this.className );
- }
</del><ins>+ // Position-in-collection
+ "first": createPositionalPseudo(function() {
+ return [ 0 ];
+ }),
</ins><span class="cx">
</span><del>- // If the element has a class name or if we're passed "false",
- // then remove the whole classname (if there was one, the above saved it).
- // Otherwise bring back whatever was previously saved (if anything),
- // falling back to the empty string if nothing was stored.
- this.className = this.className || value === false ? "" : jQuery._data( this, "__className__" ) || "";
- }
- });
- },
</del><ins>+ "last": createPositionalPseudo(function( matchIndexes, length ) {
+ return [ length - 1 ];
+ }),
</ins><span class="cx">
</span><del>- hasClass: function( selector ) {
- var className = " " + selector + " ",
- i = 0,
- l = this.length;
- for ( ; i < l; i++ ) {
- if ( this[i].nodeType === 1 && (" " + this[i].className + " ").replace(rclass, " ").indexOf( className ) >= 0 ) {
- return true;
- }
- }
</del><ins>+ "eq": createPositionalPseudo(function( matchIndexes, length, argument ) {
+ return [ argument < 0 ? argument + length : argument ];
+ }),
</ins><span class="cx">
</span><del>- return false;
- },
</del><ins>+ "even": createPositionalPseudo(function( matchIndexes, length ) {
+ var i = 0;
+ for ( ; i < length; i += 2 ) {
+ matchIndexes.push( i );
+ }
+ return matchIndexes;
+ }),
</ins><span class="cx">
</span><del>- val: function( value ) {
- var ret, hooks, isFunction,
- elem = this[0];
</del><ins>+ "odd": createPositionalPseudo(function( matchIndexes, length ) {
+ var i = 1;
+ for ( ; i < length; i += 2 ) {
+ matchIndexes.push( i );
+ }
+ return matchIndexes;
+ }),
</ins><span class="cx">
</span><del>- if ( !arguments.length ) {
- if ( elem ) {
- hooks = jQuery.valHooks[ elem.type ] || jQuery.valHooks[ elem.nodeName.toLowerCase() ];
</del><ins>+ "lt": createPositionalPseudo(function( matchIndexes, length, argument ) {
+ var i = argument < 0 ? argument + length : argument;
+ for ( ; --i >= 0; ) {
+ matchIndexes.push( i );
+ }
+ return matchIndexes;
+ }),
</ins><span class="cx">
</span><del>- if ( hooks && "get" in hooks && (ret = hooks.get( elem, "value" )) !== undefined ) {
- return ret;
</del><ins>+ "gt": createPositionalPseudo(function( matchIndexes, length, argument ) {
+ var i = argument < 0 ? argument + length : argument;
+ for ( ; ++i < length; ) {
+ matchIndexes.push( i );
+ }
+ return matchIndexes;
+ })
</ins><span class="cx"> }
</span><ins>+};
</ins><span class="cx">
</span><del>- ret = elem.value;
</del><ins>+Expr.pseudos["nth"] = Expr.pseudos["eq"];
</ins><span class="cx">
</span><del>- return typeof ret === "string" ?
- // handle most common string cases
- ret.replace(rreturn, "") :
- // handle cases where value is null/undef or number
- ret == null ? "" : ret;
- }
</del><ins>+// Add button/input type pseudos
+for ( i in { radio: true, checkbox: true, file: true, password: true, image: true } ) {
+ Expr.pseudos[ i ] = createInputPseudo( i );
+}
+for ( i in { submit: true, reset: true } ) {
+ Expr.pseudos[ i ] = createButtonPseudo( i );
+}
</ins><span class="cx">
</span><del>- return;
</del><ins>+// Easy API for creating new setFilters
+function setFilters() {}
+setFilters.prototype = Expr.filters = Expr.pseudos;
+Expr.setFilters = new setFilters();
+
+function tokenize( selector, parseOnly ) {
+ var matched, match, tokens, type,
+ soFar, groups, preFilters,
+ cached = tokenCache[ selector + " " ];
+
+ if ( cached ) {
+ return parseOnly ? 0 : cached.slice( 0 );
</ins><span class="cx"> }
</span><span class="cx">
</span><del>- isFunction = jQuery.isFunction( value );
</del><ins>+ soFar = selector;
+ groups = [];
+ preFilters = Expr.preFilter;
</ins><span class="cx">
</span><del>- return this.each(function( i ) {
- var val,
- self = jQuery(this);
</del><ins>+ while ( soFar ) {
</ins><span class="cx">
</span><del>- if ( this.nodeType !== 1 ) {
- return;
- }
</del><ins>+ // Comma and first run
+ if ( !matched || (match = rcomma.exec( soFar )) ) {
+ if ( match ) {
+ // Don't consume trailing commas as valid
+ soFar = soFar.slice( match[0].length ) || soFar;
+ }
+ groups.push( (tokens = []) );
+ }
</ins><span class="cx">
</span><del>- if ( isFunction ) {
- val = value.call( this, i, self.val() );
- } else {
- val = value;
- }
</del><ins>+ matched = false;
</ins><span class="cx">
</span><del>- // Treat null/undefined as ""; convert numbers to string
- if ( val == null ) {
- val = "";
- } else if ( typeof val === "number" ) {
- val += "";
- } else if ( jQuery.isArray( val ) ) {
- val = jQuery.map(val, function ( value ) {
- return value == null ? "" : value + "";
- });
- }
</del><ins>+ // Combinators
+ if ( (match = rcombinators.exec( soFar )) ) {
+ matched = match.shift();
+ tokens.push({
+ value: matched,
+ // Cast descendant combinators to space
+ type: match[0].replace( rtrim, " " )
+ });
+ soFar = soFar.slice( matched.length );
+ }
</ins><span class="cx">
</span><del>- hooks = jQuery.valHooks[ this.type ] || jQuery.valHooks[ this.nodeName.toLowerCase() ];
</del><ins>+ // Filters
+ for ( type in Expr.filter ) {
+ if ( (match = matchExpr[ type ].exec( soFar )) && (!preFilters[ type ] ||
+ (match = preFilters[ type ]( match ))) ) {
+ matched = match.shift();
+ tokens.push({
+ value: matched,
+ type: type,
+ matches: match
+ });
+ soFar = soFar.slice( matched.length );
+ }
+ }
</ins><span class="cx">
</span><del>- // If set returns undefined, fall back to normal setting
- if ( !hooks || !("set" in hooks) || hooks.set( this, val, "value" ) === undefined ) {
- this.value = val;
</del><ins>+ if ( !matched ) {
+ break;
+ }
</ins><span class="cx"> }
</span><del>- });
- }
-});
</del><span class="cx">
</span><del>-jQuery.extend({
- valHooks: {
- option: {
- get: function( elem ) {
- // attributes.value is undefined in Blackberry 4.7 but
- // uses .value. See #6932
- var val = elem.attributes.value;
- return !val || val.specified ? elem.value : elem.text;
</del><ins>+ // Return the length of the invalid excess
+ // if we're just parsing
+ // Otherwise, throw an error or return tokens
+ return parseOnly ?
+ soFar.length :
+ soFar ?
+ Sizzle.error( selector ) :
+ // Cache the tokens
+ tokenCache( selector, groups ).slice( 0 );
+}
+
+function toSelector( tokens ) {
+ var i = 0,
+ len = tokens.length,
+ selector = "";
+ for ( ; i < len; i++ ) {
+ selector += tokens[i].value;
</ins><span class="cx"> }
</span><del>- },
- select: {
- get: function( elem ) {
- var value, option,
- options = elem.options,
- index = elem.selectedIndex,
- one = elem.type === "select-one" || index < 0,
- values = one ? null : [],
- max = one ? index + 1 : options.length,
- i = index < 0 ?
- max :
- one ? index : 0;
</del><ins>+ return selector;
+}
</ins><span class="cx">
</span><del>- // Loop through all the selected options
- for ( ; i < max; i++ ) {
- option = options[ i ];
</del><ins>+function addCombinator( matcher, combinator, base ) {
+ var dir = combinator.dir,
+ checkNonElements = base && dir === "parentNode",
+ doneName = done++;
</ins><span class="cx">
</span><del>- // oldIE doesn't update selected after form reset (#2551)
- if ( ( option.selected || i === index ) &&
- // Don't return options that are disabled or in a disabled optgroup
- ( jQuery.support.optDisabled ? !option.disabled : option.getAttribute("disabled") === null ) &&
- ( !option.parentNode.disabled || !jQuery.nodeName( option.parentNode, "optgroup" ) ) ) {
</del><ins>+ return combinator.first ?
+ // Check against closest ancestor/preceding element
+ function( elem, context, xml ) {
+ while ( (elem = elem[ dir ]) ) {
+ if ( elem.nodeType === 1 || checkNonElements ) {
+ return matcher( elem, context, xml );
+ }
+ }
+ } :
</ins><span class="cx">
</span><del>- // Get the specific value for the option
- value = jQuery( option ).val();
</del><ins>+ // Check against all ancestor/preceding elements
+ function( elem, context, xml ) {
+ var oldCache, outerCache,
+ newCache = [ dirruns, doneName ];
</ins><span class="cx">
</span><del>- // We don't need an array for one selects
- if ( one ) {
- return value;
- }
</del><ins>+ // We can't set arbitrary data on XML nodes, so they don't benefit from dir caching
+ if ( xml ) {
+ while ( (elem = elem[ dir ]) ) {
+ if ( elem.nodeType === 1 || checkNonElements ) {
+ if ( matcher( elem, context, xml ) ) {
+ return true;
+ }
+ }
+ }
+ } else {
+ while ( (elem = elem[ dir ]) ) {
+ if ( elem.nodeType === 1 || checkNonElements ) {
+ outerCache = elem[ expando ] || (elem[ expando ] = {});
+ if ( (oldCache = outerCache[ dir ]) &&
+ oldCache[ 0 ] === dirruns && oldCache[ 1 ] === doneName ) {
</ins><span class="cx">
</span><del>- // Multi-Selects return an array
- values.push( value );
- }
- }
</del><ins>+ // Assign to newCache so results back-propagate to previous elements
+ return (newCache[ 2 ] = oldCache[ 2 ]);
+ } else {
+ // Reuse newcache so results back-propagate to previous elements
+ outerCache[ dir ] = newCache;
</ins><span class="cx">
</span><del>- return values;
- },
</del><ins>+ // A match means we're done; a fail means we have to keep checking
+ if ( (newCache[ 2 ] = matcher( elem, context, xml )) ) {
+ return true;
+ }
+ }
+ }
+ }
+ }
+ };
+}
</ins><span class="cx">
</span><del>- set: function( elem, value ) {
- var values = jQuery.makeArray( value );
</del><ins>+function elementMatcher( matchers ) {
+ return matchers.length > 1 ?
+ function( elem, context, xml ) {
+ var i = matchers.length;
+ while ( i-- ) {
+ if ( !matchers[i]( elem, context, xml ) ) {
+ return false;
+ }
+ }
+ return true;
+ } :
+ matchers[0];
+}
</ins><span class="cx">
</span><del>- jQuery(elem).find("option").each(function() {
- this.selected = jQuery.inArray( jQuery(this).val(), values ) >= 0;
- });
</del><ins>+function condense( unmatched, map, filter, context, xml ) {
+ var elem,
+ newUnmatched = [],
+ i = 0,
+ len = unmatched.length,
+ mapped = map != null;
</ins><span class="cx">
</span><del>- if ( !values.length ) {
- elem.selectedIndex = -1;
</del><ins>+ for ( ; i < len; i++ ) {
+ if ( (elem = unmatched[i]) ) {
+ if ( !filter || filter( elem, context, xml ) ) {
+ newUnmatched.push( elem );
+ if ( mapped ) {
+ map.push( i );
+ }
+ }
+ }
</ins><span class="cx"> }
</span><del>- return values;
- }
- }
- },
</del><span class="cx">
</span><del>- attr: function( elem, name, value ) {
- var hooks, notxml, ret,
- nType = elem.nodeType;
</del><ins>+ return newUnmatched;
+}
</ins><span class="cx">
</span><del>- // don't get/set attributes on text, comment and attribute nodes
- if ( !elem || nType === 3 || nType === 8 || nType === 2 ) {
- return;
</del><ins>+function setMatcher( preFilter, selector, matcher, postFilter, postFinder, postSelector ) {
+ if ( postFilter && !postFilter[ expando ] ) {
+ postFilter = setMatcher( postFilter );
</ins><span class="cx"> }
</span><del>-
- // Fallback to prop when attributes are not supported
- if ( typeof elem.getAttribute === core_strundefined ) {
- return jQuery.prop( elem, name, value );
</del><ins>+ if ( postFinder && !postFinder[ expando ] ) {
+ postFinder = setMatcher( postFinder, postSelector );
</ins><span class="cx"> }
</span><ins>+ return markFunction(function( seed, results, context, xml ) {
+ var temp, i, elem,
+ preMap = [],
+ postMap = [],
+ preexisting = results.length,
</ins><span class="cx">
</span><del>- notxml = nType !== 1 || !jQuery.isXMLDoc( elem );
</del><ins>+ // Get initial elements from seed or context
+ elems = seed || multipleContexts( selector || "*", context.nodeType ? [ context ] : context, [] ),
</ins><span class="cx">
</span><del>- // All attributes are lowercase
- // Grab necessary hook if one is defined
- if ( notxml ) {
- name = name.toLowerCase();
- hooks = jQuery.attrHooks[ name ] || ( rboolean.test( name ) ? boolHook : nodeHook );
- }
</del><ins>+ // Prefilter to get matcher input, preserving a map for seed-results synchronization
+ matcherIn = preFilter && ( seed || !selector ) ?
+ condense( elems, preMap, preFilter, context, xml ) :
+ elems,
</ins><span class="cx">
</span><del>- if ( value !== undefined ) {
</del><ins>+ matcherOut = matcher ?
+ // If we have a postFinder, or filtered seed, or non-seed postFilter or preexisting results,
+ postFinder || ( seed ? preFilter : preexisting || postFilter ) ?
</ins><span class="cx">
</span><del>- if ( value === null ) {
- jQuery.removeAttr( elem, name );
</del><ins>+ // ...intermediate processing is necessary
+ [] :
</ins><span class="cx">
</span><del>- } else if ( hooks && notxml && "set" in hooks && (ret = hooks.set( elem, value, name )) !== undefined ) {
- return ret;
</del><ins>+ // ...otherwise use results directly
+ results :
+ matcherIn;
</ins><span class="cx">
</span><del>- } else {
- elem.setAttribute( name, value + "" );
- return value;
- }
</del><ins>+ // Find primary matches
+ if ( matcher ) {
+ matcher( matcherIn, matcherOut, context, xml );
+ }
</ins><span class="cx">
</span><del>- } else if ( hooks && notxml && "get" in hooks && (ret = hooks.get( elem, name )) !== null ) {
- return ret;
</del><ins>+ // Apply postFilter
+ if ( postFilter ) {
+ temp = condense( matcherOut, postMap );
+ postFilter( temp, [], context, xml );
</ins><span class="cx">
</span><del>- } else {
</del><ins>+ // Un-match failing elements by moving them back to matcherIn
+ i = temp.length;
+ while ( i-- ) {
+ if ( (elem = temp[i]) ) {
+ matcherOut[ postMap[i] ] = !(matcherIn[ postMap[i] ] = elem);
+ }
+ }
+ }
</ins><span class="cx">
</span><del>- // In IE9+, Flash objects don't have .getAttribute (#12945)
- // Support: IE9+
- if ( typeof elem.getAttribute !== core_strundefined ) {
- ret = elem.getAttribute( name );
- }
</del><ins>+ if ( seed ) {
+ if ( postFinder || preFilter ) {
+ if ( postFinder ) {
+ // Get the final matcherOut by condensing this intermediate into postFinder contexts
+ temp = [];
+ i = matcherOut.length;
+ while ( i-- ) {
+ if ( (elem = matcherOut[i]) ) {
+ // Restore matcherIn since elem is not yet a final match
+ temp.push( (matcherIn[i] = elem) );
+ }
+ }
+ postFinder( null, (matcherOut = []), temp, xml );
+ }
</ins><span class="cx">
</span><del>- // Non-existent attributes return null, we normalize to undefined
- return ret == null ?
- undefined :
- ret;
- }
- },
</del><ins>+ // Move matched elements from seed to results to keep them synchronized
+ i = matcherOut.length;
+ while ( i-- ) {
+ if ( (elem = matcherOut[i]) &&
+ (temp = postFinder ? indexOf.call( seed, elem ) : preMap[i]) > -1 ) {
</ins><span class="cx">
</span><del>- removeAttr: function( elem, value ) {
- var name, propName,
- i = 0,
- attrNames = value && value.match( core_rnotwhite );
</del><ins>+ seed[temp] = !(results[temp] = elem);
+ }
+ }
+ }
</ins><span class="cx">
</span><del>- if ( attrNames && elem.nodeType === 1 ) {
- while ( (name = attrNames[i++]) ) {
- propName = jQuery.propFix[ name ] || name;
</del><ins>+ // Add elements to results, through postFinder if defined
+ } else {
+ matcherOut = condense(
+ matcherOut === results ?
+ matcherOut.splice( preexisting, matcherOut.length ) :
+ matcherOut
+ );
+ if ( postFinder ) {
+ postFinder( null, results, matcherOut, xml );
+ } else {
+ push.apply( results, matcherOut );
+ }
+ }
+ });
+}
</ins><span class="cx">
</span><del>- // Boolean attributes get special treatment (#10870)
- if ( rboolean.test( name ) ) {
- // Set corresponding property to false for boolean attributes
- // Also clear defaultChecked/defaultSelected (if appropriate) for IE<8
- if ( !getSetAttribute && ruseDefault.test( name ) ) {
- elem[ jQuery.camelCase( "default-" + name ) ] =
- elem[ propName ] = false;
- } else {
- elem[ propName ] = false;
- }
</del><ins>+function matcherFromTokens( tokens ) {
+ var checkContext, matcher, j,
+ len = tokens.length,
+ leadingRelative = Expr.relative[ tokens[0].type ],
+ implicitRelative = leadingRelative || Expr.relative[" "],
+ i = leadingRelative ? 1 : 0,
</ins><span class="cx">
</span><del>- // See #9699 for explanation of this approach (setting first, then removal)
- } else {
- jQuery.attr( elem, name, "" );
- }
</del><ins>+ // The foundational matcher ensures that elements are reachable from top-level context(s)
+ matchContext = addCombinator( function( elem ) {
+ return elem === checkContext;
+ }, implicitRelative, true ),
+ matchAnyContext = addCombinator( function( elem ) {
+ return indexOf.call( checkContext, elem ) > -1;
+ }, implicitRelative, true ),
+ matchers = [ function( elem, context, xml ) {
+ return ( !leadingRelative && ( xml || context !== outermostContext ) ) || (
+ (checkContext = context).nodeType ?
+ matchContext( elem, context, xml ) :
+ matchAnyContext( elem, context, xml ) );
+ } ];
</ins><span class="cx">
</span><del>- elem.removeAttribute( getSetAttribute ? name : propName );
- }
- }
- },
</del><ins>+ for ( ; i < len; i++ ) {
+ if ( (matcher = Expr.relative[ tokens[i].type ]) ) {
+ matchers = [ addCombinator(elementMatcher( matchers ), matcher) ];
+ } else {
+ matcher = Expr.filter[ tokens[i].type ].apply( null, tokens[i].matches );
</ins><span class="cx">
</span><del>- attrHooks: {
- type: {
- set: function( elem, value ) {
- if ( !jQuery.support.radioValue && value === "radio" && jQuery.nodeName(elem, "input") ) {
- // Setting the type on a radio button after the value resets the value in IE6-9
- // Reset value to default in case type is set after value during creation
- var val = elem.value;
- elem.setAttribute( "type", value );
- if ( val ) {
- elem.value = val;
</del><ins>+ // Return special upon seeing a positional matcher
+ if ( matcher[ expando ] ) {
+ // Find the next relative operator (if any) for proper handling
+ j = ++i;
+ for ( ; j < len; j++ ) {
+ if ( Expr.relative[ tokens[j].type ] ) {
+ break;
+ }
+ }
+ return setMatcher(
+ i > 1 && elementMatcher( matchers ),
+ i > 1 && toSelector(
+ // If the preceding token was a descendant combinator, insert an implicit any-element `*`
+ tokens.slice( 0, i - 1 ).concat({ value: tokens[ i - 2 ].type === " " ? "*" : "" })
+ ).replace( rtrim, "$1" ),
+ matcher,
+ i < j && matcherFromTokens( tokens.slice( i, j ) ),
+ j < len && matcherFromTokens( (tokens = tokens.slice( j )) ),
+ j < len && toSelector( tokens )
+ );
+ }
+ matchers.push( matcher );
+ }
</ins><span class="cx"> }
</span><del>- return value;
- }
- }
- }
- },
</del><span class="cx">
</span><del>- propFix: {
- tabindex: "tabIndex",
- readonly: "readOnly",
- "for": "htmlFor",
- "class": "className",
- maxlength: "maxLength",
- cellspacing: "cellSpacing",
- cellpadding: "cellPadding",
- rowspan: "rowSpan",
- colspan: "colSpan",
- usemap: "useMap",
- frameborder: "frameBorder",
- contenteditable: "contentEditable"
- },
</del><ins>+ return elementMatcher( matchers );
+}
</ins><span class="cx">
</span><del>- prop: function( elem, name, value ) {
- var ret, hooks, notxml,
- nType = elem.nodeType;
</del><ins>+function matcherFromGroupMatchers( elementMatchers, setMatchers ) {
+ var bySet = setMatchers.length > 0,
+ byElement = elementMatchers.length > 0,
+ superMatcher = function( seed, context, xml, results, outermost ) {
+ var elem, j, matcher,
+ matchedCount = 0,
+ i = "0",
+ unmatched = seed && [],
+ setMatched = [],
+ contextBackup = outermostContext,
+ // We must always have either seed elements or outermost context
+ elems = seed || byElement && Expr.find["TAG"]( "*", outermost ),
+ // Use integer dirruns iff this is the outermost matcher
+ dirrunsUnique = (dirruns += contextBackup == null ? 1 : Math.random() || 0.1),
+ len = elems.length;
</ins><span class="cx">
</span><del>- // don't get/set properties on text, comment and attribute nodes
- if ( !elem || nType === 3 || nType === 8 || nType === 2 ) {
- return;
- }
</del><ins>+ if ( outermost ) {
+ outermostContext = context !== document && context;
+ }
</ins><span class="cx">
</span><del>- notxml = nType !== 1 || !jQuery.isXMLDoc( elem );
</del><ins>+ // Add elements passing elementMatchers directly to results
+ // Keep `i` a string if there are no elements so `matchedCount` will be "00" below
+ // Support: IE<9, Safari
+ // Tolerate NodeList properties (IE: "length"; Safari: <number>) matching elements by id
+ for ( ; i !== len && (elem = elems[i]) != null; i++ ) {
+ if ( byElement && elem ) {
+ j = 0;
+ while ( (matcher = elementMatchers[j++]) ) {
+ if ( matcher( elem, context, xml ) ) {
+ results.push( elem );
+ break;
+ }
+ }
+ if ( outermost ) {
+ dirruns = dirrunsUnique;
+ }
+ }
</ins><span class="cx">
</span><del>- if ( notxml ) {
- // Fix name and attach hooks
- name = jQuery.propFix[ name ] || name;
- hooks = jQuery.propHooks[ name ];
- }
</del><ins>+ // Track unmatched elements for set filters
+ if ( bySet ) {
+ // They will have gone through all possible matchers
+ if ( (elem = !matcher && elem) ) {
+ matchedCount--;
+ }
</ins><span class="cx">
</span><del>- if ( value !== undefined ) {
- if ( hooks && "set" in hooks && (ret = hooks.set( elem, value, name )) !== undefined ) {
- return ret;
</del><ins>+ // Lengthen the array for every element, matched or not
+ if ( seed ) {
+ unmatched.push( elem );
+ }
+ }
+ }
</ins><span class="cx">
</span><del>- } else {
- return ( elem[ name ] = value );
- }
</del><ins>+ // Apply set filters to unmatched elements
+ matchedCount += i;
+ if ( bySet && i !== matchedCount ) {
+ j = 0;
+ while ( (matcher = setMatchers[j++]) ) {
+ matcher( unmatched, setMatched, context, xml );
+ }
</ins><span class="cx">
</span><del>- } else {
- if ( hooks && "get" in hooks && (ret = hooks.get( elem, name )) !== null ) {
- return ret;
</del><ins>+ if ( seed ) {
+ // Reintegrate element matches to eliminate the need for sorting
+ if ( matchedCount > 0 ) {
+ while ( i-- ) {
+ if ( !(unmatched[i] || setMatched[i]) ) {
+ setMatched[i] = pop.call( results );
+ }
+ }
+ }
</ins><span class="cx">
</span><del>- } else {
- return elem[ name ];
- }
- }
- },
</del><ins>+ // Discard index placeholder values to get only actual matches
+ setMatched = condense( setMatched );
+ }
</ins><span class="cx">
</span><del>- propHooks: {
- tabIndex: {
- get: function( elem ) {
- // elem.tabIndex doesn't always return the correct value when it hasn't been explicitly set
- // http://fluidproject.org/blog/2008/01/09/getting-setting-and-removing-tabindex-values-with-javascript/
- var attributeNode = elem.getAttributeNode("tabindex");
</del><ins>+ // Add matches to results
+ push.apply( results, setMatched );
</ins><span class="cx">
</span><del>- return attributeNode && attributeNode.specified ?
- parseInt( attributeNode.value, 10 ) :
- rfocusable.test( elem.nodeName ) || rclickable.test( elem.nodeName ) && elem.href ?
- 0 :
- undefined;
- }
- }
- }
-});
</del><ins>+ // Seedless set matches succeeding multiple successful matchers stipulate sorting
+ if ( outermost && !seed && setMatched.length > 0 &&
+ ( matchedCount + setMatchers.length ) > 1 ) {
</ins><span class="cx">
</span><del>-// Hook for boolean attributes
-boolHook = {
- get: function( elem, name ) {
- var
- // Use .prop to determine if this attribute is understood as boolean
- prop = jQuery.prop( elem, name ),
</del><ins>+ Sizzle.uniqueSort( results );
+ }
+ }
</ins><span class="cx">
</span><del>- // Fetch it accordingly
- attr = typeof prop === "boolean" && elem.getAttribute( name ),
- detail = typeof prop === "boolean" ?
</del><ins>+ // Override manipulation of globals by nested matchers
+ if ( outermost ) {
+ dirruns = dirrunsUnique;
+ outermostContext = contextBackup;
+ }
</ins><span class="cx">
</span><del>- getSetInput && getSetAttribute ?
- attr != null :
- // oldIE fabricates an empty string for missing boolean attributes
- // and conflates checked/selected into attroperties
- ruseDefault.test( name ) ?
- elem[ jQuery.camelCase( "default-" + name ) ] :
- !!attr :
</del><ins>+ return unmatched;
+ };
</ins><span class="cx">
</span><del>- // fetch an attribute node for properties not recognized as boolean
- elem.getAttributeNode( name );
</del><ins>+ return bySet ?
+ markFunction( superMatcher ) :
+ superMatcher;
+}
</ins><span class="cx">
</span><del>- return detail && detail.value !== false ?
- name.toLowerCase() :
- undefined;
- },
- set: function( elem, value, name ) {
- if ( value === false ) {
- // Remove boolean attributes when set to false
- jQuery.removeAttr( elem, name );
- } else if ( getSetInput && getSetAttribute || !ruseDefault.test( name ) ) {
- // IE<8 needs the *property* name
- elem.setAttribute( !getSetAttribute && jQuery.propFix[ name ] || name, name );
</del><ins>+compile = Sizzle.compile = function( selector, group /* Internal Use Only */ ) {
+ var i,
+ setMatchers = [],
+ elementMatchers = [],
+ cached = compilerCache[ selector + " " ];
</ins><span class="cx">
</span><del>- // Use defaultChecked and defaultSelected for oldIE
- } else {
- elem[ jQuery.camelCase( "default-" + name ) ] = elem[ name ] = true;
- }
</del><ins>+ if ( !cached ) {
+ // Generate a function of recursive functions that can be used to check each element
+ if ( !group ) {
+ group = tokenize( selector );
+ }
+ i = group.length;
+ while ( i-- ) {
+ cached = matcherFromTokens( group[i] );
+ if ( cached[ expando ] ) {
+ setMatchers.push( cached );
+ } else {
+ elementMatchers.push( cached );
+ }
+ }
</ins><span class="cx">
</span><del>- return name;
</del><ins>+ // Cache the compiled function
+ cached = compilerCache( selector, matcherFromGroupMatchers( elementMatchers, setMatchers ) );
</ins><span class="cx"> }
</span><ins>+ return cached;
</ins><span class="cx"> };
</span><span class="cx">
</span><del>-// fix oldIE value attroperty
-if ( !getSetInput || !getSetAttribute ) {
- jQuery.attrHooks.value = {
- get: function( elem, name ) {
- var ret = elem.getAttributeNode( name );
- return jQuery.nodeName( elem, "input" ) ?
-
- // Ignore the value *property* by using defaultValue
- elem.defaultValue :
-
- ret && ret.specified ? ret.value : undefined;
- },
- set: function( elem, value, name ) {
- if ( jQuery.nodeName( elem, "input" ) ) {
- // Does not return so that setAttribute is also used
- elem.defaultValue = value;
- } else {
- // Use nodeHook if defined (#1954); otherwise setAttribute is fine
- return nodeHook && nodeHook.set( elem, value, name );
</del><ins>+function multipleContexts( selector, contexts, results ) {
+ var i = 0,
+ len = contexts.length;
+ for ( ; i < len; i++ ) {
+ Sizzle( selector, contexts[i], results );
</ins><span class="cx"> }
</span><del>- }
- };
</del><ins>+ return results;
</ins><span class="cx"> }
</span><span class="cx">
</span><del>-// IE6/7 do not support getting/setting some attributes with get/setAttribute
-if ( !getSetAttribute ) {
</del><ins>+function select( selector, context, results, seed ) {
+ var i, tokens, token, type, find,
+ match = tokenize( selector );
</ins><span class="cx">
</span><del>- // Use this for any attribute in IE6/7
- // This fixes almost every IE6/7 issue
- nodeHook = jQuery.valHooks.button = {
- get: function( elem, name ) {
- var ret = elem.getAttributeNode( name );
- return ret && ( name === "id" || name === "name" || name === "coords" ? ret.value !== "" : ret.specified ) ?
- ret.value :
- undefined;
- },
- set: function( elem, value, name ) {
- // Set the existing or create a new attribute node
- var ret = elem.getAttributeNode( name );
- if ( !ret ) {
- elem.setAttributeNode(
- (ret = elem.ownerDocument.createAttribute( name ))
- );
- }
</del><ins>+ if ( !seed ) {
+ // Try to minimize operations if there is only one group
+ if ( match.length === 1 ) {
</ins><span class="cx">
</span><del>- ret.value = value += "";
</del><ins>+ // Take a shortcut and set the context if the root selector is an ID
+ tokens = match[0] = match[0].slice( 0 );
+ if ( tokens.length > 2 && (token = tokens[0]).type === "ID" &&
+ support.getById && context.nodeType === 9 && documentIsHTML &&
+ Expr.relative[ tokens[1].type ] ) {
</ins><span class="cx">
</span><del>- // Break association with cloned elements by also using setAttribute (#9646)
- return name === "value" || value === elem.getAttribute( name ) ?
- value :
- undefined;
- }
- };
</del><ins>+ context = ( Expr.find["ID"]( token.matches[0].replace(runescape, funescape), context ) || [] )[0];
+ if ( !context ) {
+ return results;
+ }
+ selector = selector.slice( tokens.shift().value.length );
+ }
</ins><span class="cx">
</span><del>- // Set contenteditable to false on removals(#10429)
- // Setting to empty string throws an error as an invalid value
- jQuery.attrHooks.contenteditable = {
- get: nodeHook.get,
- set: function( elem, value, name ) {
- nodeHook.set( elem, value === "" ? false : value, name );
- }
- };
</del><ins>+ // Fetch a seed set for right-to-left matching
+ i = matchExpr["needsContext"].test( selector ) ? 0 : tokens.length;
+ while ( i-- ) {
+ token = tokens[i];
</ins><span class="cx">
</span><del>- // Set width and height to auto instead of 0 on empty string( Bug #8150 )
- // This is for removals
- jQuery.each([ "width", "height" ], function( i, name ) {
- jQuery.attrHooks[ name ] = jQuery.extend( jQuery.attrHooks[ name ], {
- set: function( elem, value ) {
- if ( value === "" ) {
- elem.setAttribute( name, "auto" );
- return value;
</del><ins>+ // Abort if we hit a combinator
+ if ( Expr.relative[ (type = token.type) ] ) {
+ break;
+ }
+ if ( (find = Expr.find[ type ]) ) {
+ // Search, expanding context for leading sibling combinators
+ if ( (seed = find(
+ token.matches[0].replace( runescape, funescape ),
+ rsibling.test( tokens[0].type ) && testContext( context.parentNode ) || context
+ )) ) {
+
+ // If seed is empty or no tokens remain, we can return early
+ tokens.splice( i, 1 );
+ selector = seed.length && toSelector( tokens );
+ if ( !selector ) {
+ push.apply( results, seed );
+ return results;
+ }
+
+ break;
+ }
+ }
+ }
+ }
</ins><span class="cx"> }
</span><del>- }
- });
- });
</del><ins>+
+ // Compile and execute a filtering function
+ // Provide `match` to avoid retokenization if we modified the selector above
+ compile( selector, match )(
+ seed,
+ context,
+ !documentIsHTML,
+ results,
+ rsibling.test( selector ) && testContext( context.parentNode ) || context
+ );
+ return results;
</ins><span class="cx"> }
</span><span class="cx">
</span><ins>+// One-time assignments
</ins><span class="cx">
</span><del>-// Some attributes require a special call on IE
</del><ins>+// Sort stability
+support.sortStable = expando.split("").sort( sortOrder ).join("") === expando;
+
+// Support: Chrome<14
+// Always assume duplicates if they aren't passed to the comparison function
+support.detectDuplicates = !!hasDuplicate;
+
+// Initialize against the default document
+setDocument();
+
+// Support: Webkit<537.32 - Safari 6.0.3/Chrome 25 (fixed in Chrome 27)
+// Detached nodes confoundingly follow *each other*
+support.sortDetached = assert(function( div1 ) {
+ // Should return 1, but returns 4 (following)
+ return div1.compareDocumentPosition( document.createElement("div") ) & 1;
+});
+
+// Support: IE<8
+// Prevent attribute/property "interpolation"
</ins><span class="cx"> // http://msdn.microsoft.com/en-us/library/ms536429%28VS.85%29.aspx
</span><del>-if ( !jQuery.support.hrefNormalized ) {
- jQuery.each([ "href", "src", "width", "height" ], function( i, name ) {
- jQuery.attrHooks[ name ] = jQuery.extend( jQuery.attrHooks[ name ], {
- get: function( elem ) {
- var ret = elem.getAttribute( name, 2 );
- return ret == null ? undefined : ret;
- }
</del><ins>+if ( !assert(function( div ) {
+ div.innerHTML = "<a href='#'></a>";
+ return div.firstChild.getAttribute("href") === "#" ;
+}) ) {
+ addHandle( "type|href|height|width", function( elem, name, isXML ) {
+ if ( !isXML ) {
+ return elem.getAttribute( name, name.toLowerCase() === "type" ? 1 : 2 );
+ }
</ins><span class="cx"> });
</span><del>- });
</del><ins>+}
</ins><span class="cx">
</span><del>- // href/src property should get the full normalized URL (#10299/#12915)
- jQuery.each([ "href", "src" ], function( i, name ) {
- jQuery.propHooks[ name ] = {
- get: function( elem ) {
- return elem.getAttribute( name, 4 );
- }
- };
</del><ins>+// Support: IE<9
+// Use defaultValue in place of getAttribute("value")
+if ( !support.attributes || !assert(function( div ) {
+ div.innerHTML = "<input/>";
+ div.firstChild.setAttribute( "value", "" );
+ return div.firstChild.getAttribute( "value" ) === "";
+}) ) {
+ addHandle( "value", function( elem, name, isXML ) {
+ if ( !isXML && elem.nodeName.toLowerCase() === "input" ) {
+ return elem.defaultValue;
+ }
</ins><span class="cx"> });
</span><span class="cx"> }
</span><span class="cx">
</span><del>-if ( !jQuery.support.style ) {
- jQuery.attrHooks.style = {
- get: function( elem ) {
- // Return undefined in the case of empty string
- // Note: IE uppercases css property names, but if we were to .toLowerCase()
- // .cssText, that would destroy case senstitivity in URL's, like in "background"
- return elem.style.cssText || undefined;
- },
- set: function( elem, value ) {
- return ( elem.style.cssText = value + "" );
- }
- };
</del><ins>+// Support: IE<9
+// Use getAttributeNode to fetch booleans when getAttribute lies
+if ( !assert(function( div ) {
+ return div.getAttribute("disabled") == null;
+}) ) {
+ addHandle( booleans, function( elem, name, isXML ) {
+ var val;
+ if ( !isXML ) {
+ return elem[ name ] === true ? name.toLowerCase() :
+ (val = elem.getAttributeNode( name )) && val.specified ?
+ val.value :
+ null;
+ }
+ });
</ins><span class="cx"> }
</span><span class="cx">
</span><del>-// Safari mis-reports the default selected property of an option
-// Accessing the parent's selectedIndex property fixes it
-if ( !jQuery.support.optSelected ) {
- jQuery.propHooks.selected = jQuery.extend( jQuery.propHooks.selected, {
- get: function( elem ) {
- var parent = elem.parentNode;
</del><ins>+return Sizzle;
</ins><span class="cx">
</span><del>- if ( parent ) {
- parent.selectedIndex;
</del><ins>+})( window );
</ins><span class="cx">
</span><del>- // Make sure that it also works with optgroups, see #5701
- if ( parent.parentNode ) {
- parent.parentNode.selectedIndex;
</del><ins>+
+
+jQuery.find = Sizzle;
+jQuery.expr = Sizzle.selectors;
+jQuery.expr[":"] = jQuery.expr.pseudos;
+jQuery.unique = Sizzle.uniqueSort;
+jQuery.text = Sizzle.getText;
+jQuery.isXMLDoc = Sizzle.isXML;
+jQuery.contains = Sizzle.contains;
+
+
+
+var rneedsContext = jQuery.expr.match.needsContext;
+
+var rsingleTag = (/^<(\w+)\s*\/?>(?:<\/\1>|)$/);
+
+
+
+var risSimple = /^.[^:#\[\.,]*$/;
+
+// Implement the identical functionality for filter and not
+function winnow( elements, qualifier, not ) {
+ if ( jQuery.isFunction( qualifier ) ) {
+ return jQuery.grep( elements, function( elem, i ) {
+ /* jshint -W018 */
+ return !!qualifier.call( elem, i, elem ) !== not;
+ });
+
</ins><span class="cx"> }
</span><ins>+
+ if ( qualifier.nodeType ) {
+ return jQuery.grep( elements, function( elem ) {
+ return ( elem === qualifier ) !== not;
+ });
+
</ins><span class="cx"> }
</span><del>- return null;
</del><ins>+
+ if ( typeof qualifier === "string" ) {
+ if ( risSimple.test( qualifier ) ) {
+ return jQuery.filter( qualifier, elements, not );
+ }
+
+ qualifier = jQuery.filter( qualifier, elements );
</ins><span class="cx"> }
</span><ins>+
+ return jQuery.grep( elements, function( elem ) {
+ return ( indexOf.call( qualifier, elem ) >= 0 ) !== not;
</ins><span class="cx"> });
</span><span class="cx"> }
</span><span class="cx">
</span><del>-// IE6/7 call enctype encoding
-if ( !jQuery.support.enctype ) {
- jQuery.propFix.enctype = "encoding";
-}
</del><ins>+jQuery.filter = function( expr, elems, not ) {
+ var elem = elems[ 0 ];
</ins><span class="cx">
</span><del>-// Radios and checkboxes getter/setter
-if ( !jQuery.support.checkOn ) {
- jQuery.each([ "radio", "checkbox" ], function() {
- jQuery.valHooks[ this ] = {
- get: function( elem ) {
- // Handle the case where in Webkit "" is returned instead of "on" if a value isn't specified
- return elem.getAttribute("value") === null ? "on" : elem.value;
</del><ins>+ if ( not ) {
+ expr = ":not(" + expr + ")";
</ins><span class="cx"> }
</span><del>- };
- });
-}
-jQuery.each([ "radio", "checkbox" ], function() {
- jQuery.valHooks[ this ] = jQuery.extend( jQuery.valHooks[ this ], {
- set: function( elem, value ) {
- if ( jQuery.isArray( value ) ) {
- return ( elem.checked = jQuery.inArray( jQuery(elem).val(), value ) >= 0 );
- }
- }
- });
-});
-var rformElems = /^(?:input|select|textarea)$/i,
- rkeyEvent = /^key/,
- rmouseEvent = /^(?:mouse|contextmenu)|click/,
- rfocusMorph = /^(?:focusinfocus|focusoutblur)$/,
- rtypenamespace = /^([^.]*)(?:\.(.+)|)$/;
</del><span class="cx">
</span><del>-function returnTrue() {
- return true;
-}
</del><ins>+ return elems.length === 1 && elem.nodeType === 1 ?
+ jQuery.find.matchesSelector( elem, expr ) ? [ elem ] : [] :
+ jQuery.find.matches( expr, jQuery.grep( elems, function( elem ) {
+ return elem.nodeType === 1;
+ }));
+};
</ins><span class="cx">
</span><del>-function returnFalse() {
- return false;
-}
</del><ins>+jQuery.fn.extend({
+ find: function( selector ) {
+ var i,
+ len = this.length,
+ ret = [],
+ self = this;
</ins><span class="cx">
</span><del>-/*
- * Helper functions for managing events -- not part of the public interface.
- * Props to Dean Edwards' addEvent library for many of the ideas.
- */
-jQuery.event = {
</del><ins>+ if ( typeof selector !== "string" ) {
+ return this.pushStack( jQuery( selector ).filter(function() {
+ for ( i = 0; i < len; i++ ) {
+ if ( jQuery.contains( self[ i ], this ) ) {
+ return true;
+ }
+ }
+ }) );
+ }
</ins><span class="cx">
</span><del>- global: {},
</del><ins>+ for ( i = 0; i < len; i++ ) {
+ jQuery.find( selector, self[ i ], ret );
+ }
</ins><span class="cx">
</span><del>- add: function( elem, types, handler, data, selector ) {
- var tmp, events, t, handleObjIn,
- special, eventHandle, handleObj,
- handlers, type, namespaces, origType,
- elemData = jQuery._data( elem );
</del><ins>+ // Needed because $( selector, context ) becomes $( context ).find( selector )
+ ret = this.pushStack( len > 1 ? jQuery.unique( ret ) : ret );
+ ret.selector = this.selector ? this.selector + " " + selector : selector;
+ return ret;
+ },
+ filter: function( selector ) {
+ return this.pushStack( winnow(this, selector || [], false) );
+ },
+ not: function( selector ) {
+ return this.pushStack( winnow(this, selector || [], true) );
+ },
+ is: function( selector ) {
+ return !!winnow(
+ this,
</ins><span class="cx">
</span><del>- // Don't attach events to noData or text/comment nodes (but allow plain objects)
- if ( !elemData ) {
- return;
</del><ins>+ // If this is a positional/relative selector, check membership in the returned set
+ // so $("p:first").is("p:last") won't return true for a doc with two "p".
+ typeof selector === "string" && rneedsContext.test( selector ) ?
+ jQuery( selector ) :
+ selector || [],
+ false
+ ).length;
</ins><span class="cx"> }
</span><ins>+});
</ins><span class="cx">
</span><del>- // Caller can pass in an object of custom data in lieu of the handler
- if ( handler.handler ) {
- handleObjIn = handler;
- handler = handleObjIn.handler;
- selector = handleObjIn.selector;
- }
</del><span class="cx">
</span><del>- // Make sure that the handler has a unique ID, used to find/remove it later
- if ( !handler.guid ) {
- handler.guid = jQuery.guid++;
- }
</del><ins>+// Initialize a jQuery object
</ins><span class="cx">
</span><del>- // Init the element's event structure and main handler, if this is the first
- if ( !(events = elemData.events) ) {
- events = elemData.events = {};
- }
- if ( !(eventHandle = elemData.handle) ) {
- eventHandle = elemData.handle = function( e ) {
- // Discard the second event of a jQuery.event.trigger() and
- // when an event is called after a page has unloaded
- return typeof jQuery !== core_strundefined && (!e || jQuery.event.triggered !== e.type) ?
- jQuery.event.dispatch.apply( eventHandle.elem, arguments ) :
- undefined;
- };
- // Add elem as a property of the handle fn to prevent a memory leak with IE non-native events
- eventHandle.elem = elem;
- }
</del><span class="cx">
</span><del>- // Handle multiple events separated by a space
- // jQuery(...).bind("mouseover mouseout", fn);
- types = ( types || "" ).match( core_rnotwhite ) || [""];
- t = types.length;
- while ( t-- ) {
- tmp = rtypenamespace.exec( types[t] ) || [];
- type = origType = tmp[1];
- namespaces = ( tmp[2] || "" ).split( "." ).sort();
</del><ins>+// A central reference to the root jQuery(document)
+var rootjQuery,
</ins><span class="cx">
</span><del>- // If event changes its type, use the special event handlers for the changed type
- special = jQuery.event.special[ type ] || {};
</del><ins>+ // A simple way to check for HTML strings
+ // Prioritize #id over <tag> to avoid XSS via location.hash (#9521)
+ // Strict HTML recognition (#11290: must start with <)
+ rquickExpr = /^(?:\s*(<[\w\W]+>)[^>]*|#([\w-]*))$/,
</ins><span class="cx">
</span><del>- // If selector defined, determine special event api type, otherwise given type
- type = ( selector ? special.delegateType : special.bindType ) || type;
</del><ins>+ init = jQuery.fn.init = function( selector, context ) {
+ var match, elem;
</ins><span class="cx">
</span><del>- // Update special based on newly reset type
- special = jQuery.event.special[ type ] || {};
</del><ins>+ // HANDLE: $(""), $(null), $(undefined), $(false)
+ if ( !selector ) {
+ return this;
+ }
</ins><span class="cx">
</span><del>- // handleObj is passed to all event handlers
- handleObj = jQuery.extend({
- type: type,
- origType: origType,
- data: data,
- handler: handler,
- guid: handler.guid,
- selector: selector,
- needsContext: selector && jQuery.expr.match.needsContext.test( selector ),
- namespace: namespaces.join(".")
- }, handleObjIn );
</del><ins>+ // Handle HTML strings
+ if ( typeof selector === "string" ) {
+ if ( selector[0] === "<" && selector[ selector.length - 1 ] === ">" && selector.length >= 3 ) {
+ // Assume that strings that start and end with <> are HTML and skip the regex check
+ match = [ null, selector, null ];
</ins><span class="cx">
</span><del>- // Init the event handler queue if we're the first
- if ( !(handlers = events[ type ]) ) {
- handlers = events[ type ] = [];
- handlers.delegateCount = 0;
</del><ins>+ } else {
+ match = rquickExpr.exec( selector );
+ }
</ins><span class="cx">
</span><del>- // Only use addEventListener/attachEvent if the special events handler returns false
- if ( !special.setup || special.setup.call( elem, data, namespaces, eventHandle ) === false ) {
- // Bind the global event handler to the element
- if ( elem.addEventListener ) {
- elem.addEventListener( type, eventHandle, false );
</del><ins>+ // Match html or make sure no context is specified for #id
+ if ( match && (match[1] || !context) ) {
</ins><span class="cx">
</span><del>- } else if ( elem.attachEvent ) {
- elem.attachEvent( "on" + type, eventHandle );
- }
- }
- }
</del><ins>+ // HANDLE: $(html) -> $(array)
+ if ( match[1] ) {
+ context = context instanceof jQuery ? context[0] : context;
</ins><span class="cx">
</span><del>- if ( special.add ) {
- special.add.call( elem, handleObj );
</del><ins>+ // scripts is true for back-compat
+ // Intentionally let the error be thrown if parseHTML is not present
+ jQuery.merge( this, jQuery.parseHTML(
+ match[1],
+ context && context.nodeType ? context.ownerDocument || context : document,
+ true
+ ) );
</ins><span class="cx">
</span><del>- if ( !handleObj.handler.guid ) {
- handleObj.handler.guid = handler.guid;
- }
- }
</del><ins>+ // HANDLE: $(html, props)
+ if ( rsingleTag.test( match[1] ) && jQuery.isPlainObject( context ) ) {
+ for ( match in context ) {
+ // Properties of context are called as methods if possible
+ if ( jQuery.isFunction( this[ match ] ) ) {
+ this[ match ]( context[ match ] );
</ins><span class="cx">
</span><del>- // Add to the element's handler list, delegates in front
- if ( selector ) {
- handlers.splice( handlers.delegateCount++, 0, handleObj );
- } else {
- handlers.push( handleObj );
- }
</del><ins>+ // ...and otherwise set as attributes
+ } else {
+ this.attr( match, context[ match ] );
+ }
+ }
+ }
</ins><span class="cx">
</span><del>- // Keep track of which events have ever been used, for event optimization
- jQuery.event.global[ type ] = true;
- }
</del><ins>+ return this;
</ins><span class="cx">
</span><del>- // Nullify elem to prevent memory leaks in IE
- elem = null;
- },
</del><ins>+ // HANDLE: $(#id)
+ } else {
+ elem = document.getElementById( match[2] );
</ins><span class="cx">
</span><del>- // Detach an event or set of events from an element
- remove: function( elem, types, handler, selector, mappedTypes ) {
- var j, handleObj, tmp,
- origCount, t, events,
- special, handlers, type,
- namespaces, origType,
- elemData = jQuery.hasData( elem ) && jQuery._data( elem );
</del><ins>+ // Check parentNode to catch when Blackberry 4.6 returns
+ // nodes that are no longer in the document #6963
+ if ( elem && elem.parentNode ) {
+ // Inject the element directly into the jQuery object
+ this.length = 1;
+ this[0] = elem;
+ }
</ins><span class="cx">
</span><del>- if ( !elemData || !(events = elemData.events) ) {
- return;
- }
</del><ins>+ this.context = document;
+ this.selector = selector;
+ return this;
+ }
</ins><span class="cx">
</span><del>- // Once for each type.namespace in types; type may be omitted
- types = ( types || "" ).match( core_rnotwhite ) || [""];
- t = types.length;
- while ( t-- ) {
- tmp = rtypenamespace.exec( types[t] ) || [];
- type = origType = tmp[1];
- namespaces = ( tmp[2] || "" ).split( "." ).sort();
</del><ins>+ // HANDLE: $(expr, $(...))
+ } else if ( !context || context.jquery ) {
+ return ( context || rootjQuery ).find( selector );
</ins><span class="cx">
</span><del>- // Unbind all events (on this namespace, if provided) for the element
- if ( !type ) {
- for ( type in events ) {
- jQuery.event.remove( elem, type + types[ t ], handler, selector, true );
- }
- continue;
- }
</del><ins>+ // HANDLE: $(expr, context)
+ // (which is just equivalent to: $(context).find(expr)
+ } else {
+ return this.constructor( context ).find( selector );
+ }
</ins><span class="cx">
</span><del>- special = jQuery.event.special[ type ] || {};
- type = ( selector ? special.delegateType : special.bindType ) || type;
- handlers = events[ type ] || [];
- tmp = tmp[2] && new RegExp( "(^|\\.)" + namespaces.join("\\.(?:.*\\.|)") + "(\\.|$)" );
</del><ins>+ // HANDLE: $(DOMElement)
+ } else if ( selector.nodeType ) {
+ this.context = this[0] = selector;
+ this.length = 1;
+ return this;
</ins><span class="cx">
</span><del>- // Remove matching events
- origCount = j = handlers.length;
- while ( j-- ) {
- handleObj = handlers[ j ];
</del><ins>+ // HANDLE: $(function)
+ // Shortcut for document ready
+ } else if ( jQuery.isFunction( selector ) ) {
+ return typeof rootjQuery.ready !== "undefined" ?
+ rootjQuery.ready( selector ) :
+ // Execute immediately if ready is not present
+ selector( jQuery );
+ }
</ins><span class="cx">
</span><del>- if ( ( mappedTypes || origType === handleObj.origType ) &&
- ( !handler || handler.guid === handleObj.guid ) &&
- ( !tmp || tmp.test( handleObj.namespace ) ) &&
- ( !selector || selector === handleObj.selector || selector === "**" && handleObj.selector ) ) {
- handlers.splice( j, 1 );
</del><ins>+ if ( selector.selector !== undefined ) {
+ this.selector = selector.selector;
+ this.context = selector.context;
+ }
</ins><span class="cx">
</span><del>- if ( handleObj.selector ) {
- handlers.delegateCount--;
- }
- if ( special.remove ) {
- special.remove.call( elem, handleObj );
- }
- }
- }
</del><ins>+ return jQuery.makeArray( selector, this );
+ };
</ins><span class="cx">
</span><del>- // Remove generic event handler if we removed something and no more handlers exist
- // (avoids potential for endless recursion during removal of special event handlers)
- if ( origCount && !handlers.length ) {
- if ( !special.teardown || special.teardown.call( elem, namespaces, elemData.handle ) === false ) {
- jQuery.removeEvent( elem, type, elemData.handle );
- }
</del><ins>+// Give the init function the jQuery prototype for later instantiation
+init.prototype = jQuery.fn;
</ins><span class="cx">
</span><del>- delete events[ type ];
- }
- }
</del><ins>+// Initialize central reference
+rootjQuery = jQuery( document );
</ins><span class="cx">
</span><del>- // Remove the expando if it's no longer used
- if ( jQuery.isEmptyObject( events ) ) {
- delete elemData.handle;
</del><span class="cx">
</span><del>- // removeData also checks for emptiness and clears the expando if empty
- // so use it instead of delete
- jQuery._removeData( elem, "events" );
- }
</del><ins>+var rparentsprev = /^(?:parents|prev(?:Until|All))/,
+ // methods guaranteed to produce a unique set when starting from a unique set
+ guaranteedUnique = {
+ children: true,
+ contents: true,
+ next: true,
+ prev: true
+ };
+
+jQuery.extend({
+ dir: function( elem, dir, until ) {
+ var matched = [],
+ truncate = until !== undefined;
+
+ while ( (elem = elem[ dir ]) && elem.nodeType !== 9 ) {
+ if ( elem.nodeType === 1 ) {
+ if ( truncate && jQuery( elem ).is( until ) ) {
+ break;
+ }
+ matched.push( elem );
+ }
+ }
+ return matched;
</ins><span class="cx"> },
</span><span class="cx">
</span><del>- trigger: function( event, data, elem, onlyHandlers ) {
- var handle, ontype, cur,
- bubbleType, special, tmp, i,
- eventPath = [ elem || document ],
- type = core_hasOwn.call( event, "type" ) ? event.type : event,
- namespaces = core_hasOwn.call( event, "namespace" ) ? event.namespace.split(".") : [];
</del><ins>+ sibling: function( n, elem ) {
+ var matched = [];
</ins><span class="cx">
</span><del>- cur = tmp = elem = elem || document;
</del><ins>+ for ( ; n; n = n.nextSibling ) {
+ if ( n.nodeType === 1 && n !== elem ) {
+ matched.push( n );
+ }
+ }
</ins><span class="cx">
</span><del>- // Don't do events on text and comment nodes
- if ( elem.nodeType === 3 || elem.nodeType === 8 ) {
- return;
</del><ins>+ return matched;
</ins><span class="cx"> }
</span><ins>+});
</ins><span class="cx">
</span><del>- // focus/blur morphs to focusin/out; ensure we're not firing them right now
- if ( rfocusMorph.test( type + jQuery.event.triggered ) ) {
- return;
- }
</del><ins>+jQuery.fn.extend({
+ has: function( target ) {
+ var targets = jQuery( target, this ),
+ l = targets.length;
</ins><span class="cx">
</span><del>- if ( type.indexOf(".") >= 0 ) {
- // Namespaced trigger; create a regexp to match event type in handle()
- namespaces = type.split(".");
- type = namespaces.shift();
- namespaces.sort();
- }
- ontype = type.indexOf(":") < 0 && "on" + type;
</del><ins>+ return this.filter(function() {
+ var i = 0;
+ for ( ; i < l; i++ ) {
+ if ( jQuery.contains( this, targets[i] ) ) {
+ return true;
+ }
+ }
+ });
+ },
</ins><span class="cx">
</span><del>- // Caller can pass in a jQuery.Event object, Object, or just an event type string
- event = event[ jQuery.expando ] ?
- event :
- new jQuery.Event( type, typeof event === "object" && event );
</del><ins>+ closest: function( selectors, context ) {
+ var cur,
+ i = 0,
+ l = this.length,
+ matched = [],
+ pos = rneedsContext.test( selectors ) || typeof selectors !== "string" ?
+ jQuery( selectors, context || this.context ) :
+ 0;
</ins><span class="cx">
</span><del>- event.isTrigger = true;
- event.namespace = namespaces.join(".");
- event.namespace_re = event.namespace ?
- new RegExp( "(^|\\.)" + namespaces.join("\\.(?:.*\\.|)") + "(\\.|$)" ) :
- null;
</del><ins>+ for ( ; i < l; i++ ) {
+ for ( cur = this[i]; cur && cur !== context; cur = cur.parentNode ) {
+ // Always skip document fragments
+ if ( cur.nodeType < 11 && (pos ?
+ pos.index(cur) > -1 :
</ins><span class="cx">
</span><del>- // Clean up the event in case it is being reused
- event.result = undefined;
- if ( !event.target ) {
- event.target = elem;
- }
</del><ins>+ // Don't pass non-elements to Sizzle
+ cur.nodeType === 1 &&
+ jQuery.find.matchesSelector(cur, selectors)) ) {
</ins><span class="cx">
</span><del>- // Clone any incoming data and prepend the event, creating the handler arg list
- data = data == null ?
- [ event ] :
- jQuery.makeArray( data, [ event ] );
</del><ins>+ matched.push( cur );
+ break;
+ }
+ }
+ }
</ins><span class="cx">
</span><del>- // Allow special events to draw outside the lines
- special = jQuery.event.special[ type ] || {};
- if ( !onlyHandlers && special.trigger && special.trigger.apply( elem, data ) === false ) {
- return;
- }
</del><ins>+ return this.pushStack( matched.length > 1 ? jQuery.unique( matched ) : matched );
+ },
</ins><span class="cx">
</span><del>- // Determine event propagation path in advance, per W3C events spec (#9951)
- // Bubble up to document, then to window; watch for a global ownerDocument var (#9724)
- if ( !onlyHandlers && !special.noBubble && !jQuery.isWindow( elem ) ) {
</del><ins>+ // Determine the position of an element within
+ // the matched set of elements
+ index: function( elem ) {
</ins><span class="cx">
</span><del>- bubbleType = special.delegateType || type;
- if ( !rfocusMorph.test( bubbleType + type ) ) {
- cur = cur.parentNode;
- }
- for ( ; cur; cur = cur.parentNode ) {
- eventPath.push( cur );
- tmp = cur;
- }
</del><ins>+ // No argument, return index in parent
+ if ( !elem ) {
+ return ( this[ 0 ] && this[ 0 ].parentNode ) ? this.first().prevAll().length : -1;
+ }
</ins><span class="cx">
</span><del>- // Only add window if we got to document (e.g., not plain obj or detached DOM)
- if ( tmp === (elem.ownerDocument || document) ) {
- eventPath.push( tmp.defaultView || tmp.parentWindow || window );
- }
- }
</del><ins>+ // index in selector
+ if ( typeof elem === "string" ) {
+ return indexOf.call( jQuery( elem ), this[ 0 ] );
+ }
</ins><span class="cx">
</span><del>- // Fire handlers on the event path
- i = 0;
- while ( (cur = eventPath[i++]) && !event.isPropagationStopped() ) {
</del><ins>+ // Locate the position of the desired element
+ return indexOf.call( this,
</ins><span class="cx">
</span><del>- event.type = i > 1 ?
- bubbleType :
- special.bindType || type;
</del><ins>+ // If it receives a jQuery object, the first element is used
+ elem.jquery ? elem[ 0 ] : elem
+ );
+ },
</ins><span class="cx">
</span><del>- // jQuery handler
- handle = ( jQuery._data( cur, "events" ) || {} )[ event.type ] && jQuery._data( cur, "handle" );
- if ( handle ) {
- handle.apply( cur, data );
</del><ins>+ add: function( selector, context ) {
+ return this.pushStack(
+ jQuery.unique(
+ jQuery.merge( this.get(), jQuery( selector, context ) )
+ )
+ );
+ },
+
+ addBack: function( selector ) {
+ return this.add( selector == null ?
+ this.prevObject : this.prevObject.filter(selector)
+ );
</ins><span class="cx"> }
</span><ins>+});
</ins><span class="cx">
</span><del>- // Native handler
- handle = ontype && cur[ ontype ];
- if ( handle && jQuery.acceptData( cur ) && handle.apply && handle.apply( cur, data ) === false ) {
- event.preventDefault();
</del><ins>+function sibling( cur, dir ) {
+ while ( (cur = cur[dir]) && cur.nodeType !== 1 ) {}
+ return cur;
+}
+
+jQuery.each({
+ parent: function( elem ) {
+ var parent = elem.parentNode;
+ return parent && parent.nodeType !== 11 ? parent : null;
+ },
+ parents: function( elem ) {
+ return jQuery.dir( elem, "parentNode" );
+ },
+ parentsUntil: function( elem, i, until ) {
+ return jQuery.dir( elem, "parentNode", until );
+ },
+ next: function( elem ) {
+ return sibling( elem, "nextSibling" );
+ },
+ prev: function( elem ) {
+ return sibling( elem, "previousSibling" );
+ },
+ nextAll: function( elem ) {
+ return jQuery.dir( elem, "nextSibling" );
+ },
+ prevAll: function( elem ) {
+ return jQuery.dir( elem, "previousSibling" );
+ },
+ nextUntil: function( elem, i, until ) {
+ return jQuery.dir( elem, "nextSibling", until );
+ },
+ prevUntil: function( elem, i, until ) {
+ return jQuery.dir( elem, "previousSibling", until );
+ },
+ siblings: function( elem ) {
+ return jQuery.sibling( ( elem.parentNode || {} ).firstChild, elem );
+ },
+ children: function( elem ) {
+ return jQuery.sibling( elem.firstChild );
+ },
+ contents: function( elem ) {
+ return elem.contentDocument || jQuery.merge( [], elem.childNodes );
</ins><span class="cx"> }
</span><del>- }
- event.type = type;
</del><ins>+}, function( name, fn ) {
+ jQuery.fn[ name ] = function( until, selector ) {
+ var matched = jQuery.map( this, fn, until );
</ins><span class="cx">
</span><del>- // If nobody prevented the default action, do it now
- if ( !onlyHandlers && !event.isDefaultPrevented() ) {
</del><ins>+ if ( name.slice( -5 ) !== "Until" ) {
+ selector = until;
+ }
</ins><span class="cx">
</span><del>- if ( (!special._default || special._default.apply( elem.ownerDocument, data ) === false) &&
- !(type === "click" && jQuery.nodeName( elem, "a" )) && jQuery.acceptData( elem ) ) {
</del><ins>+ if ( selector && typeof selector === "string" ) {
+ matched = jQuery.filter( selector, matched );
+ }
</ins><span class="cx">
</span><del>- // Call a native DOM method on the target with the same name name as the event.
- // Can't use an .isFunction() check here because IE6/7 fails that test.
- // Don't do default actions on window, that's where global variables be (#6170)
- if ( ontype && elem[ type ] && !jQuery.isWindow( elem ) ) {
</del><ins>+ if ( this.length > 1 ) {
+ // Remove duplicates
+ if ( !guaranteedUnique[ name ] ) {
+ jQuery.unique( matched );
+ }
</ins><span class="cx">
</span><del>- // Don't re-trigger an onFOO event when we call its FOO() method
- tmp = elem[ ontype ];
</del><ins>+ // Reverse order for parents* and prev-derivatives
+ if ( rparentsprev.test( name ) ) {
+ matched.reverse();
+ }
+ }
</ins><span class="cx">
</span><del>- if ( tmp ) {
- elem[ ontype ] = null;
- }
</del><ins>+ return this.pushStack( matched );
+ };
+});
+var rnotwhite = (/\S+/g);
</ins><span class="cx">
</span><del>- // Prevent re-triggering of the same event, since we already bubbled it above
- jQuery.event.triggered = type;
- try {
- elem[ type ]();
- } catch ( e ) {
- // IE<9 dies on focus/blur to hidden element (#1486,#12518)
- // only reproducible on winXP IE8 native, not IE9 in IE8 mode
- }
- jQuery.event.triggered = undefined;
</del><span class="cx">
</span><del>- if ( tmp ) {
- elem[ ontype ] = tmp;
- }
- }
- }
- }
</del><span class="cx">
</span><del>- return event.result;
- },
</del><ins>+// String to Object options format cache
+var optionsCache = {};
</ins><span class="cx">
</span><del>- dispatch: function( event ) {
</del><ins>+// Convert String-formatted options into Object-formatted ones and store in cache
+function createOptions( options ) {
+ var object = optionsCache[ options ] = {};
+ jQuery.each( options.match( rnotwhite ) || [], function( _, flag ) {
+ object[ flag ] = true;
+ });
+ return object;
+}
</ins><span class="cx">
</span><del>- // Make a writable jQuery.Event from the native event object
- event = jQuery.event.fix( event );
</del><ins>+/*
+ * Create a callback list using the following parameters:
+ *
+ * options: an optional list of space-separated options that will change how
+ * the callback list behaves or a more traditional option object
+ *
+ * By default a callback list will act like an event callback list and can be
+ * "fired" multiple times.
+ *
+ * Possible options:
+ *
+ * once: will ensure the callback list can only be fired once (like a Deferred)
+ *
+ * memory: will keep track of previous values and will call any callback added
+ * after the list has been fired right away with the latest "memorized"
+ * values (like a Deferred)
+ *
+ * unique: will ensure a callback can only be added once (no duplicate in the list)
+ *
+ * stopOnFalse: interrupt callings when a callback returns false
+ *
+ */
+jQuery.Callbacks = function( options ) {
</ins><span class="cx">
</span><del>- var i, ret, handleObj, matched, j,
- handlerQueue = [],
- args = core_slice.call( arguments ),
- handlers = ( jQuery._data( this, "events" ) || {} )[ event.type ] || [],
- special = jQuery.event.special[ event.type ] || {};
</del><ins>+ // Convert options from String-formatted to Object-formatted if needed
+ // (we check in cache first)
+ options = typeof options === "string" ?
+ ( optionsCache[ options ] || createOptions( options ) ) :
+ jQuery.extend( {}, options );
</ins><span class="cx">
</span><del>- // Use the fix-ed jQuery.Event rather than the (read-only) native event
- args[0] = event;
- event.delegateTarget = this;
</del><ins>+ var // Last fire value (for non-forgettable lists)
+ memory,
+ // Flag to know if list was already fired
+ fired,
+ // Flag to know if list is currently firing
+ firing,
+ // First callback to fire (used internally by add and fireWith)
+ firingStart,
+ // End of the loop when firing
+ firingLength,
+ // Index of currently firing callback (modified by remove if needed)
+ firingIndex,
+ // Actual callback list
+ list = [],
+ // Stack of fire calls for repeatable lists
+ stack = !options.once && [],
+ // Fire callbacks
+ fire = function( data ) {
+ memory = options.memory && data;
+ fired = true;
+ firingIndex = firingStart || 0;
+ firingStart = 0;
+ firingLength = list.length;
+ firing = true;
+ for ( ; list && firingIndex < firingLength; firingIndex++ ) {
+ if ( list[ firingIndex ].apply( data[ 0 ], data[ 1 ] ) === false && options.stopOnFalse ) {
+ memory = false; // To prevent further calls using add
+ break;
+ }
+ }
+ firing = false;
+ if ( list ) {
+ if ( stack ) {
+ if ( stack.length ) {
+ fire( stack.shift() );
+ }
+ } else if ( memory ) {
+ list = [];
+ } else {
+ self.disable();
+ }
+ }
+ },
+ // Actual Callbacks object
+ self = {
+ // Add a callback or a collection of callbacks to the list
+ add: function() {
+ if ( list ) {
+ // First, we save the current length
+ var start = list.length;
+ (function add( args ) {
+ jQuery.each( args, function( _, arg ) {
+ var type = jQuery.type( arg );
+ if ( type === "function" ) {
+ if ( !options.unique || !self.has( arg ) ) {
+ list.push( arg );
+ }
+ } else if ( arg && arg.length && type !== "string" ) {
+ // Inspect recursively
+ add( arg );
+ }
+ });
+ })( arguments );
+ // Do we need to add the callbacks to the
+ // current firing batch?
+ if ( firing ) {
+ firingLength = list.length;
+ // With memory, if we're not firing then
+ // we should call right away
+ } else if ( memory ) {
+ firingStart = start;
+ fire( memory );
+ }
+ }
+ return this;
+ },
+ // Remove a callback from the list
+ remove: function() {
+ if ( list ) {
+ jQuery.each( arguments, function( _, arg ) {
+ var index;
+ while ( ( index = jQuery.inArray( arg, list, index ) ) > -1 ) {
+ list.splice( index, 1 );
+ // Handle firing indexes
+ if ( firing ) {
+ if ( index <= firingLength ) {
+ firingLength--;
+ }
+ if ( index <= firingIndex ) {
+ firingIndex--;
+ }
+ }
+ }
+ });
+ }
+ return this;
+ },
+ // Check if a given callback is in the list.
+ // If no argument is given, return whether or not list has callbacks attached.
+ has: function( fn ) {
+ return fn ? jQuery.inArray( fn, list ) > -1 : !!( list && list.length );
+ },
+ // Remove all callbacks from the list
+ empty: function() {
+ list = [];
+ firingLength = 0;
+ return this;
+ },
+ // Have the list do nothing anymore
+ disable: function() {
+ list = stack = memory = undefined;
+ return this;
+ },
+ // Is it disabled?
+ disabled: function() {
+ return !list;
+ },
+ // Lock the list in its current state
+ lock: function() {
+ stack = undefined;
+ if ( !memory ) {
+ self.disable();
+ }
+ return this;
+ },
+ // Is it locked?
+ locked: function() {
+ return !stack;
+ },
+ // Call all callbacks with the given context and arguments
+ fireWith: function( context, args ) {
+ if ( list && ( !fired || stack ) ) {
+ args = args || [];
+ args = [ context, args.slice ? args.slice() : args ];
+ if ( firing ) {
+ stack.push( args );
+ } else {
+ fire( args );
+ }
+ }
+ return this;
+ },
+ // Call all the callbacks with the given arguments
+ fire: function() {
+ self.fireWith( this, arguments );
+ return this;
+ },
+ // To know if the callbacks have already been called at least once
+ fired: function() {
+ return !!fired;
+ }
+ };
</ins><span class="cx">
</span><del>- // Call the preDispatch hook for the mapped type, and let it bail if desired
- if ( special.preDispatch && special.preDispatch.call( this, event ) === false ) {
- return;
- }
</del><ins>+ return self;
+};
</ins><span class="cx">
</span><del>- // Determine handlers
- handlerQueue = jQuery.event.handlers.call( this, event, handlers );
</del><span class="cx">
</span><del>- // Run delegates first; they may want to stop propagation beneath us
- i = 0;
- while ( (matched = handlerQueue[ i++ ]) && !event.isPropagationStopped() ) {
- event.currentTarget = matched.elem;
</del><ins>+jQuery.extend({
</ins><span class="cx">
</span><del>- j = 0;
- while ( (handleObj = matched.handlers[ j++ ]) && !event.isImmediatePropagationStopped() ) {
</del><ins>+ Deferred: function( func ) {
+ var tuples = [
+ // action, add listener, listener list, final state
+ [ "resolve", "done", jQuery.Callbacks("once memory"), "resolved" ],
+ [ "reject", "fail", jQuery.Callbacks("once memory"), "rejected" ],
+ [ "notify", "progress", jQuery.Callbacks("memory") ]
+ ],
+ state = "pending",
+ promise = {
+ state: function() {
+ return state;
+ },
+ always: function() {
+ deferred.done( arguments ).fail( arguments );
+ return this;
+ },
+ then: function( /* fnDone, fnFail, fnProgress */ ) {
+ var fns = arguments;
+ return jQuery.Deferred(function( newDefer ) {
+ jQuery.each( tuples, function( i, tuple ) {
+ var fn = jQuery.isFunction( fns[ i ] ) && fns[ i ];
+ // deferred[ done | fail | progress ] for forwarding actions to newDefer
+ deferred[ tuple[1] ](function() {
+ var returned = fn && fn.apply( this, arguments );
+ if ( returned && jQuery.isFunction( returned.promise ) ) {
+ returned.promise()
+ .done( newDefer.resolve )
+ .fail( newDefer.reject )
+ .progress( newDefer.notify );
+ } else {
+ newDefer[ tuple[ 0 ] + "With" ]( this === promise ? newDefer.promise() : this, fn ? [ returned ] : arguments );
+ }
+ });
+ });
+ fns = null;
+ }).promise();
+ },
+ // Get a promise for this deferred
+ // If obj is provided, the promise aspect is added to the object
+ promise: function( obj ) {
+ return obj != null ? jQuery.extend( obj, promise ) : promise;
+ }
+ },
+ deferred = {};
</ins><span class="cx">
</span><del>- // Triggered event must either 1) have no namespace, or
- // 2) have namespace(s) a subset or equal to those in the bound event (both can have no namespace).
- if ( !event.namespace_re || event.namespace_re.test( handleObj.namespace ) ) {
</del><ins>+ // Keep pipe for back-compat
+ promise.pipe = promise.then;
</ins><span class="cx">
</span><del>- event.handleObj = handleObj;
- event.data = handleObj.data;
</del><ins>+ // Add list-specific methods
+ jQuery.each( tuples, function( i, tuple ) {
+ var list = tuple[ 2 ],
+ stateString = tuple[ 3 ];
</ins><span class="cx">
</span><del>- ret = ( (jQuery.event.special[ handleObj.origType ] || {}).handle || handleObj.handler )
- .apply( matched.elem, args );
</del><ins>+ // promise[ done | fail | progress ] = list.add
+ promise[ tuple[1] ] = list.add;
</ins><span class="cx">
</span><del>- if ( ret !== undefined ) {
- if ( (event.result = ret) === false ) {
- event.preventDefault();
- event.stopPropagation();
- }
- }
- }
- }
- }
</del><ins>+ // Handle state
+ if ( stateString ) {
+ list.add(function() {
+ // state = [ resolved | rejected ]
+ state = stateString;
</ins><span class="cx">
</span><del>- // Call the postDispatch hook for the mapped type
- if ( special.postDispatch ) {
- special.postDispatch.call( this, event );
- }
</del><ins>+ // [ reject_list | resolve_list ].disable; progress_list.lock
+ }, tuples[ i ^ 1 ][ 2 ].disable, tuples[ 2 ][ 2 ].lock );
+ }
</ins><span class="cx">
</span><del>- return event.result;
- },
</del><ins>+ // deferred[ resolve | reject | notify ]
+ deferred[ tuple[0] ] = function() {
+ deferred[ tuple[0] + "With" ]( this === deferred ? promise : this, arguments );
+ return this;
+ };
+ deferred[ tuple[0] + "With" ] = list.fireWith;
+ });
</ins><span class="cx">
</span><del>- handlers: function( event, handlers ) {
- var sel, handleObj, matches, i,
- handlerQueue = [],
- delegateCount = handlers.delegateCount,
- cur = event.target;
</del><ins>+ // Make the deferred a promise
+ promise.promise( deferred );
</ins><span class="cx">
</span><del>- // Find delegate handlers
- // Black-hole SVG <use> instance trees (#13180)
- // Avoid non-left-click bubbling in Firefox (#3861)
- if ( delegateCount && cur.nodeType && (!event.button || event.type !== "click") ) {
</del><ins>+ // Call given func if any
+ if ( func ) {
+ func.call( deferred, deferred );
+ }
</ins><span class="cx">
</span><del>- for ( ; cur != this; cur = cur.parentNode || this ) {
</del><ins>+ // All done!
+ return deferred;
+ },
</ins><span class="cx">
</span><del>- // Don't check non-elements (#13208)
- // Don't process clicks on disabled elements (#6911, #8165, #11382, #11764)
- if ( cur.nodeType === 1 && (cur.disabled !== true || event.type !== "click") ) {
- matches = [];
- for ( i = 0; i < delegateCount; i++ ) {
- handleObj = handlers[ i ];
</del><ins>+ // Deferred helper
+ when: function( subordinate /* , ..., subordinateN */ ) {
+ var i = 0,
+ resolveValues = slice.call( arguments ),
+ length = resolveValues.length,
</ins><span class="cx">
</span><del>- // Don't conflict with Object.prototype properties (#13203)
- sel = handleObj.selector + " ";
</del><ins>+ // the count of uncompleted subordinates
+ remaining = length !== 1 || ( subordinate && jQuery.isFunction( subordinate.promise ) ) ? length : 0,
</ins><span class="cx">
</span><del>- if ( matches[ sel ] === undefined ) {
- matches[ sel ] = handleObj.needsContext ?
- jQuery( sel, this ).index( cur ) >= 0 :
- jQuery.find( sel, this, null, [ cur ] ).length;
- }
- if ( matches[ sel ] ) {
- matches.push( handleObj );
- }
- }
- if ( matches.length ) {
- handlerQueue.push({ elem: cur, handlers: matches });
- }
- }
- }
- }
</del><ins>+ // the master Deferred. If resolveValues consist of only a single Deferred, just use that.
+ deferred = remaining === 1 ? subordinate : jQuery.Deferred(),
</ins><span class="cx">
</span><del>- // Add the remaining (directly-bound) handlers
- if ( delegateCount < handlers.length ) {
- handlerQueue.push({ elem: this, handlers: handlers.slice( delegateCount ) });
- }
</del><ins>+ // Update function for both resolve and progress values
+ updateFunc = function( i, contexts, values ) {
+ return function( value ) {
+ contexts[ i ] = this;
+ values[ i ] = arguments.length > 1 ? slice.call( arguments ) : value;
+ if ( values === progressValues ) {
+ deferred.notifyWith( contexts, values );
+ } else if ( !( --remaining ) ) {
+ deferred.resolveWith( contexts, values );
+ }
+ };
+ },
</ins><span class="cx">
</span><del>- return handlerQueue;
- },
</del><ins>+ progressValues, progressContexts, resolveContexts;
</ins><span class="cx">
</span><del>- fix: function( event ) {
- if ( event[ jQuery.expando ] ) {
- return event;
- }
</del><ins>+ // add listeners to Deferred subordinates; treat others as resolved
+ if ( length > 1 ) {
+ progressValues = new Array( length );
+ progressContexts = new Array( length );
+ resolveContexts = new Array( length );
+ for ( ; i < length; i++ ) {
+ if ( resolveValues[ i ] && jQuery.isFunction( resolveValues[ i ].promise ) ) {
+ resolveValues[ i ].promise()
+ .done( updateFunc( i, resolveContexts, resolveValues ) )
+ .fail( deferred.reject )
+ .progress( updateFunc( i, progressContexts, progressValues ) );
+ } else {
+ --remaining;
+ }
+ }
+ }
</ins><span class="cx">
</span><del>- // Create a writable copy of the event object and normalize some properties
- var i, prop, copy,
- type = event.type,
- originalEvent = event,
- fixHook = this.fixHooks[ type ];
</del><ins>+ // if we're not waiting on anything, resolve the master
+ if ( !remaining ) {
+ deferred.resolveWith( resolveContexts, resolveValues );
+ }
</ins><span class="cx">
</span><del>- if ( !fixHook ) {
- this.fixHooks[ type ] = fixHook =
- rmouseEvent.test( type ) ? this.mouseHooks :
- rkeyEvent.test( type ) ? this.keyHooks :
- {};
</del><ins>+ return deferred.promise();
</ins><span class="cx"> }
</span><del>- copy = fixHook.props ? this.props.concat( fixHook.props ) : this.props;
</del><ins>+});
</ins><span class="cx">
</span><del>- event = new jQuery.Event( originalEvent );
</del><span class="cx">
</span><del>- i = copy.length;
- while ( i-- ) {
- prop = copy[ i ];
- event[ prop ] = originalEvent[ prop ];
- }
</del><ins>+// The deferred used on DOM ready
+var readyList;
</ins><span class="cx">
</span><del>- // Support: IE<9
- // Fix target property (#1925)
- if ( !event.target ) {
- event.target = originalEvent.srcElement || document;
- }
</del><ins>+jQuery.fn.ready = function( fn ) {
+ // Add the callback
+ jQuery.ready.promise().done( fn );
</ins><span class="cx">
</span><del>- // Support: Chrome 23+, Safari?
- // Target should not be a text node (#504, #13143)
- if ( event.target.nodeType === 3 ) {
- event.target = event.target.parentNode;
- }
</del><ins>+ return this;
+};
</ins><span class="cx">
</span><del>- // Support: IE<9
- // For mouse/key events, metaKey==false if it's undefined (#3368, #11328)
- event.metaKey = !!event.metaKey;
</del><ins>+jQuery.extend({
+ // Is the DOM ready to be used? Set to true once it occurs.
+ isReady: false,
</ins><span class="cx">
</span><del>- return fixHook.filter ? fixHook.filter( event, originalEvent ) : event;
</del><ins>+ // A counter to track how many items to wait for before
+ // the ready event fires. See #6781
+ readyWait: 1,
+
+ // Hold (or release) the ready event
+ holdReady: function( hold ) {
+ if ( hold ) {
+ jQuery.readyWait++;
+ } else {
+ jQuery.ready( true );
+ }
</ins><span class="cx"> },
</span><span class="cx">
</span><del>- // Includes some event props shared by KeyEvent and MouseEvent
- props: "altKey bubbles cancelable ctrlKey currentTarget eventPhase metaKey relatedTarget shiftKey target timeStamp view which".split(" "),
</del><ins>+ // Handle when the DOM is ready
+ ready: function( wait ) {
</ins><span class="cx">
</span><del>- fixHooks: {},
</del><ins>+ // Abort if there are pending holds or we're already ready
+ if ( wait === true ? --jQuery.readyWait : jQuery.isReady ) {
+ return;
+ }
</ins><span class="cx">
</span><del>- keyHooks: {
- props: "char charCode key keyCode".split(" "),
- filter: function( event, original ) {
</del><ins>+ // Remember that the DOM is ready
+ jQuery.isReady = true;
</ins><span class="cx">
</span><del>- // Add which for key events
- if ( event.which == null ) {
- event.which = original.charCode != null ? original.charCode : original.keyCode;
- }
</del><ins>+ // If a normal DOM Ready event fired, decrement, and wait if need be
+ if ( wait !== true && --jQuery.readyWait > 0 ) {
+ return;
+ }
</ins><span class="cx">
</span><del>- return event;
</del><ins>+ // If there are functions bound, to execute
+ readyList.resolveWith( document, [ jQuery ] );
+
+ // Trigger any bound ready events
+ if ( jQuery.fn.trigger ) {
+ jQuery( document ).trigger("ready").off("ready");
+ }
</ins><span class="cx"> }
</span><del>- },
</del><ins>+});
</ins><span class="cx">
</span><del>- mouseHooks: {
- props: "button buttons clientX clientY fromElement offsetX offsetY pageX pageY screenX screenY toElement".split(" "),
- filter: function( event, original ) {
- var body, eventDoc, doc,
- button = original.button,
- fromElement = original.fromElement;
</del><ins>+/**
+ * The ready event handler and self cleanup method
+ */
+function completed() {
+ document.removeEventListener( "DOMContentLoaded", completed, false );
+ window.removeEventListener( "load", completed, false );
+ jQuery.ready();
+}
</ins><span class="cx">
</span><del>- // Calculate pageX/Y if missing and clientX/Y available
- if ( event.pageX == null && original.clientX != null ) {
- eventDoc = event.target.ownerDocument || document;
- doc = eventDoc.documentElement;
- body = eventDoc.body;
</del><ins>+jQuery.ready.promise = function( obj ) {
+ if ( !readyList ) {
</ins><span class="cx">
</span><del>- event.pageX = original.clientX + ( doc && doc.scrollLeft || body && body.scrollLeft || 0 ) - ( doc && doc.clientLeft || body && body.clientLeft || 0 );
- event.pageY = original.clientY + ( doc && doc.scrollTop || body && body.scrollTop || 0 ) - ( doc && doc.clientTop || body && body.clientTop || 0 );
- }
</del><ins>+ readyList = jQuery.Deferred();
</ins><span class="cx">
</span><del>- // Add relatedTarget, if necessary
- if ( !event.relatedTarget && fromElement ) {
- event.relatedTarget = fromElement === event.target ? original.toElement : fromElement;
- }
</del><ins>+ // Catch cases where $(document).ready() is called after the browser event has already occurred.
+ // we once tried to use readyState "interactive" here, but it caused issues like the one
+ // discovered by ChrisS here: http://bugs.jquery.com/ticket/12282#comment:15
+ if ( document.readyState === "complete" ) {
+ // Handle it asynchronously to allow scripts the opportunity to delay ready
+ setTimeout( jQuery.ready );
</ins><span class="cx">
</span><del>- // Add which for click: 1 === left; 2 === middle; 3 === right
- // Note: button is not normalized, so don't use it
- if ( !event.which && button !== undefined ) {
- event.which = ( button & 1 ? 1 : ( button & 2 ? 3 : ( button & 4 ? 2 : 0 ) ) );
- }
</del><ins>+ } else {
</ins><span class="cx">
</span><del>- return event;
- }
- },
</del><ins>+ // Use the handy event callback
+ document.addEventListener( "DOMContentLoaded", completed, false );
</ins><span class="cx">
</span><del>- special: {
- load: {
- // Prevent triggered image.load events from bubbling to window.load
- noBubble: true
- },
- click: {
- // For checkbox, fire native event so checked state will be right
- trigger: function() {
- if ( jQuery.nodeName( this, "input" ) && this.type === "checkbox" && this.click ) {
- this.click();
- return false;
</del><ins>+ // A fallback to window.onload, that will always work
+ window.addEventListener( "load", completed, false );
+ }
</ins><span class="cx"> }
</span><del>- }
- },
- focus: {
- // Fire native event if possible so blur/focus sequence is correct
- trigger: function() {
- if ( this !== document.activeElement && this.focus ) {
- try {
- this.focus();
- return false;
- } catch ( e ) {
- // Support: IE<9
- // If we error on focus to hidden element (#1486, #12518),
- // let .trigger() run the handlers
- }
- }
- },
- delegateType: "focusin"
- },
- blur: {
- trigger: function() {
- if ( this === document.activeElement && this.blur ) {
- this.blur();
- return false;
- }
- },
- delegateType: "focusout"
- },
</del><ins>+ return readyList.promise( obj );
+};
</ins><span class="cx">
</span><del>- beforeunload: {
- postDispatch: function( event ) {
</del><ins>+// Kick off the DOM ready check even if the user does not
+jQuery.ready.promise();
</ins><span class="cx">
</span><del>- // Even when returnValue equals to undefined Firefox will still show alert
- if ( event.result !== undefined ) {
- event.originalEvent.returnValue = event.result;
- }
- }
- }
- },
</del><span class="cx">
</span><del>- simulate: function( type, elem, event, bubble ) {
- // Piggyback on a donor event to simulate a different one.
- // Fake originalEvent to avoid donor's stopPropagation, but if the
- // simulated event prevents default then we do the same on the donor.
- var e = jQuery.extend(
- new jQuery.Event(),
- event,
- { type: type,
- isSimulated: true,
- originalEvent: {}
- }
- );
- if ( bubble ) {
- jQuery.event.trigger( e, null, elem );
- } else {
- jQuery.event.dispatch.call( elem, e );
- }
- if ( e.isDefaultPrevented() ) {
- event.preventDefault();
- }
- }
-};
</del><span class="cx">
</span><del>-jQuery.removeEvent = document.removeEventListener ?
- function( elem, type, handle ) {
- if ( elem.removeEventListener ) {
- elem.removeEventListener( type, handle, false );
- }
- } :
- function( elem, type, handle ) {
- var name = "on" + type;
</del><span class="cx">
</span><del>- if ( elem.detachEvent ) {
</del><ins>+// Multifunctional method to get and set values of a collection
+// The value/s can optionally be executed if it's a function
+var access = jQuery.access = function( elems, fn, key, value, chainable, emptyGet, raw ) {
+ var i = 0,
+ len = elems.length,
+ bulk = key == null;
</ins><span class="cx">
</span><del>- // #8545, #7054, preventing memory leaks for custom events in IE6-8
- // detachEvent needed property on element, by name of that event, to properly expose it to GC
- if ( typeof elem[ name ] === core_strundefined ) {
- elem[ name ] = null;
- }
</del><ins>+ // Sets many values
+ if ( jQuery.type( key ) === "object" ) {
+ chainable = true;
+ for ( i in key ) {
+ jQuery.access( elems, fn, i, key[i], true, emptyGet, raw );
+ }
</ins><span class="cx">
</span><del>- elem.detachEvent( name, handle );
- }
- };
</del><ins>+ // Sets one value
+ } else if ( value !== undefined ) {
+ chainable = true;
</ins><span class="cx">
</span><del>-jQuery.Event = function( src, props ) {
- // Allow instantiation without the 'new' keyword
- if ( !(this instanceof jQuery.Event) ) {
- return new jQuery.Event( src, props );
- }
</del><ins>+ if ( !jQuery.isFunction( value ) ) {
+ raw = true;
+ }
</ins><span class="cx">
</span><del>- // Event object
- if ( src && src.type ) {
- this.originalEvent = src;
- this.type = src.type;
</del><ins>+ if ( bulk ) {
+ // Bulk operations run against the entire set
+ if ( raw ) {
+ fn.call( elems, value );
+ fn = null;
</ins><span class="cx">
</span><del>- // Events bubbling up the document may have been marked as prevented
- // by a handler lower down the tree; reflect the correct value.
- this.isDefaultPrevented = ( src.defaultPrevented || src.returnValue === false ||
- src.getPreventDefault && src.getPreventDefault() ) ? returnTrue : returnFalse;
</del><ins>+ // ...except when executing function values
+ } else {
+ bulk = fn;
+ fn = function( elem, key, value ) {
+ return bulk.call( jQuery( elem ), value );
+ };
+ }
+ }
</ins><span class="cx">
</span><del>- // Event type
- } else {
- this.type = src;
</del><ins>+ if ( fn ) {
+ for ( ; i < len; i++ ) {
+ fn( elems[i], key, raw ? value : value.call( elems[i], i, fn( elems[i], key ) ) );
+ }
+ }
</ins><span class="cx"> }
</span><span class="cx">
</span><del>- // Put explicitly provided properties onto the event object
- if ( props ) {
- jQuery.extend( this, props );
- }
</del><ins>+ return chainable ?
+ elems :
</ins><span class="cx">
</span><del>- // Create a timestamp if incoming event doesn't have one
- this.timeStamp = src && src.timeStamp || jQuery.now();
</del><ins>+ // Gets
+ bulk ?
+ fn.call( elems ) :
+ len ? fn( elems[0], key ) : emptyGet;
+};
</ins><span class="cx">
</span><del>- // Mark it as fixed
- this[ jQuery.expando ] = true;
</del><ins>+
+/**
+ * Determines whether an object can have data
+ */
+jQuery.acceptData = function( owner ) {
+ // Accepts only:
+ // - Node
+ // - Node.ELEMENT_NODE
+ // - Node.DOCUMENT_NODE
+ // - Object
+ // - Any
+ /* jshint -W018 */
+ return owner.nodeType === 1 || owner.nodeType === 9 || !( +owner.nodeType );
</ins><span class="cx"> };
</span><span class="cx">
</span><del>-// jQuery.Event is based on DOM3 Events as specified by the ECMAScript Language Binding
-// http://www.w3.org/TR/2003/WD-DOM-Level-3-Events-20030331/ecma-script-binding.html
-jQuery.Event.prototype = {
- isDefaultPrevented: returnFalse,
- isPropagationStopped: returnFalse,
- isImmediatePropagationStopped: returnFalse,
</del><span class="cx">
</span><del>- preventDefault: function() {
- var e = this.originalEvent;
</del><ins>+function Data() {
+ // Support: Android < 4,
+ // Old WebKit does not have Object.preventExtensions/freeze method,
+ // return new empty object instead with no [[set]] accessor
+ Object.defineProperty( this.cache = {}, 0, {
+ get: function() {
+ return {};
+ }
+ });
</ins><span class="cx">
</span><del>- this.isDefaultPrevented = returnTrue;
- if ( !e ) {
- return;
- }
</del><ins>+ this.expando = jQuery.expando + Math.random();
+}
</ins><span class="cx">
</span><del>- // If preventDefault exists, run it on the original event
- if ( e.preventDefault ) {
- e.preventDefault();
</del><ins>+Data.uid = 1;
+Data.accepts = jQuery.acceptData;
</ins><span class="cx">
</span><del>- // Support: IE
- // Otherwise set the returnValue property of the original event to false
- } else {
- e.returnValue = false;
- }
- },
- stopPropagation: function() {
- var e = this.originalEvent;
</del><ins>+Data.prototype = {
+ key: function( owner ) {
+ // We can accept data for non-element nodes in modern browsers,
+ // but we should not, see #8335.
+ // Always return the key for a frozen object.
+ if ( !Data.accepts( owner ) ) {
+ return 0;
+ }
</ins><span class="cx">
</span><del>- this.isPropagationStopped = returnTrue;
- if ( !e ) {
- return;
- }
- // If stopPropagation exists, run it on the original event
- if ( e.stopPropagation ) {
- e.stopPropagation();
- }
</del><ins>+ var descriptor = {},
+ // Check if the owner object already has a cache key
+ unlock = owner[ this.expando ];
</ins><span class="cx">
</span><del>- // Support: IE
- // Set the cancelBubble property of the original event to true
- e.cancelBubble = true;
- },
- stopImmediatePropagation: function() {
- this.isImmediatePropagationStopped = returnTrue;
- this.stopPropagation();
- }
-};
</del><ins>+ // If not, create one
+ if ( !unlock ) {
+ unlock = Data.uid++;
</ins><span class="cx">
</span><del>-// Create mouseenter/leave events using mouseover/out and event-time checks
-jQuery.each({
- mouseenter: "mouseover",
- mouseleave: "mouseout"
-}, function( orig, fix ) {
- jQuery.event.special[ orig ] = {
- delegateType: fix,
- bindType: fix,
</del><ins>+ // Secure it in a non-enumerable, non-writable property
+ try {
+ descriptor[ this.expando ] = { value: unlock };
+ Object.defineProperties( owner, descriptor );
</ins><span class="cx">
</span><del>- handle: function( event ) {
- var ret,
- target = this,
- related = event.relatedTarget,
- handleObj = event.handleObj;
</del><ins>+ // Support: Android < 4
+ // Fallback to a less secure definition
+ } catch ( e ) {
+ descriptor[ this.expando ] = unlock;
+ jQuery.extend( owner, descriptor );
+ }
+ }
</ins><span class="cx">
</span><del>- // For mousenter/leave call the handler if related is outside the target.
- // NB: No relatedTarget if the mouse left/entered the browser window
- if ( !related || (related !== target && !jQuery.contains( target, related )) ) {
- event.type = handleObj.origType;
- ret = handleObj.handler.apply( this, arguments );
- event.type = fix;
- }
- return ret;
- }
- };
-});
</del><ins>+ // Ensure the cache object
+ if ( !this.cache[ unlock ] ) {
+ this.cache[ unlock ] = {};
+ }
</ins><span class="cx">
</span><del>-// IE submit delegation
-if ( !jQuery.support.submitBubbles ) {
</del><ins>+ return unlock;
+ },
+ set: function( owner, data, value ) {
+ var prop,
+ // There may be an unlock assigned to this node,
+ // if there is no entry for this "owner", create one inline
+ // and set the unlock as though an owner entry had always existed
+ unlock = this.key( owner ),
+ cache = this.cache[ unlock ];
</ins><span class="cx">
</span><del>- jQuery.event.special.submit = {
- setup: function() {
- // Only need this for delegated form submit events
- if ( jQuery.nodeName( this, "form" ) ) {
- return false;
- }
</del><ins>+ // Handle: [ owner, key, value ] args
+ if ( typeof data === "string" ) {
+ cache[ data ] = value;
</ins><span class="cx">
</span><del>- // Lazy-add a submit handler when a descendant form may potentially be submitted
- jQuery.event.add( this, "click._submit keypress._submit", function( e ) {
- // Node name check avoids a VML-related crash in IE (#9807)
- var elem = e.target,
- form = jQuery.nodeName( elem, "input" ) || jQuery.nodeName( elem, "button" ) ? elem.form : undefined;
- if ( form && !jQuery._data( form, "submitBubbles" ) ) {
- jQuery.event.add( form, "submit._submit", function( event ) {
- event._submit_bubble = true;
- });
- jQuery._data( form, "submitBubbles", true );
- }
- });
- // return undefined since we don't need an event listener
</del><ins>+ // Handle: [ owner, { properties } ] args
+ } else {
+ // Fresh assignments by object are shallow copied
+ if ( jQuery.isEmptyObject( cache ) ) {
+ jQuery.extend( this.cache[ unlock ], data );
+ // Otherwise, copy the properties one-by-one to the cache object
+ } else {
+ for ( prop in data ) {
+ cache[ prop ] = data[ prop ];
+ }
+ }
+ }
+ return cache;
</ins><span class="cx"> },
</span><ins>+ get: function( owner, key ) {
+ // Either a valid cache is found, or will be created.
+ // New caches will be created and the unlock returned,
+ // allowing direct access to the newly created
+ // empty data object. A valid owner object must be provided.
+ var cache = this.cache[ this.key( owner ) ];
</ins><span class="cx">
</span><del>- postDispatch: function( event ) {
- // If form was submitted by the user, bubble the event up the tree
- if ( event._submit_bubble ) {
- delete event._submit_bubble;
- if ( this.parentNode && !event.isTrigger ) {
- jQuery.event.simulate( "submit", this.parentNode, event, true );
- }
- }
</del><ins>+ return key === undefined ?
+ cache : cache[ key ];
</ins><span class="cx"> },
</span><ins>+ access: function( owner, key, value ) {
+ var stored;
+ // In cases where either:
+ //
+ // 1. No key was specified
+ // 2. A string key was specified, but no value provided
+ //
+ // Take the "read" path and allow the get method to determine
+ // which value to return, respectively either:
+ //
+ // 1. The entire cache object
+ // 2. The data stored at the key
+ //
+ if ( key === undefined ||
+ ((key && typeof key === "string") && value === undefined) ) {
</ins><span class="cx">
</span><del>- teardown: function() {
- // Only need this for delegated form submit events
- if ( jQuery.nodeName( this, "form" ) ) {
- return false;
- }
</del><ins>+ stored = this.get( owner, key );
</ins><span class="cx">
</span><del>- // Remove delegated handlers; cleanData eventually reaps submit handlers attached above
- jQuery.event.remove( this, "._submit" );
- }
- };
-}
</del><ins>+ return stored !== undefined ?
+ stored : this.get( owner, jQuery.camelCase(key) );
+ }
</ins><span class="cx">
</span><del>-// IE change delegation and checkbox/radio fix
-if ( !jQuery.support.changeBubbles ) {
</del><ins>+ // [*]When the key is not a string, or both a key and value
+ // are specified, set or extend (existing objects) with either:
+ //
+ // 1. An object of properties
+ // 2. A key and value
+ //
+ this.set( owner, key, value );
</ins><span class="cx">
</span><del>- jQuery.event.special.change = {
</del><ins>+ // Since the "set" path can have two possible entry points
+ // return the expected data based on which path was taken[*]
+ return value !== undefined ? value : key;
+ },
+ remove: function( owner, key ) {
+ var i, name, camel,
+ unlock = this.key( owner ),
+ cache = this.cache[ unlock ];
</ins><span class="cx">
</span><del>- setup: function() {
</del><ins>+ if ( key === undefined ) {
+ this.cache[ unlock ] = {};
</ins><span class="cx">
</span><del>- if ( rformElems.test( this.nodeName ) ) {
- // IE doesn't fire change on a check/radio until blur; trigger it on click
- // after a propertychange. Eat the blur-change in special.change.handle.
- // This still fires onchange a second time for check/radio after blur.
- if ( this.type === "checkbox" || this.type === "radio" ) {
- jQuery.event.add( this, "propertychange._change", function( event ) {
- if ( event.originalEvent.propertyName === "checked" ) {
- this._just_changed = true;
- }
- });
- jQuery.event.add( this, "click._change", function( event ) {
- if ( this._just_changed && !event.isTrigger ) {
- this._just_changed = false;
- }
- // Allow triggered, simulated change events (#11500)
- jQuery.event.simulate( "change", this, event, true );
- });
- }
- return false;
- }
- // Delegated event; lazy-add a change handler on descendant inputs
- jQuery.event.add( this, "beforeactivate._change", function( e ) {
- var elem = e.target;
</del><ins>+ } else {
+ // Support array or space separated string of keys
+ if ( jQuery.isArray( key ) ) {
+ // If "name" is an array of keys...
+ // When data is initially created, via ("key", "val") signature,
+ // keys will be converted to camelCase.
+ // Since there is no way to tell _how_ a key was added, remove
+ // both plain key and camelCase key. #12786
+ // This will only penalize the array argument path.
+ name = key.concat( key.map( jQuery.camelCase ) );
+ } else {
+ camel = jQuery.camelCase( key );
+ // Try the string as a key before any manipulation
+ if ( key in cache ) {
+ name = [ key, camel ];
+ } else {
+ // If a key with the spaces exists, use it.
+ // Otherwise, create an array by matching non-whitespace
+ name = camel;
+ name = name in cache ?
+ [ name ] : ( name.match( rnotwhite ) || [] );
+ }
+ }
</ins><span class="cx">
</span><del>- if ( rformElems.test( elem.nodeName ) && !jQuery._data( elem, "changeBubbles" ) ) {
- jQuery.event.add( elem, "change._change", function( event ) {
- if ( this.parentNode && !event.isSimulated && !event.isTrigger ) {
- jQuery.event.simulate( "change", this.parentNode, event, true );
- }
- });
- jQuery._data( elem, "changeBubbles", true );
- }
- });
</del><ins>+ i = name.length;
+ while ( i-- ) {
+ delete cache[ name[ i ] ];
+ }
+ }
</ins><span class="cx"> },
</span><del>-
- handle: function( event ) {
- var elem = event.target;
-
- // Swallow native change events from checkbox/radio, we already triggered them above
- if ( this !== elem || event.isSimulated || event.isTrigger || (elem.type !== "radio" && elem.type !== "checkbox") ) {
- return event.handleObj.handler.apply( this, arguments );
- }
</del><ins>+ hasData: function( owner ) {
+ return !jQuery.isEmptyObject(
+ this.cache[ owner[ this.expando ] ] || {}
+ );
</ins><span class="cx"> },
</span><ins>+ discard: function( owner ) {
+ if ( owner[ this.expando ] ) {
+ delete this.cache[ owner[ this.expando ] ];
+ }
+ }
+};
+var data_priv = new Data();
</ins><span class="cx">
</span><del>- teardown: function() {
- jQuery.event.remove( this, "._change" );
</del><ins>+var data_user = new Data();
</ins><span class="cx">
</span><del>- return !rformElems.test( this.nodeName );
- }
- };
-}
</del><span class="cx">
</span><del>-// Create "bubbling" focus and blur events
-if ( !jQuery.support.focusinBubbles ) {
- jQuery.each({ focus: "focusin", blur: "focusout" }, function( orig, fix ) {
</del><span class="cx">
</span><del>- // Attach a single capturing handler while someone wants focusin/focusout
- var attaches = 0,
- handler = function( event ) {
- jQuery.event.simulate( fix, event.target, jQuery.event.fix( event ), true );
- };
</del><ins>+/*
+ Implementation Summary
</ins><span class="cx">
</span><del>- jQuery.event.special[ fix ] = {
- setup: function() {
- if ( attaches++ === 0 ) {
- document.addEventListener( orig, handler, true );
- }
- },
- teardown: function() {
- if ( --attaches === 0 ) {
- document.removeEventListener( orig, handler, true );
- }
- }
- };
- });
-}
</del><ins>+ 1. Enforce API surface and semantic compatibility with 1.9.x branch
+ 2. Improve the module's maintainability by reducing the storage
+ paths to a single mechanism.
+ 3. Use the same single mechanism to support "private" and "user" data.
+ 4. _Never_ expose "private" data to user code (TODO: Drop _data, _removeData)
+ 5. Avoid exposing implementation details on user objects (eg. expando properties)
+ 6. Provide a clear path for implementation upgrade to WeakMap in 2014
+*/
+var rbrace = /^(?:\{[\w\W]*\}|\[[\w\W]*\])$/,
+ rmultiDash = /([A-Z])/g;
</ins><span class="cx">
</span><del>-jQuery.fn.extend({
</del><ins>+function dataAttr( elem, key, data ) {
+ var name;
</ins><span class="cx">
</span><del>- on: function( types, selector, data, fn, /*INTERNAL*/ one ) {
- var type, origFn;
</del><ins>+ // If nothing was found internally, try to fetch any
+ // data from the HTML5 data-* attribute
+ if ( data === undefined && elem.nodeType === 1 ) {
+ name = "data-" + key.replace( rmultiDash, "-$1" ).toLowerCase();
+ data = elem.getAttribute( name );
</ins><span class="cx">
</span><del>- // Types can be a map of types/handlers
- if ( typeof types === "object" ) {
- // ( types-Object, selector, data )
- if ( typeof selector !== "string" ) {
- // ( types-Object, data )
- data = data || selector;
- selector = undefined;
- }
- for ( type in types ) {
- this.on( type, selector, data, types[ type ], one );
- }
- return this;
- }
</del><ins>+ if ( typeof data === "string" ) {
+ try {
+ data = data === "true" ? true :
+ data === "false" ? false :
+ data === "null" ? null :
+ // Only convert to a number if it doesn't change the string
+ +data + "" === data ? +data :
+ rbrace.test( data ) ? jQuery.parseJSON( data ) :
+ data;
+ } catch( e ) {}
</ins><span class="cx">
</span><del>- if ( data == null && fn == null ) {
- // ( types, fn )
- fn = selector;
- data = selector = undefined;
- } else if ( fn == null ) {
- if ( typeof selector === "string" ) {
- // ( types, selector, fn )
- fn = data;
- data = undefined;
- } else {
- // ( types, data, fn )
- fn = data;
- data = selector;
- selector = undefined;
</del><ins>+ // Make sure we set the data so it isn't changed later
+ data_user.set( elem, key, data );
+ } else {
+ data = undefined;
+ }
</ins><span class="cx"> }
</span><del>- }
- if ( fn === false ) {
- fn = returnFalse;
- } else if ( !fn ) {
- return this;
- }
</del><ins>+ return data;
+}
</ins><span class="cx">
</span><del>- if ( one === 1 ) {
- origFn = fn;
- fn = function( event ) {
- // Can use an empty set, since event contains the info
- jQuery().off( event );
- return origFn.apply( this, arguments );
- };
- // Use same guid so caller can remove using origFn
- fn.guid = origFn.guid || ( origFn.guid = jQuery.guid++ );
- }
- return this.each( function() {
- jQuery.event.add( this, types, fn, data, selector );
- });
</del><ins>+jQuery.extend({
+ hasData: function( elem ) {
+ return data_user.hasData( elem ) || data_priv.hasData( elem );
</ins><span class="cx"> },
</span><del>- one: function( types, selector, data, fn ) {
- return this.on( types, selector, data, fn, 1 );
- },
- off: function( types, selector, fn ) {
- var handleObj, type;
- if ( types && types.preventDefault && types.handleObj ) {
- // ( event ) dispatched jQuery.Event
- handleObj = types.handleObj;
- jQuery( types.delegateTarget ).off(
- handleObj.namespace ? handleObj.origType + "." + handleObj.namespace : handleObj.origType,
- handleObj.selector,
- handleObj.handler
- );
- return this;
- }
- if ( typeof types === "object" ) {
- // ( types-object [, selector] )
- for ( type in types ) {
- this.off( type, selector, types[ type ] );
- }
- return this;
- }
- if ( selector === false || typeof selector === "function" ) {
- // ( types [, fn] )
- fn = selector;
- selector = undefined;
- }
- if ( fn === false ) {
- fn = returnFalse;
- }
- return this.each(function() {
- jQuery.event.remove( this, types, fn, selector );
- });
- },
</del><span class="cx">
</span><del>- bind: function( types, data, fn ) {
- return this.on( types, null, data, fn );
</del><ins>+ data: function( elem, name, data ) {
+ return data_user.access( elem, name, data );
</ins><span class="cx"> },
</span><del>- unbind: function( types, fn ) {
- return this.off( types, null, fn );
- },
</del><span class="cx">
</span><del>- delegate: function( selector, types, data, fn ) {
- return this.on( types, selector, data, fn );
</del><ins>+ removeData: function( elem, name ) {
+ data_user.remove( elem, name );
</ins><span class="cx"> },
</span><del>- undelegate: function( selector, types, fn ) {
- // ( namespace ) or ( selector, types [, fn] )
- return arguments.length === 1 ? this.off( selector, "**" ) : this.off( types, selector || "**", fn );
- },
</del><span class="cx">
</span><del>- trigger: function( type, data ) {
- return this.each(function() {
- jQuery.event.trigger( type, data, this );
- });
</del><ins>+ // TODO: Now that all calls to _data and _removeData have been replaced
+ // with direct calls to data_priv methods, these can be deprecated.
+ _data: function( elem, name, data ) {
+ return data_priv.access( elem, name, data );
</ins><span class="cx"> },
</span><del>- triggerHandler: function( type, data ) {
- var elem = this[0];
- if ( elem ) {
- return jQuery.event.trigger( type, data, elem, true );
</del><ins>+
+ _removeData: function( elem, name ) {
+ data_priv.remove( elem, name );
</ins><span class="cx"> }
</span><del>- }
</del><span class="cx"> });
</span><del>-/*!
- * Sizzle CSS Selector Engine
- * Copyright 2012 jQuery Foundation and other contributors
- * Released under the MIT license
- * http://sizzlejs.com/
- */
-(function( window, undefined ) {
</del><span class="cx">
</span><del>-var i,
- cachedruns,
- Expr,
- getText,
- isXML,
- compile,
- hasDuplicate,
- outermostContext,
</del><ins>+jQuery.fn.extend({
+ data: function( key, value ) {
+ var i, name, data,
+ elem = this[ 0 ],
+ attrs = elem && elem.attributes;
</ins><span class="cx">
</span><del>- // Local document vars
- setDocument,
- document,
- docElem,
- documentIsXML,
- rbuggyQSA,
- rbuggyMatches,
- matches,
- contains,
- sortOrder,
</del><ins>+ // Gets all values
+ if ( key === undefined ) {
+ if ( this.length ) {
+ data = data_user.get( elem );
</ins><span class="cx">
</span><del>- // Instance-specific data
- expando = "sizzle" + -(new Date()),
- preferredDoc = window.document,
- support = {},
- dirruns = 0,
- done = 0,
- classCache = createCache(),
- tokenCache = createCache(),
- compilerCache = createCache(),
</del><ins>+ if ( elem.nodeType === 1 && !data_priv.get( elem, "hasDataAttrs" ) ) {
+ i = attrs.length;
+ while ( i-- ) {
+ name = attrs[ i ].name;
</ins><span class="cx">
</span><del>- // General-purpose constants
- strundefined = typeof undefined,
- MAX_NEGATIVE = 1 << 31,
</del><ins>+ if ( name.indexOf( "data-" ) === 0 ) {
+ name = jQuery.camelCase( name.slice(5) );
+ dataAttr( elem, name, data[ name ] );
+ }
+ }
+ data_priv.set( elem, "hasDataAttrs", true );
+ }
+ }
</ins><span class="cx">
</span><del>- // Array methods
- arr = [],
- pop = arr.pop,
- push = arr.push,
- slice = arr.slice,
- // Use a stripped-down indexOf if we can't use a native one
- indexOf = arr.indexOf || function( elem ) {
- var i = 0,
- len = this.length;
- for ( ; i < len; i++ ) {
- if ( this[i] === elem ) {
- return i;
- }
- }
- return -1;
- },
</del><ins>+ return data;
+ }
</ins><span class="cx">
</span><ins>+ // Sets multiple values
+ if ( typeof key === "object" ) {
+ return this.each(function() {
+ data_user.set( this, key );
+ });
+ }
</ins><span class="cx">
</span><del>- // Regular expressions
</del><ins>+ return access( this, function( value ) {
+ var data,
+ camelKey = jQuery.camelCase( key );
</ins><span class="cx">
</span><del>- // Whitespace characters http://www.w3.org/TR/css3-selectors/#whitespace
- whitespace = "[\\x20\\t\\r\\n\\f]",
- // http://www.w3.org/TR/css3-syntax/#characters
- characterEncoding = "(?:\\\\.|[\\w-]|[^\\x00-\\xa0])+",
</del><ins>+ // The calling jQuery object (element matches) is not empty
+ // (and therefore has an element appears at this[ 0 ]) and the
+ // `value` parameter was not undefined. An empty jQuery object
+ // will result in `undefined` for elem = this[ 0 ] which will
+ // throw an exception if an attempt to read a data cache is made.
+ if ( elem && value === undefined ) {
+ // Attempt to get data from the cache
+ // with the key as-is
+ data = data_user.get( elem, key );
+ if ( data !== undefined ) {
+ return data;
+ }
</ins><span class="cx">
</span><del>- // Loosely modeled on CSS identifier characters
- // An unquoted value should be a CSS identifier http://www.w3.org/TR/css3-selectors/#attribute-selectors
- // Proper syntax: http://www.w3.org/TR/CSS21/syndata.html#value-def-identifier
- identifier = characterEncoding.replace( "w", "w#" ),
</del><ins>+ // Attempt to get data from the cache
+ // with the key camelized
+ data = data_user.get( elem, camelKey );
+ if ( data !== undefined ) {
+ return data;
+ }
</ins><span class="cx">
</span><del>- // Acceptable operators http://www.w3.org/TR/selectors/#attribute-selectors
- operators = "([*^$|!~]?=)",
- attributes = "\\[" + whitespace + "*(" + characterEncoding + ")" + whitespace +
- "*(?:" + operators + whitespace + "*(?:(['\"])((?:\\\\.|[^\\\\])*?)\\3|(" + identifier + ")|)|)" + whitespace + "*\\]",
</del><ins>+ // Attempt to "discover" the data in
+ // HTML5 custom data-* attrs
+ data = dataAttr( elem, camelKey, undefined );
+ if ( data !== undefined ) {
+ return data;
+ }
</ins><span class="cx">
</span><del>- // Prefer arguments quoted,
- // then not containing pseudos/brackets,
- // then attribute selectors/non-parenthetical expressions,
- // then anything else
- // These preferences are here to reduce the number of selectors
- // needing tokenize in the PSEUDO preFilter
- pseudos = ":(" + characterEncoding + ")(?:\\(((['\"])((?:\\\\.|[^\\\\])*?)\\3|((?:\\\\.|[^\\\\()[\\]]|" + attributes.replace( 3, 8 ) + ")*)|.*)\\)|)",
</del><ins>+ // We tried really hard, but the data doesn't exist.
+ return;
+ }
</ins><span class="cx">
</span><del>- // Leading and non-escaped trailing whitespace, capturing some non-whitespace characters preceding the latter
- rtrim = new RegExp( "^" + whitespace + "+|((?:^|[^\\\\])(?:\\\\.)*)" + whitespace + "+$", "g" ),
</del><ins>+ // Set the data...
+ this.each(function() {
+ // First, attempt to store a copy or reference of any
+ // data that might've been store with a camelCased key.
+ var data = data_user.get( this, camelKey );
</ins><span class="cx">
</span><del>- rcomma = new RegExp( "^" + whitespace + "*," + whitespace + "*" ),
- rcombinators = new RegExp( "^" + whitespace + "*([\\x20\\t\\r\\n\\f>+~])" + whitespace + "*" ),
- rpseudo = new RegExp( pseudos ),
- ridentifier = new RegExp( "^" + identifier + "$" ),
</del><ins>+ // For HTML5 data-* attribute interop, we have to
+ // store property names with dashes in a camelCase form.
+ // This might not apply to all properties...*
+ data_user.set( this, camelKey, value );
</ins><span class="cx">
</span><del>- matchExpr = {
- "ID": new RegExp( "^#(" + characterEncoding + ")" ),
- "CLASS": new RegExp( "^\\.(" + characterEncoding + ")" ),
- "NAME": new RegExp( "^\\[name=['\"]?(" + characterEncoding + ")['\"]?\\]" ),
- "TAG": new RegExp( "^(" + characterEncoding.replace( "w", "w*" ) + ")" ),
- "ATTR": new RegExp( "^" + attributes ),
- "PSEUDO": new RegExp( "^" + pseudos ),
- "CHILD": new RegExp( "^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\(" + whitespace +
- "*(even|odd|(([+-]|)(\\d*)n|)" + whitespace + "*(?:([+-]|)" + whitespace +
- "*(\\d+)|))" + whitespace + "*\\)|)", "i" ),
- // For use in libraries implementing .is()
- // We use this for POS matching in `select`
- "needsContext": new RegExp( "^" + whitespace + "*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\(" +
- whitespace + "*((?:-\\d)?\\d*)" + whitespace + "*\\)|)(?=[^-]|$)", "i" )
</del><ins>+ // *... In the case of properties that might _actually_
+ // have dashes, we need to also store a copy of that
+ // unchanged property.
+ if ( key.indexOf("-") !== -1 && data !== undefined ) {
+ data_user.set( this, key, value );
+ }
+ });
+ }, null, value, arguments.length > 1, null, true );
</ins><span class="cx"> },
</span><span class="cx">
</span><del>- rsibling = /[\x20\t\r\n\f]*[+~]/,
</del><ins>+ removeData: function( key ) {
+ return this.each(function() {
+ data_user.remove( this, key );
+ });
+ }
+});
</ins><span class="cx">
</span><del>- rnative = /^[^{]+\{\s*\[native code/,
</del><span class="cx">
</span><del>- // Easily-parseable/retrievable ID or TAG or CLASS selectors
- rquickExpr = /^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/,
</del><ins>+jQuery.extend({
+ queue: function( elem, type, data ) {
+ var queue;
</ins><span class="cx">
</span><del>- rinputs = /^(?:input|select|textarea|button)$/i,
- rheader = /^h\d$/i,
</del><ins>+ if ( elem ) {
+ type = ( type || "fx" ) + "queue";
+ queue = data_priv.get( elem, type );
</ins><span class="cx">
</span><del>- rescape = /'|\\/g,
- rattributeQuotes = /\=[\x20\t\r\n\f]*([^'"\]]*)[\x20\t\r\n\f]*\]/g,
</del><ins>+ // Speed up dequeue by getting out quickly if this is just a lookup
+ if ( data ) {
+ if ( !queue || jQuery.isArray( data ) ) {
+ queue = data_priv.access( elem, type, jQuery.makeArray(data) );
+ } else {
+ queue.push( data );
+ }
+ }
+ return queue || [];
+ }
+ },
</ins><span class="cx">
</span><del>- // CSS escapes http://www.w3.org/TR/CSS21/syndata.html#escaped-characters
- runescape = /\\([\da-fA-F]{1,6}[\x20\t\r\n\f]?|.)/g,
- funescape = function( _, escaped ) {
- var high = "0x" + escaped - 0x10000;
- // NaN means non-codepoint
- return high !== high ?
- escaped :
- // BMP codepoint
- high < 0 ?
- String.fromCharCode( high + 0x10000 ) :
- // Supplemental Plane codepoint (surrogate pair)
- String.fromCharCode( high >> 10 | 0xD800, high & 0x3FF | 0xDC00 );
- };
</del><ins>+ dequeue: function( elem, type ) {
+ type = type || "fx";
</ins><span class="cx">
</span><del>-// Use a stripped-down slice if we can't use a native one
-try {
- slice.call( preferredDoc.documentElement.childNodes, 0 )[0].nodeType;
-} catch ( e ) {
- slice = function( i ) {
- var elem,
- results = [];
- while ( (elem = this[i++]) ) {
- results.push( elem );
- }
- return results;
- };
-}
</del><ins>+ var queue = jQuery.queue( elem, type ),
+ startLength = queue.length,
+ fn = queue.shift(),
+ hooks = jQuery._queueHooks( elem, type ),
+ next = function() {
+ jQuery.dequeue( elem, type );
+ };
</ins><span class="cx">
</span><del>-/**
- * For feature detection
- * @param {Function} fn The function to test for native support
- */
-function isNative( fn ) {
- return rnative.test( fn + "" );
-}
</del><ins>+ // If the fx queue is dequeued, always remove the progress sentinel
+ if ( fn === "inprogress" ) {
+ fn = queue.shift();
+ startLength--;
+ }
</ins><span class="cx">
</span><del>-/**
- * Create key-value caches of limited size
- * @returns {Function(string, Object)} Returns the Object data after storing it on itself with
- * property name the (space-suffixed) string and (if the cache is larger than Expr.cacheLength)
- * deleting the oldest entry
- */
-function createCache() {
- var cache,
- keys = [];
</del><ins>+ if ( fn ) {
</ins><span class="cx">
</span><del>- return (cache = function( key, value ) {
- // Use (key + " ") to avoid collision with native prototype properties (see Issue #157)
- if ( keys.push( key += " " ) > Expr.cacheLength ) {
- // Only keep the most recent entries
- delete cache[ keys.shift() ];
- }
- return (cache[ key ] = value);
- });
-}
</del><ins>+ // Add a progress sentinel to prevent the fx queue from being
+ // automatically dequeued
+ if ( type === "fx" ) {
+ queue.unshift( "inprogress" );
+ }
</ins><span class="cx">
</span><del>-/**
- * Mark a function for special use by Sizzle
- * @param {Function} fn The function to mark
- */
-function markFunction( fn ) {
- fn[ expando ] = true;
- return fn;
-}
</del><ins>+ // clear up the last queue stop function
+ delete hooks.stop;
+ fn.call( elem, next, hooks );
+ }
</ins><span class="cx">
</span><del>-/**
- * Support testing using an element
- * @param {Function} fn Passed the created div and expects a boolean result
- */
-function assert( fn ) {
- var div = document.createElement("div");
</del><ins>+ if ( !startLength && hooks ) {
+ hooks.empty.fire();
+ }
+ },
</ins><span class="cx">
</span><del>- try {
- return fn( div );
- } catch (e) {
- return false;
- } finally {
- // release memory in IE
- div = null;
</del><ins>+ // not intended for public consumption - generates a queueHooks object, or returns the current one
+ _queueHooks: function( elem, type ) {
+ var key = type + "queueHooks";
+ return data_priv.get( elem, key ) || data_priv.access( elem, key, {
+ empty: jQuery.Callbacks("once memory").add(function() {
+ data_priv.remove( elem, [ type + "queue", key ] );
+ })
+ });
</ins><span class="cx"> }
</span><del>-}
</del><ins>+});
</ins><span class="cx">
</span><del>-function Sizzle( selector, context, results, seed ) {
- var match, elem, m, nodeType,
- // QSA vars
- i, groups, old, nid, newContext, newSelector;
</del><ins>+jQuery.fn.extend({
+ queue: function( type, data ) {
+ var setter = 2;
</ins><span class="cx">
</span><del>- if ( ( context ? context.ownerDocument || context : preferredDoc ) !== document ) {
- setDocument( context );
- }
</del><ins>+ if ( typeof type !== "string" ) {
+ data = type;
+ type = "fx";
+ setter--;
+ }
</ins><span class="cx">
</span><del>- context = context || document;
- results = results || [];
</del><ins>+ if ( arguments.length < setter ) {
+ return jQuery.queue( this[0], type );
+ }
</ins><span class="cx">
</span><del>- if ( !selector || typeof selector !== "string" ) {
- return results;
- }
</del><ins>+ return data === undefined ?
+ this :
+ this.each(function() {
+ var queue = jQuery.queue( this, type, data );
</ins><span class="cx">
</span><del>- if ( (nodeType = context.nodeType) !== 1 && nodeType !== 9 ) {
- return [];
- }
</del><ins>+ // ensure a hooks for this queue
+ jQuery._queueHooks( this, type );
</ins><span class="cx">
</span><del>- if ( !documentIsXML && !seed ) {
</del><ins>+ if ( type === "fx" && queue[0] !== "inprogress" ) {
+ jQuery.dequeue( this, type );
+ }
+ });
+ },
+ dequeue: function( type ) {
+ return this.each(function() {
+ jQuery.dequeue( this, type );
+ });
+ },
+ clearQueue: function( type ) {
+ return this.queue( type || "fx", [] );
+ },
+ // Get a promise resolved when queues of a certain type
+ // are emptied (fx is the type by default)
+ promise: function( type, obj ) {
+ var tmp,
+ count = 1,
+ defer = jQuery.Deferred(),
+ elements = this,
+ i = this.length,
+ resolve = function() {
+ if ( !( --count ) ) {
+ defer.resolveWith( elements, [ elements ] );
+ }
+ };
</ins><span class="cx">
</span><del>- // Shortcuts
- if ( (match = rquickExpr.exec( selector )) ) {
- // Speed-up: Sizzle("#ID")
- if ( (m = match[1]) ) {
- if ( nodeType === 9 ) {
- elem = context.getElementById( m );
- // Check parentNode to catch when Blackberry 4.6 returns
- // nodes that are no longer in the document #6963
- if ( elem && elem.parentNode ) {
- // Handle the case where IE, Opera, and Webkit return items
- // by name instead of ID
- if ( elem.id === m ) {
- results.push( elem );
- return results;
</del><ins>+ if ( typeof type !== "string" ) {
+ obj = type;
+ type = undefined;
+ }
+ type = type || "fx";
+
+ while ( i-- ) {
+ tmp = data_priv.get( elements[ i ], type + "queueHooks" );
+ if ( tmp && tmp.empty ) {
+ count++;
+ tmp.empty.add( resolve );
+ }
+ }
+ resolve();
+ return defer.promise( obj );
</ins><span class="cx"> }
</span><del>- } else {
- return results;
- }
- } else {
- // Context is not a document
- if ( context.ownerDocument && (elem = context.ownerDocument.getElementById( m )) &&
- contains( context, elem ) && elem.id === m ) {
- results.push( elem );
- return results;
- }
- }
</del><ins>+});
+var pnum = (/[+-]?(?:\d*\.|)\d+(?:[eE][+-]?\d+|)/).source;
</ins><span class="cx">
</span><del>- // Speed-up: Sizzle("TAG")
- } else if ( match[2] ) {
- push.apply( results, slice.call(context.getElementsByTagName( selector ), 0) );
- return results;
</del><ins>+var cssExpand = [ "Top", "Right", "Bottom", "Left" ];
</ins><span class="cx">
</span><del>- // Speed-up: Sizzle(".CLASS")
- } else if ( (m = match[3]) && support.getByClassName && context.getElementsByClassName ) {
- push.apply( results, slice.call(context.getElementsByClassName( m ), 0) );
- return results;
- }
- }
</del><ins>+var isHidden = function( elem, el ) {
+ // isHidden might be called from jQuery#filter function;
+ // in that case, element will be second argument
+ elem = el || elem;
+ return jQuery.css( elem, "display" ) === "none" || !jQuery.contains( elem.ownerDocument, elem );
+ };
</ins><span class="cx">
</span><del>- // QSA path
- if ( support.qsa && !rbuggyQSA.test(selector) ) {
- old = true;
- nid = expando;
- newContext = context;
- newSelector = nodeType === 9 && selector;
</del><ins>+var rcheckableType = (/^(?:checkbox|radio)$/i);
</ins><span class="cx">
</span><del>- // qSA works strangely on Element-rooted queries
- // We can work around this by specifying an extra ID on the root
- // and working up from there (Thanks to Andrew Dupont for the technique)
- // IE 8 doesn't work on object elements
- if ( nodeType === 1 && context.nodeName.toLowerCase() !== "object" ) {
- groups = tokenize( selector );
</del><span class="cx">
</span><del>- if ( (old = context.getAttribute("id")) ) {
- nid = old.replace( rescape, "\\$&" );
- } else {
- context.setAttribute( "id", nid );
- }
- nid = "[id='" + nid + "'] ";
</del><span class="cx">
</span><del>- i = groups.length;
- while ( i-- ) {
- groups[i] = nid + toSelector( groups[i] );
- }
- newContext = rsibling.test( selector ) && context.parentNode || context;
- newSelector = groups.join(",");
- }
</del><ins>+(function() {
+ var fragment = document.createDocumentFragment(),
+ div = fragment.appendChild( document.createElement( "div" ) );
</ins><span class="cx">
</span><del>- if ( newSelector ) {
- try {
- push.apply( results, slice.call( newContext.querySelectorAll(
- newSelector
- ), 0 ) );
- return results;
- } catch(qsaError) {
- } finally {
- if ( !old ) {
- context.removeAttribute("id");
- }
- }
- }
- }
- }
</del><ins>+ // #11217 - WebKit loses check when the name is after the checked attribute
+ div.innerHTML = "<input type='radio' checked='checked' name='t'/>";
</ins><span class="cx">
</span><del>- // All others
- return select( selector.replace( rtrim, "$1" ), context, results, seed );
-}
</del><ins>+ // Support: Safari 5.1, iOS 5.1, Android 4.x, Android 2.3
+ // old WebKit doesn't clone checked state correctly in fragments
+ support.checkClone = div.cloneNode( true ).cloneNode( true ).lastChild.checked;
</ins><span class="cx">
</span><del>-/**
- * Detect xml
- * @param {Element|Object} elem An element or a document
- */
-isXML = Sizzle.isXML = function( elem ) {
- // documentElement is verified for cases where it doesn't yet exist
- // (such as loading iframes in IE - #4833)
- var documentElement = elem && (elem.ownerDocument || elem).documentElement;
- return documentElement ? documentElement.nodeName !== "HTML" : false;
-};
</del><ins>+ // Make sure textarea (and checkbox) defaultValue is properly cloned
+ // Support: IE9-IE11+
+ div.innerHTML = "<textarea>x</textarea>";
+ support.noCloneChecked = !!div.cloneNode( true ).lastChild.defaultValue;
+})();
+var strundefined = typeof undefined;
</ins><span class="cx">
</span><del>-/**
- * Sets document-related variables once based on the current document
- * @param {Element|Object} [doc] An element or document object to use to set the document
- * @returns {Object} Returns the current document
- */
-setDocument = Sizzle.setDocument = function( node ) {
- var doc = node ? node.ownerDocument || node : preferredDoc;
</del><span class="cx">
</span><del>- // If no document and documentElement is available, return
- if ( doc === document || doc.nodeType !== 9 || !doc.documentElement ) {
- return document;
- }
</del><span class="cx">
</span><del>- // Set our document
- document = doc;
- docElem = doc.documentElement;
</del><ins>+support.focusinBubbles = "onfocusin" in window;
</ins><span class="cx">
</span><del>- // Support tests
- documentIsXML = isXML( doc );
</del><span class="cx">
</span><del>- // Check if getElementsByTagName("*") returns only elements
- support.tagNameNoComments = assert(function( div ) {
- div.appendChild( doc.createComment("") );
- return !div.getElementsByTagName("*").length;
- });
</del><ins>+var
+ rkeyEvent = /^key/,
+ rmouseEvent = /^(?:mouse|contextmenu)|click/,
+ rfocusMorph = /^(?:focusinfocus|focusoutblur)$/,
+ rtypenamespace = /^([^.]*)(?:\.(.+)|)$/;
</ins><span class="cx">
</span><del>- // Check if attributes should be retrieved by attribute nodes
- support.attributes = assert(function( div ) {
- div.innerHTML = "<select></select>";
- var type = typeof div.lastChild.getAttribute("multiple");
- // IE8 returns a string for some attributes even when not present
- return type !== "boolean" && type !== "string";
- });
</del><ins>+function returnTrue() {
+ return true;
+}
</ins><span class="cx">
</span><del>- // Check if getElementsByClassName can be trusted
- support.getByClassName = assert(function( div ) {
- // Opera can't find a second classname (in 9.6)
- div.innerHTML = "<div class='hidden e'></div><div class='hidden'></div>";
- if ( !div.getElementsByClassName || !div.getElementsByClassName("e").length ) {
</del><ins>+function returnFalse() {
</ins><span class="cx"> return false;
</span><del>- }
</del><ins>+}
</ins><span class="cx">
</span><del>- // Safari 3.2 caches class attributes and doesn't catch changes
- div.lastChild.className = "e";
- return div.getElementsByClassName("e").length === 2;
- });
</del><ins>+function safeActiveElement() {
+ try {
+ return document.activeElement;
+ } catch ( err ) { }
+}
</ins><span class="cx">
</span><del>- // Check if getElementById returns elements by name
- // Check if getElementsByName privileges form controls or returns elements by ID
- support.getByName = assert(function( div ) {
- // Inject content
- div.id = expando + 0;
- div.innerHTML = "<a name='" + expando + "'></a><div name='" + expando + "'></div>";
- docElem.insertBefore( div, docElem.firstChild );
</del><ins>+/*
+ * Helper functions for managing events -- not part of the public interface.
+ * Props to Dean Edwards' addEvent library for many of the ideas.
+ */
+jQuery.event = {
</ins><span class="cx">
</span><del>- // Test
- var pass = doc.getElementsByName &&
- // buggy browsers will return fewer than the correct 2
- doc.getElementsByName( expando ).length === 2 +
- // buggy browsers will return more than the correct 0
- doc.getElementsByName( expando + 0 ).length;
- support.getIdNotName = !doc.getElementById( expando );
</del><ins>+ global: {},
</ins><span class="cx">
</span><del>- // Cleanup
- docElem.removeChild( div );
</del><ins>+ add: function( elem, types, handler, data, selector ) {
</ins><span class="cx">
</span><del>- return pass;
- });
</del><ins>+ var handleObjIn, eventHandle, tmp,
+ events, t, handleObj,
+ special, handlers, type, namespaces, origType,
+ elemData = data_priv.get( elem );
</ins><span class="cx">
</span><del>- // IE6/7 return modified attributes
- Expr.attrHandle = assert(function( div ) {
- div.innerHTML = "<a href='#'></a>";
- return div.firstChild && typeof div.firstChild.getAttribute !== strundefined &&
- div.firstChild.getAttribute("href") === "#";
- }) ?
- {} :
- {
- "href": function( elem ) {
- return elem.getAttribute( "href", 2 );
- },
- "type": function( elem ) {
- return elem.getAttribute("type");
- }
- };
</del><ins>+ // Don't attach events to noData or text/comment nodes (but allow plain objects)
+ if ( !elemData ) {
+ return;
+ }
</ins><span class="cx">
</span><del>- // ID find and filter
- if ( support.getIdNotName ) {
- Expr.find["ID"] = function( id, context ) {
- if ( typeof context.getElementById !== strundefined && !documentIsXML ) {
- var m = context.getElementById( id );
- // Check parentNode to catch when Blackberry 4.6 returns
- // nodes that are no longer in the document #6963
- return m && m.parentNode ? [m] : [];
- }
- };
- Expr.filter["ID"] = function( id ) {
- var attrId = id.replace( runescape, funescape );
- return function( elem ) {
- return elem.getAttribute("id") === attrId;
- };
- };
- } else {
- Expr.find["ID"] = function( id, context ) {
- if ( typeof context.getElementById !== strundefined && !documentIsXML ) {
- var m = context.getElementById( id );
</del><ins>+ // Caller can pass in an object of custom data in lieu of the handler
+ if ( handler.handler ) {
+ handleObjIn = handler;
+ handler = handleObjIn.handler;
+ selector = handleObjIn.selector;
+ }
</ins><span class="cx">
</span><del>- return m ?
- m.id === id || typeof m.getAttributeNode !== strundefined && m.getAttributeNode("id").value === id ?
- [m] :
- undefined :
- [];
- }
- };
- Expr.filter["ID"] = function( id ) {
- var attrId = id.replace( runescape, funescape );
- return function( elem ) {
- var node = typeof elem.getAttributeNode !== strundefined && elem.getAttributeNode("id");
- return node && node.value === attrId;
- };
- };
- }
</del><ins>+ // Make sure that the handler has a unique ID, used to find/remove it later
+ if ( !handler.guid ) {
+ handler.guid = jQuery.guid++;
+ }
</ins><span class="cx">
</span><del>- // Tag
- Expr.find["TAG"] = support.tagNameNoComments ?
- function( tag, context ) {
- if ( typeof context.getElementsByTagName !== strundefined ) {
- return context.getElementsByTagName( tag );
- }
- } :
- function( tag, context ) {
- var elem,
- tmp = [],
- i = 0,
- results = context.getElementsByTagName( tag );
</del><ins>+ // Init the element's event structure and main handler, if this is the first
+ if ( !(events = elemData.events) ) {
+ events = elemData.events = {};
+ }
+ if ( !(eventHandle = elemData.handle) ) {
+ eventHandle = elemData.handle = function( e ) {
+ // Discard the second event of a jQuery.event.trigger() and
+ // when an event is called after a page has unloaded
+ return typeof jQuery !== strundefined && jQuery.event.triggered !== e.type ?
+ jQuery.event.dispatch.apply( elem, arguments ) : undefined;
+ };
+ }
</ins><span class="cx">
</span><del>- // Filter out possible comments
- if ( tag === "*" ) {
- while ( (elem = results[i++]) ) {
- if ( elem.nodeType === 1 ) {
- tmp.push( elem );
- }
- }
</del><ins>+ // Handle multiple events separated by a space
+ types = ( types || "" ).match( rnotwhite ) || [ "" ];
+ t = types.length;
+ while ( t-- ) {
+ tmp = rtypenamespace.exec( types[t] ) || [];
+ type = origType = tmp[1];
+ namespaces = ( tmp[2] || "" ).split( "." ).sort();
</ins><span class="cx">
</span><del>- return tmp;
- }
- return results;
- };
</del><ins>+ // There *must* be a type, no attaching namespace-only handlers
+ if ( !type ) {
+ continue;
+ }
</ins><span class="cx">
</span><del>- // Name
- Expr.find["NAME"] = support.getByName && function( tag, context ) {
- if ( typeof context.getElementsByName !== strundefined ) {
- return context.getElementsByName( name );
- }
- };
</del><ins>+ // If event changes its type, use the special event handlers for the changed type
+ special = jQuery.event.special[ type ] || {};
</ins><span class="cx">
</span><del>- // Class
- Expr.find["CLASS"] = support.getByClassName && function( className, context ) {
- if ( typeof context.getElementsByClassName !== strundefined && !documentIsXML ) {
- return context.getElementsByClassName( className );
- }
- };
</del><ins>+ // If selector defined, determine special event api type, otherwise given type
+ type = ( selector ? special.delegateType : special.bindType ) || type;
</ins><span class="cx">
</span><del>- // QSA and matchesSelector support
</del><ins>+ // Update special based on newly reset type
+ special = jQuery.event.special[ type ] || {};
</ins><span class="cx">
</span><del>- // matchesSelector(:active) reports false when true (IE9/Opera 11.5)
- rbuggyMatches = [];
</del><ins>+ // handleObj is passed to all event handlers
+ handleObj = jQuery.extend({
+ type: type,
+ origType: origType,
+ data: data,
+ handler: handler,
+ guid: handler.guid,
+ selector: selector,
+ needsContext: selector && jQuery.expr.match.needsContext.test( selector ),
+ namespace: namespaces.join(".")
+ }, handleObjIn );
</ins><span class="cx">
</span><del>- // qSa(:focus) reports false when true (Chrome 21),
- // no need to also add to buggyMatches since matches checks buggyQSA
- // A support test would require too much code (would include document ready)
- rbuggyQSA = [ ":focus" ];
</del><ins>+ // Init the event handler queue if we're the first
+ if ( !(handlers = events[ type ]) ) {
+ handlers = events[ type ] = [];
+ handlers.delegateCount = 0;
</ins><span class="cx">
</span><del>- if ( (support.qsa = isNative(doc.querySelectorAll)) ) {
- // Build QSA regex
- // Regex strategy adopted from Diego Perini
- assert(function( div ) {
- // Select is set to empty string on purpose
- // This is to test IE's treatment of not explictly
- // setting a boolean content attribute,
- // since its presence should be enough
- // http://bugs.jquery.com/ticket/12359
- div.innerHTML = "<select><option selected=''></option></select>";
</del><ins>+ // Only use addEventListener if the special events handler returns false
+ if ( !special.setup || special.setup.call( elem, data, namespaces, eventHandle ) === false ) {
+ if ( elem.addEventListener ) {
+ elem.addEventListener( type, eventHandle, false );
+ }
+ }
+ }
</ins><span class="cx">
</span><del>- // IE8 - Some boolean attributes are not treated correctly
- if ( !div.querySelectorAll("[selected]").length ) {
- rbuggyQSA.push( "\\[" + whitespace + "*(?:checked|disabled|ismap|multiple|readonly|selected|value)" );
- }
</del><ins>+ if ( special.add ) {
+ special.add.call( elem, handleObj );
</ins><span class="cx">
</span><del>- // Webkit/Opera - :checked should return selected option elements
- // http://www.w3.org/TR/2011/REC-css3-selectors-20110929/#checked
- // IE8 throws error here and will not see later tests
- if ( !div.querySelectorAll(":checked").length ) {
- rbuggyQSA.push(":checked");
- }
- });
</del><ins>+ if ( !handleObj.handler.guid ) {
+ handleObj.handler.guid = handler.guid;
+ }
+ }
</ins><span class="cx">
</span><del>- assert(function( div ) {
</del><ins>+ // Add to the element's handler list, delegates in front
+ if ( selector ) {
+ handlers.splice( handlers.delegateCount++, 0, handleObj );
+ } else {
+ handlers.push( handleObj );
+ }
</ins><span class="cx">
</span><del>- // Opera 10-12/IE8 - ^= $= *= and empty values
- // Should not select anything
- div.innerHTML = "<input type='hidden' i=''/>";
- if ( div.querySelectorAll("[i^='']").length ) {
- rbuggyQSA.push( "[*^$]=" + whitespace + "*(?:\"\"|'')" );
- }
</del><ins>+ // Keep track of which events have ever been used, for event optimization
+ jQuery.event.global[ type ] = true;
+ }
</ins><span class="cx">
</span><del>- // FF 3.5 - :enabled/:disabled and hidden elements (hidden elements are still enabled)
- // IE8 throws error here and will not see later tests
- if ( !div.querySelectorAll(":enabled").length ) {
- rbuggyQSA.push( ":enabled", ":disabled" );
- }
</del><ins>+ },
</ins><span class="cx">
</span><del>- // Opera 10-11 does not throw on post-comma invalid pseudos
- div.querySelectorAll("*,:x");
- rbuggyQSA.push(",.*:");
- });
- }
</del><ins>+ // Detach an event or set of events from an element
+ remove: function( elem, types, handler, selector, mappedTypes ) {
</ins><span class="cx">
</span><del>- if ( (support.matchesSelector = isNative( (matches = docElem.matchesSelector ||
- docElem.mozMatchesSelector ||
- docElem.webkitMatchesSelector ||
- docElem.oMatchesSelector ||
- docElem.msMatchesSelector) )) ) {
</del><ins>+ var j, origCount, tmp,
+ events, t, handleObj,
+ special, handlers, type, namespaces, origType,
+ elemData = data_priv.hasData( elem ) && data_priv.get( elem );
</ins><span class="cx">
</span><del>- assert(function( div ) {
- // Check to see if it's possible to do matchesSelector
- // on a disconnected node (IE 9)
- support.disconnectedMatch = matches.call( div, "div" );
</del><ins>+ if ( !elemData || !(events = elemData.events) ) {
+ return;
+ }
</ins><span class="cx">
</span><del>- // This should fail with an exception
- // Gecko does not error, returns false instead
- matches.call( div, "[s!='']:x" );
- rbuggyMatches.push( "!=", pseudos );
- });
- }
</del><ins>+ // Once for each type.namespace in types; type may be omitted
+ types = ( types || "" ).match( rnotwhite ) || [ "" ];
+ t = types.length;
+ while ( t-- ) {
+ tmp = rtypenamespace.exec( types[t] ) || [];
+ type = origType = tmp[1];
+ namespaces = ( tmp[2] || "" ).split( "." ).sort();
</ins><span class="cx">
</span><del>- rbuggyQSA = new RegExp( rbuggyQSA.join("|") );
- rbuggyMatches = new RegExp( rbuggyMatches.join("|") );
</del><ins>+ // Unbind all events (on this namespace, if provided) for the element
+ if ( !type ) {
+ for ( type in events ) {
+ jQuery.event.remove( elem, type + types[ t ], handler, selector, true );
+ }
+ continue;
+ }
</ins><span class="cx">
</span><del>- // Element contains another
- // Purposefully does not implement inclusive descendent
- // As in, an element does not contain itself
- contains = isNative(docElem.contains) || docElem.compareDocumentPosition ?
- function( a, b ) {
- var adown = a.nodeType === 9 ? a.documentElement : a,
- bup = b && b.parentNode;
- return a === bup || !!( bup && bup.nodeType === 1 && (
- adown.contains ?
- adown.contains( bup ) :
- a.compareDocumentPosition && a.compareDocumentPosition( bup ) & 16
- ));
- } :
- function( a, b ) {
- if ( b ) {
- while ( (b = b.parentNode) ) {
- if ( b === a ) {
- return true;
- }
- }
- }
- return false;
- };
</del><ins>+ special = jQuery.event.special[ type ] || {};
+ type = ( selector ? special.delegateType : special.bindType ) || type;
+ handlers = events[ type ] || [];
+ tmp = tmp[2] && new RegExp( "(^|\\.)" + namespaces.join("\\.(?:.*\\.|)") + "(\\.|$)" );
</ins><span class="cx">
</span><del>- // Document order sorting
- sortOrder = docElem.compareDocumentPosition ?
- function( a, b ) {
- var compare;
</del><ins>+ // Remove matching events
+ origCount = j = handlers.length;
+ while ( j-- ) {
+ handleObj = handlers[ j ];
</ins><span class="cx">
</span><del>- if ( a === b ) {
- hasDuplicate = true;
- return 0;
- }
</del><ins>+ if ( ( mappedTypes || origType === handleObj.origType ) &&
+ ( !handler || handler.guid === handleObj.guid ) &&
+ ( !tmp || tmp.test( handleObj.namespace ) ) &&
+ ( !selector || selector === handleObj.selector || selector === "**" && handleObj.selector ) ) {
+ handlers.splice( j, 1 );
</ins><span class="cx">
</span><del>- if ( (compare = b.compareDocumentPosition && a.compareDocumentPosition && a.compareDocumentPosition( b )) ) {
- if ( compare & 1 || a.parentNode && a.parentNode.nodeType === 11 ) {
- if ( a === doc || contains( preferredDoc, a ) ) {
- return -1;
- }
- if ( b === doc || contains( preferredDoc, b ) ) {
- return 1;
- }
- return 0;
- }
- return compare & 4 ? -1 : 1;
- }
</del><ins>+ if ( handleObj.selector ) {
+ handlers.delegateCount--;
+ }
+ if ( special.remove ) {
+ special.remove.call( elem, handleObj );
+ }
+ }
+ }
</ins><span class="cx">
</span><del>- return a.compareDocumentPosition ? -1 : 1;
- } :
- function( a, b ) {
- var cur,
- i = 0,
- aup = a.parentNode,
- bup = b.parentNode,
- ap = [ a ],
- bp = [ b ];
</del><ins>+ // Remove generic event handler if we removed something and no more handlers exist
+ // (avoids potential for endless recursion during removal of special event handlers)
+ if ( origCount && !handlers.length ) {
+ if ( !special.teardown || special.teardown.call( elem, namespaces, elemData.handle ) === false ) {
+ jQuery.removeEvent( elem, type, elemData.handle );
+ }
</ins><span class="cx">
</span><del>- // Exit early if the nodes are identical
- if ( a === b ) {
- hasDuplicate = true;
- return 0;
</del><ins>+ delete events[ type ];
+ }
+ }
</ins><span class="cx">
</span><del>- // Parentless nodes are either documents or disconnected
- } else if ( !aup || !bup ) {
- return a === doc ? -1 :
- b === doc ? 1 :
- aup ? -1 :
- bup ? 1 :
- 0;
</del><ins>+ // Remove the expando if it's no longer used
+ if ( jQuery.isEmptyObject( events ) ) {
+ delete elemData.handle;
+ data_priv.remove( elem, "events" );
+ }
+ },
</ins><span class="cx">
</span><del>- // If the nodes are siblings, we can do a quick check
- } else if ( aup === bup ) {
- return siblingCheck( a, b );
- }
</del><ins>+ trigger: function( event, data, elem, onlyHandlers ) {
</ins><span class="cx">
</span><del>- // Otherwise we need full lists of their ancestors for comparison
- cur = a;
- while ( (cur = cur.parentNode) ) {
- ap.unshift( cur );
- }
- cur = b;
- while ( (cur = cur.parentNode) ) {
- bp.unshift( cur );
- }
</del><ins>+ var i, cur, tmp, bubbleType, ontype, handle, special,
+ eventPath = [ elem || document ],
+ type = hasOwn.call( event, "type" ) ? event.type : event,
+ namespaces = hasOwn.call( event, "namespace" ) ? event.namespace.split(".") : [];
</ins><span class="cx">
</span><del>- // Walk down the tree looking for a discrepancy
- while ( ap[i] === bp[i] ) {
- i++;
- }
</del><ins>+ cur = tmp = elem = elem || document;
</ins><span class="cx">
</span><del>- return i ?
- // Do a sibling check if the nodes have a common ancestor
- siblingCheck( ap[i], bp[i] ) :
</del><ins>+ // Don't do events on text and comment nodes
+ if ( elem.nodeType === 3 || elem.nodeType === 8 ) {
+ return;
+ }
</ins><span class="cx">
</span><del>- // Otherwise nodes in our document sort first
- ap[i] === preferredDoc ? -1 :
- bp[i] === preferredDoc ? 1 :
- 0;
- };
</del><ins>+ // focus/blur morphs to focusin/out; ensure we're not firing them right now
+ if ( rfocusMorph.test( type + jQuery.event.triggered ) ) {
+ return;
+ }
</ins><span class="cx">
</span><del>- // Always assume the presence of duplicates if sort doesn't
- // pass them to our comparison function (as in Google Chrome).
- hasDuplicate = false;
- [0, 0].sort( sortOrder );
- support.detectDuplicates = hasDuplicate;
</del><ins>+ if ( type.indexOf(".") >= 0 ) {
+ // Namespaced trigger; create a regexp to match event type in handle()
+ namespaces = type.split(".");
+ type = namespaces.shift();
+ namespaces.sort();
+ }
+ ontype = type.indexOf(":") < 0 && "on" + type;
</ins><span class="cx">
</span><del>- return document;
-};
</del><ins>+ // Caller can pass in a jQuery.Event object, Object, or just an event type string
+ event = event[ jQuery.expando ] ?
+ event :
+ new jQuery.Event( type, typeof event === "object" && event );
</ins><span class="cx">
</span><del>-Sizzle.matches = function( expr, elements ) {
- return Sizzle( expr, null, null, elements );
-};
</del><ins>+ // Trigger bitmask: & 1 for native handlers; & 2 for jQuery (always true)
+ event.isTrigger = onlyHandlers ? 2 : 3;
+ event.namespace = namespaces.join(".");
+ event.namespace_re = event.namespace ?
+ new RegExp( "(^|\\.)" + namespaces.join("\\.(?:.*\\.|)") + "(\\.|$)" ) :
+ null;
</ins><span class="cx">
</span><del>-Sizzle.matchesSelector = function( elem, expr ) {
- // Set document vars if needed
- if ( ( elem.ownerDocument || elem ) !== document ) {
- setDocument( elem );
- }
</del><ins>+ // Clean up the event in case it is being reused
+ event.result = undefined;
+ if ( !event.target ) {
+ event.target = elem;
+ }
</ins><span class="cx">
</span><del>- // Make sure that attribute selectors are quoted
- expr = expr.replace( rattributeQuotes, "='$1']" );
</del><ins>+ // Clone any incoming data and prepend the event, creating the handler arg list
+ data = data == null ?
+ [ event ] :
+ jQuery.makeArray( data, [ event ] );
</ins><span class="cx">
</span><del>- // rbuggyQSA always contains :focus, so no need for an existence check
- if ( support.matchesSelector && !documentIsXML && (!rbuggyMatches || !rbuggyMatches.test(expr)) && !rbuggyQSA.test(expr) ) {
- try {
- var ret = matches.call( elem, expr );
</del><ins>+ // Allow special events to draw outside the lines
+ special = jQuery.event.special[ type ] || {};
+ if ( !onlyHandlers && special.trigger && special.trigger.apply( elem, data ) === false ) {
+ return;
+ }
</ins><span class="cx">
</span><del>- // IE 9's matchesSelector returns false on disconnected nodes
- if ( ret || support.disconnectedMatch ||
- // As well, disconnected nodes are said to be in a document
- // fragment in IE 9
- elem.document && elem.document.nodeType !== 11 ) {
- return ret;
- }
- } catch(e) {}
- }
</del><ins>+ // Determine event propagation path in advance, per W3C events spec (#9951)
+ // Bubble up to document, then to window; watch for a global ownerDocument var (#9724)
+ if ( !onlyHandlers && !special.noBubble && !jQuery.isWindow( elem ) ) {
</ins><span class="cx">
</span><del>- return Sizzle( expr, document, null, [elem] ).length > 0;
-};
</del><ins>+ bubbleType = special.delegateType || type;
+ if ( !rfocusMorph.test( bubbleType + type ) ) {
+ cur = cur.parentNode;
+ }
+ for ( ; cur; cur = cur.parentNode ) {
+ eventPath.push( cur );
+ tmp = cur;
+ }
</ins><span class="cx">
</span><del>-Sizzle.contains = function( context, elem ) {
- // Set document vars if needed
- if ( ( context.ownerDocument || context ) !== document ) {
- setDocument( context );
- }
- return contains( context, elem );
-};
</del><ins>+ // Only add window if we got to document (e.g., not plain obj or detached DOM)
+ if ( tmp === (elem.ownerDocument || document) ) {
+ eventPath.push( tmp.defaultView || tmp.parentWindow || window );
+ }
+ }
</ins><span class="cx">
</span><del>-Sizzle.attr = function( elem, name ) {
- var val;
</del><ins>+ // Fire handlers on the event path
+ i = 0;
+ while ( (cur = eventPath[i++]) && !event.isPropagationStopped() ) {
</ins><span class="cx">
</span><del>- // Set document vars if needed
- if ( ( elem.ownerDocument || elem ) !== document ) {
- setDocument( elem );
- }
</del><ins>+ event.type = i > 1 ?
+ bubbleType :
+ special.bindType || type;
</ins><span class="cx">
</span><del>- if ( !documentIsXML ) {
- name = name.toLowerCase();
- }
- if ( (val = Expr.attrHandle[ name ]) ) {
- return val( elem );
- }
- if ( documentIsXML || support.attributes ) {
- return elem.getAttribute( name );
- }
- return ( (val = elem.getAttributeNode( name )) || elem.getAttribute( name ) ) && elem[ name ] === true ?
- name :
- val && val.specified ? val.value : null;
-};
</del><ins>+ // jQuery handler
+ handle = ( data_priv.get( cur, "events" ) || {} )[ event.type ] && data_priv.get( cur, "handle" );
+ if ( handle ) {
+ handle.apply( cur, data );
+ }
</ins><span class="cx">
</span><del>-Sizzle.error = function( msg ) {
- throw new Error( "Syntax error, unrecognized expression: " + msg );
-};
</del><ins>+ // Native handler
+ handle = ontype && cur[ ontype ];
+ if ( handle && handle.apply && jQuery.acceptData( cur ) ) {
+ event.result = handle.apply( cur, data );
+ if ( event.result === false ) {
+ event.preventDefault();
+ }
+ }
+ }
+ event.type = type;
</ins><span class="cx">
</span><del>-// Document sorting and removing duplicates
-Sizzle.uniqueSort = function( results ) {
- var elem,
- duplicates = [],
- i = 1,
- j = 0;
</del><ins>+ // If nobody prevented the default action, do it now
+ if ( !onlyHandlers && !event.isDefaultPrevented() ) {
</ins><span class="cx">
</span><del>- // Unless we *know* we can detect duplicates, assume their presence
- hasDuplicate = !support.detectDuplicates;
- results.sort( sortOrder );
</del><ins>+ if ( (!special._default || special._default.apply( eventPath.pop(), data ) === false) &&
+ jQuery.acceptData( elem ) ) {
</ins><span class="cx">
</span><del>- if ( hasDuplicate ) {
- for ( ; (elem = results[i]); i++ ) {
- if ( elem === results[ i - 1 ] ) {
- j = duplicates.push( i );
- }
- }
- while ( j-- ) {
- results.splice( duplicates[ j ], 1 );
- }
- }
</del><ins>+ // Call a native DOM method on the target with the same name name as the event.
+ // Don't do default actions on window, that's where global variables be (#6170)
+ if ( ontype && jQuery.isFunction( elem[ type ] ) && !jQuery.isWindow( elem ) ) {
</ins><span class="cx">
</span><del>- return results;
-};
</del><ins>+ // Don't re-trigger an onFOO event when we call its FOO() method
+ tmp = elem[ ontype ];
</ins><span class="cx">
</span><del>-function siblingCheck( a, b ) {
- var cur = b && a,
- diff = cur && ( ~b.sourceIndex || MAX_NEGATIVE ) - ( ~a.sourceIndex || MAX_NEGATIVE );
</del><ins>+ if ( tmp ) {
+ elem[ ontype ] = null;
+ }
</ins><span class="cx">
</span><del>- // Use IE sourceIndex if available on both nodes
- if ( diff ) {
- return diff;
- }
</del><ins>+ // Prevent re-triggering of the same event, since we already bubbled it above
+ jQuery.event.triggered = type;
+ elem[ type ]();
+ jQuery.event.triggered = undefined;
</ins><span class="cx">
</span><del>- // Check if b follows a
- if ( cur ) {
- while ( (cur = cur.nextSibling) ) {
- if ( cur === b ) {
- return -1;
- }
- }
- }
</del><ins>+ if ( tmp ) {
+ elem[ ontype ] = tmp;
+ }
+ }
+ }
+ }
</ins><span class="cx">
</span><del>- return a ? 1 : -1;
-}
</del><ins>+ return event.result;
+ },
</ins><span class="cx">
</span><del>-// Returns a function to use in pseudos for input types
-function createInputPseudo( type ) {
- return function( elem ) {
- var name = elem.nodeName.toLowerCase();
- return name === "input" && elem.type === type;
- };
-}
</del><ins>+ dispatch: function( event ) {
</ins><span class="cx">
</span><del>-// Returns a function to use in pseudos for buttons
-function createButtonPseudo( type ) {
- return function( elem ) {
- var name = elem.nodeName.toLowerCase();
- return (name === "input" || name === "button") && elem.type === type;
- };
-}
</del><ins>+ // Make a writable jQuery.Event from the native event object
+ event = jQuery.event.fix( event );
</ins><span class="cx">
</span><del>-// Returns a function to use in pseudos for positionals
-function createPositionalPseudo( fn ) {
- return markFunction(function( argument ) {
- argument = +argument;
- return markFunction(function( seed, matches ) {
- var j,
- matchIndexes = fn( [], seed.length, argument ),
- i = matchIndexes.length;
</del><ins>+ var i, j, ret, matched, handleObj,
+ handlerQueue = [],
+ args = slice.call( arguments ),
+ handlers = ( data_priv.get( this, "events" ) || {} )[ event.type ] || [],
+ special = jQuery.event.special[ event.type ] || {};
</ins><span class="cx">
</span><del>- // Match elements found at the specified indexes
- while ( i-- ) {
- if ( seed[ (j = matchIndexes[i]) ] ) {
- seed[j] = !(matches[j] = seed[j]);
- }
- }
- });
- });
-}
</del><ins>+ // Use the fix-ed jQuery.Event rather than the (read-only) native event
+ args[0] = event;
+ event.delegateTarget = this;
</ins><span class="cx">
</span><del>-/**
- * Utility function for retrieving the text value of an array of DOM nodes
- * @param {Array|Element} elem
- */
-getText = Sizzle.getText = function( elem ) {
- var node,
- ret = "",
- i = 0,
- nodeType = elem.nodeType;
</del><ins>+ // Call the preDispatch hook for the mapped type, and let it bail if desired
+ if ( special.preDispatch && special.preDispatch.call( this, event ) === false ) {
+ return;
+ }
</ins><span class="cx">
</span><del>- if ( !nodeType ) {
- // If no nodeType, this is expected to be an array
- for ( ; (node = elem[i]); i++ ) {
- // Do not traverse comment nodes
- ret += getText( node );
- }
- } else if ( nodeType === 1 || nodeType === 9 || nodeType === 11 ) {
- // Use textContent for elements
- // innerText usage removed for consistency of new lines (see #11153)
- if ( typeof elem.textContent === "string" ) {
- return elem.textContent;
- } else {
- // Traverse its children
- for ( elem = elem.firstChild; elem; elem = elem.nextSibling ) {
- ret += getText( elem );
- }
- }
- } else if ( nodeType === 3 || nodeType === 4 ) {
- return elem.nodeValue;
- }
- // Do not include comment or processing instruction nodes
</del><ins>+ // Determine handlers
+ handlerQueue = jQuery.event.handlers.call( this, event, handlers );
</ins><span class="cx">
</span><del>- return ret;
-};
</del><ins>+ // Run delegates first; they may want to stop propagation beneath us
+ i = 0;
+ while ( (matched = handlerQueue[ i++ ]) && !event.isPropagationStopped() ) {
+ event.currentTarget = matched.elem;
</ins><span class="cx">
</span><del>-Expr = Sizzle.selectors = {
</del><ins>+ j = 0;
+ while ( (handleObj = matched.handlers[ j++ ]) && !event.isImmediatePropagationStopped() ) {
</ins><span class="cx">
</span><del>- // Can be adjusted by the user
- cacheLength: 50,
</del><ins>+ // Triggered event must either 1) have no namespace, or
+ // 2) have namespace(s) a subset or equal to those in the bound event (both can have no namespace).
+ if ( !event.namespace_re || event.namespace_re.test( handleObj.namespace ) ) {
</ins><span class="cx">
</span><del>- createPseudo: markFunction,
</del><ins>+ event.handleObj = handleObj;
+ event.data = handleObj.data;
</ins><span class="cx">
</span><del>- match: matchExpr,
</del><ins>+ ret = ( (jQuery.event.special[ handleObj.origType ] || {}).handle || handleObj.handler )
+ .apply( matched.elem, args );
</ins><span class="cx">
</span><del>- find: {},
</del><ins>+ if ( ret !== undefined ) {
+ if ( (event.result = ret) === false ) {
+ event.preventDefault();
+ event.stopPropagation();
+ }
+ }
+ }
+ }
+ }
</ins><span class="cx">
</span><del>- relative: {
- ">": { dir: "parentNode", first: true },
- " ": { dir: "parentNode" },
- "+": { dir: "previousSibling", first: true },
- "~": { dir: "previousSibling" }
</del><ins>+ // Call the postDispatch hook for the mapped type
+ if ( special.postDispatch ) {
+ special.postDispatch.call( this, event );
+ }
+
+ return event.result;
</ins><span class="cx"> },
</span><span class="cx">
</span><del>- preFilter: {
- "ATTR": function( match ) {
- match[1] = match[1].replace( runescape, funescape );
</del><ins>+ handlers: function( event, handlers ) {
+ var i, matches, sel, handleObj,
+ handlerQueue = [],
+ delegateCount = handlers.delegateCount,
+ cur = event.target;
</ins><span class="cx">
</span><del>- // Move the given value to match[3] whether quoted or unquoted
- match[3] = ( match[4] || match[5] || "" ).replace( runescape, funescape );
</del><ins>+ // Find delegate handlers
+ // Black-hole SVG <use> instance trees (#13180)
+ // Avoid non-left-click bubbling in Firefox (#3861)
+ if ( delegateCount && cur.nodeType && (!event.button || event.type !== "click") ) {
</ins><span class="cx">
</span><del>- if ( match[2] === "~=" ) {
- match[3] = " " + match[3] + " ";
- }
</del><ins>+ for ( ; cur !== this; cur = cur.parentNode || this ) {
</ins><span class="cx">
</span><del>- return match.slice( 0, 4 );
- },
</del><ins>+ // Don't process clicks on disabled elements (#6911, #8165, #11382, #11764)
+ if ( cur.disabled !== true || event.type !== "click" ) {
+ matches = [];
+ for ( i = 0; i < delegateCount; i++ ) {
+ handleObj = handlers[ i ];
</ins><span class="cx">
</span><del>- "CHILD": function( match ) {
- /* matches from matchExpr["CHILD"]
- 1 type (only|nth|...)
- 2 what (child|of-type)
- 3 argument (even|odd|\d*|\d*n([+-]\d+)?|...)
- 4 xn-component of xn+y argument ([+-]?\d*n|)
- 5 sign of xn-component
- 6 x of xn-component
- 7 sign of y-component
- 8 y of y-component
- */
- match[1] = match[1].toLowerCase();
</del><ins>+ // Don't conflict with Object.prototype properties (#13203)
+ sel = handleObj.selector + " ";
</ins><span class="cx">
</span><del>- if ( match[1].slice( 0, 3 ) === "nth" ) {
- // nth-* requires argument
- if ( !match[3] ) {
- Sizzle.error( match[0] );
- }
</del><ins>+ if ( matches[ sel ] === undefined ) {
+ matches[ sel ] = handleObj.needsContext ?
+ jQuery( sel, this ).index( cur ) >= 0 :
+ jQuery.find( sel, this, null, [ cur ] ).length;
+ }
+ if ( matches[ sel ] ) {
+ matches.push( handleObj );
+ }
+ }
+ if ( matches.length ) {
+ handlerQueue.push({ elem: cur, handlers: matches });
+ }
+ }
+ }
+ }
</ins><span class="cx">
</span><del>- // numeric x and y parameters for Expr.filter.CHILD
- // remember that false/true cast respectively to 0/1
- match[4] = +( match[4] ? match[5] + (match[6] || 1) : 2 * ( match[3] === "even" || match[3] === "odd" ) );
- match[5] = +( ( match[7] + match[8] ) || match[3] === "odd" );
</del><ins>+ // Add the remaining (directly-bound) handlers
+ if ( delegateCount < handlers.length ) {
+ handlerQueue.push({ elem: this, handlers: handlers.slice( delegateCount ) });
+ }
</ins><span class="cx">
</span><del>- // other types prohibit arguments
- } else if ( match[3] ) {
- Sizzle.error( match[0] );
- }
-
- return match;
</del><ins>+ return handlerQueue;
</ins><span class="cx"> },
</span><span class="cx">
</span><del>- "PSEUDO": function( match ) {
- var excess,
- unquoted = !match[5] && match[2];
</del><ins>+ // Includes some event props shared by KeyEvent and MouseEvent
+ props: "altKey bubbles cancelable ctrlKey currentTarget eventPhase metaKey relatedTarget shiftKey target timeStamp view which".split(" "),
</ins><span class="cx">
</span><del>- if ( matchExpr["CHILD"].test( match[0] ) ) {
- return null;
- }
</del><ins>+ fixHooks: {},
</ins><span class="cx">
</span><del>- // Accept quoted arguments as-is
- if ( match[4] ) {
- match[2] = match[4];
</del><ins>+ keyHooks: {
+ props: "char charCode key keyCode".split(" "),
+ filter: function( event, original ) {
</ins><span class="cx">
</span><del>- // Strip excess characters from unquoted arguments
- } else if ( unquoted && rpseudo.test( unquoted ) &&
- // Get excess from tokenize (recursively)
- (excess = tokenize( unquoted, true )) &&
- // advance to the next closing parenthesis
- (excess = unquoted.indexOf( ")", unquoted.length - excess ) - unquoted.length) ) {
</del><ins>+ // Add which for key events
+ if ( event.which == null ) {
+ event.which = original.charCode != null ? original.charCode : original.keyCode;
+ }
</ins><span class="cx">
</span><del>- // excess is a negative index
- match[0] = match[0].slice( 0, excess );
- match[2] = unquoted.slice( 0, excess );
- }
-
- // Return only captures needed by the pseudo filter method (type and argument)
- return match.slice( 0, 3 );
- }
</del><ins>+ return event;
+ }
</ins><span class="cx"> },
</span><span class="cx">
</span><del>- filter: {
</del><ins>+ mouseHooks: {
+ props: "button buttons clientX clientY offsetX offsetY pageX pageY screenX screenY toElement".split(" "),
+ filter: function( event, original ) {
+ var eventDoc, doc, body,
+ button = original.button;
</ins><span class="cx">
</span><del>- "TAG": function( nodeName ) {
- if ( nodeName === "*" ) {
- return function() { return true; };
- }
</del><ins>+ // Calculate pageX/Y if missing and clientX/Y available
+ if ( event.pageX == null && original.clientX != null ) {
+ eventDoc = event.target.ownerDocument || document;
+ doc = eventDoc.documentElement;
+ body = eventDoc.body;
</ins><span class="cx">
</span><del>- nodeName = nodeName.replace( runescape, funescape ).toLowerCase();
- return function( elem ) {
- return elem.nodeName && elem.nodeName.toLowerCase() === nodeName;
- };
- },
</del><ins>+ event.pageX = original.clientX + ( doc && doc.scrollLeft || body && body.scrollLeft || 0 ) - ( doc && doc.clientLeft || body && body.clientLeft || 0 );
+ event.pageY = original.clientY + ( doc && doc.scrollTop || body && body.scrollTop || 0 ) - ( doc && doc.clientTop || body && body.clientTop || 0 );
+ }
</ins><span class="cx">
</span><del>- "CLASS": function( className ) {
- var pattern = classCache[ className + " " ];
</del><ins>+ // Add which for click: 1 === left; 2 === middle; 3 === right
+ // Note: button is not normalized, so don't use it
+ if ( !event.which && button !== undefined ) {
+ event.which = ( button & 1 ? 1 : ( button & 2 ? 3 : ( button & 4 ? 2 : 0 ) ) );
+ }
</ins><span class="cx">
</span><del>- return pattern ||
- (pattern = new RegExp( "(^|" + whitespace + ")" + className + "(" + whitespace + "|$)" )) &&
- classCache( className, function( elem ) {
- return pattern.test( elem.className || (typeof elem.getAttribute !== strundefined && elem.getAttribute("class")) || "" );
- });
</del><ins>+ return event;
+ }
</ins><span class="cx"> },
</span><span class="cx">
</span><del>- "ATTR": function( name, operator, check ) {
- return function( elem ) {
- var result = Sizzle.attr( elem, name );
</del><ins>+ fix: function( event ) {
+ if ( event[ jQuery.expando ] ) {
+ return event;
+ }
</ins><span class="cx">
</span><del>- if ( result == null ) {
- return operator === "!=";
- }
- if ( !operator ) {
- return true;
- }
</del><ins>+ // Create a writable copy of the event object and normalize some properties
+ var i, prop, copy,
+ type = event.type,
+ originalEvent = event,
+ fixHook = this.fixHooks[ type ];
</ins><span class="cx">
</span><del>- result += "";
</del><ins>+ if ( !fixHook ) {
+ this.fixHooks[ type ] = fixHook =
+ rmouseEvent.test( type ) ? this.mouseHooks :
+ rkeyEvent.test( type ) ? this.keyHooks :
+ {};
+ }
+ copy = fixHook.props ? this.props.concat( fixHook.props ) : this.props;
</ins><span class="cx">
</span><del>- return operator === "=" ? result === check :
- operator === "!=" ? result !== check :
- operator === "^=" ? check && result.indexOf( check ) === 0 :
- operator === "*=" ? check && result.indexOf( check ) > -1 :
- operator === "$=" ? check && result.slice( -check.length ) === check :
- operator === "~=" ? ( " " + result + " " ).indexOf( check ) > -1 :
- operator === "|=" ? result === check || result.slice( 0, check.length + 1 ) === check + "-" :
- false;
- };
- },
</del><ins>+ event = new jQuery.Event( originalEvent );
</ins><span class="cx">
</span><del>- "CHILD": function( type, what, argument, first, last ) {
- var simple = type.slice( 0, 3 ) !== "nth",
- forward = type.slice( -4 ) !== "last",
- ofType = what === "of-type";
</del><ins>+ i = copy.length;
+ while ( i-- ) {
+ prop = copy[ i ];
+ event[ prop ] = originalEvent[ prop ];
+ }
</ins><span class="cx">
</span><del>- return first === 1 && last === 0 ?
</del><ins>+ // Support: Cordova 2.5 (WebKit) (#13255)
+ // All events should have a target; Cordova deviceready doesn't
+ if ( !event.target ) {
+ event.target = document;
+ }
</ins><span class="cx">
</span><del>- // Shortcut for :nth-*(n)
- function( elem ) {
- return !!elem.parentNode;
- } :
</del><ins>+ // Support: Safari 6.0+, Chrome < 28
+ // Target should not be a text node (#504, #13143)
+ if ( event.target.nodeType === 3 ) {
+ event.target = event.target.parentNode;
+ }
</ins><span class="cx">
</span><del>- function( elem, context, xml ) {
- var cache, outerCache, node, diff, nodeIndex, start,
- dir = simple !== forward ? "nextSibling" : "previousSibling",
- parent = elem.parentNode,
- name = ofType && elem.nodeName.toLowerCase(),
- useCache = !xml && !ofType;
</del><ins>+ return fixHook.filter ? fixHook.filter( event, originalEvent ) : event;
+ },
</ins><span class="cx">
</span><del>- if ( parent ) {
</del><ins>+ special: {
+ load: {
+ // Prevent triggered image.load events from bubbling to window.load
+ noBubble: true
+ },
+ focus: {
+ // Fire native event if possible so blur/focus sequence is correct
+ trigger: function() {
+ if ( this !== safeActiveElement() && this.focus ) {
+ this.focus();
+ return false;
+ }
+ },
+ delegateType: "focusin"
+ },
+ blur: {
+ trigger: function() {
+ if ( this === safeActiveElement() && this.blur ) {
+ this.blur();
+ return false;
+ }
+ },
+ delegateType: "focusout"
+ },
+ click: {
+ // For checkbox, fire native event so checked state will be right
+ trigger: function() {
+ if ( this.type === "checkbox" && this.click && jQuery.nodeName( this, "input" ) ) {
+ this.click();
+ return false;
+ }
+ },
</ins><span class="cx">
</span><del>- // :(first|last|only)-(child|of-type)
- if ( simple ) {
- while ( dir ) {
- node = elem;
- while ( (node = node[ dir ]) ) {
- if ( ofType ? node.nodeName.toLowerCase() === name : node.nodeType === 1 ) {
- return false;
- }
- }
- // Reverse direction for :only-* (if we haven't yet done so)
- start = dir = type === "only" && !start && "nextSibling";
- }
- return true;
- }
</del><ins>+ // For cross-browser consistency, don't fire native .click() on links
+ _default: function( event ) {
+ return jQuery.nodeName( event.target, "a" );
+ }
+ },
</ins><span class="cx">
</span><del>- start = [ forward ? parent.firstChild : parent.lastChild ];
</del><ins>+ beforeunload: {
+ postDispatch: function( event ) {
</ins><span class="cx">
</span><del>- // non-xml :nth-child(...) stores cache data on `parent`
- if ( forward && useCache ) {
- // Seek `elem` from a previously-cached index
- outerCache = parent[ expando ] || (parent[ expando ] = {});
- cache = outerCache[ type ] || [];
- nodeIndex = cache[0] === dirruns && cache[1];
- diff = cache[0] === dirruns && cache[2];
- node = nodeIndex && parent.childNodes[ nodeIndex ];
</del><ins>+ // Support: Firefox 20+
+ // Firefox doesn't alert if the returnValue field is not set.
+ if ( event.result !== undefined ) {
+ event.originalEvent.returnValue = event.result;
+ }
+ }
+ }
+ },
</ins><span class="cx">
</span><del>- while ( (node = ++nodeIndex && node && node[ dir ] ||
</del><ins>+ simulate: function( type, elem, event, bubble ) {
+ // Piggyback on a donor event to simulate a different one.
+ // Fake originalEvent to avoid donor's stopPropagation, but if the
+ // simulated event prevents default then we do the same on the donor.
+ var e = jQuery.extend(
+ new jQuery.Event(),
+ event,
+ {
+ type: type,
+ isSimulated: true,
+ originalEvent: {}
+ }
+ );
+ if ( bubble ) {
+ jQuery.event.trigger( e, null, elem );
+ } else {
+ jQuery.event.dispatch.call( elem, e );
+ }
+ if ( e.isDefaultPrevented() ) {
+ event.preventDefault();
+ }
+ }
+};
</ins><span class="cx">
</span><del>- // Fallback to seeking `elem` from the start
- (diff = nodeIndex = 0) || start.pop()) ) {
</del><ins>+jQuery.removeEvent = function( elem, type, handle ) {
+ if ( elem.removeEventListener ) {
+ elem.removeEventListener( type, handle, false );
+ }
+};
</ins><span class="cx">
</span><del>- // When found, cache indexes on `parent` and break
- if ( node.nodeType === 1 && ++diff && node === elem ) {
- outerCache[ type ] = [ dirruns, nodeIndex, diff ];
- break;
</del><ins>+jQuery.Event = function( src, props ) {
+ // Allow instantiation without the 'new' keyword
+ if ( !(this instanceof jQuery.Event) ) {
+ return new jQuery.Event( src, props );
</ins><span class="cx"> }
</span><del>- }
</del><span class="cx">
</span><del>- // Use previously-cached element index if available
- } else if ( useCache && (cache = (elem[ expando ] || (elem[ expando ] = {}))[ type ]) && cache[0] === dirruns ) {
- diff = cache[1];
</del><ins>+ // Event object
+ if ( src && src.type ) {
+ this.originalEvent = src;
+ this.type = src.type;
</ins><span class="cx">
</span><del>- // xml :nth-child(...) or :nth-last-child(...) or :nth(-last)?-of-type(...)
- } else {
- // Use the same loop as above to seek `elem` from the start
- while ( (node = ++nodeIndex && node && node[ dir ] ||
- (diff = nodeIndex = 0) || start.pop()) ) {
</del><ins>+ // Events bubbling up the document may have been marked as prevented
+ // by a handler lower down the tree; reflect the correct value.
+ this.isDefaultPrevented = src.defaultPrevented ||
+ // Support: Android < 4.0
+ src.defaultPrevented === undefined &&
+ src.getPreventDefault && src.getPreventDefault() ?
+ returnTrue :
+ returnFalse;
</ins><span class="cx">
</span><del>- if ( ( ofType ? node.nodeName.toLowerCase() === name : node.nodeType === 1 ) && ++diff ) {
- // Cache the index of each encountered element
- if ( useCache ) {
- (node[ expando ] || (node[ expando ] = {}))[ type ] = [ dirruns, diff ];
</del><ins>+ // Event type
+ } else {
+ this.type = src;
</ins><span class="cx"> }
</span><span class="cx">
</span><del>- if ( node === elem ) {
- break;
</del><ins>+ // Put explicitly provided properties onto the event object
+ if ( props ) {
+ jQuery.extend( this, props );
</ins><span class="cx"> }
</span><del>- }
- }
- }
</del><span class="cx">
</span><del>- // Incorporate the offset, then check against cycle size
- diff -= last;
- return diff === first || ( diff % first === 0 && diff / first >= 0 );
- }
- };
- },
</del><ins>+ // Create a timestamp if incoming event doesn't have one
+ this.timeStamp = src && src.timeStamp || jQuery.now();
</ins><span class="cx">
</span><del>- "PSEUDO": function( pseudo, argument ) {
- // pseudo-class names are case-insensitive
- // http://www.w3.org/TR/selectors/#pseudo-classes
- // Prioritize by case sensitivity in case custom pseudos are added with uppercase letters
- // Remember that setFilters inherits from pseudos
- var args,
- fn = Expr.pseudos[ pseudo ] || Expr.setFilters[ pseudo.toLowerCase() ] ||
- Sizzle.error( "unsupported pseudo: " + pseudo );
</del><ins>+ // Mark it as fixed
+ this[ jQuery.expando ] = true;
+};
</ins><span class="cx">
</span><del>- // The user may use createPseudo to indicate that
- // arguments are needed to create the filter function
- // just as Sizzle does
- if ( fn[ expando ] ) {
- return fn( argument );
- }
</del><ins>+// jQuery.Event is based on DOM3 Events as specified by the ECMAScript Language Binding
+// http://www.w3.org/TR/2003/WD-DOM-Level-3-Events-20030331/ecma-script-binding.html
+jQuery.Event.prototype = {
+ isDefaultPrevented: returnFalse,
+ isPropagationStopped: returnFalse,
+ isImmediatePropagationStopped: returnFalse,
</ins><span class="cx">
</span><del>- // But maintain support for old signatures
- if ( fn.length > 1 ) {
- args = [ pseudo, pseudo, "", argument ];
- return Expr.setFilters.hasOwnProperty( pseudo.toLowerCase() ) ?
- markFunction(function( seed, matches ) {
- var idx,
- matched = fn( seed, argument ),
- i = matched.length;
- while ( i-- ) {
- idx = indexOf.call( seed, matched[i] );
- seed[ idx ] = !( matches[ idx ] = matched[i] );
- }
- }) :
- function( elem ) {
- return fn( elem, 0, args );
- };
- }
</del><ins>+ preventDefault: function() {
+ var e = this.originalEvent;
</ins><span class="cx">
</span><del>- return fn;
- }
</del><ins>+ this.isDefaultPrevented = returnTrue;
+
+ if ( e && e.preventDefault ) {
+ e.preventDefault();
+ }
</ins><span class="cx"> },
</span><ins>+ stopPropagation: function() {
+ var e = this.originalEvent;
</ins><span class="cx">
</span><del>- pseudos: {
- // Potentially complex pseudos
- "not": markFunction(function( selector ) {
- // Trim the selector passed to compile
- // to avoid treating leading and trailing
- // spaces as combinators
- var input = [],
- results = [],
- matcher = compile( selector.replace( rtrim, "$1" ) );
</del><ins>+ this.isPropagationStopped = returnTrue;
</ins><span class="cx">
</span><del>- return matcher[ expando ] ?
- markFunction(function( seed, matches, context, xml ) {
- var elem,
- unmatched = matcher( seed, null, xml, [] ),
- i = seed.length;
-
- // Match elements unmatched by `matcher`
- while ( i-- ) {
- if ( (elem = unmatched[i]) ) {
- seed[i] = !(matches[i] = elem);
</del><ins>+ if ( e && e.stopPropagation ) {
+ e.stopPropagation();
+ }
+ },
+ stopImmediatePropagation: function() {
+ this.isImmediatePropagationStopped = returnTrue;
+ this.stopPropagation();
</ins><span class="cx"> }
</span><del>- }
- }) :
- function( elem, context, xml ) {
- input[0] = elem;
- matcher( input, null, xml, results );
- return !results.pop();
- };
- }),
</del><ins>+};
</ins><span class="cx">
</span><del>- "has": markFunction(function( selector ) {
- return function( elem ) {
- return Sizzle( selector, elem ).length > 0;
- };
- }),
</del><ins>+// Create mouseenter/leave events using mouseover/out and event-time checks
+// Support: Chrome 15+
+jQuery.each({
+ mouseenter: "mouseover",
+ mouseleave: "mouseout"
+}, function( orig, fix ) {
+ jQuery.event.special[ orig ] = {
+ delegateType: fix,
+ bindType: fix,
</ins><span class="cx">
</span><del>- "contains": markFunction(function( text ) {
- return function( elem ) {
- return ( elem.textContent || elem.innerText || getText( elem ) ).indexOf( text ) > -1;
</del><ins>+ handle: function( event ) {
+ var ret,
+ target = this,
+ related = event.relatedTarget,
+ handleObj = event.handleObj;
+
+ // For mousenter/leave call the handler if related is outside the target.
+ // NB: No relatedTarget if the mouse left/entered the browser window
+ if ( !related || (related !== target && !jQuery.contains( target, related )) ) {
+ event.type = handleObj.origType;
+ ret = handleObj.handler.apply( this, arguments );
+ event.type = fix;
+ }
+ return ret;
+ }
</ins><span class="cx"> };
</span><del>- }),
</del><ins>+});
</ins><span class="cx">
</span><del>- // "Whether an element is represented by a :lang() selector
- // is based solely on the element's language value
- // being equal to the identifier C,
- // or beginning with the identifier C immediately followed by "-".
- // The matching of C against the element's language value is performed case-insensitively.
- // The identifier C does not have to be a valid language name."
- // http://www.w3.org/TR/selectors/#lang-pseudo
- "lang": markFunction( function( lang ) {
- // lang value must be a valid identifider
- if ( !ridentifier.test(lang || "") ) {
- Sizzle.error( "unsupported lang: " + lang );
- }
- lang = lang.replace( runescape, funescape ).toLowerCase();
- return function( elem ) {
- var elemLang;
- do {
- if ( (elemLang = documentIsXML ?
- elem.getAttribute("xml:lang") || elem.getAttribute("lang") :
- elem.lang) ) {
</del><ins>+// Create "bubbling" focus and blur events
+// Support: Firefox, Chrome, Safari
+if ( !support.focusinBubbles ) {
+ jQuery.each({ focus: "focusin", blur: "focusout" }, function( orig, fix ) {
</ins><span class="cx">
</span><del>- elemLang = elemLang.toLowerCase();
- return elemLang === lang || elemLang.indexOf( lang + "-" ) === 0;
- }
- } while ( (elem = elem.parentNode) && elem.nodeType === 1 );
- return false;
- };
- }),
</del><ins>+ // Attach a single capturing handler on the document while someone wants focusin/focusout
+ var handler = function( event ) {
+ jQuery.event.simulate( fix, event.target, jQuery.event.fix( event ), true );
+ };
</ins><span class="cx">
</span><del>- // Miscellaneous
- "target": function( elem ) {
- var hash = window.location && window.location.hash;
- return hash && hash.slice( 1 ) === elem.id;
- },
</del><ins>+ jQuery.event.special[ fix ] = {
+ setup: function() {
+ var doc = this.ownerDocument || this,
+ attaches = data_priv.access( doc, fix );
</ins><span class="cx">
</span><del>- "root": function( elem ) {
- return elem === docElem;
- },
</del><ins>+ if ( !attaches ) {
+ doc.addEventListener( orig, handler, true );
+ }
+ data_priv.access( doc, fix, ( attaches || 0 ) + 1 );
+ },
+ teardown: function() {
+ var doc = this.ownerDocument || this,
+ attaches = data_priv.access( doc, fix ) - 1;
</ins><span class="cx">
</span><del>- "focus": function( elem ) {
- return elem === document.activeElement && (!document.hasFocus || document.hasFocus()) && !!(elem.type || elem.href || ~elem.tabIndex);
- },
</del><ins>+ if ( !attaches ) {
+ doc.removeEventListener( orig, handler, true );
+ data_priv.remove( doc, fix );
</ins><span class="cx">
</span><del>- // Boolean properties
- "enabled": function( elem ) {
- return elem.disabled === false;
- },
</del><ins>+ } else {
+ data_priv.access( doc, fix, attaches );
+ }
+ }
+ };
+ });
+}
</ins><span class="cx">
</span><del>- "disabled": function( elem ) {
- return elem.disabled === true;
- },
</del><ins>+jQuery.fn.extend({
</ins><span class="cx">
</span><del>- "checked": function( elem ) {
- // In CSS3, :checked should return both checked and selected elements
- // http://www.w3.org/TR/2011/REC-css3-selectors-20110929/#checked
- var nodeName = elem.nodeName.toLowerCase();
- return (nodeName === "input" && !!elem.checked) || (nodeName === "option" && !!elem.selected);
- },
</del><ins>+ on: function( types, selector, data, fn, /*INTERNAL*/ one ) {
+ var origFn, type;
</ins><span class="cx">
</span><del>- "selected": function( elem ) {
- // Accessing this property makes selected-by-default
- // options in Safari work properly
- if ( elem.parentNode ) {
- elem.parentNode.selectedIndex;
- }
</del><ins>+ // Types can be a map of types/handlers
+ if ( typeof types === "object" ) {
+ // ( types-Object, selector, data )
+ if ( typeof selector !== "string" ) {
+ // ( types-Object, data )
+ data = data || selector;
+ selector = undefined;
+ }
+ for ( type in types ) {
+ this.on( type, selector, data, types[ type ], one );
+ }
+ return this;
+ }
</ins><span class="cx">
</span><del>- return elem.selected === true;
- },
</del><ins>+ if ( data == null && fn == null ) {
+ // ( types, fn )
+ fn = selector;
+ data = selector = undefined;
+ } else if ( fn == null ) {
+ if ( typeof selector === "string" ) {
+ // ( types, selector, fn )
+ fn = data;
+ data = undefined;
+ } else {
+ // ( types, data, fn )
+ fn = data;
+ data = selector;
+ selector = undefined;
+ }
+ }
+ if ( fn === false ) {
+ fn = returnFalse;
+ } else if ( !fn ) {
+ return this;
+ }
</ins><span class="cx">
</span><del>- // Contents
- "empty": function( elem ) {
- // http://www.w3.org/TR/selectors/#empty-pseudo
- // :empty is only affected by element nodes and content nodes(including text(3), cdata(4)),
- // not comment, processing instructions, or others
- // Thanks to Diego Perini for the nodeName shortcut
- // Greater than "@" means alpha characters (specifically not starting with "#" or "?")
- for ( elem = elem.firstChild; elem; elem = elem.nextSibling ) {
- if ( elem.nodeName > "@" || elem.nodeType === 3 || elem.nodeType === 4 ) {
- return false;
- }
- }
- return true;
</del><ins>+ if ( one === 1 ) {
+ origFn = fn;
+ fn = function( event ) {
+ // Can use an empty set, since event contains the info
+ jQuery().off( event );
+ return origFn.apply( this, arguments );
+ };
+ // Use same guid so caller can remove using origFn
+ fn.guid = origFn.guid || ( origFn.guid = jQuery.guid++ );
+ }
+ return this.each( function() {
+ jQuery.event.add( this, types, fn, data, selector );
+ });
</ins><span class="cx"> },
</span><del>-
- "parent": function( elem ) {
- return !Expr.pseudos["empty"]( elem );
</del><ins>+ one: function( types, selector, data, fn ) {
+ return this.on( types, selector, data, fn, 1 );
</ins><span class="cx"> },
</span><del>-
- // Element/input types
- "header": function( elem ) {
- return rheader.test( elem.nodeName );
</del><ins>+ off: function( types, selector, fn ) {
+ var handleObj, type;
+ if ( types && types.preventDefault && types.handleObj ) {
+ // ( event ) dispatched jQuery.Event
+ handleObj = types.handleObj;
+ jQuery( types.delegateTarget ).off(
+ handleObj.namespace ? handleObj.origType + "." + handleObj.namespace : handleObj.origType,
+ handleObj.selector,
+ handleObj.handler
+ );
+ return this;
+ }
+ if ( typeof types === "object" ) {
+ // ( types-object [, selector] )
+ for ( type in types ) {
+ this.off( type, selector, types[ type ] );
+ }
+ return this;
+ }
+ if ( selector === false || typeof selector === "function" ) {
+ // ( types [, fn] )
+ fn = selector;
+ selector = undefined;
+ }
+ if ( fn === false ) {
+ fn = returnFalse;
+ }
+ return this.each(function() {
+ jQuery.event.remove( this, types, fn, selector );
+ });
</ins><span class="cx"> },
</span><span class="cx">
</span><del>- "input": function( elem ) {
- return rinputs.test( elem.nodeName );
</del><ins>+ trigger: function( type, data ) {
+ return this.each(function() {
+ jQuery.event.trigger( type, data, this );
+ });
</ins><span class="cx"> },
</span><ins>+ triggerHandler: function( type, data ) {
+ var elem = this[0];
+ if ( elem ) {
+ return jQuery.event.trigger( type, data, elem, true );
+ }
+ }
+});
</ins><span class="cx">
</span><del>- "button": function( elem ) {
- var name = elem.nodeName.toLowerCase();
- return name === "input" && elem.type === "button" || name === "button";
- },
</del><span class="cx">
</span><del>- "text": function( elem ) {
- var attr;
- // IE6 and 7 will map elem.type to 'text' for new HTML5 types (search, etc)
- // use getAttribute instead to test this case
- return elem.nodeName.toLowerCase() === "input" &&
- elem.type === "text" &&
- ( (attr = elem.getAttribute("type")) == null || attr.toLowerCase() === elem.type );
- },
</del><ins>+var
+ rxhtmlTag = /<(?!area|br|col|embed|hr|img|input|link|meta|param)(([\w:]+)[^>]*)\/>/gi,
+ rtagName = /<([\w:]+)/,
+ rhtml = /<|&#?\w+;/,
+ rnoInnerhtml = /<(?:script|style|link)/i,
+ // checked="checked" or checked
+ rchecked = /checked\s*(?:[^=]|=\s*.checked.)/i,
+ rscriptType = /^$|\/(?:java|ecma)script/i,
+ rscriptTypeMasked = /^true\/(.*)/,
+ rcleanScript = /^\s*<!(?:\[CDATA\[|--)|(?:\]\]|--)>\s*$/g,
</ins><span class="cx">
</span><del>- // Position-in-collection
- "first": createPositionalPseudo(function() {
- return [ 0 ];
- }),
</del><ins>+ // We have to close these tags to support XHTML (#13200)
+ wrapMap = {
</ins><span class="cx">
</span><del>- "last": createPositionalPseudo(function( matchIndexes, length ) {
- return [ length - 1 ];
- }),
</del><ins>+ // Support: IE 9
+ option: [ 1, "<select multiple='multiple'>", "</select>" ],
</ins><span class="cx">
</span><del>- "eq": createPositionalPseudo(function( matchIndexes, length, argument ) {
- return [ argument < 0 ? argument + length : argument ];
- }),
</del><ins>+ thead: [ 1, "<table>", "</table>" ],
+ col: [ 2, "<table><colgroup>", "</colgroup></table>" ],
+ tr: [ 2, "<table><tbody>", "</tbody></table>" ],
+ td: [ 3, "<table><tbody><tr>", "</tr></tbody></table>" ],
</ins><span class="cx">
</span><del>- "even": createPositionalPseudo(function( matchIndexes, length ) {
- var i = 0;
- for ( ; i < length; i += 2 ) {
- matchIndexes.push( i );
- }
- return matchIndexes;
- }),
</del><ins>+ _default: [ 0, "", "" ]
+ };
</ins><span class="cx">
</span><del>- "odd": createPositionalPseudo(function( matchIndexes, length ) {
- var i = 1;
- for ( ; i < length; i += 2 ) {
- matchIndexes.push( i );
- }
- return matchIndexes;
- }),
</del><ins>+// Support: IE 9
+wrapMap.optgroup = wrapMap.option;
</ins><span class="cx">
</span><del>- "lt": createPositionalPseudo(function( matchIndexes, length, argument ) {
- var i = argument < 0 ? argument + length : argument;
- for ( ; --i >= 0; ) {
- matchIndexes.push( i );
- }
- return matchIndexes;
- }),
</del><ins>+wrapMap.tbody = wrapMap.tfoot = wrapMap.colgroup = wrapMap.caption = wrapMap.thead;
+wrapMap.th = wrapMap.td;
</ins><span class="cx">
</span><del>- "gt": createPositionalPseudo(function( matchIndexes, length, argument ) {
- var i = argument < 0 ? argument + length : argument;
- for ( ; ++i < length; ) {
- matchIndexes.push( i );
- }
- return matchIndexes;
- })
- }
-};
</del><ins>+// Support: 1.x compatibility
+// Manipulating tables requires a tbody
+function manipulationTarget( elem, content ) {
+ return jQuery.nodeName( elem, "table" ) &&
+ jQuery.nodeName( content.nodeType !== 11 ? content : content.firstChild, "tr" ) ?
</ins><span class="cx">
</span><del>-// Add button/input type pseudos
-for ( i in { radio: true, checkbox: true, file: true, password: true, image: true } ) {
- Expr.pseudos[ i ] = createInputPseudo( i );
</del><ins>+ elem.getElementsByTagName("tbody")[0] ||
+ elem.appendChild( elem.ownerDocument.createElement("tbody") ) :
+ elem;
</ins><span class="cx"> }
</span><del>-for ( i in { submit: true, reset: true } ) {
- Expr.pseudos[ i ] = createButtonPseudo( i );
</del><ins>+
+// Replace/restore the type attribute of script elements for safe DOM manipulation
+function disableScript( elem ) {
+ elem.type = (elem.getAttribute("type") !== null) + "/" + elem.type;
+ return elem;
</ins><span class="cx"> }
</span><ins>+function restoreScript( elem ) {
+ var match = rscriptTypeMasked.exec( elem.type );
</ins><span class="cx">
</span><del>-function tokenize( selector, parseOnly ) {
- var matched, match, tokens, type,
- soFar, groups, preFilters,
- cached = tokenCache[ selector + " " ];
-
- if ( cached ) {
- return parseOnly ? 0 : cached.slice( 0 );
</del><ins>+ if ( match ) {
+ elem.type = match[ 1 ];
+ } else {
+ elem.removeAttribute("type");
</ins><span class="cx"> }
</span><span class="cx">
</span><del>- soFar = selector;
- groups = [];
- preFilters = Expr.preFilter;
</del><ins>+ return elem;
+}
</ins><span class="cx">
</span><del>- while ( soFar ) {
</del><ins>+// Mark scripts as having already been evaluated
+function setGlobalEval( elems, refElements ) {
+ var i = 0,
+ l = elems.length;
</ins><span class="cx">
</span><del>- // Comma and first run
- if ( !matched || (match = rcomma.exec( soFar )) ) {
- if ( match ) {
- // Don't consume trailing commas as valid
- soFar = soFar.slice( match[0].length ) || soFar;
</del><ins>+ for ( ; i < l; i++ ) {
+ data_priv.set(
+ elems[ i ], "globalEval", !refElements || data_priv.get( refElements[ i ], "globalEval" )
+ );
</ins><span class="cx"> }
</span><del>- groups.push( tokens = [] );
- }
</del><ins>+}
</ins><span class="cx">
</span><del>- matched = false;
</del><ins>+function cloneCopyEvent( src, dest ) {
+ var i, l, type, pdataOld, pdataCur, udataOld, udataCur, events;
</ins><span class="cx">
</span><del>- // Combinators
- if ( (match = rcombinators.exec( soFar )) ) {
- matched = match.shift();
- tokens.push( {
- value: matched,
- // Cast descendant combinators to space
- type: match[0].replace( rtrim, " " )
- } );
- soFar = soFar.slice( matched.length );
</del><ins>+ if ( dest.nodeType !== 1 ) {
+ return;
</ins><span class="cx"> }
</span><span class="cx">
</span><del>- // Filters
- for ( type in Expr.filter ) {
- if ( (match = matchExpr[ type ].exec( soFar )) && (!preFilters[ type ] ||
- (match = preFilters[ type ]( match ))) ) {
- matched = match.shift();
- tokens.push( {
- value: matched,
- type: type,
- matches: match
- } );
- soFar = soFar.slice( matched.length );
- }
- }
</del><ins>+ // 1. Copy private data: events, handlers, etc.
+ if ( data_priv.hasData( src ) ) {
+ pdataOld = data_priv.access( src );
+ pdataCur = data_priv.set( dest, pdataOld );
+ events = pdataOld.events;
</ins><span class="cx">
</span><del>- if ( !matched ) {
- break;
</del><ins>+ if ( events ) {
+ delete pdataCur.handle;
+ pdataCur.events = {};
+
+ for ( type in events ) {
+ for ( i = 0, l = events[ type ].length; i < l; i++ ) {
+ jQuery.event.add( dest, type, events[ type ][ i ] );
+ }
+ }
+ }
</ins><span class="cx"> }
</span><del>- }
</del><span class="cx">
</span><del>- // Return the length of the invalid excess
- // if we're just parsing
- // Otherwise, throw an error or return tokens
- return parseOnly ?
- soFar.length :
- soFar ?
- Sizzle.error( selector ) :
- // Cache the tokens
- tokenCache( selector, groups ).slice( 0 );
-}
</del><ins>+ // 2. Copy user data
+ if ( data_user.hasData( src ) ) {
+ udataOld = data_user.access( src );
+ udataCur = jQuery.extend( {}, udataOld );
</ins><span class="cx">
</span><del>-function toSelector( tokens ) {
- var i = 0,
- len = tokens.length,
- selector = "";
- for ( ; i < len; i++ ) {
- selector += tokens[i].value;
</del><ins>+ data_user.set( dest, udataCur );
</ins><span class="cx"> }
</span><del>- return selector;
</del><span class="cx"> }
</span><span class="cx">
</span><del>-function addCombinator( matcher, combinator, base ) {
- var dir = combinator.dir,
- checkNonElements = base && dir === "parentNode",
- doneName = done++;
</del><ins>+function getAll( context, tag ) {
+ var ret = context.getElementsByTagName ? context.getElementsByTagName( tag || "*" ) :
+ context.querySelectorAll ? context.querySelectorAll( tag || "*" ) :
+ [];
</ins><span class="cx">
</span><del>- return combinator.first ?
- // Check against closest ancestor/preceding element
- function( elem, context, xml ) {
- while ( (elem = elem[ dir ]) ) {
- if ( elem.nodeType === 1 || checkNonElements ) {
- return matcher( elem, context, xml );
- }
- }
- } :
</del><ins>+ return tag === undefined || tag && jQuery.nodeName( context, tag ) ?
+ jQuery.merge( [ context ], ret ) :
+ ret;
+}
</ins><span class="cx">
</span><del>- // Check against all ancestor/preceding elements
- function( elem, context, xml ) {
- var data, cache, outerCache,
- dirkey = dirruns + " " + doneName;
</del><ins>+// Support: IE >= 9
+function fixInput( src, dest ) {
+ var nodeName = dest.nodeName.toLowerCase();
</ins><span class="cx">
</span><del>- // We can't set arbitrary data on XML nodes, so they don't benefit from dir caching
- if ( xml ) {
- while ( (elem = elem[ dir ]) ) {
- if ( elem.nodeType === 1 || checkNonElements ) {
- if ( matcher( elem, context, xml ) ) {
- return true;
- }
- }
- }
- } else {
- while ( (elem = elem[ dir ]) ) {
- if ( elem.nodeType === 1 || checkNonElements ) {
- outerCache = elem[ expando ] || (elem[ expando ] = {});
- if ( (cache = outerCache[ dir ]) && cache[0] === dirkey ) {
- if ( (data = cache[1]) === true || data === cachedruns ) {
- return data === true;
- }
- } else {
- cache = outerCache[ dir ] = [ dirkey ];
- cache[1] = matcher( elem, context, xml ) || cachedruns;
- if ( cache[1] === true ) {
- return true;
- }
- }
- }
- }
- }
- };
-}
</del><ins>+ // Fails to persist the checked state of a cloned checkbox or radio button.
+ if ( nodeName === "input" && rcheckableType.test( src.type ) ) {
+ dest.checked = src.checked;
</ins><span class="cx">
</span><del>-function elementMatcher( matchers ) {
- return matchers.length > 1 ?
- function( elem, context, xml ) {
- var i = matchers.length;
- while ( i-- ) {
- if ( !matchers[i]( elem, context, xml ) ) {
- return false;
</del><ins>+ // Fails to return the selected option to the default selected state when cloning options
+ } else if ( nodeName === "input" || nodeName === "textarea" ) {
+ dest.defaultValue = src.defaultValue;
</ins><span class="cx"> }
</span><del>- }
- return true;
- } :
- matchers[0];
</del><span class="cx"> }
</span><span class="cx">
</span><del>-function condense( unmatched, map, filter, context, xml ) {
- var elem,
- newUnmatched = [],
- i = 0,
- len = unmatched.length,
- mapped = map != null;
</del><ins>+jQuery.extend({
+ clone: function( elem, dataAndEvents, deepDataAndEvents ) {
+ var i, l, srcElements, destElements,
+ clone = elem.cloneNode( true ),
+ inPage = jQuery.contains( elem.ownerDocument, elem );
</ins><span class="cx">
</span><del>- for ( ; i < len; i++ ) {
- if ( (elem = unmatched[i]) ) {
- if ( !filter || filter( elem, context, xml ) ) {
- newUnmatched.push( elem );
- if ( mapped ) {
- map.push( i );
- }
- }
- }
- }
</del><ins>+ // Support: IE >= 9
+ // Fix Cloning issues
+ if ( !support.noCloneChecked && ( elem.nodeType === 1 || elem.nodeType === 11 ) &&
+ !jQuery.isXMLDoc( elem ) ) {
</ins><span class="cx">
</span><del>- return newUnmatched;
-}
</del><ins>+ // We eschew Sizzle here for performance reasons: http://jsperf.com/getall-vs-sizzle/2
+ destElements = getAll( clone );
+ srcElements = getAll( elem );
</ins><span class="cx">
</span><del>-function setMatcher( preFilter, selector, matcher, postFilter, postFinder, postSelector ) {
- if ( postFilter && !postFilter[ expando ] ) {
- postFilter = setMatcher( postFilter );
- }
- if ( postFinder && !postFinder[ expando ] ) {
- postFinder = setMatcher( postFinder, postSelector );
- }
- return markFunction(function( seed, results, context, xml ) {
- var temp, i, elem,
- preMap = [],
- postMap = [],
- preexisting = results.length,
</del><ins>+ for ( i = 0, l = srcElements.length; i < l; i++ ) {
+ fixInput( srcElements[ i ], destElements[ i ] );
+ }
+ }
</ins><span class="cx">
</span><del>- // Get initial elements from seed or context
- elems = seed || multipleContexts( selector || "*", context.nodeType ? [ context ] : context, [] ),
</del><ins>+ // Copy the events from the original to the clone
+ if ( dataAndEvents ) {
+ if ( deepDataAndEvents ) {
+ srcElements = srcElements || getAll( elem );
+ destElements = destElements || getAll( clone );
</ins><span class="cx">
</span><del>- // Prefilter to get matcher input, preserving a map for seed-results synchronization
- matcherIn = preFilter && ( seed || !selector ) ?
- condense( elems, preMap, preFilter, context, xml ) :
- elems,
</del><ins>+ for ( i = 0, l = srcElements.length; i < l; i++ ) {
+ cloneCopyEvent( srcElements[ i ], destElements[ i ] );
+ }
+ } else {
+ cloneCopyEvent( elem, clone );
+ }
+ }
</ins><span class="cx">
</span><del>- matcherOut = matcher ?
- // If we have a postFinder, or filtered seed, or non-seed postFilter or preexisting results,
- postFinder || ( seed ? preFilter : preexisting || postFilter ) ?
</del><ins>+ // Preserve script evaluation history
+ destElements = getAll( clone, "script" );
+ if ( destElements.length > 0 ) {
+ setGlobalEval( destElements, !inPage && getAll( elem, "script" ) );
+ }
</ins><span class="cx">
</span><del>- // ...intermediate processing is necessary
- [] :
</del><ins>+ // Return the cloned set
+ return clone;
+ },
</ins><span class="cx">
</span><del>- // ...otherwise use results directly
- results :
- matcherIn;
</del><ins>+ buildFragment: function( elems, context, scripts, selection ) {
+ var elem, tmp, tag, wrap, contains, j,
+ fragment = context.createDocumentFragment(),
+ nodes = [],
+ i = 0,
+ l = elems.length;
</ins><span class="cx">
</span><del>- // Find primary matches
- if ( matcher ) {
- matcher( matcherIn, matcherOut, context, xml );
- }
</del><ins>+ for ( ; i < l; i++ ) {
+ elem = elems[ i ];
</ins><span class="cx">
</span><del>- // Apply postFilter
- if ( postFilter ) {
- temp = condense( matcherOut, postMap );
- postFilter( temp, [], context, xml );
</del><ins>+ if ( elem || elem === 0 ) {
</ins><span class="cx">
</span><del>- // Un-match failing elements by moving them back to matcherIn
- i = temp.length;
- while ( i-- ) {
- if ( (elem = temp[i]) ) {
- matcherOut[ postMap[i] ] = !(matcherIn[ postMap[i] ] = elem);
- }
- }
- }
</del><ins>+ // Add nodes directly
+ if ( jQuery.type( elem ) === "object" ) {
+ // Support: QtWebKit
+ // jQuery.merge because push.apply(_, arraylike) throws
+ jQuery.merge( nodes, elem.nodeType ? [ elem ] : elem );
</ins><span class="cx">
</span><del>- if ( seed ) {
- if ( postFinder || preFilter ) {
- if ( postFinder ) {
- // Get the final matcherOut by condensing this intermediate into postFinder contexts
- temp = [];
- i = matcherOut.length;
- while ( i-- ) {
- if ( (elem = matcherOut[i]) ) {
- // Restore matcherIn since elem is not yet a final match
- temp.push( (matcherIn[i] = elem) );
- }
- }
- postFinder( null, (matcherOut = []), temp, xml );
- }
</del><ins>+ // Convert non-html into a text node
+ } else if ( !rhtml.test( elem ) ) {
+ nodes.push( context.createTextNode( elem ) );
</ins><span class="cx">
</span><del>- // Move matched elements from seed to results to keep them synchronized
- i = matcherOut.length;
- while ( i-- ) {
- if ( (elem = matcherOut[i]) &&
- (temp = postFinder ? indexOf.call( seed, elem ) : preMap[i]) > -1 ) {
</del><ins>+ // Convert html into DOM nodes
+ } else {
+ tmp = tmp || fragment.appendChild( context.createElement("div") );
</ins><span class="cx">
</span><del>- seed[temp] = !(results[temp] = elem);
- }
- }
- }
</del><ins>+ // Deserialize a standard representation
+ tag = ( rtagName.exec( elem ) || [ "", "" ] )[ 1 ].toLowerCase();
+ wrap = wrapMap[ tag ] || wrapMap._default;
+ tmp.innerHTML = wrap[ 1 ] + elem.replace( rxhtmlTag, "<$1></$2>" ) + wrap[ 2 ];
</ins><span class="cx">
</span><del>- // Add elements to results, through postFinder if defined
- } else {
- matcherOut = condense(
- matcherOut === results ?
- matcherOut.splice( preexisting, matcherOut.length ) :
- matcherOut
- );
- if ( postFinder ) {
- postFinder( null, results, matcherOut, xml );
- } else {
- push.apply( results, matcherOut );
- }
- }
- });
-}
</del><ins>+ // Descend through wrappers to the right content
+ j = wrap[ 0 ];
+ while ( j-- ) {
+ tmp = tmp.lastChild;
+ }
</ins><span class="cx">
</span><del>-function matcherFromTokens( tokens ) {
- var checkContext, matcher, j,
- len = tokens.length,
- leadingRelative = Expr.relative[ tokens[0].type ],
- implicitRelative = leadingRelative || Expr.relative[" "],
- i = leadingRelative ? 1 : 0,
</del><ins>+ // Support: QtWebKit
+ // jQuery.merge because push.apply(_, arraylike) throws
+ jQuery.merge( nodes, tmp.childNodes );
</ins><span class="cx">
</span><del>- // The foundational matcher ensures that elements are reachable from top-level context(s)
- matchContext = addCombinator( function( elem ) {
- return elem === checkContext;
- }, implicitRelative, true ),
- matchAnyContext = addCombinator( function( elem ) {
- return indexOf.call( checkContext, elem ) > -1;
- }, implicitRelative, true ),
- matchers = [ function( elem, context, xml ) {
- return ( !leadingRelative && ( xml || context !== outermostContext ) ) || (
- (checkContext = context).nodeType ?
- matchContext( elem, context, xml ) :
- matchAnyContext( elem, context, xml ) );
- } ];
</del><ins>+ // Remember the top-level container
+ tmp = fragment.firstChild;
</ins><span class="cx">
</span><del>- for ( ; i < len; i++ ) {
- if ( (matcher = Expr.relative[ tokens[i].type ]) ) {
- matchers = [ addCombinator(elementMatcher( matchers ), matcher) ];
- } else {
- matcher = Expr.filter[ tokens[i].type ].apply( null, tokens[i].matches );
</del><ins>+ // Fixes #12346
+ // Support: Webkit, IE
+ tmp.textContent = "";
+ }
+ }
+ }
</ins><span class="cx">
</span><del>- // Return special upon seeing a positional matcher
- if ( matcher[ expando ] ) {
- // Find the next relative operator (if any) for proper handling
- j = ++i;
- for ( ; j < len; j++ ) {
- if ( Expr.relative[ tokens[j].type ] ) {
- break;
- }
- }
- return setMatcher(
- i > 1 && elementMatcher( matchers ),
- i > 1 && toSelector( tokens.slice( 0, i - 1 ) ).replace( rtrim, "$1" ),
- matcher,
- i < j && matcherFromTokens( tokens.slice( i, j ) ),
- j < len && matcherFromTokens( (tokens = tokens.slice( j )) ),
- j < len && toSelector( tokens )
- );
- }
- matchers.push( matcher );
- }
- }
</del><ins>+ // Remove wrapper from fragment
+ fragment.textContent = "";
</ins><span class="cx">
</span><del>- return elementMatcher( matchers );
-}
</del><ins>+ i = 0;
+ while ( (elem = nodes[ i++ ]) ) {
</ins><span class="cx">
</span><del>-function matcherFromGroupMatchers( elementMatchers, setMatchers ) {
- // A counter to specify which element is currently being matched
- var matcherCachedRuns = 0,
- bySet = setMatchers.length > 0,
- byElement = elementMatchers.length > 0,
- superMatcher = function( seed, context, xml, results, expandContext ) {
- var elem, j, matcher,
- setMatched = [],
- matchedCount = 0,
- i = "0",
- unmatched = seed && [],
- outermost = expandContext != null,
- contextBackup = outermostContext,
- // We must always have either seed elements or context
- elems = seed || byElement && Expr.find["TAG"]( "*", expandContext && context.parentNode || context ),
- // Use integer dirruns iff this is the outermost matcher
- dirrunsUnique = (dirruns += contextBackup == null ? 1 : Math.random() || 0.1);
</del><ins>+ // #4087 - If origin and destination elements are the same, and this is
+ // that element, do not do anything
+ if ( selection && jQuery.inArray( elem, selection ) !== -1 ) {
+ continue;
+ }
</ins><span class="cx">
</span><del>- if ( outermost ) {
- outermostContext = context !== document && context;
- cachedruns = matcherCachedRuns;
- }
</del><ins>+ contains = jQuery.contains( elem.ownerDocument, elem );
</ins><span class="cx">
</span><del>- // Add elements passing elementMatchers directly to results
- // Keep `i` a string if there are no elements so `matchedCount` will be "00" below
- for ( ; (elem = elems[i]) != null; i++ ) {
- if ( byElement && elem ) {
- j = 0;
- while ( (matcher = elementMatchers[j++]) ) {
- if ( matcher( elem, context, xml ) ) {
- results.push( elem );
- break;
- }
- }
- if ( outermost ) {
- dirruns = dirrunsUnique;
- cachedruns = ++matcherCachedRuns;
- }
- }
</del><ins>+ // Append to fragment
+ tmp = getAll( fragment.appendChild( elem ), "script" );
</ins><span class="cx">
</span><del>- // Track unmatched elements for set filters
- if ( bySet ) {
- // They will have gone through all possible matchers
- if ( (elem = !matcher && elem) ) {
- matchedCount--;
- }
</del><ins>+ // Preserve script evaluation history
+ if ( contains ) {
+ setGlobalEval( tmp );
+ }
</ins><span class="cx">
</span><del>- // Lengthen the array for every element, matched or not
- if ( seed ) {
- unmatched.push( elem );
- }
- }
- }
</del><ins>+ // Capture executables
+ if ( scripts ) {
+ j = 0;
+ while ( (elem = tmp[ j++ ]) ) {
+ if ( rscriptType.test( elem.type || "" ) ) {
+ scripts.push( elem );
+ }
+ }
+ }
+ }
</ins><span class="cx">
</span><del>- // Apply set filters to unmatched elements
- matchedCount += i;
- if ( bySet && i !== matchedCount ) {
- j = 0;
- while ( (matcher = setMatchers[j++]) ) {
- matcher( unmatched, setMatched, context, xml );
- }
</del><ins>+ return fragment;
+ },
</ins><span class="cx">
</span><del>- if ( seed ) {
- // Reintegrate element matches to eliminate the need for sorting
- if ( matchedCount > 0 ) {
- while ( i-- ) {
- if ( !(unmatched[i] || setMatched[i]) ) {
- setMatched[i] = pop.call( results );
- }
- }
- }
</del><ins>+ cleanData: function( elems ) {
+ var data, elem, events, type, key, j,
+ special = jQuery.event.special,
+ i = 0;
</ins><span class="cx">
</span><del>- // Discard index placeholder values to get only actual matches
- setMatched = condense( setMatched );
- }
</del><ins>+ for ( ; (elem = elems[ i ]) !== undefined; i++ ) {
+ if ( jQuery.acceptData( elem ) ) {
+ key = elem[ data_priv.expando ];
</ins><span class="cx">
</span><del>- // Add matches to results
- push.apply( results, setMatched );
</del><ins>+ if ( key && (data = data_priv.cache[ key ]) ) {
+ events = Object.keys( data.events || {} );
+ if ( events.length ) {
+ for ( j = 0; (type = events[j]) !== undefined; j++ ) {
+ if ( special[ type ] ) {
+ jQuery.event.remove( elem, type );
</ins><span class="cx">
</span><del>- // Seedless set matches succeeding multiple successful matchers stipulate sorting
- if ( outermost && !seed && setMatched.length > 0 &&
- ( matchedCount + setMatchers.length ) > 1 ) {
-
- Sizzle.uniqueSort( results );
</del><ins>+ // This is a shortcut to avoid jQuery.event.remove's overhead
+ } else {
+ jQuery.removeEvent( elem, type, data.handle );
+ }
+ }
+ }
+ if ( data_priv.cache[ key ] ) {
+ // Discard any remaining `private` data
+ delete data_priv.cache[ key ];
+ }
+ }
+ }
+ // Discard any remaining `user` data
+ delete data_user.cache[ elem[ data_user.expando ] ];
+ }
</ins><span class="cx"> }
</span><del>- }
</del><ins>+});
</ins><span class="cx">
</span><del>- // Override manipulation of globals by nested matchers
- if ( outermost ) {
- dirruns = dirrunsUnique;
- outermostContext = contextBackup;
- }
</del><ins>+jQuery.fn.extend({
+ text: function( value ) {
+ return access( this, function( value ) {
+ return value === undefined ?
+ jQuery.text( this ) :
+ this.empty().each(function() {
+ if ( this.nodeType === 1 || this.nodeType === 11 || this.nodeType === 9 ) {
+ this.textContent = value;
+ }
+ });
+ }, null, value, arguments.length );
+ },
</ins><span class="cx">
</span><del>- return unmatched;
- };
</del><ins>+ append: function() {
+ return this.domManip( arguments, function( elem ) {
+ if ( this.nodeType === 1 || this.nodeType === 11 || this.nodeType === 9 ) {
+ var target = manipulationTarget( this, elem );
+ target.appendChild( elem );
+ }
+ });
+ },
</ins><span class="cx">
</span><del>- return bySet ?
- markFunction( superMatcher ) :
- superMatcher;
-}
</del><ins>+ prepend: function() {
+ return this.domManip( arguments, function( elem ) {
+ if ( this.nodeType === 1 || this.nodeType === 11 || this.nodeType === 9 ) {
+ var target = manipulationTarget( this, elem );
+ target.insertBefore( elem, target.firstChild );
+ }
+ });
+ },
</ins><span class="cx">
</span><del>-compile = Sizzle.compile = function( selector, group /* Internal Use Only */ ) {
- var i,
- setMatchers = [],
- elementMatchers = [],
- cached = compilerCache[ selector + " " ];
</del><ins>+ before: function() {
+ return this.domManip( arguments, function( elem ) {
+ if ( this.parentNode ) {
+ this.parentNode.insertBefore( elem, this );
+ }
+ });
+ },
</ins><span class="cx">
</span><del>- if ( !cached ) {
- // Generate a function of recursive functions that can be used to check each element
- if ( !group ) {
- group = tokenize( selector );
- }
- i = group.length;
- while ( i-- ) {
- cached = matcherFromTokens( group[i] );
- if ( cached[ expando ] ) {
- setMatchers.push( cached );
- } else {
- elementMatchers.push( cached );
- }
- }
</del><ins>+ after: function() {
+ return this.domManip( arguments, function( elem ) {
+ if ( this.parentNode ) {
+ this.parentNode.insertBefore( elem, this.nextSibling );
+ }
+ });
+ },
</ins><span class="cx">
</span><del>- // Cache the compiled function
- cached = compilerCache( selector, matcherFromGroupMatchers( elementMatchers, setMatchers ) );
- }
- return cached;
-};
</del><ins>+ remove: function( selector, keepData /* Internal Use Only */ ) {
+ var elem,
+ elems = selector ? jQuery.filter( selector, this ) : this,
+ i = 0;
</ins><span class="cx">
</span><del>-function multipleContexts( selector, contexts, results ) {
- var i = 0,
- len = contexts.length;
- for ( ; i < len; i++ ) {
- Sizzle( selector, contexts[i], results );
- }
- return results;
-}
</del><ins>+ for ( ; (elem = elems[i]) != null; i++ ) {
+ if ( !keepData && elem.nodeType === 1 ) {
+ jQuery.cleanData( getAll( elem ) );
+ }
</ins><span class="cx">
</span><del>-function select( selector, context, results, seed ) {
- var i, tokens, token, type, find,
- match = tokenize( selector );
</del><ins>+ if ( elem.parentNode ) {
+ if ( keepData && jQuery.contains( elem.ownerDocument, elem ) ) {
+ setGlobalEval( getAll( elem, "script" ) );
+ }
+ elem.parentNode.removeChild( elem );
+ }
+ }
</ins><span class="cx">
</span><del>- if ( !seed ) {
- // Try to minimize operations if there is only one group
- if ( match.length === 1 ) {
</del><ins>+ return this;
+ },
</ins><span class="cx">
</span><del>- // Take a shortcut and set the context if the root selector is an ID
- tokens = match[0] = match[0].slice( 0 );
- if ( tokens.length > 2 && (token = tokens[0]).type === "ID" &&
- context.nodeType === 9 && !documentIsXML &&
- Expr.relative[ tokens[1].type ] ) {
</del><ins>+ empty: function() {
+ var elem,
+ i = 0;
</ins><span class="cx">
</span><del>- context = Expr.find["ID"]( token.matches[0].replace( runescape, funescape ), context )[0];
- if ( !context ) {
- return results;
- }
</del><ins>+ for ( ; (elem = this[i]) != null; i++ ) {
+ if ( elem.nodeType === 1 ) {
</ins><span class="cx">
</span><del>- selector = selector.slice( tokens.shift().value.length );
- }
</del><ins>+ // Prevent memory leaks
+ jQuery.cleanData( getAll( elem, false ) );
</ins><span class="cx">
</span><del>- // Fetch a seed set for right-to-left matching
- i = matchExpr["needsContext"].test( selector ) ? 0 : tokens.length;
- while ( i-- ) {
- token = tokens[i];
</del><ins>+ // Remove any remaining nodes
+ elem.textContent = "";
+ }
+ }
</ins><span class="cx">
</span><del>- // Abort if we hit a combinator
- if ( Expr.relative[ (type = token.type) ] ) {
- break;
- }
- if ( (find = Expr.find[ type ]) ) {
- // Search, expanding context for leading sibling combinators
- if ( (seed = find(
- token.matches[0].replace( runescape, funescape ),
- rsibling.test( tokens[0].type ) && context.parentNode || context
- )) ) {
</del><ins>+ return this;
+ },
</ins><span class="cx">
</span><del>- // If seed is empty or no tokens remain, we can return early
- tokens.splice( i, 1 );
- selector = seed.length && toSelector( tokens );
- if ( !selector ) {
- push.apply( results, slice.call( seed, 0 ) );
- return results;
- }
</del><ins>+ clone: function( dataAndEvents, deepDataAndEvents ) {
+ dataAndEvents = dataAndEvents == null ? false : dataAndEvents;
+ deepDataAndEvents = deepDataAndEvents == null ? dataAndEvents : deepDataAndEvents;
</ins><span class="cx">
</span><del>- break;
- }
- }
- }
- }
- }
</del><ins>+ return this.map(function() {
+ return jQuery.clone( this, dataAndEvents, deepDataAndEvents );
+ });
+ },
</ins><span class="cx">
</span><del>- // Compile and execute a filtering function
- // Provide `match` to avoid retokenization if we modified the selector above
- compile( selector, match )(
- seed,
- context,
- documentIsXML,
- results,
- rsibling.test( selector )
- );
- return results;
-}
</del><ins>+ html: function( value ) {
+ return access( this, function( value ) {
+ var elem = this[ 0 ] || {},
+ i = 0,
+ l = this.length;
</ins><span class="cx">
</span><del>-// Deprecated
-Expr.pseudos["nth"] = Expr.pseudos["eq"];
</del><ins>+ if ( value === undefined && elem.nodeType === 1 ) {
+ return elem.innerHTML;
+ }
</ins><span class="cx">
</span><del>-// Easy API for creating new setFilters
-function setFilters() {}
-Expr.filters = setFilters.prototype = Expr.pseudos;
-Expr.setFilters = new setFilters();
</del><ins>+ // See if we can take a shortcut and just use innerHTML
+ if ( typeof value === "string" && !rnoInnerhtml.test( value ) &&
+ !wrapMap[ ( rtagName.exec( value ) || [ "", "" ] )[ 1 ].toLowerCase() ] ) {
</ins><span class="cx">
</span><del>-// Initialize with the default document
-setDocument();
</del><ins>+ value = value.replace( rxhtmlTag, "<$1></$2>" );
</ins><span class="cx">
</span><del>-// Override sizzle attribute retrieval
-Sizzle.attr = jQuery.attr;
-jQuery.find = Sizzle;
-jQuery.expr = Sizzle.selectors;
-jQuery.expr[":"] = jQuery.expr.pseudos;
-jQuery.unique = Sizzle.uniqueSort;
-jQuery.text = Sizzle.getText;
-jQuery.isXMLDoc = Sizzle.isXML;
-jQuery.contains = Sizzle.contains;
</del><ins>+ try {
+ for ( ; i < l; i++ ) {
+ elem = this[ i ] || {};
</ins><span class="cx">
</span><ins>+ // Remove element nodes and prevent memory leaks
+ if ( elem.nodeType === 1 ) {
+ jQuery.cleanData( getAll( elem, false ) );
+ elem.innerHTML = value;
+ }
+ }
</ins><span class="cx">
</span><del>-})( window );
-var runtil = /Until$/,
- rparentsprev = /^(?:parents|prev(?:Until|All))/,
- isSimple = /^.[^:#\[\.,]*$/,
- rneedsContext = jQuery.expr.match.needsContext,
- // methods guaranteed to produce a unique set when starting from a unique set
- guaranteedUnique = {
- children: true,
- contents: true,
- next: true,
- prev: true
- };
</del><ins>+ elem = 0;
</ins><span class="cx">
</span><del>-jQuery.fn.extend({
- find: function( selector ) {
- var i, ret, self,
- len = this.length;
</del><ins>+ // If using innerHTML throws an exception, use the fallback method
+ } catch( e ) {}
+ }
</ins><span class="cx">
</span><del>- if ( typeof selector !== "string" ) {
- self = this;
- return this.pushStack( jQuery( selector ).filter(function() {
- for ( i = 0; i < len; i++ ) {
- if ( jQuery.contains( self[ i ], this ) ) {
- return true;
- }
- }
- }) );
- }
</del><ins>+ if ( elem ) {
+ this.empty().append( value );
+ }
+ }, null, value, arguments.length );
+ },
</ins><span class="cx">
</span><del>- ret = [];
- for ( i = 0; i < len; i++ ) {
- jQuery.find( selector, this[ i ], ret );
- }
</del><ins>+ replaceWith: function() {
+ var arg = arguments[ 0 ];
</ins><span class="cx">
</span><del>- // Needed because $( selector, context ) becomes $( context ).find( selector )
- ret = this.pushStack( len > 1 ? jQuery.unique( ret ) : ret );
- ret.selector = ( this.selector ? this.selector + " " : "" ) + selector;
- return ret;
- },
</del><ins>+ // Make the changes, replacing each context element with the new content
+ this.domManip( arguments, function( elem ) {
+ arg = this.parentNode;
</ins><span class="cx">
</span><del>- has: function( target ) {
- var i,
- targets = jQuery( target, this ),
- len = targets.length;
</del><ins>+ jQuery.cleanData( getAll( this ) );
</ins><span class="cx">
</span><del>- return this.filter(function() {
- for ( i = 0; i < len; i++ ) {
- if ( jQuery.contains( this, targets[i] ) ) {
- return true;
- }
- }
- });
- },
</del><ins>+ if ( arg ) {
+ arg.replaceChild( elem, this );
+ }
+ });
</ins><span class="cx">
</span><del>- not: function( selector ) {
- return this.pushStack( winnow(this, selector, false) );
</del><ins>+ // Force removal if there was no new content (e.g., from empty arguments)
+ return arg && (arg.length || arg.nodeType) ? this : this.remove();
</ins><span class="cx"> },
</span><span class="cx">
</span><del>- filter: function( selector ) {
- return this.pushStack( winnow(this, selector, true) );
</del><ins>+ detach: function( selector ) {
+ return this.remove( selector, true );
</ins><span class="cx"> },
</span><span class="cx">
</span><del>- is: function( selector ) {
- return !!selector && (
- typeof selector === "string" ?
- // If this is a positional/relative selector, check membership in the returned set
- // so $("p:first").is("p:last") won't return true for a doc with two "p".
- rneedsContext.test( selector ) ?
- jQuery( selector, this.context ).index( this[0] ) >= 0 :
- jQuery.filter( selector, this ).length > 0 :
- this.filter( selector ).length > 0 );
- },
</del><ins>+ domManip: function( args, callback ) {
</ins><span class="cx">
</span><del>- closest: function( selectors, context ) {
- var cur,
- i = 0,
- l = this.length,
- ret = [],
- pos = rneedsContext.test( selectors ) || typeof selectors !== "string" ?
- jQuery( selectors, context || this.context ) :
- 0;
</del><ins>+ // Flatten any nested arrays
+ args = concat.apply( [], args );
</ins><span class="cx">
</span><del>- for ( ; i < l; i++ ) {
- cur = this[i];
</del><ins>+ var fragment, first, scripts, hasScripts, node, doc,
+ i = 0,
+ l = this.length,
+ set = this,
+ iNoClone = l - 1,
+ value = args[ 0 ],
+ isFunction = jQuery.isFunction( value );
</ins><span class="cx">
</span><del>- while ( cur && cur.ownerDocument && cur !== context && cur.nodeType !== 11 ) {
- if ( pos ? pos.index(cur) > -1 : jQuery.find.matchesSelector(cur, selectors) ) {
- ret.push( cur );
- break;
- }
- cur = cur.parentNode;
- }
- }
</del><ins>+ // We can't cloneNode fragments that contain checked, in WebKit
+ if ( isFunction ||
+ ( l > 1 && typeof value === "string" &&
+ !support.checkClone && rchecked.test( value ) ) ) {
+ return this.each(function( index ) {
+ var self = set.eq( index );
+ if ( isFunction ) {
+ args[ 0 ] = value.call( this, index, self.html() );
+ }
+ self.domManip( args, callback );
+ });
+ }
</ins><span class="cx">
</span><del>- return this.pushStack( ret.length > 1 ? jQuery.unique( ret ) : ret );
- },
</del><ins>+ if ( l ) {
+ fragment = jQuery.buildFragment( args, this[ 0 ].ownerDocument, false, this );
+ first = fragment.firstChild;
</ins><span class="cx">
</span><del>- // Determine the position of an element within
- // the matched set of elements
- index: function( elem ) {
</del><ins>+ if ( fragment.childNodes.length === 1 ) {
+ fragment = first;
+ }
</ins><span class="cx">
</span><del>- // No argument, return index in parent
- if ( !elem ) {
- return ( this[0] && this[0].parentNode ) ? this.first().prevAll().length : -1;
- }
</del><ins>+ if ( first ) {
+ scripts = jQuery.map( getAll( fragment, "script" ), disableScript );
+ hasScripts = scripts.length;
</ins><span class="cx">
</span><del>- // index in selector
- if ( typeof elem === "string" ) {
- return jQuery.inArray( this[0], jQuery( elem ) );
- }
</del><ins>+ // Use the original fragment for the last item instead of the first because it can end up
+ // being emptied incorrectly in certain situations (#8070).
+ for ( ; i < l; i++ ) {
+ node = fragment;
</ins><span class="cx">
</span><del>- // Locate the position of the desired element
- return jQuery.inArray(
- // If it receives a jQuery object, the first element is used
- elem.jquery ? elem[0] : elem, this );
- },
</del><ins>+ if ( i !== iNoClone ) {
+ node = jQuery.clone( node, true, true );
</ins><span class="cx">
</span><del>- add: function( selector, context ) {
- var set = typeof selector === "string" ?
- jQuery( selector, context ) :
- jQuery.makeArray( selector && selector.nodeType ? [ selector ] : selector ),
- all = jQuery.merge( this.get(), set );
</del><ins>+ // Keep references to cloned scripts for later restoration
+ if ( hasScripts ) {
+ // Support: QtWebKit
+ // jQuery.merge because push.apply(_, arraylike) throws
+ jQuery.merge( scripts, getAll( node, "script" ) );
+ }
+ }
</ins><span class="cx">
</span><del>- return this.pushStack( jQuery.unique(all) );
- },
</del><ins>+ callback.call( this[ i ], node, i );
+ }
</ins><span class="cx">
</span><del>- addBack: function( selector ) {
- return this.add( selector == null ?
- this.prevObject : this.prevObject.filter(selector)
- );
</del><ins>+ if ( hasScripts ) {
+ doc = scripts[ scripts.length - 1 ].ownerDocument;
+
+ // Reenable scripts
+ jQuery.map( scripts, restoreScript );
+
+ // Evaluate executable scripts on first document insertion
+ for ( i = 0; i < hasScripts; i++ ) {
+ node = scripts[ i ];
+ if ( rscriptType.test( node.type || "" ) &&
+ !data_priv.access( node, "globalEval" ) && jQuery.contains( doc, node ) ) {
+
+ if ( node.src ) {
+ // Optional AJAX dependency, but won't run scripts if not present
+ if ( jQuery._evalUrl ) {
+ jQuery._evalUrl( node.src );
+ }
+ } else {
+ jQuery.globalEval( node.textContent.replace( rcleanScript, "" ) );
+ }
+ }
+ }
+ }
+ }
+ }
+
+ return this;
</ins><span class="cx"> }
</span><span class="cx"> });
</span><span class="cx">
</span><del>-jQuery.fn.andSelf = jQuery.fn.addBack;
</del><ins>+jQuery.each({
+ appendTo: "append",
+ prependTo: "prepend",
+ insertBefore: "before",
+ insertAfter: "after",
+ replaceAll: "replaceWith"
+}, function( name, original ) {
+ jQuery.fn[ name ] = function( selector ) {
+ var elems,
+ ret = [],
+ insert = jQuery( selector ),
+ last = insert.length - 1,
+ i = 0;
</ins><span class="cx">
</span><del>-function sibling( cur, dir ) {
- do {
- cur = cur[ dir ];
- } while ( cur && cur.nodeType !== 1 );
</del><ins>+ for ( ; i <= last; i++ ) {
+ elems = i === last ? this : this.clone( true );
+ jQuery( insert[ i ] )[ original ]( elems );
</ins><span class="cx">
</span><del>- return cur;
</del><ins>+ // Support: QtWebKit
+ // .get() because push.apply(_, arraylike) throws
+ push.apply( ret, elems.get() );
+ }
+
+ return this.pushStack( ret );
+ };
+});
+
+
+var iframe,
+ elemdisplay = {};
+
+/**
+ * Retrieve the actual display of a element
+ * @param {String} name nodeName of the element
+ * @param {Object} doc Document object
+ */
+// Called only from within defaultDisplay
+function actualDisplay( name, doc ) {
+ var elem = jQuery( doc.createElement( name ) ).appendTo( doc.body ),
+
+ // getDefaultComputedStyle might be reliably used only on attached element
+ display = window.getDefaultComputedStyle ?
+
+ // Use of this method is a temporary fix (more like optmization) until something better comes along,
+ // since it was removed from specification and supported only in FF
+ window.getDefaultComputedStyle( elem[ 0 ] ).display : jQuery.css( elem[ 0 ], "display" );
+
+ // We don't have any data stored on the element,
+ // so use "detach" method as fast way to get rid of the element
+ elem.detach();
+
+ return display;
</ins><span class="cx"> }
</span><span class="cx">
</span><del>-jQuery.each({
- parent: function( elem ) {
- var parent = elem.parentNode;
- return parent && parent.nodeType !== 11 ? parent : null;
- },
- parents: function( elem ) {
- return jQuery.dir( elem, "parentNode" );
- },
- parentsUntil: function( elem, i, until ) {
- return jQuery.dir( elem, "parentNode", until );
- },
- next: function( elem ) {
- return sibling( elem, "nextSibling" );
- },
- prev: function( elem ) {
- return sibling( elem, "previousSibling" );
- },
- nextAll: function( elem ) {
- return jQuery.dir( elem, "nextSibling" );
- },
- prevAll: function( elem ) {
- return jQuery.dir( elem, "previousSibling" );
- },
- nextUntil: function( elem, i, until ) {
- return jQuery.dir( elem, "nextSibling", until );
- },
- prevUntil: function( elem, i, until ) {
- return jQuery.dir( elem, "previousSibling", until );
- },
- siblings: function( elem ) {
- return jQuery.sibling( ( elem.parentNode || {} ).firstChild, elem );
- },
- children: function( elem ) {
- return jQuery.sibling( elem.firstChild );
- },
- contents: function( elem ) {
- return jQuery.nodeName( elem, "iframe" ) ?
- elem.contentDocument || elem.contentWindow.document :
- jQuery.merge( [], elem.childNodes );
- }
-}, function( name, fn ) {
- jQuery.fn[ name ] = function( until, selector ) {
- var ret = jQuery.map( this, fn, until );
</del><ins>+/**
+ * Try to determine the default display value of an element
+ * @param {String} nodeName
+ */
+function defaultDisplay( nodeName ) {
+ var doc = document,
+ display = elemdisplay[ nodeName ];
</ins><span class="cx">
</span><del>- if ( !runtil.test( name ) ) {
- selector = until;
- }
</del><ins>+ if ( !display ) {
+ display = actualDisplay( nodeName, doc );
</ins><span class="cx">
</span><del>- if ( selector && typeof selector === "string" ) {
- ret = jQuery.filter( selector, ret );
- }
</del><ins>+ // If the simple way fails, read from inside an iframe
+ if ( display === "none" || !display ) {
</ins><span class="cx">
</span><del>- ret = this.length > 1 && !guaranteedUnique[ name ] ? jQuery.unique( ret ) : ret;
</del><ins>+ // Use the already-created iframe if possible
+ iframe = (iframe || jQuery( "<iframe frameborder='0' width='0' height='0'/>" )).appendTo( doc.documentElement );
</ins><span class="cx">
</span><del>- if ( this.length > 1 && rparentsprev.test( name ) ) {
- ret = ret.reverse();
- }
</del><ins>+ // Always write a new HTML skeleton so Webkit and Firefox don't choke on reuse
+ doc = iframe[ 0 ].contentDocument;
</ins><span class="cx">
</span><del>- return this.pushStack( ret );
- };
-});
</del><ins>+ // Support: IE
+ doc.write();
+ doc.close();
</ins><span class="cx">
</span><del>-jQuery.extend({
- filter: function( expr, elems, not ) {
- if ( not ) {
- expr = ":not(" + expr + ")";
</del><ins>+ display = actualDisplay( nodeName, doc );
+ iframe.detach();
+ }
+
+ // Store the correct default display
+ elemdisplay[ nodeName ] = display;
</ins><span class="cx"> }
</span><span class="cx">
</span><del>- return elems.length === 1 ?
- jQuery.find.matchesSelector(elems[0], expr) ? [ elems[0] ] : [] :
- jQuery.find.matches(expr, elems);
- },
</del><ins>+ return display;
+}
+var rmargin = (/^margin/);
</ins><span class="cx">
</span><del>- dir: function( elem, dir, until ) {
- var matched = [],
- cur = elem[ dir ];
</del><ins>+var rnumnonpx = new RegExp( "^(" + pnum + ")(?!px)[a-z%]+$", "i" );
</ins><span class="cx">
</span><del>- while ( cur && cur.nodeType !== 9 && (until === undefined || cur.nodeType !== 1 || !jQuery( cur ).is( until )) ) {
- if ( cur.nodeType === 1 ) {
- matched.push( cur );
- }
- cur = cur[dir];
- }
- return matched;
- },
</del><ins>+var getStyles = function( elem ) {
+ return elem.ownerDocument.defaultView.getComputedStyle( elem, null );
+ };
</ins><span class="cx">
</span><del>- sibling: function( n, elem ) {
- var r = [];
</del><span class="cx">
</span><del>- for ( ; n; n = n.nextSibling ) {
- if ( n.nodeType === 1 && n !== elem ) {
- r.push( n );
- }
- }
</del><span class="cx">
</span><del>- return r;
</del><ins>+function curCSS( elem, name, computed ) {
+ var width, minWidth, maxWidth, ret,
+ style = elem.style;
+
+ computed = computed || getStyles( elem );
+
+ // Support: IE9
+ // getPropertyValue is only needed for .css('filter') in IE9, see #12537
+ if ( computed ) {
+ ret = computed.getPropertyValue( name ) || computed[ name ];
</ins><span class="cx"> }
</span><del>-});
</del><span class="cx">
</span><del>-// Implement the identical functionality for filter and not
-function winnow( elements, qualifier, keep ) {
</del><ins>+ if ( computed ) {
</ins><span class="cx">
</span><del>- // Can't pass null or undefined to indexOf in Firefox 4
- // Set to 0 to skip string check
- qualifier = qualifier || 0;
</del><ins>+ if ( ret === "" && !jQuery.contains( elem.ownerDocument, elem ) ) {
+ ret = jQuery.style( elem, name );
+ }
</ins><span class="cx">
</span><del>- if ( jQuery.isFunction( qualifier ) ) {
- return jQuery.grep(elements, function( elem, i ) {
- var retVal = !!qualifier.call( elem, i, elem );
- return retVal === keep;
- });
</del><ins>+ // Support: iOS < 6
+ // A tribute to the "awesome hack by Dean Edwards"
+ // iOS < 6 (at least) returns percentage for a larger set of values, but width seems to be reliably pixels
+ // this is against the CSSOM draft spec: http://dev.w3.org/csswg/cssom/#resolved-values
+ if ( rnumnonpx.test( ret ) && rmargin.test( name ) ) {
</ins><span class="cx">
</span><del>- } else if ( qualifier.nodeType ) {
- return jQuery.grep(elements, function( elem ) {
- return ( elem === qualifier ) === keep;
- });
</del><ins>+ // Remember the original values
+ width = style.width;
+ minWidth = style.minWidth;
+ maxWidth = style.maxWidth;
</ins><span class="cx">
</span><del>- } else if ( typeof qualifier === "string" ) {
- var filtered = jQuery.grep(elements, function( elem ) {
- return elem.nodeType === 1;
- });
</del><ins>+ // Put in the new values to get a computed value out
+ style.minWidth = style.maxWidth = style.width = ret;
+ ret = computed.width;
</ins><span class="cx">
</span><del>- if ( isSimple.test( qualifier ) ) {
- return jQuery.filter(qualifier, filtered, !keep);
- } else {
- qualifier = jQuery.filter( qualifier, filtered );
</del><ins>+ // Revert the changed values
+ style.width = width;
+ style.minWidth = minWidth;
+ style.maxWidth = maxWidth;
+ }
</ins><span class="cx"> }
</span><del>- }
</del><span class="cx">
</span><del>- return jQuery.grep(elements, function( elem ) {
- return ( jQuery.inArray( elem, qualifier ) >= 0 ) === keep;
- });
</del><ins>+ return ret !== undefined ?
+ // Support: IE
+ // IE returns zIndex value as an integer.
+ ret + "" :
+ ret;
</ins><span class="cx"> }
</span><del>-function createSafeFragment( document ) {
- var list = nodeNames.split( "|" ),
- safeFrag = document.createDocumentFragment();
</del><span class="cx">
</span><del>- if ( safeFrag.createElement ) {
- while ( list.length ) {
- safeFrag.createElement(
- list.pop()
- );
- }
- }
- return safeFrag;
</del><ins>+
+function addGetHookIf( conditionFn, hookFn ) {
+ // Define the hook, we'll check on the first run if it's really needed.
+ return {
+ get: function() {
+ if ( conditionFn() ) {
+ // Hook not needed (or it's not possible to use it due to missing dependency),
+ // remove it.
+ // Since there are no other hooks for marginRight, remove the whole object.
+ delete this.get;
+ return;
+ }
+
+ // Hook needed; redefine it so that the support test is not executed again.
+
+ return (this.get = hookFn).apply( this, arguments );
+ }
+ };
</ins><span class="cx"> }
</span><span class="cx">
</span><del>-var nodeNames = "abbr|article|aside|audio|bdi|canvas|data|datalist|details|figcaption|figure|footer|" +
- "header|hgroup|mark|meter|nav|output|progress|section|summary|time|video",
- rinlinejQuery = / jQuery\d+="(?:null|\d+)"/g,
- rnoshimcache = new RegExp("<(?:" + nodeNames + ")[\\s/>]", "i"),
- rleadingWhitespace = /^\s+/,
- rxhtmlTag = /<(?!area|br|col|embed|hr|img|input|link|meta|param)(([\w:]+)[^>]*)\/>/gi,
- rtagName = /<([\w:]+)/,
- rtbody = /<tbody/i,
- rhtml = /<|&#?\w+;/,
- rnoInnerhtml = /<(?:script|style|link)/i,
- manipulation_rcheckableType = /^(?:checkbox|radio)$/i,
- // checked="checked" or checked
- rchecked = /checked\s*(?:[^=]|=\s*.checked.)/i,
- rscriptType = /^$|\/(?:java|ecma)script/i,
- rscriptTypeMasked = /^true\/(.*)/,
- rcleanScript = /^\s*<!(?:\[CDATA\[|--)|(?:\]\]|--)>\s*$/g,
</del><span class="cx">
</span><del>- // We have to close these tags to support XHTML (#13200)
- wrapMap = {
- option: [ 1, "<select multiple='multiple'>", "</select>" ],
- legend: [ 1, "<fieldset>", "</fieldset>" ],
- area: [ 1, "<map>", "</map>" ],
- param: [ 1, "<object>", "</object>" ],
- thead: [ 1, "<table>", "</table>" ],
- tr: [ 2, "<table><tbody>", "</tbody></table>" ],
- col: [ 2, "<table><tbody></tbody><colgroup>", "</colgroup></table>" ],
- td: [ 3, "<table><tbody><tr>", "</tr></tbody></table>" ],
</del><ins>+(function() {
+ var pixelPositionVal, boxSizingReliableVal,
+ // Support: Firefox, Android 2.3 (Prefixed box-sizing versions).
+ divReset = "padding:0;margin:0;border:0;display:block;-webkit-box-sizing:content-box;" +
+ "-moz-box-sizing:content-box;box-sizing:content-box",
+ docElem = document.documentElement,
+ container = document.createElement( "div" ),
+ div = document.createElement( "div" );
</ins><span class="cx">
</span><del>- // IE6-8 can't serialize link, script, style, or any html5 (NoScope) tags,
- // unless wrapped in a div with non-breaking characters in front of it.
- _default: jQuery.support.htmlSerialize ? [ 0, "", "" ] : [ 1, "X<div>", "</div>" ]
- },
- safeFragment = createSafeFragment( document ),
- fragmentDiv = safeFragment.appendChild( document.createElement("div") );
</del><ins>+ div.style.backgroundClip = "content-box";
+ div.cloneNode( true ).style.backgroundClip = "";
+ support.clearCloneStyle = div.style.backgroundClip === "content-box";
</ins><span class="cx">
</span><del>-wrapMap.optgroup = wrapMap.option;
-wrapMap.tbody = wrapMap.tfoot = wrapMap.colgroup = wrapMap.caption = wrapMap.thead;
-wrapMap.th = wrapMap.td;
</del><ins>+ container.style.cssText = "border:0;width:0;height:0;position:absolute;top:0;left:-9999px;" +
+ "margin-top:1px";
+ container.appendChild( div );
</ins><span class="cx">
</span><del>-jQuery.fn.extend({
- text: function( value ) {
- return jQuery.access( this, function( value ) {
- return value === undefined ?
- jQuery.text( this ) :
- this.empty().append( ( this[0] && this[0].ownerDocument || document ).createTextNode( value ) );
- }, null, value, arguments.length );
- },
</del><ins>+ // Executing both pixelPosition & boxSizingReliable tests require only one layout
+ // so they're executed at the same time to save the second computation.
+ function computePixelPositionAndBoxSizingReliable() {
+ // Support: Firefox, Android 2.3 (Prefixed box-sizing versions).
+ div.style.cssText = "-webkit-box-sizing:border-box;-moz-box-sizing:border-box;" +
+ "box-sizing:border-box;padding:1px;border:1px;display:block;width:4px;margin-top:1%;" +
+ "position:absolute;top:1%";
+ docElem.appendChild( container );
</ins><span class="cx">
</span><del>- wrapAll: function( html ) {
- if ( jQuery.isFunction( html ) ) {
- return this.each(function(i) {
- jQuery(this).wrapAll( html.call(this, i) );
- });
</del><ins>+ var divStyle = window.getComputedStyle( div, null );
+ pixelPositionVal = divStyle.top !== "1%";
+ boxSizingReliableVal = divStyle.width === "4px";
+
+ docElem.removeChild( container );
</ins><span class="cx"> }
</span><span class="cx">
</span><del>- if ( this[0] ) {
- // The elements to wrap the target around
- var wrap = jQuery( html, this[0].ownerDocument ).eq(0).clone(true);
</del><ins>+ // Use window.getComputedStyle because jsdom on node.js will break without it.
+ if ( window.getComputedStyle ) {
+ jQuery.extend(support, {
+ pixelPosition: function() {
+ // This test is executed only once but we still do memoizing
+ // since we can use the boxSizingReliable pre-computing.
+ // No need to check if the test was already performed, though.
+ computePixelPositionAndBoxSizingReliable();
+ return pixelPositionVal;
+ },
+ boxSizingReliable: function() {
+ if ( boxSizingReliableVal == null ) {
+ computePixelPositionAndBoxSizingReliable();
+ }
+ return boxSizingReliableVal;
+ },
+ reliableMarginRight: function() {
+ // Support: Android 2.3
+ // Check if div with explicit width and no margin-right incorrectly
+ // gets computed margin-right based on width of container. (#3333)
+ // WebKit Bug 13343 - getComputedStyle returns wrong value for margin-right
+ // This support function is only executed once so no memoizing is needed.
+ var ret,
+ marginDiv = div.appendChild( document.createElement( "div" ) );
+ marginDiv.style.cssText = div.style.cssText = divReset;
+ marginDiv.style.marginRight = marginDiv.style.width = "0";
+ div.style.width = "1px";
+ docElem.appendChild( container );
</ins><span class="cx">
</span><del>- if ( this[0].parentNode ) {
- wrap.insertBefore( this[0] );
- }
</del><ins>+ ret = !parseFloat( window.getComputedStyle( marginDiv, null ).marginRight );
</ins><span class="cx">
</span><del>- wrap.map(function() {
- var elem = this;
</del><ins>+ docElem.removeChild( container );
</ins><span class="cx">
</span><del>- while ( elem.firstChild && elem.firstChild.nodeType === 1 ) {
- elem = elem.firstChild;
</del><ins>+ // Clean up the div for other support tests.
+ div.innerHTML = "";
+
+ return ret;
+ }
+ });
</ins><span class="cx"> }
</span><ins>+})();
</ins><span class="cx">
</span><del>- return elem;
- }).append( this );
</del><ins>+
+// A method for quickly swapping in/out CSS properties to get correct calculations.
+jQuery.swap = function( elem, options, callback, args ) {
+ var ret, name,
+ old = {};
+
+ // Remember the old values, and insert the new ones
+ for ( name in options ) {
+ old[ name ] = elem.style[ name ];
+ elem.style[ name ] = options[ name ];
</ins><span class="cx"> }
</span><span class="cx">
</span><del>- return this;
- },
</del><ins>+ ret = callback.apply( elem, args || [] );
</ins><span class="cx">
</span><del>- wrapInner: function( html ) {
- if ( jQuery.isFunction( html ) ) {
- return this.each(function(i) {
- jQuery(this).wrapInner( html.call(this, i) );
- });
</del><ins>+ // Revert the old values
+ for ( name in options ) {
+ elem.style[ name ] = old[ name ];
</ins><span class="cx"> }
</span><span class="cx">
</span><del>- return this.each(function() {
- var self = jQuery( this ),
- contents = self.contents();
</del><ins>+ return ret;
+};
</ins><span class="cx">
</span><del>- if ( contents.length ) {
- contents.wrapAll( html );
</del><span class="cx">
</span><del>- } else {
- self.append( html );
- }
- });
</del><ins>+var
+ // swappable if display is none or starts with table except "table", "table-cell", or "table-caption"
+ // see here for display values: https://developer.mozilla.org/en-US/docs/CSS/display
+ rdisplayswap = /^(none|table(?!-c[ea]).+)/,
+ rnumsplit = new RegExp( "^(" + pnum + ")(.*)$", "i" ),
+ rrelNum = new RegExp( "^([+-])=(" + pnum + ")", "i" ),
+
+ cssShow = { position: "absolute", visibility: "hidden", display: "block" },
+ cssNormalTransform = {
+ letterSpacing: 0,
+ fontWeight: 400
</ins><span class="cx"> },
</span><span class="cx">
</span><del>- wrap: function( html ) {
- var isFunction = jQuery.isFunction( html );
</del><ins>+ cssPrefixes = [ "Webkit", "O", "Moz", "ms" ];
</ins><span class="cx">
</span><del>- return this.each(function(i) {
- jQuery( this ).wrapAll( isFunction ? html.call(this, i) : html );
- });
- },
</del><ins>+// return a css property mapped to a potentially vendor prefixed property
+function vendorPropName( style, name ) {
</ins><span class="cx">
</span><del>- unwrap: function() {
- return this.parent().each(function() {
- if ( !jQuery.nodeName( this, "body" ) ) {
- jQuery( this ).replaceWith( this.childNodes );
</del><ins>+ // shortcut for names that are not vendor prefixed
+ if ( name in style ) {
+ return name;
</ins><span class="cx"> }
</span><del>- }).end();
- },
</del><span class="cx">
</span><del>- append: function() {
- return this.domManip(arguments, true, function( elem ) {
- if ( this.nodeType === 1 || this.nodeType === 11 || this.nodeType === 9 ) {
- this.appendChild( elem );
- }
- });
- },
</del><ins>+ // check for vendor prefixed names
+ var capName = name[0].toUpperCase() + name.slice(1),
+ origName = name,
+ i = cssPrefixes.length;
</ins><span class="cx">
</span><del>- prepend: function() {
- return this.domManip(arguments, true, function( elem ) {
- if ( this.nodeType === 1 || this.nodeType === 11 || this.nodeType === 9 ) {
- this.insertBefore( elem, this.firstChild );
</del><ins>+ while ( i-- ) {
+ name = cssPrefixes[ i ] + capName;
+ if ( name in style ) {
+ return name;
+ }
</ins><span class="cx"> }
</span><del>- });
- },
</del><span class="cx">
</span><del>- before: function() {
- return this.domManip( arguments, false, function( elem ) {
- if ( this.parentNode ) {
- this.parentNode.insertBefore( elem, this );
- }
- });
- },
</del><ins>+ return origName;
+}
</ins><span class="cx">
</span><del>- after: function() {
- return this.domManip( arguments, false, function( elem ) {
- if ( this.parentNode ) {
- this.parentNode.insertBefore( elem, this.nextSibling );
- }
- });
- },
</del><ins>+function setPositiveNumber( elem, value, subtract ) {
+ var matches = rnumsplit.exec( value );
+ return matches ?
+ // Guard against undefined "subtract", e.g., when used as in cssHooks
+ Math.max( 0, matches[ 1 ] - ( subtract || 0 ) ) + ( matches[ 2 ] || "px" ) :
+ value;
+}
</ins><span class="cx">
</span><del>- // keepData is for internal use only--do not document
- remove: function( selector, keepData ) {
- var elem,
- i = 0;
</del><ins>+function augmentWidthOrHeight( elem, name, extra, isBorderBox, styles ) {
+ var i = extra === ( isBorderBox ? "border" : "content" ) ?
+ // If we already have the right measurement, avoid augmentation
+ 4 :
+ // Otherwise initialize for horizontal or vertical properties
+ name === "width" ? 1 : 0,
</ins><span class="cx">
</span><del>- for ( ; (elem = this[i]) != null; i++ ) {
- if ( !selector || jQuery.filter( selector, [ elem ] ).length > 0 ) {
- if ( !keepData && elem.nodeType === 1 ) {
- jQuery.cleanData( getAll( elem ) );
- }
</del><ins>+ val = 0;
</ins><span class="cx">
</span><del>- if ( elem.parentNode ) {
- if ( keepData && jQuery.contains( elem.ownerDocument, elem ) ) {
- setGlobalEval( getAll( elem, "script" ) );
</del><ins>+ for ( ; i < 4; i += 2 ) {
+ // both box models exclude margin, so add it if we want it
+ if ( extra === "margin" ) {
+ val += jQuery.css( elem, extra + cssExpand[ i ], true, styles );
+ }
+
+ if ( isBorderBox ) {
+ // border-box includes padding, so remove it if we want content
+ if ( extra === "content" ) {
+ val -= jQuery.css( elem, "padding" + cssExpand[ i ], true, styles );
+ }
+
+ // at this point, extra isn't border nor margin, so remove border
+ if ( extra !== "margin" ) {
+ val -= jQuery.css( elem, "border" + cssExpand[ i ] + "Width", true, styles );
+ }
+ } else {
+ // at this point, extra isn't content, so add padding
+ val += jQuery.css( elem, "padding" + cssExpand[ i ], true, styles );
+
+ // at this point, extra isn't content nor padding, so add border
+ if ( extra !== "padding" ) {
+ val += jQuery.css( elem, "border" + cssExpand[ i ] + "Width", true, styles );
+ }
+ }
</ins><span class="cx"> }
</span><del>- elem.parentNode.removeChild( elem );
- }
- }
- }
</del><span class="cx">
</span><del>- return this;
- },
</del><ins>+ return val;
+}
</ins><span class="cx">
</span><del>- empty: function() {
- var elem,
- i = 0;
</del><ins>+function getWidthOrHeight( elem, name, extra ) {
</ins><span class="cx">
</span><del>- for ( ; (elem = this[i]) != null; i++ ) {
- // Remove element nodes and prevent memory leaks
- if ( elem.nodeType === 1 ) {
- jQuery.cleanData( getAll( elem, false ) );
</del><ins>+ // Start with offset property, which is equivalent to the border-box value
+ var valueIsBorderBox = true,
+ val = name === "width" ? elem.offsetWidth : elem.offsetHeight,
+ styles = getStyles( elem ),
+ isBorderBox = jQuery.css( elem, "boxSizing", false, styles ) === "border-box";
+
+ // some non-html elements return undefined for offsetWidth, so check for null/undefined
+ // svg - https://bugzilla.mozilla.org/show_bug.cgi?id=649285
+ // MathML - https://bugzilla.mozilla.org/show_bug.cgi?id=491668
+ if ( val <= 0 || val == null ) {
+ // Fall back to computed then uncomputed css if necessary
+ val = curCSS( elem, name, styles );
+ if ( val < 0 || val == null ) {
+ val = elem.style[ name ];
+ }
+
+ // Computed unit is not pixels. Stop here and return.
+ if ( rnumnonpx.test(val) ) {
+ return val;
+ }
+
+ // we need the check for style in case a browser which returns unreliable values
+ // for getComputedStyle silently falls back to the reliable elem.style
+ valueIsBorderBox = isBorderBox &&
+ ( support.boxSizingReliable() || val === elem.style[ name ] );
+
+ // Normalize "", auto, and prepare for extra
+ val = parseFloat( val ) || 0;
</ins><span class="cx"> }
</span><span class="cx">
</span><del>- // Remove any remaining nodes
- while ( elem.firstChild ) {
- elem.removeChild( elem.firstChild );
</del><ins>+ // use the active box-sizing model to add/subtract irrelevant styles
+ return ( val +
+ augmentWidthOrHeight(
+ elem,
+ name,
+ extra || ( isBorderBox ? "border" : "content" ),
+ valueIsBorderBox,
+ styles
+ )
+ ) + "px";
+}
+
+function showHide( elements, show ) {
+ var display, elem, hidden,
+ values = [],
+ index = 0,
+ length = elements.length;
+
+ for ( ; index < length; index++ ) {
+ elem = elements[ index ];
+ if ( !elem.style ) {
+ continue;
+ }
+
+ values[ index ] = data_priv.get( elem, "olddisplay" );
+ display = elem.style.display;
+ if ( show ) {
+ // Reset the inline display of this element to learn if it is
+ // being hidden by cascaded rules or not
+ if ( !values[ index ] && display === "none" ) {
+ elem.style.display = "";
+ }
+
+ // Set elements which have been overridden with display: none
+ // in a stylesheet to whatever the default browser style is
+ // for such an element
+ if ( elem.style.display === "" && isHidden( elem ) ) {
+ values[ index ] = data_priv.access( elem, "olddisplay", defaultDisplay(elem.nodeName) );
+ }
+ } else {
+
+ if ( !values[ index ] ) {
+ hidden = isHidden( elem );
+
+ if ( display && display !== "none" || !hidden ) {
+ data_priv.set( elem, "olddisplay", hidden ? display : jQuery.css(elem, "display") );
+ }
+ }
+ }
</ins><span class="cx"> }
</span><span class="cx">
</span><del>- // If this is a select, ensure that it displays empty (#12336)
- // Support: IE<9
- if ( elem.options && jQuery.nodeName( elem, "select" ) ) {
- elem.options.length = 0;
</del><ins>+ // Set the display of most of the elements in a second loop
+ // to avoid the constant reflow
+ for ( index = 0; index < length; index++ ) {
+ elem = elements[ index ];
+ if ( !elem.style ) {
+ continue;
+ }
+ if ( !show || elem.style.display === "none" || elem.style.display === "" ) {
+ elem.style.display = show ? values[ index ] || "" : "none";
+ }
</ins><span class="cx"> }
</span><del>- }
</del><span class="cx">
</span><del>- return this;
</del><ins>+ return elements;
+}
+
+jQuery.extend({
+ // Add in style property hooks for overriding the default
+ // behavior of getting and setting a style property
+ cssHooks: {
+ opacity: {
+ get: function( elem, computed ) {
+ if ( computed ) {
+ // We should always get a number back from opacity
+ var ret = curCSS( elem, "opacity" );
+ return ret === "" ? "1" : ret;
+ }
+ }
+ }
</ins><span class="cx"> },
</span><span class="cx">
</span><del>- clone: function( dataAndEvents, deepDataAndEvents ) {
- dataAndEvents = dataAndEvents == null ? false : dataAndEvents;
- deepDataAndEvents = deepDataAndEvents == null ? dataAndEvents : deepDataAndEvents;
</del><ins>+ // Don't automatically add "px" to these possibly-unitless properties
+ cssNumber: {
+ "columnCount": true,
+ "fillOpacity": true,
+ "fontWeight": true,
+ "lineHeight": true,
+ "opacity": true,
+ "order": true,
+ "orphans": true,
+ "widows": true,
+ "zIndex": true,
+ "zoom": true
+ },
</ins><span class="cx">
</span><del>- return this.map( function () {
- return jQuery.clone( this, dataAndEvents, deepDataAndEvents );
- });
</del><ins>+ // Add in properties whose names you wish to fix before
+ // setting or getting the value
+ cssProps: {
+ // normalize float css property
+ "float": "cssFloat"
</ins><span class="cx"> },
</span><span class="cx">
</span><del>- html: function( value ) {
- return jQuery.access( this, function( value ) {
- var elem = this[0] || {},
- i = 0,
- l = this.length;
</del><ins>+ // Get and set the style property on a DOM Node
+ style: function( elem, name, value, extra ) {
+ // Don't set styles on text and comment nodes
+ if ( !elem || elem.nodeType === 3 || elem.nodeType === 8 || !elem.style ) {
+ return;
+ }
</ins><span class="cx">
</span><del>- if ( value === undefined ) {
- return elem.nodeType === 1 ?
- elem.innerHTML.replace( rinlinejQuery, "" ) :
- undefined;
- }
</del><ins>+ // Make sure that we're working with the right name
+ var ret, type, hooks,
+ origName = jQuery.camelCase( name ),
+ style = elem.style;
</ins><span class="cx">
</span><del>- // See if we can take a shortcut and just use innerHTML
- if ( typeof value === "string" && !rnoInnerhtml.test( value ) &&
- ( jQuery.support.htmlSerialize || !rnoshimcache.test( value ) ) &&
- ( jQuery.support.leadingWhitespace || !rleadingWhitespace.test( value ) ) &&
- !wrapMap[ ( rtagName.exec( value ) || ["", ""] )[1].toLowerCase() ] ) {
</del><ins>+ name = jQuery.cssProps[ origName ] || ( jQuery.cssProps[ origName ] = vendorPropName( style, origName ) );
</ins><span class="cx">
</span><del>- value = value.replace( rxhtmlTag, "<$1></$2>" );
</del><ins>+ // gets hook for the prefixed version
+ // followed by the unprefixed version
+ hooks = jQuery.cssHooks[ name ] || jQuery.cssHooks[ origName ];
</ins><span class="cx">
</span><del>- try {
- for (; i < l; i++ ) {
- // Remove element nodes and prevent memory leaks
- elem = this[i] || {};
- if ( elem.nodeType === 1 ) {
- jQuery.cleanData( getAll( elem, false ) );
- elem.innerHTML = value;
- }
- }
</del><ins>+ // Check if we're setting a value
+ if ( value !== undefined ) {
+ type = typeof value;
</ins><span class="cx">
</span><del>- elem = 0;
</del><ins>+ // convert relative number strings (+= or -=) to relative numbers. #7345
+ if ( type === "string" && (ret = rrelNum.exec( value )) ) {
+ value = ( ret[1] + 1 ) * ret[2] + parseFloat( jQuery.css( elem, name ) );
+ // Fixes bug #9237
+ type = "number";
+ }
</ins><span class="cx">
</span><del>- // If using innerHTML throws an exception, use the fallback method
- } catch(e) {}
- }
</del><ins>+ // Make sure that null and NaN values aren't set. See: #7116
+ if ( value == null || value !== value ) {
+ return;
+ }
</ins><span class="cx">
</span><del>- if ( elem ) {
- this.empty().append( value );
- }
- }, null, value, arguments.length );
- },
</del><ins>+ // If a number was passed in, add 'px' to the (except for certain CSS properties)
+ if ( type === "number" && !jQuery.cssNumber[ origName ] ) {
+ value += "px";
+ }
</ins><span class="cx">
</span><del>- replaceWith: function( value ) {
- var isFunc = jQuery.isFunction( value );
</del><ins>+ // Fixes #8908, it can be done more correctly by specifying setters in cssHooks,
+ // but it would mean to define eight (for every problematic property) identical functions
+ if ( !support.clearCloneStyle && value === "" && name.indexOf( "background" ) === 0 ) {
+ style[ name ] = "inherit";
+ }
</ins><span class="cx">
</span><del>- // Make sure that the elements are removed from the DOM before they are inserted
- // this can help fix replacing a parent with child elements
- if ( !isFunc && typeof value !== "string" ) {
- value = jQuery( value ).not( this ).detach();
- }
</del><ins>+ // If a hook was provided, use that value, otherwise just set the specified value
+ if ( !hooks || !("set" in hooks) || (value = hooks.set( elem, value, extra )) !== undefined ) {
+ // Support: Chrome, Safari
+ // Setting style to blank string required to delete "style: x !important;"
+ style[ name ] = "";
+ style[ name ] = value;
+ }
</ins><span class="cx">
</span><del>- return this.domManip( [ value ], true, function( elem ) {
- var next = this.nextSibling,
- parent = this.parentNode;
</del><ins>+ } else {
+ // If a hook was provided get the non-computed value from there
+ if ( hooks && "get" in hooks && (ret = hooks.get( elem, false, extra )) !== undefined ) {
+ return ret;
+ }
</ins><span class="cx">
</span><del>- if ( parent ) {
- jQuery( this ).remove();
- parent.insertBefore( elem, next );
- }
- });
</del><ins>+ // Otherwise just get the value from the style object
+ return style[ name ];
+ }
</ins><span class="cx"> },
</span><span class="cx">
</span><del>- detach: function( selector ) {
- return this.remove( selector, true );
- },
</del><ins>+ css: function( elem, name, extra, styles ) {
+ var val, num, hooks,
+ origName = jQuery.camelCase( name );
</ins><span class="cx">
</span><del>- domManip: function( args, table, callback ) {
</del><ins>+ // Make sure that we're working with the right name
+ name = jQuery.cssProps[ origName ] || ( jQuery.cssProps[ origName ] = vendorPropName( elem.style, origName ) );
</ins><span class="cx">
</span><del>- // Flatten any nested arrays
- args = core_concat.apply( [], args );
</del><ins>+ // gets hook for the prefixed version
+ // followed by the unprefixed version
+ hooks = jQuery.cssHooks[ name ] || jQuery.cssHooks[ origName ];
</ins><span class="cx">
</span><del>- var first, node, hasScripts,
- scripts, doc, fragment,
- i = 0,
- l = this.length,
- set = this,
- iNoClone = l - 1,
- value = args[0],
- isFunction = jQuery.isFunction( value );
</del><ins>+ // If a hook was provided get the computed value from there
+ if ( hooks && "get" in hooks ) {
+ val = hooks.get( elem, true, extra );
+ }
</ins><span class="cx">
</span><del>- // We can't cloneNode fragments that contain checked, in WebKit
- if ( isFunction || !( l <= 1 || typeof value !== "string" || jQuery.support.checkClone || !rchecked.test( value ) ) ) {
- return this.each(function( index ) {
- var self = set.eq( index );
- if ( isFunction ) {
- args[0] = value.call( this, index, table ? self.html() : undefined );
</del><ins>+ // Otherwise, if a way to get the computed value exists, use that
+ if ( val === undefined ) {
+ val = curCSS( elem, name, styles );
+ }
+
+ //convert "normal" to computed value
+ if ( val === "normal" && name in cssNormalTransform ) {
+ val = cssNormalTransform[ name ];
+ }
+
+ // Return, converting to number if forced or a qualifier was provided and val looks numeric
+ if ( extra === "" || extra ) {
+ num = parseFloat( val );
+ return extra === true || jQuery.isNumeric( num ) ? num || 0 : val;
+ }
+ return val;
</ins><span class="cx"> }
</span><del>- self.domManip( args, table, callback );
- });
- }
</del><ins>+});
</ins><span class="cx">
</span><del>- if ( l ) {
- fragment = jQuery.buildFragment( args, this[ 0 ].ownerDocument, false, this );
- first = fragment.firstChild;
</del><ins>+jQuery.each([ "height", "width" ], function( i, name ) {
+ jQuery.cssHooks[ name ] = {
+ get: function( elem, computed, extra ) {
+ if ( computed ) {
+ // certain elements can have dimension info if we invisibly show them
+ // however, it must have a current display style that would benefit from this
+ return elem.offsetWidth === 0 && rdisplayswap.test( jQuery.css( elem, "display" ) ) ?
+ jQuery.swap( elem, cssShow, function() {
+ return getWidthOrHeight( elem, name, extra );
+ }) :
+ getWidthOrHeight( elem, name, extra );
+ }
+ },
</ins><span class="cx">
</span><del>- if ( fragment.childNodes.length === 1 ) {
- fragment = first;
</del><ins>+ set: function( elem, value, extra ) {
+ var styles = extra && getStyles( elem );
+ return setPositiveNumber( elem, value, extra ?
+ augmentWidthOrHeight(
+ elem,
+ name,
+ extra,
+ jQuery.css( elem, "boxSizing", false, styles ) === "border-box",
+ styles
+ ) : 0
+ );
+ }
+ };
+});
+
+// Support: Android 2.3
+jQuery.cssHooks.marginRight = addGetHookIf( support.reliableMarginRight,
+ function( elem, computed ) {
+ if ( computed ) {
+ // WebKit Bug 13343 - getComputedStyle returns wrong value for margin-right
+ // Work around by temporarily setting element display to inline-block
+ return jQuery.swap( elem, { "display": "inline-block" },
+ curCSS, [ elem, "marginRight" ] );
+ }
</ins><span class="cx"> }
</span><ins>+);
</ins><span class="cx">
</span><del>- if ( first ) {
- table = table && jQuery.nodeName( first, "tr" );
- scripts = jQuery.map( getAll( fragment, "script" ), disableScript );
- hasScripts = scripts.length;
</del><ins>+// These hooks are used by animate to expand properties
+jQuery.each({
+ margin: "",
+ padding: "",
+ border: "Width"
+}, function( prefix, suffix ) {
+ jQuery.cssHooks[ prefix + suffix ] = {
+ expand: function( value ) {
+ var i = 0,
+ expanded = {},
</ins><span class="cx">
</span><del>- // Use the original fragment for the last item instead of the first because it can end up
- // being emptied incorrectly in certain situations (#8070).
- for ( ; i < l; i++ ) {
- node = fragment;
</del><ins>+ // assumes a single number if not a string
+ parts = typeof value === "string" ? value.split(" ") : [ value ];
</ins><span class="cx">
</span><del>- if ( i !== iNoClone ) {
- node = jQuery.clone( node, true, true );
</del><ins>+ for ( ; i < 4; i++ ) {
+ expanded[ prefix + cssExpand[ i ] + suffix ] =
+ parts[ i ] || parts[ i - 2 ] || parts[ 0 ];
+ }
</ins><span class="cx">
</span><del>- // Keep references to cloned scripts for later restoration
- if ( hasScripts ) {
- jQuery.merge( scripts, getAll( node, "script" ) );
- }
- }
</del><ins>+ return expanded;
+ }
+ };
</ins><span class="cx">
</span><del>- callback.call(
- table && jQuery.nodeName( this[i], "table" ) ?
- findOrAppend( this[i], "tbody" ) :
- this[i],
- node,
- i
- );
</del><ins>+ if ( !rmargin.test( prefix ) ) {
+ jQuery.cssHooks[ prefix + suffix ].set = setPositiveNumber;
</ins><span class="cx"> }
</span><ins>+});
</ins><span class="cx">
</span><del>- if ( hasScripts ) {
- doc = scripts[ scripts.length - 1 ].ownerDocument;
</del><ins>+jQuery.fn.extend({
+ css: function( name, value ) {
+ return access( this, function( elem, name, value ) {
+ var styles, len,
+ map = {},
+ i = 0;
</ins><span class="cx">
</span><del>- // Reenable scripts
- jQuery.map( scripts, restoreScript );
</del><ins>+ if ( jQuery.isArray( name ) ) {
+ styles = getStyles( elem );
+ len = name.length;
</ins><span class="cx">
</span><del>- // Evaluate executable scripts on first document insertion
- for ( i = 0; i < hasScripts; i++ ) {
- node = scripts[ i ];
- if ( rscriptType.test( node.type || "" ) &&
- !jQuery._data( node, "globalEval" ) && jQuery.contains( doc, node ) ) {
</del><ins>+ for ( ; i < len; i++ ) {
+ map[ name[ i ] ] = jQuery.css( elem, name[ i ], false, styles );
+ }
</ins><span class="cx">
</span><del>- if ( node.src ) {
- // Hope ajax is available...
- jQuery.ajax({
- url: node.src,
- type: "GET",
- dataType: "script",
- async: false,
- global: false,
- "throws": true
- });
- } else {
- jQuery.globalEval( ( node.text || node.textContent || node.innerHTML || "" ).replace( rcleanScript, "" ) );
- }
- }
- }
- }
</del><ins>+ return map;
+ }
</ins><span class="cx">
</span><del>- // Fix #11809: Avoid leaking memory
- fragment = first = null;
- }
- }
</del><ins>+ return value !== undefined ?
+ jQuery.style( elem, name, value ) :
+ jQuery.css( elem, name );
+ }, name, value, arguments.length > 1 );
+ },
+ show: function() {
+ return showHide( this, true );
+ },
+ hide: function() {
+ return showHide( this );
+ },
+ toggle: function( state ) {
+ if ( typeof state === "boolean" ) {
+ return state ? this.show() : this.hide();
+ }
</ins><span class="cx">
</span><del>- return this;
</del><ins>+ return this.each(function() {
+ if ( isHidden( this ) ) {
+ jQuery( this ).show();
+ } else {
+ jQuery( this ).hide();
+ }
+ });
</ins><span class="cx"> }
</span><span class="cx"> });
</span><span class="cx">
</span><del>-function findOrAppend( elem, tag ) {
- return elem.getElementsByTagName( tag )[0] || elem.appendChild( elem.ownerDocument.createElement( tag ) );
-}
</del><span class="cx">
</span><del>-// Replace/restore the type attribute of script elements for safe DOM manipulation
-function disableScript( elem ) {
- var attr = elem.getAttributeNode("type");
- elem.type = ( attr && attr.specified ) + "/" + elem.type;
- return elem;
</del><ins>+function Tween( elem, options, prop, end, easing ) {
+ return new Tween.prototype.init( elem, options, prop, end, easing );
</ins><span class="cx"> }
</span><del>-function restoreScript( elem ) {
- var match = rscriptTypeMasked.exec( elem.type );
- if ( match ) {
- elem.type = match[1];
- } else {
- elem.removeAttribute("type");
- }
- return elem;
-}
</del><ins>+jQuery.Tween = Tween;
</ins><span class="cx">
</span><del>-// Mark scripts as having already been evaluated
-function setGlobalEval( elems, refElements ) {
- var elem,
- i = 0;
- for ( ; (elem = elems[i]) != null; i++ ) {
- jQuery._data( elem, "globalEval", !refElements || jQuery._data( refElements[i], "globalEval" ) );
- }
-}
</del><ins>+Tween.prototype = {
+ constructor: Tween,
+ init: function( elem, options, prop, end, easing, unit ) {
+ this.elem = elem;
+ this.prop = prop;
+ this.easing = easing || "swing";
+ this.options = options;
+ this.start = this.now = this.cur();
+ this.end = end;
+ this.unit = unit || ( jQuery.cssNumber[ prop ] ? "" : "px" );
+ },
+ cur: function() {
+ var hooks = Tween.propHooks[ this.prop ];
</ins><span class="cx">
</span><del>-function cloneCopyEvent( src, dest ) {
</del><ins>+ return hooks && hooks.get ?
+ hooks.get( this ) :
+ Tween.propHooks._default.get( this );
+ },
+ run: function( percent ) {
+ var eased,
+ hooks = Tween.propHooks[ this.prop ];
</ins><span class="cx">
</span><del>- if ( dest.nodeType !== 1 || !jQuery.hasData( src ) ) {
- return;
</del><ins>+ if ( this.options.duration ) {
+ this.pos = eased = jQuery.easing[ this.easing ](
+ percent, this.options.duration * percent, 0, 1, this.options.duration
+ );
+ } else {
+ this.pos = eased = percent;
+ }
+ this.now = ( this.end - this.start ) * eased + this.start;
+
+ if ( this.options.step ) {
+ this.options.step.call( this.elem, this.now, this );
+ }
+
+ if ( hooks && hooks.set ) {
+ hooks.set( this );
+ } else {
+ Tween.propHooks._default.set( this );
+ }
+ return this;
</ins><span class="cx"> }
</span><ins>+};
</ins><span class="cx">
</span><del>- var type, i, l,
- oldData = jQuery._data( src ),
- curData = jQuery._data( dest, oldData ),
- events = oldData.events;
</del><ins>+Tween.prototype.init.prototype = Tween.prototype;
</ins><span class="cx">
</span><del>- if ( events ) {
- delete curData.handle;
- curData.events = {};
</del><ins>+Tween.propHooks = {
+ _default: {
+ get: function( tween ) {
+ var result;
</ins><span class="cx">
</span><del>- for ( type in events ) {
- for ( i = 0, l = events[ type ].length; i < l; i++ ) {
- jQuery.event.add( dest, type, events[ type ][ i ] );
- }
- }
- }
</del><ins>+ if ( tween.elem[ tween.prop ] != null &&
+ (!tween.elem.style || tween.elem.style[ tween.prop ] == null) ) {
+ return tween.elem[ tween.prop ];
+ }
</ins><span class="cx">
</span><del>- // make the cloned public data object a copy from the original
- if ( curData.data ) {
- curData.data = jQuery.extend( {}, curData.data );
</del><ins>+ // passing an empty string as a 3rd parameter to .css will automatically
+ // attempt a parseFloat and fallback to a string if the parse fails
+ // so, simple values such as "10px" are parsed to Float.
+ // complex values such as "rotate(1rad)" are returned as is.
+ result = jQuery.css( tween.elem, tween.prop, "" );
+ // Empty strings, null, undefined and "auto" are converted to 0.
+ return !result || result === "auto" ? 0 : result;
+ },
+ set: function( tween ) {
+ // use step hook for back compat - use cssHook if its there - use .style if its
+ // available and use plain properties where available
+ if ( jQuery.fx.step[ tween.prop ] ) {
+ jQuery.fx.step[ tween.prop ]( tween );
+ } else if ( tween.elem.style && ( tween.elem.style[ jQuery.cssProps[ tween.prop ] ] != null || jQuery.cssHooks[ tween.prop ] ) ) {
+ jQuery.style( tween.elem, tween.prop, tween.now + tween.unit );
+ } else {
+ tween.elem[ tween.prop ] = tween.now;
+ }
+ }
</ins><span class="cx"> }
</span><del>-}
</del><ins>+};
</ins><span class="cx">
</span><del>-function fixCloneNodeIssues( src, dest ) {
- var nodeName, e, data;
</del><ins>+// Support: IE9
+// Panic based approach to setting things on disconnected nodes
</ins><span class="cx">
</span><del>- // We do not need to do anything for non-Elements
- if ( dest.nodeType !== 1 ) {
- return;
</del><ins>+Tween.propHooks.scrollTop = Tween.propHooks.scrollLeft = {
+ set: function( tween ) {
+ if ( tween.elem.nodeType && tween.elem.parentNode ) {
+ tween.elem[ tween.prop ] = tween.now;
+ }
</ins><span class="cx"> }
</span><ins>+};
</ins><span class="cx">
</span><del>- nodeName = dest.nodeName.toLowerCase();
</del><ins>+jQuery.easing = {
+ linear: function( p ) {
+ return p;
+ },
+ swing: function( p ) {
+ return 0.5 - Math.cos( p * Math.PI ) / 2;
+ }
+};
</ins><span class="cx">
</span><del>- // IE6-8 copies events bound via attachEvent when using cloneNode.
- if ( !jQuery.support.noCloneEvent && dest[ jQuery.expando ] ) {
- data = jQuery._data( dest );
</del><ins>+jQuery.fx = Tween.prototype.init;
</ins><span class="cx">
</span><del>- for ( e in data.events ) {
- jQuery.removeEvent( dest, e, data.handle );
- }
</del><ins>+// Back Compat <1.8 extension point
+jQuery.fx.step = {};
</ins><span class="cx">
</span><del>- // Event data gets referenced instead of copied if the expando gets copied too
- dest.removeAttribute( jQuery.expando );
- }
</del><span class="cx">
</span><del>- // IE blanks contents when cloning scripts, and tries to evaluate newly-set text
- if ( nodeName === "script" && dest.text !== src.text ) {
- disableScript( dest ).text = src.text;
- restoreScript( dest );
</del><span class="cx">
</span><del>- // IE6-10 improperly clones children of object elements using classid.
- // IE10 throws NoModificationAllowedError if parent is null, #12132.
- } else if ( nodeName === "object" ) {
- if ( dest.parentNode ) {
- dest.outerHTML = src.outerHTML;
- }
</del><span class="cx">
</span><del>- // This path appears unavoidable for IE9. When cloning an object
- // element in IE9, the outerHTML strategy above is not sufficient.
- // If the src has innerHTML and the destination does not,
- // copy the src.innerHTML into the dest.innerHTML. #10324
- if ( jQuery.support.html5Clone && ( src.innerHTML && !jQuery.trim(dest.innerHTML) ) ) {
- dest.innerHTML = src.innerHTML;
- }
</del><ins>+var
+ fxNow, timerId,
+ rfxtypes = /^(?:toggle|show|hide)$/,
+ rfxnum = new RegExp( "^(?:([+-])=|)(" + pnum + ")([a-z%]*)$", "i" ),
+ rrun = /queueHooks$/,
+ animationPrefilters = [ defaultPrefilter ],
+ tweeners = {
+ "*": [ function( prop, value ) {
+ var tween = this.createTween( prop, value ),
+ target = tween.cur(),
+ parts = rfxnum.exec( value ),
+ unit = parts && parts[ 3 ] || ( jQuery.cssNumber[ prop ] ? "" : "px" ),
</ins><span class="cx">
</span><del>- } else if ( nodeName === "input" && manipulation_rcheckableType.test( src.type ) ) {
- // IE6-8 fails to persist the checked state of a cloned checkbox
- // or radio button. Worse, IE6-7 fail to give the cloned element
- // a checked appearance if the defaultChecked value isn't also set
</del><ins>+ // Starting value computation is required for potential unit mismatches
+ start = ( jQuery.cssNumber[ prop ] || unit !== "px" && +target ) &&
+ rfxnum.exec( jQuery.css( tween.elem, prop ) ),
+ scale = 1,
+ maxIterations = 20;
</ins><span class="cx">
</span><del>- dest.defaultChecked = dest.checked = src.checked;
</del><ins>+ if ( start && start[ 3 ] !== unit ) {
+ // Trust units reported by jQuery.css
+ unit = unit || start[ 3 ];
</ins><span class="cx">
</span><del>- // IE6-7 get confused and end up setting the value of a cloned
- // checkbox/radio button to an empty string instead of "on"
- if ( dest.value !== src.value ) {
- dest.value = src.value;
- }
</del><ins>+ // Make sure we update the tween properties later on
+ parts = parts || [];
</ins><span class="cx">
</span><del>- // IE6-8 fails to return the selected option to the default selected
- // state when cloning options
- } else if ( nodeName === "option" ) {
- dest.defaultSelected = dest.selected = src.defaultSelected;
</del><ins>+ // Iteratively approximate from a nonzero starting point
+ start = +target || 1;
</ins><span class="cx">
</span><del>- // IE6-8 fails to set the defaultValue to the correct value when
- // cloning other types of input fields
- } else if ( nodeName === "input" || nodeName === "textarea" ) {
- dest.defaultValue = src.defaultValue;
- }
-}
</del><ins>+ do {
+ // If previous iteration zeroed out, double until we get *something*
+ // Use a string for doubling factor so we don't accidentally see scale as unchanged below
+ scale = scale || ".5";
</ins><span class="cx">
</span><del>-jQuery.each({
- appendTo: "append",
- prependTo: "prepend",
- insertBefore: "before",
- insertAfter: "after",
- replaceAll: "replaceWith"
-}, function( name, original ) {
- jQuery.fn[ name ] = function( selector ) {
- var elems,
- i = 0,
- ret = [],
- insert = jQuery( selector ),
- last = insert.length - 1;
</del><ins>+ // Adjust and apply
+ start = start / scale;
+ jQuery.style( tween.elem, prop, start + unit );
</ins><span class="cx">
</span><del>- for ( ; i <= last; i++ ) {
- elems = i === last ? this : this.clone(true);
- jQuery( insert[i] )[ original ]( elems );
</del><ins>+ // Update scale, tolerating zero or NaN from tween.cur()
+ // And breaking the loop if scale is unchanged or perfect, or if we've just had enough
+ } while ( scale !== (scale = tween.cur() / target) && scale !== 1 && --maxIterations );
+ }
</ins><span class="cx">
</span><del>- // Modern browsers can apply jQuery collections as arrays, but oldIE needs a .get()
- core_push.apply( ret, elems.get() );
- }
</del><ins>+ // Update tween properties
+ if ( parts ) {
+ start = tween.start = +start || +target || 0;
+ tween.unit = unit;
+ // If a +=/-= token was provided, we're doing a relative animation
+ tween.end = parts[ 1 ] ?
+ start + ( parts[ 1 ] + 1 ) * parts[ 2 ] :
+ +parts[ 2 ];
+ }
</ins><span class="cx">
</span><del>- return this.pushStack( ret );
</del><ins>+ return tween;
+ } ]
</ins><span class="cx"> };
</span><del>-});
</del><span class="cx">
</span><del>-function getAll( context, tag ) {
- var elems, elem,
- i = 0,
- found = typeof context.getElementsByTagName !== core_strundefined ? context.getElementsByTagName( tag || "*" ) :
- typeof context.querySelectorAll !== core_strundefined ? context.querySelectorAll( tag || "*" ) :
- undefined;
</del><ins>+// Animations created synchronously will run synchronously
+function createFxNow() {
+ setTimeout(function() {
+ fxNow = undefined;
+ });
+ return ( fxNow = jQuery.now() );
+}
</ins><span class="cx">
</span><del>- if ( !found ) {
- for ( found = [], elems = context.childNodes || context; (elem = elems[i]) != null; i++ ) {
- if ( !tag || jQuery.nodeName( elem, tag ) ) {
- found.push( elem );
- } else {
- jQuery.merge( found, getAll( elem, tag ) );
</del><ins>+// Generate parameters to create a standard animation
+function genFx( type, includeWidth ) {
+ var which,
+ i = 0,
+ attrs = { height: type };
+
+ // if we include width, step value is 1 to do all cssExpand values,
+ // if we don't include width, step value is 2 to skip over Left and Right
+ includeWidth = includeWidth ? 1 : 0;
+ for ( ; i < 4 ; i += 2 - includeWidth ) {
+ which = cssExpand[ i ];
+ attrs[ "margin" + which ] = attrs[ "padding" + which ] = type;
</ins><span class="cx"> }
</span><ins>+
+ if ( includeWidth ) {
+ attrs.opacity = attrs.width = type;
</ins><span class="cx"> }
</span><del>- }
</del><span class="cx">
</span><del>- return tag === undefined || tag && jQuery.nodeName( context, tag ) ?
- jQuery.merge( [ context ], found ) :
- found;
</del><ins>+ return attrs;
</ins><span class="cx"> }
</span><span class="cx">
</span><del>-// Used in buildFragment, fixes the defaultChecked property
-function fixDefaultChecked( elem ) {
- if ( manipulation_rcheckableType.test( elem.type ) ) {
- elem.defaultChecked = elem.checked;
</del><ins>+function createTween( value, prop, animation ) {
+ var tween,
+ collection = ( tweeners[ prop ] || [] ).concat( tweeners[ "*" ] ),
+ index = 0,
+ length = collection.length;
+ for ( ; index < length; index++ ) {
+ if ( (tween = collection[ index ].call( animation, prop, value )) ) {
+
+ // we're done with this property
+ return tween;
+ }
</ins><span class="cx"> }
</span><span class="cx"> }
</span><span class="cx">
</span><del>-jQuery.extend({
- clone: function( elem, dataAndEvents, deepDataAndEvents ) {
- var destElements, node, clone, i, srcElements,
- inPage = jQuery.contains( elem.ownerDocument, elem );
</del><ins>+function defaultPrefilter( elem, props, opts ) {
+ /* jshint validthis: true */
+ var prop, value, toggle, tween, hooks, oldfire, display,
+ anim = this,
+ orig = {},
+ style = elem.style,
+ hidden = elem.nodeType && isHidden( elem ),
+ dataShow = data_priv.get( elem, "fxshow" );
</ins><span class="cx">
</span><del>- if ( jQuery.support.html5Clone || jQuery.isXMLDoc(elem) || !rnoshimcache.test( "<" + elem.nodeName + ">" ) ) {
- clone = elem.cloneNode( true );
</del><ins>+ // handle queue: false promises
+ if ( !opts.queue ) {
+ hooks = jQuery._queueHooks( elem, "fx" );
+ if ( hooks.unqueued == null ) {
+ hooks.unqueued = 0;
+ oldfire = hooks.empty.fire;
+ hooks.empty.fire = function() {
+ if ( !hooks.unqueued ) {
+ oldfire();
+ }
+ };
+ }
+ hooks.unqueued++;
</ins><span class="cx">
</span><del>- // IE<=8 does not properly clone detached, unknown element nodes
- } else {
- fragmentDiv.innerHTML = elem.outerHTML;
- fragmentDiv.removeChild( clone = fragmentDiv.firstChild );
</del><ins>+ anim.always(function() {
+ // doing this makes sure that the complete handler will be called
+ // before this completes
+ anim.always(function() {
+ hooks.unqueued--;
+ if ( !jQuery.queue( elem, "fx" ).length ) {
+ hooks.empty.fire();
+ }
+ });
+ });
</ins><span class="cx"> }
</span><span class="cx">
</span><del>- if ( (!jQuery.support.noCloneEvent || !jQuery.support.noCloneChecked) &&
- (elem.nodeType === 1 || elem.nodeType === 11) && !jQuery.isXMLDoc(elem) ) {
</del><ins>+ // height/width overflow pass
+ if ( elem.nodeType === 1 && ( "height" in props || "width" in props ) ) {
+ // Make sure that nothing sneaks out
+ // Record all 3 overflow attributes because IE9-10 do not
+ // change the overflow attribute when overflowX and
+ // overflowY are set to the same value
+ opts.overflow = [ style.overflow, style.overflowX, style.overflowY ];
</ins><span class="cx">
</span><del>- // We eschew Sizzle here for performance reasons: http://jsperf.com/getall-vs-sizzle/2
- destElements = getAll( clone );
- srcElements = getAll( elem );
</del><ins>+ // Set display property to inline-block for height/width
+ // animations on inline elements that are having width/height animated
+ display = jQuery.css( elem, "display" );
+ // Get default display if display is currently "none"
+ if ( display === "none" ) {
+ display = defaultDisplay( elem.nodeName );
+ }
+ if ( display === "inline" &&
+ jQuery.css( elem, "float" ) === "none" ) {
</ins><span class="cx">
</span><del>- // Fix all IE cloning issues
- for ( i = 0; (node = srcElements[i]) != null; ++i ) {
- // Ensure that the destination node is not null; Fixes #9587
- if ( destElements[i] ) {
- fixCloneNodeIssues( node, destElements[i] );
</del><ins>+ style.display = "inline-block";
+ }
</ins><span class="cx"> }
</span><ins>+
+ if ( opts.overflow ) {
+ style.overflow = "hidden";
+ anim.always(function() {
+ style.overflow = opts.overflow[ 0 ];
+ style.overflowX = opts.overflow[ 1 ];
+ style.overflowY = opts.overflow[ 2 ];
+ });
</ins><span class="cx"> }
</span><del>- }
</del><span class="cx">
</span><del>- // Copy the events from the original to the clone
- if ( dataAndEvents ) {
- if ( deepDataAndEvents ) {
- srcElements = srcElements || getAll( elem );
- destElements = destElements || getAll( clone );
</del><ins>+ // show/hide pass
+ for ( prop in props ) {
+ value = props[ prop ];
+ if ( rfxtypes.exec( value ) ) {
+ delete props[ prop ];
+ toggle = toggle || value === "toggle";
+ if ( value === ( hidden ? "hide" : "show" ) ) {
</ins><span class="cx">
</span><del>- for ( i = 0; (node = srcElements[i]) != null; i++ ) {
- cloneCopyEvent( node, destElements[i] );
</del><ins>+ // If there is dataShow left over from a stopped hide or show and we are going to proceed with show, we should pretend to be hidden
+ if ( value === "show" && dataShow && dataShow[ prop ] !== undefined ) {
+ hidden = true;
+ } else {
+ continue;
+ }
+ }
+ orig[ prop ] = dataShow && dataShow[ prop ] || jQuery.style( elem, prop );
+ }
</ins><span class="cx"> }
</span><del>- } else {
- cloneCopyEvent( elem, clone );
- }
- }
</del><span class="cx">
</span><del>- // Preserve script evaluation history
- destElements = getAll( clone, "script" );
- if ( destElements.length > 0 ) {
- setGlobalEval( destElements, !inPage && getAll( elem, "script" ) );
- }
</del><ins>+ if ( !jQuery.isEmptyObject( orig ) ) {
+ if ( dataShow ) {
+ if ( "hidden" in dataShow ) {
+ hidden = dataShow.hidden;
+ }
+ } else {
+ dataShow = data_priv.access( elem, "fxshow", {} );
+ }
</ins><span class="cx">
</span><del>- destElements = srcElements = node = null;
</del><ins>+ // store state if its toggle - enables .stop().toggle() to "reverse"
+ if ( toggle ) {
+ dataShow.hidden = !hidden;
+ }
+ if ( hidden ) {
+ jQuery( elem ).show();
+ } else {
+ anim.done(function() {
+ jQuery( elem ).hide();
+ });
+ }
+ anim.done(function() {
+ var prop;
</ins><span class="cx">
</span><del>- // Return the cloned set
- return clone;
- },
</del><ins>+ data_priv.remove( elem, "fxshow" );
+ for ( prop in orig ) {
+ jQuery.style( elem, prop, orig[ prop ] );
+ }
+ });
+ for ( prop in orig ) {
+ tween = createTween( hidden ? dataShow[ prop ] : 0, prop, anim );
</ins><span class="cx">
</span><del>- buildFragment: function( elems, context, scripts, selection ) {
- var j, elem, contains,
- tmp, tag, tbody, wrap,
- l = elems.length,
</del><ins>+ if ( !( prop in dataShow ) ) {
+ dataShow[ prop ] = tween.start;
+ if ( hidden ) {
+ tween.end = tween.start;
+ tween.start = prop === "width" || prop === "height" ? 1 : 0;
+ }
+ }
+ }
+ }
+}
</ins><span class="cx">
</span><del>- // Ensure a safe fragment
- safe = createSafeFragment( context ),
</del><ins>+function propFilter( props, specialEasing ) {
+ var index, name, easing, value, hooks;
</ins><span class="cx">
</span><del>- nodes = [],
- i = 0;
</del><ins>+ // camelCase, specialEasing and expand cssHook pass
+ for ( index in props ) {
+ name = jQuery.camelCase( index );
+ easing = specialEasing[ name ];
+ value = props[ index ];
+ if ( jQuery.isArray( value ) ) {
+ easing = value[ 1 ];
+ value = props[ index ] = value[ 0 ];
+ }
</ins><span class="cx">
</span><del>- for ( ; i < l; i++ ) {
- elem = elems[ i ];
</del><ins>+ if ( index !== name ) {
+ props[ name ] = value;
+ delete props[ index ];
+ }
</ins><span class="cx">
</span><del>- if ( elem || elem === 0 ) {
</del><ins>+ hooks = jQuery.cssHooks[ name ];
+ if ( hooks && "expand" in hooks ) {
+ value = hooks.expand( value );
+ delete props[ name ];
</ins><span class="cx">
</span><del>- // Add nodes directly
- if ( jQuery.type( elem ) === "object" ) {
- jQuery.merge( nodes, elem.nodeType ? [ elem ] : elem );
</del><ins>+ // not quite $.extend, this wont overwrite keys already present.
+ // also - reusing 'index' from above because we have the correct "name"
+ for ( index in value ) {
+ if ( !( index in props ) ) {
+ props[ index ] = value[ index ];
+ specialEasing[ index ] = easing;
+ }
+ }
+ } else {
+ specialEasing[ name ] = easing;
+ }
+ }
+}
</ins><span class="cx">
</span><del>- // Convert non-html into a text node
- } else if ( !rhtml.test( elem ) ) {
- nodes.push( context.createTextNode( elem ) );
</del><ins>+function Animation( elem, properties, options ) {
+ var result,
+ stopped,
+ index = 0,
+ length = animationPrefilters.length,
+ deferred = jQuery.Deferred().always( function() {
+ // don't match elem in the :animated selector
+ delete tick.elem;
+ }),
+ tick = function() {
+ if ( stopped ) {
+ return false;
+ }
+ var currentTime = fxNow || createFxNow(),
+ remaining = Math.max( 0, animation.startTime + animation.duration - currentTime ),
+ // archaic crash bug won't allow us to use 1 - ( 0.5 || 0 ) (#12497)
+ temp = remaining / animation.duration || 0,
+ percent = 1 - temp,
+ index = 0,
+ length = animation.tweens.length;
</ins><span class="cx">
</span><del>- // Convert html into DOM nodes
- } else {
- tmp = tmp || safe.appendChild( context.createElement("div") );
</del><ins>+ for ( ; index < length ; index++ ) {
+ animation.tweens[ index ].run( percent );
+ }
</ins><span class="cx">
</span><del>- // Deserialize a standard representation
- tag = ( rtagName.exec( elem ) || ["", ""] )[1].toLowerCase();
- wrap = wrapMap[ tag ] || wrapMap._default;
</del><ins>+ deferred.notifyWith( elem, [ animation, percent, remaining ]);
</ins><span class="cx">
</span><del>- tmp.innerHTML = wrap[1] + elem.replace( rxhtmlTag, "<$1></$2>" ) + wrap[2];
</del><ins>+ if ( percent < 1 && length ) {
+ return remaining;
+ } else {
+ deferred.resolveWith( elem, [ animation ] );
+ return false;
+ }
+ },
+ animation = deferred.promise({
+ elem: elem,
+ props: jQuery.extend( {}, properties ),
+ opts: jQuery.extend( true, { specialEasing: {} }, options ),
+ originalProperties: properties,
+ originalOptions: options,
+ startTime: fxNow || createFxNow(),
+ duration: options.duration,
+ tweens: [],
+ createTween: function( prop, end ) {
+ var tween = jQuery.Tween( elem, animation.opts, prop, end,
+ animation.opts.specialEasing[ prop ] || animation.opts.easing );
+ animation.tweens.push( tween );
+ return tween;
+ },
+ stop: function( gotoEnd ) {
+ var index = 0,
+ // if we are going to the end, we want to run all the tweens
+ // otherwise we skip this part
+ length = gotoEnd ? animation.tweens.length : 0;
+ if ( stopped ) {
+ return this;
+ }
+ stopped = true;
+ for ( ; index < length ; index++ ) {
+ animation.tweens[ index ].run( 1 );
+ }
</ins><span class="cx">
</span><del>- // Descend through wrappers to the right content
- j = wrap[0];
- while ( j-- ) {
- tmp = tmp.lastChild;
</del><ins>+ // resolve when we played the last frame
+ // otherwise, reject
+ if ( gotoEnd ) {
+ deferred.resolveWith( elem, [ animation, gotoEnd ] );
+ } else {
+ deferred.rejectWith( elem, [ animation, gotoEnd ] );
+ }
+ return this;
+ }
+ }),
+ props = animation.props;
+
+ propFilter( props, animation.opts.specialEasing );
+
+ for ( ; index < length ; index++ ) {
+ result = animationPrefilters[ index ].call( animation, elem, props, animation.opts );
+ if ( result ) {
+ return result;
+ }
</ins><span class="cx"> }
</span><span class="cx">
</span><del>- // Manually add leading whitespace removed by IE
- if ( !jQuery.support.leadingWhitespace && rleadingWhitespace.test( elem ) ) {
- nodes.push( context.createTextNode( rleadingWhitespace.exec( elem )[0] ) );
</del><ins>+ jQuery.map( props, createTween, animation );
+
+ if ( jQuery.isFunction( animation.opts.start ) ) {
+ animation.opts.start.call( elem, animation );
</ins><span class="cx"> }
</span><span class="cx">
</span><del>- // Remove IE's autoinserted <tbody> from table fragments
- if ( !jQuery.support.tbody ) {
</del><ins>+ jQuery.fx.timer(
+ jQuery.extend( tick, {
+ elem: elem,
+ anim: animation,
+ queue: animation.opts.queue
+ })
+ );
</ins><span class="cx">
</span><del>- // String was a <table>, *may* have spurious <tbody>
- elem = tag === "table" && !rtbody.test( elem ) ?
- tmp.firstChild :
</del><ins>+ // attach callbacks from options
+ return animation.progress( animation.opts.progress )
+ .done( animation.opts.done, animation.opts.complete )
+ .fail( animation.opts.fail )
+ .always( animation.opts.always );
+}
</ins><span class="cx">
</span><del>- // String was a bare <thead> or <tfoot>
- wrap[1] === "<table>" && !rtbody.test( elem ) ?
- tmp :
- 0;
</del><ins>+jQuery.Animation = jQuery.extend( Animation, {
</ins><span class="cx">
</span><del>- j = elem && elem.childNodes.length;
- while ( j-- ) {
- if ( jQuery.nodeName( (tbody = elem.childNodes[j]), "tbody" ) && !tbody.childNodes.length ) {
- elem.removeChild( tbody );
- }
- }
- }
</del><ins>+ tweener: function( props, callback ) {
+ if ( jQuery.isFunction( props ) ) {
+ callback = props;
+ props = [ "*" ];
+ } else {
+ props = props.split(" ");
+ }
</ins><span class="cx">
</span><del>- jQuery.merge( nodes, tmp.childNodes );
</del><ins>+ var prop,
+ index = 0,
+ length = props.length;
</ins><span class="cx">
</span><del>- // Fix #12392 for WebKit and IE > 9
- tmp.textContent = "";
</del><ins>+ for ( ; index < length ; index++ ) {
+ prop = props[ index ];
+ tweeners[ prop ] = tweeners[ prop ] || [];
+ tweeners[ prop ].unshift( callback );
+ }
+ },
</ins><span class="cx">
</span><del>- // Fix #12392 for oldIE
- while ( tmp.firstChild ) {
- tmp.removeChild( tmp.firstChild );
</del><ins>+ prefilter: function( callback, prepend ) {
+ if ( prepend ) {
+ animationPrefilters.unshift( callback );
+ } else {
+ animationPrefilters.push( callback );
+ }
</ins><span class="cx"> }
</span><ins>+});
</ins><span class="cx">
</span><del>- // Remember the top-level container for proper cleanup
- tmp = safe.lastChild;
- }
- }
- }
</del><ins>+jQuery.speed = function( speed, easing, fn ) {
+ var opt = speed && typeof speed === "object" ? jQuery.extend( {}, speed ) : {
+ complete: fn || !fn && easing ||
+ jQuery.isFunction( speed ) && speed,
+ duration: speed,
+ easing: fn && easing || easing && !jQuery.isFunction( easing ) && easing
+ };
</ins><span class="cx">
</span><del>- // Fix #11356: Clear elements from fragment
- if ( tmp ) {
- safe.removeChild( tmp );
- }
</del><ins>+ opt.duration = jQuery.fx.off ? 0 : typeof opt.duration === "number" ? opt.duration :
+ opt.duration in jQuery.fx.speeds ? jQuery.fx.speeds[ opt.duration ] : jQuery.fx.speeds._default;
</ins><span class="cx">
</span><del>- // Reset defaultChecked for any radios and checkboxes
- // about to be appended to the DOM in IE 6/7 (#8060)
- if ( !jQuery.support.appendChecked ) {
- jQuery.grep( getAll( nodes, "input" ), fixDefaultChecked );
</del><ins>+ // normalize opt.queue - true/undefined/null -> "fx"
+ if ( opt.queue == null || opt.queue === true ) {
+ opt.queue = "fx";
</ins><span class="cx"> }
</span><span class="cx">
</span><del>- i = 0;
- while ( (elem = nodes[ i++ ]) ) {
</del><ins>+ // Queueing
+ opt.old = opt.complete;
</ins><span class="cx">
</span><del>- // #4087 - If origin and destination elements are the same, and this is
- // that element, do not do anything
- if ( selection && jQuery.inArray( elem, selection ) !== -1 ) {
- continue;
- }
</del><ins>+ opt.complete = function() {
+ if ( jQuery.isFunction( opt.old ) ) {
+ opt.old.call( this );
+ }
</ins><span class="cx">
</span><del>- contains = jQuery.contains( elem.ownerDocument, elem );
</del><ins>+ if ( opt.queue ) {
+ jQuery.dequeue( this, opt.queue );
+ }
+ };
</ins><span class="cx">
</span><del>- // Append to fragment
- tmp = getAll( safe.appendChild( elem ), "script" );
</del><ins>+ return opt;
+};
</ins><span class="cx">
</span><del>- // Preserve script evaluation history
- if ( contains ) {
- setGlobalEval( tmp );
- }
</del><ins>+jQuery.fn.extend({
+ fadeTo: function( speed, to, easing, callback ) {
</ins><span class="cx">
</span><del>- // Capture executables
- if ( scripts ) {
- j = 0;
- while ( (elem = tmp[ j++ ]) ) {
- if ( rscriptType.test( elem.type || "" ) ) {
- scripts.push( elem );
- }
- }
- }
- }
</del><ins>+ // show any hidden elements after setting opacity to 0
+ return this.filter( isHidden ).css( "opacity", 0 ).show()
</ins><span class="cx">
</span><del>- tmp = null;
</del><ins>+ // animate to the value specified
+ .end().animate({ opacity: to }, speed, easing, callback );
+ },
+ animate: function( prop, speed, easing, callback ) {
+ var empty = jQuery.isEmptyObject( prop ),
+ optall = jQuery.speed( speed, easing, callback ),
+ doAnimation = function() {
+ // Operate on a copy of prop so per-property easing won't be lost
+ var anim = Animation( this, jQuery.extend( {}, prop ), optall );
</ins><span class="cx">
</span><del>- return safe;
</del><ins>+ // Empty animations, or finishing resolves immediately
+ if ( empty || data_priv.get( this, "finish" ) ) {
+ anim.stop( true );
+ }
+ };
+ doAnimation.finish = doAnimation;
+
+ return empty || optall.queue === false ?
+ this.each( doAnimation ) :
+ this.queue( optall.queue, doAnimation );
</ins><span class="cx"> },
</span><ins>+ stop: function( type, clearQueue, gotoEnd ) {
+ var stopQueue = function( hooks ) {
+ var stop = hooks.stop;
+ delete hooks.stop;
+ stop( gotoEnd );
+ };
</ins><span class="cx">
</span><del>- cleanData: function( elems, /* internal */ acceptData ) {
- var elem, type, id, data,
- i = 0,
- internalKey = jQuery.expando,
- cache = jQuery.cache,
- deleteExpando = jQuery.support.deleteExpando,
- special = jQuery.event.special;
</del><ins>+ if ( typeof type !== "string" ) {
+ gotoEnd = clearQueue;
+ clearQueue = type;
+ type = undefined;
+ }
+ if ( clearQueue && type !== false ) {
+ this.queue( type || "fx", [] );
+ }
</ins><span class="cx">
</span><del>- for ( ; (elem = elems[i]) != null; i++ ) {
</del><ins>+ return this.each(function() {
+ var dequeue = true,
+ index = type != null && type + "queueHooks",
+ timers = jQuery.timers,
+ data = data_priv.get( this );
</ins><span class="cx">
</span><del>- if ( acceptData || jQuery.acceptData( elem ) ) {
</del><ins>+ if ( index ) {
+ if ( data[ index ] && data[ index ].stop ) {
+ stopQueue( data[ index ] );
+ }
+ } else {
+ for ( index in data ) {
+ if ( data[ index ] && data[ index ].stop && rrun.test( index ) ) {
+ stopQueue( data[ index ] );
+ }
+ }
+ }
</ins><span class="cx">
</span><del>- id = elem[ internalKey ];
- data = id && cache[ id ];
</del><ins>+ for ( index = timers.length; index--; ) {
+ if ( timers[ index ].elem === this && (type == null || timers[ index ].queue === type) ) {
+ timers[ index ].anim.stop( gotoEnd );
+ dequeue = false;
+ timers.splice( index, 1 );
+ }
+ }
</ins><span class="cx">
</span><del>- if ( data ) {
- if ( data.events ) {
- for ( type in data.events ) {
- if ( special[ type ] ) {
- jQuery.event.remove( elem, type );
</del><ins>+ // start the next in the queue if the last step wasn't forced
+ // timers currently will call their complete callbacks, which will dequeue
+ // but only if they were gotoEnd
+ if ( dequeue || !gotoEnd ) {
+ jQuery.dequeue( this, type );
+ }
+ });
+ },
+ finish: function( type ) {
+ if ( type !== false ) {
+ type = type || "fx";
+ }
+ return this.each(function() {
+ var index,
+ data = data_priv.get( this ),
+ queue = data[ type + "queue" ],
+ hooks = data[ type + "queueHooks" ],
+ timers = jQuery.timers,
+ length = queue ? queue.length : 0;
</ins><span class="cx">
</span><del>- // This is a shortcut to avoid jQuery.event.remove's overhead
- } else {
- jQuery.removeEvent( elem, type, data.handle );
- }
- }
- }
</del><ins>+ // enable finishing flag on private data
+ data.finish = true;
</ins><span class="cx">
</span><del>- // Remove cache only if it was not already removed by jQuery.event.remove
- if ( cache[ id ] ) {
</del><ins>+ // empty the queue first
+ jQuery.queue( this, type, [] );
</ins><span class="cx">
</span><del>- delete cache[ id ];
</del><ins>+ if ( hooks && hooks.stop ) {
+ hooks.stop.call( this, true );
+ }
</ins><span class="cx">
</span><del>- // IE does not allow us to delete expando properties from nodes,
- // nor does it have a removeAttribute function on Document nodes;
- // we must handle all of these cases
- if ( deleteExpando ) {
- delete elem[ internalKey ];
</del><ins>+ // look for any active animations, and finish them
+ for ( index = timers.length; index--; ) {
+ if ( timers[ index ].elem === this && timers[ index ].queue === type ) {
+ timers[ index ].anim.stop( true );
+ timers.splice( index, 1 );
+ }
+ }
</ins><span class="cx">
</span><del>- } else if ( typeof elem.removeAttribute !== core_strundefined ) {
- elem.removeAttribute( internalKey );
</del><ins>+ // look for any animations in the old queue and finish them
+ for ( index = 0; index < length; index++ ) {
+ if ( queue[ index ] && queue[ index ].finish ) {
+ queue[ index ].finish.call( this );
+ }
+ }
</ins><span class="cx">
</span><del>- } else {
- elem[ internalKey ] = null;
</del><ins>+ // turn off finishing flag
+ delete data.finish;
+ });
</ins><span class="cx"> }
</span><ins>+});
</ins><span class="cx">
</span><del>- core_deletedIds.push( id );
- }
- }
- }
- }
- }
</del><ins>+jQuery.each([ "toggle", "show", "hide" ], function( i, name ) {
+ var cssFn = jQuery.fn[ name ];
+ jQuery.fn[ name ] = function( speed, easing, callback ) {
+ return speed == null || typeof speed === "boolean" ?
+ cssFn.apply( this, arguments ) :
+ this.animate( genFx( name, true ), speed, easing, callback );
+ };
</ins><span class="cx"> });
</span><del>-var iframe, getStyles, curCSS,
- ralpha = /alpha\([^)]*\)/i,
- ropacity = /opacity\s*=\s*([^)]*)/,
- rposition = /^(top|right|bottom|left)$/,
- // swappable if display is none or starts with table except "table", "table-cell", or "table-caption"
- // see here for display values: https://developer.mozilla.org/en-US/docs/CSS/display
- rdisplayswap = /^(none|table(?!-c[ea]).+)/,
- rmargin = /^margin/,
- rnumsplit = new RegExp( "^(" + core_pnum + ")(.*)$", "i" ),
- rnumnonpx = new RegExp( "^(" + core_pnum + ")(?!px)[a-z%]+$", "i" ),
- rrelNum = new RegExp( "^([+-])=(" + core_pnum + ")", "i" ),
- elemdisplay = { BODY: "block" },
</del><span class="cx">
</span><del>- cssShow = { position: "absolute", visibility: "hidden", display: "block" },
- cssNormalTransform = {
- letterSpacing: 0,
- fontWeight: 400
- },
</del><ins>+// Generate shortcuts for custom animations
+jQuery.each({
+ slideDown: genFx("show"),
+ slideUp: genFx("hide"),
+ slideToggle: genFx("toggle"),
+ fadeIn: { opacity: "show" },
+ fadeOut: { opacity: "hide" },
+ fadeToggle: { opacity: "toggle" }
+}, function( name, props ) {
+ jQuery.fn[ name ] = function( speed, easing, callback ) {
+ return this.animate( props, speed, easing, callback );
+ };
+});
</ins><span class="cx">
</span><del>- cssExpand = [ "Top", "Right", "Bottom", "Left" ],
- cssPrefixes = [ "Webkit", "O", "Moz", "ms" ];
</del><ins>+jQuery.timers = [];
+jQuery.fx.tick = function() {
+ var timer,
+ i = 0,
+ timers = jQuery.timers;
</ins><span class="cx">
</span><del>-// return a css property mapped to a potentially vendor prefixed property
-function vendorPropName( style, name ) {
</del><ins>+ fxNow = jQuery.now();
</ins><span class="cx">
</span><del>- // shortcut for names that are not vendor prefixed
- if ( name in style ) {
- return name;
</del><ins>+ for ( ; i < timers.length; i++ ) {
+ timer = timers[ i ];
+ // Checks the timer has not already been removed
+ if ( !timer() && timers[ i ] === timer ) {
+ timers.splice( i--, 1 );
+ }
</ins><span class="cx"> }
</span><span class="cx">
</span><del>- // check for vendor prefixed names
- var capName = name.charAt(0).toUpperCase() + name.slice(1),
- origName = name,
- i = cssPrefixes.length;
</del><ins>+ if ( !timers.length ) {
+ jQuery.fx.stop();
+ }
+ fxNow = undefined;
+};
</ins><span class="cx">
</span><del>- while ( i-- ) {
- name = cssPrefixes[ i ] + capName;
- if ( name in style ) {
- return name;
</del><ins>+jQuery.fx.timer = function( timer ) {
+ jQuery.timers.push( timer );
+ if ( timer() ) {
+ jQuery.fx.start();
+ } else {
+ jQuery.timers.pop();
</ins><span class="cx"> }
</span><ins>+};
+
+jQuery.fx.interval = 13;
+
+jQuery.fx.start = function() {
+ if ( !timerId ) {
+ timerId = setInterval( jQuery.fx.tick, jQuery.fx.interval );
</ins><span class="cx"> }
</span><ins>+};
</ins><span class="cx">
</span><del>- return origName;
-}
</del><ins>+jQuery.fx.stop = function() {
+ clearInterval( timerId );
+ timerId = null;
+};
</ins><span class="cx">
</span><del>-function isHidden( elem, el ) {
- // isHidden might be called from jQuery#filter function;
- // in that case, element will be second argument
- elem = el || elem;
- return jQuery.css( elem, "display" ) === "none" || !jQuery.contains( elem.ownerDocument, elem );
-}
</del><ins>+jQuery.fx.speeds = {
+ slow: 600,
+ fast: 200,
+ // Default speed
+ _default: 400
+};
</ins><span class="cx">
</span><del>-function showHide( elements, show ) {
- var display, elem, hidden,
- values = [],
- index = 0,
- length = elements.length;
</del><span class="cx">
</span><del>- for ( ; index < length; index++ ) {
- elem = elements[ index ];
- if ( !elem.style ) {
- continue;
- }
</del><ins>+// Based off of the plugin by Clint Helfers, with permission.
+// http://blindsignals.com/index.php/2009/07/jquery-delay/
+jQuery.fn.delay = function( time, type ) {
+ time = jQuery.fx ? jQuery.fx.speeds[ time ] || time : time;
+ type = type || "fx";
</ins><span class="cx">
</span><del>- values[ index ] = jQuery._data( elem, "olddisplay" );
- display = elem.style.display;
- if ( show ) {
- // Reset the inline display of this element to learn if it is
- // being hidden by cascaded rules or not
- if ( !values[ index ] && display === "none" ) {
- elem.style.display = "";
- }
</del><ins>+ return this.queue( type, function( next, hooks ) {
+ var timeout = setTimeout( next, time );
+ hooks.stop = function() {
+ clearTimeout( timeout );
+ };
+ });
+};
</ins><span class="cx">
</span><del>- // Set elements which have been overridden with display: none
- // in a stylesheet to whatever the default browser style is
- // for such an element
- if ( elem.style.display === "" && isHidden( elem ) ) {
- values[ index ] = jQuery._data( elem, "olddisplay", css_defaultDisplay(elem.nodeName) );
- }
- } else {
</del><span class="cx">
</span><del>- if ( !values[ index ] ) {
- hidden = isHidden( elem );
</del><ins>+(function() {
+ var input = document.createElement( "input" ),
+ select = document.createElement( "select" ),
+ opt = select.appendChild( document.createElement( "option" ) );
</ins><span class="cx">
</span><del>- if ( display && display !== "none" || !hidden ) {
- jQuery._data( elem, "olddisplay", hidden ? display : jQuery.css( elem, "display" ) );
- }
- }
- }
- }
</del><ins>+ input.type = "checkbox";
</ins><span class="cx">
</span><del>- // Set the display of most of the elements in a second loop
- // to avoid the constant reflow
- for ( index = 0; index < length; index++ ) {
- elem = elements[ index ];
- if ( !elem.style ) {
- continue;
- }
- if ( !show || elem.style.display === "none" || elem.style.display === "" ) {
- elem.style.display = show ? values[ index ] || "" : "none";
- }
- }
</del><ins>+ // Support: iOS 5.1, Android 4.x, Android 2.3
+ // Check the default checkbox/radio value ("" on old WebKit; "on" elsewhere)
+ support.checkOn = input.value !== "";
</ins><span class="cx">
</span><del>- return elements;
-}
</del><ins>+ // Must access the parent to make an option select properly
+ // Support: IE9, IE10
+ support.optSelected = opt.selected;
</ins><span class="cx">
</span><del>-jQuery.fn.extend({
- css: function( name, value ) {
- return jQuery.access( this, function( elem, name, value ) {
- var len, styles,
- map = {},
- i = 0;
</del><ins>+ // Make sure that the options inside disabled selects aren't marked as disabled
+ // (WebKit marks them as disabled)
+ select.disabled = true;
+ support.optDisabled = !opt.disabled;
</ins><span class="cx">
</span><del>- if ( jQuery.isArray( name ) ) {
- styles = getStyles( elem );
- len = name.length;
</del><ins>+ // Check if an input maintains its value after becoming a radio
+ // Support: IE9, IE10
+ input = document.createElement( "input" );
+ input.value = "t";
+ input.type = "radio";
+ support.radioValue = input.value === "t";
+})();
</ins><span class="cx">
</span><del>- for ( ; i < len; i++ ) {
- map[ name[ i ] ] = jQuery.css( elem, name[ i ], false, styles );
- }
</del><span class="cx">
</span><del>- return map;
- }
</del><ins>+var nodeHook, boolHook,
+ attrHandle = jQuery.expr.attrHandle;
</ins><span class="cx">
</span><del>- return value !== undefined ?
- jQuery.style( elem, name, value ) :
- jQuery.css( elem, name );
- }, name, value, arguments.length > 1 );
</del><ins>+jQuery.fn.extend({
+ attr: function( name, value ) {
+ return access( this, jQuery.attr, name, value, arguments.length > 1 );
</ins><span class="cx"> },
</span><del>- show: function() {
- return showHide( this, true );
- },
- hide: function() {
- return showHide( this );
- },
- toggle: function( state ) {
- var bool = typeof state === "boolean";
</del><span class="cx">
</span><del>- return this.each(function() {
- if ( bool ? state : isHidden( this ) ) {
- jQuery( this ).show();
- } else {
- jQuery( this ).hide();
</del><ins>+ removeAttr: function( name ) {
+ return this.each(function() {
+ jQuery.removeAttr( this, name );
+ });
</ins><span class="cx"> }
</span><del>- });
- }
</del><span class="cx"> });
</span><span class="cx">
</span><span class="cx"> jQuery.extend({
</span><del>- // Add in style property hooks for overriding the default
- // behavior of getting and setting a style property
- cssHooks: {
- opacity: {
- get: function( elem, computed ) {
- if ( computed ) {
- // We should always get a number back from opacity
- var ret = curCSS( elem, "opacity" );
- return ret === "" ? "1" : ret;
- }
- }
- }
- },
</del><ins>+ attr: function( elem, name, value ) {
+ var hooks, ret,
+ nType = elem.nodeType;
</ins><span class="cx">
</span><del>- // Exclude the following css properties to add px
- cssNumber: {
- "columnCount": true,
- "fillOpacity": true,
- "fontWeight": true,
- "lineHeight": true,
- "opacity": true,
- "orphans": true,
- "widows": true,
- "zIndex": true,
- "zoom": true
- },
</del><ins>+ // don't get/set attributes on text, comment and attribute nodes
+ if ( !elem || nType === 3 || nType === 8 || nType === 2 ) {
+ return;
+ }
</ins><span class="cx">
</span><del>- // Add in properties whose names you wish to fix before
- // setting or getting the value
- cssProps: {
- // normalize float css property
- "float": jQuery.support.cssFloat ? "cssFloat" : "styleFloat"
- },
</del><ins>+ // Fallback to prop when attributes are not supported
+ if ( typeof elem.getAttribute === strundefined ) {
+ return jQuery.prop( elem, name, value );
+ }
</ins><span class="cx">
</span><del>- // Get and set the style property on a DOM Node
- style: function( elem, name, value, extra ) {
- // Don't set styles on text and comment nodes
- if ( !elem || elem.nodeType === 3 || elem.nodeType === 8 || !elem.style ) {
- return;
- }
</del><ins>+ // All attributes are lowercase
+ // Grab necessary hook if one is defined
+ if ( nType !== 1 || !jQuery.isXMLDoc( elem ) ) {
+ name = name.toLowerCase();
+ hooks = jQuery.attrHooks[ name ] ||
+ ( jQuery.expr.match.bool.test( name ) ? boolHook : nodeHook );
+ }
</ins><span class="cx">
</span><del>- // Make sure that we're working with the right name
- var ret, type, hooks,
- origName = jQuery.camelCase( name ),
- style = elem.style;
</del><ins>+ if ( value !== undefined ) {
</ins><span class="cx">
</span><del>- name = jQuery.cssProps[ origName ] || ( jQuery.cssProps[ origName ] = vendorPropName( style, origName ) );
</del><ins>+ if ( value === null ) {
+ jQuery.removeAttr( elem, name );
</ins><span class="cx">
</span><del>- // gets hook for the prefixed version
- // followed by the unprefixed version
- hooks = jQuery.cssHooks[ name ] || jQuery.cssHooks[ origName ];
</del><ins>+ } else if ( hooks && "set" in hooks && (ret = hooks.set( elem, value, name )) !== undefined ) {
+ return ret;
</ins><span class="cx">
</span><del>- // Check if we're setting a value
- if ( value !== undefined ) {
- type = typeof value;
</del><ins>+ } else {
+ elem.setAttribute( name, value + "" );
+ return value;
+ }
</ins><span class="cx">
</span><del>- // convert relative number strings (+= or -=) to relative numbers. #7345
- if ( type === "string" && (ret = rrelNum.exec( value )) ) {
- value = ( ret[1] + 1 ) * ret[2] + parseFloat( jQuery.css( elem, name ) );
- // Fixes bug #9237
- type = "number";
- }
</del><ins>+ } else if ( hooks && "get" in hooks && (ret = hooks.get( elem, name )) !== null ) {
+ return ret;
</ins><span class="cx">
</span><del>- // Make sure that NaN and null values aren't set. See: #7116
- if ( value == null || type === "number" && isNaN( value ) ) {
- return;
- }
</del><ins>+ } else {
+ ret = jQuery.find.attr( elem, name );
</ins><span class="cx">
</span><del>- // If a number was passed in, add 'px' to the (except for certain CSS properties)
- if ( type === "number" && !jQuery.cssNumber[ origName ] ) {
- value += "px";
- }
</del><ins>+ // Non-existent attributes return null, we normalize to undefined
+ return ret == null ?
+ undefined :
+ ret;
+ }
+ },
</ins><span class="cx">
</span><del>- // Fixes #8908, it can be done more correctly by specifing setters in cssHooks,
- // but it would mean to define eight (for every problematic property) identical functions
- if ( !jQuery.support.clearCloneStyle && value === "" && name.indexOf("background") === 0 ) {
- style[ name ] = "inherit";
- }
</del><ins>+ removeAttr: function( elem, value ) {
+ var name, propName,
+ i = 0,
+ attrNames = value && value.match( rnotwhite );
</ins><span class="cx">
</span><del>- // If a hook was provided, use that value, otherwise just set the specified value
- if ( !hooks || !("set" in hooks) || (value = hooks.set( elem, value, extra )) !== undefined ) {
</del><ins>+ if ( attrNames && elem.nodeType === 1 ) {
+ while ( (name = attrNames[i++]) ) {
+ propName = jQuery.propFix[ name ] || name;
</ins><span class="cx">
</span><del>- // Wrapped to prevent IE from throwing errors when 'invalid' values are provided
- // Fixes bug #5509
- try {
- style[ name ] = value;
- } catch(e) {}
- }
</del><ins>+ // Boolean attributes get special treatment (#10870)
+ if ( jQuery.expr.match.bool.test( name ) ) {
+ // Set corresponding property to false
+ elem[ propName ] = false;
+ }
</ins><span class="cx">
</span><del>- } else {
- // If a hook was provided get the non-computed value from there
- if ( hooks && "get" in hooks && (ret = hooks.get( elem, false, extra )) !== undefined ) {
- return ret;
</del><ins>+ elem.removeAttribute( name );
+ }
+ }
+ },
+
+ attrHooks: {
+ type: {
+ set: function( elem, value ) {
+ if ( !support.radioValue && value === "radio" &&
+ jQuery.nodeName( elem, "input" ) ) {
+ // Setting the type on a radio button after the value resets the value in IE6-9
+ // Reset value to default in case type is set after value during creation
+ var val = elem.value;
+ elem.setAttribute( "type", value );
+ if ( val ) {
+ elem.value = val;
+ }
+ return value;
+ }
+ }
+ }
</ins><span class="cx"> }
</span><ins>+});
</ins><span class="cx">
</span><del>- // Otherwise just get the value from the style object
- return style[ name ];
</del><ins>+// Hooks for boolean attributes
+boolHook = {
+ set: function( elem, value, name ) {
+ if ( value === false ) {
+ // Remove boolean attributes when set to false
+ jQuery.removeAttr( elem, name );
+ } else {
+ elem.setAttribute( name, name );
+ }
+ return name;
</ins><span class="cx"> }
</span><del>- },
</del><ins>+};
+jQuery.each( jQuery.expr.match.bool.source.match( /\w+/g ), function( i, name ) {
+ var getter = attrHandle[ name ] || jQuery.find.attr;
</ins><span class="cx">
</span><del>- css: function( elem, name, extra, styles ) {
- var num, val, hooks,
- origName = jQuery.camelCase( name );
</del><ins>+ attrHandle[ name ] = function( elem, name, isXML ) {
+ var ret, handle;
+ if ( !isXML ) {
+ // Avoid an infinite loop by temporarily removing this function from the getter
+ handle = attrHandle[ name ];
+ attrHandle[ name ] = ret;
+ ret = getter( elem, name, isXML ) != null ?
+ name.toLowerCase() :
+ null;
+ attrHandle[ name ] = handle;
+ }
+ return ret;
+ };
+});
</ins><span class="cx">
</span><del>- // Make sure that we're working with the right name
- name = jQuery.cssProps[ origName ] || ( jQuery.cssProps[ origName ] = vendorPropName( elem.style, origName ) );
</del><span class="cx">
</span><del>- // gets hook for the prefixed version
- // followed by the unprefixed version
- hooks = jQuery.cssHooks[ name ] || jQuery.cssHooks[ origName ];
</del><span class="cx">
</span><del>- // If a hook was provided get the computed value from there
- if ( hooks && "get" in hooks ) {
- val = hooks.get( elem, true, extra );
- }
</del><span class="cx">
</span><del>- // Otherwise, if a way to get the computed value exists, use that
- if ( val === undefined ) {
- val = curCSS( elem, name, styles );
- }
</del><ins>+var rfocusable = /^(?:input|select|textarea|button)$/i;
</ins><span class="cx">
</span><del>- //convert "normal" to computed value
- if ( val === "normal" && name in cssNormalTransform ) {
- val = cssNormalTransform[ name ];
- }
</del><ins>+jQuery.fn.extend({
+ prop: function( name, value ) {
+ return access( this, jQuery.prop, name, value, arguments.length > 1 );
+ },
</ins><span class="cx">
</span><del>- // Return, converting to number if forced or a qualifier was provided and val looks numeric
- if ( extra === "" || extra ) {
- num = parseFloat( val );
- return extra === true || jQuery.isNumeric( num ) ? num || 0 : val;
</del><ins>+ removeProp: function( name ) {
+ return this.each(function() {
+ delete this[ jQuery.propFix[ name ] || name ];
+ });
</ins><span class="cx"> }
</span><del>- return val;
</del><ins>+});
+
+jQuery.extend({
+ propFix: {
+ "for": "htmlFor",
+ "class": "className"
</ins><span class="cx"> },
</span><span class="cx">
</span><del>- // A method for quickly swapping in/out CSS properties to get correct calculations
- swap: function( elem, options, callback, args ) {
- var ret, name,
- old = {};
</del><ins>+ prop: function( elem, name, value ) {
+ var ret, hooks, notxml,
+ nType = elem.nodeType;
</ins><span class="cx">
</span><del>- // Remember the old values, and insert the new ones
- for ( name in options ) {
- old[ name ] = elem.style[ name ];
- elem.style[ name ] = options[ name ];
- }
</del><ins>+ // don't get/set properties on text, comment and attribute nodes
+ if ( !elem || nType === 3 || nType === 8 || nType === 2 ) {
+ return;
+ }
</ins><span class="cx">
</span><del>- ret = callback.apply( elem, args || [] );
</del><ins>+ notxml = nType !== 1 || !jQuery.isXMLDoc( elem );
</ins><span class="cx">
</span><del>- // Revert the old values
- for ( name in options ) {
- elem.style[ name ] = old[ name ];
- }
</del><ins>+ if ( notxml ) {
+ // Fix name and attach hooks
+ name = jQuery.propFix[ name ] || name;
+ hooks = jQuery.propHooks[ name ];
+ }
</ins><span class="cx">
</span><del>- return ret;
</del><ins>+ if ( value !== undefined ) {
+ return hooks && "set" in hooks && (ret = hooks.set( elem, value, name )) !== undefined ?
+ ret :
+ ( elem[ name ] = value );
+
+ } else {
+ return hooks && "get" in hooks && (ret = hooks.get( elem, name )) !== null ?
+ ret :
+ elem[ name ];
+ }
+ },
+
+ propHooks: {
+ tabIndex: {
+ get: function( elem ) {
+ return elem.hasAttribute( "tabindex" ) || rfocusable.test( elem.nodeName ) || elem.href ?
+ elem.tabIndex :
+ -1;
+ }
+ }
</ins><span class="cx"> }
</span><span class="cx"> });
</span><span class="cx">
</span><del>-// NOTE: we've included the "window" in window.getComputedStyle
-// because jsdom on node.js will break without it.
-if ( window.getComputedStyle ) {
- getStyles = function( elem ) {
- return window.getComputedStyle( elem, null );
</del><ins>+// Support: IE9+
+// Selectedness for an option in an optgroup can be inaccurate
+if ( !support.optSelected ) {
+ jQuery.propHooks.selected = {
+ get: function( elem ) {
+ var parent = elem.parentNode;
+ if ( parent && parent.parentNode ) {
+ parent.parentNode.selectedIndex;
+ }
+ return null;
+ }
</ins><span class="cx"> };
</span><ins>+}
</ins><span class="cx">
</span><del>- curCSS = function( elem, name, _computed ) {
- var width, minWidth, maxWidth,
- computed = _computed || getStyles( elem ),
</del><ins>+jQuery.each([
+ "tabIndex",
+ "readOnly",
+ "maxLength",
+ "cellSpacing",
+ "cellPadding",
+ "rowSpan",
+ "colSpan",
+ "useMap",
+ "frameBorder",
+ "contentEditable"
+], function() {
+ jQuery.propFix[ this.toLowerCase() ] = this;
+});
</ins><span class="cx">
</span><del>- // getPropertyValue is only needed for .css('filter') in IE9, see #12537
- ret = computed ? computed.getPropertyValue( name ) || computed[ name ] : undefined,
- style = elem.style;
</del><span class="cx">
</span><del>- if ( computed ) {
</del><span class="cx">
</span><del>- if ( ret === "" && !jQuery.contains( elem.ownerDocument, elem ) ) {
- ret = jQuery.style( elem, name );
- }
</del><span class="cx">
</span><del>- // A tribute to the "awesome hack by Dean Edwards"
- // Chrome < 17 and Safari 5.0 uses "computed value" instead of "used value" for margin-right
- // Safari 5.1.7 (at least) returns percentage for a larger set of values, but width seems to be reliably pixels
- // this is against the CSSOM draft spec: http://dev.w3.org/csswg/cssom/#resolved-values
- if ( rnumnonpx.test( ret ) && rmargin.test( name ) ) {
</del><ins>+var rclass = /[\t\r\n\f]/g;
</ins><span class="cx">
</span><del>- // Remember the original values
- width = style.width;
- minWidth = style.minWidth;
- maxWidth = style.maxWidth;
</del><ins>+jQuery.fn.extend({
+ addClass: function( value ) {
+ var classes, elem, cur, clazz, j, finalValue,
+ proceed = typeof value === "string" && value,
+ i = 0,
+ len = this.length;
</ins><span class="cx">
</span><del>- // Put in the new values to get a computed value out
- style.minWidth = style.maxWidth = style.width = ret;
- ret = computed.width;
</del><ins>+ if ( jQuery.isFunction( value ) ) {
+ return this.each(function( j ) {
+ jQuery( this ).addClass( value.call( this, j, this.className ) );
+ });
+ }
</ins><span class="cx">
</span><del>- // Revert the changed values
- style.width = width;
- style.minWidth = minWidth;
- style.maxWidth = maxWidth;
- }
- }
</del><ins>+ if ( proceed ) {
+ // The disjunction here is for better compressibility (see removeClass)
+ classes = ( value || "" ).match( rnotwhite ) || [];
</ins><span class="cx">
</span><del>- return ret;
- };
-} else if ( document.documentElement.currentStyle ) {
- getStyles = function( elem ) {
- return elem.currentStyle;
- };
</del><ins>+ for ( ; i < len; i++ ) {
+ elem = this[ i ];
+ cur = elem.nodeType === 1 && ( elem.className ?
+ ( " " + elem.className + " " ).replace( rclass, " " ) :
+ " "
+ );
</ins><span class="cx">
</span><del>- curCSS = function( elem, name, _computed ) {
- var left, rs, rsLeft,
- computed = _computed || getStyles( elem ),
- ret = computed ? computed[ name ] : undefined,
- style = elem.style;
</del><ins>+ if ( cur ) {
+ j = 0;
+ while ( (clazz = classes[j++]) ) {
+ if ( cur.indexOf( " " + clazz + " " ) < 0 ) {
+ cur += clazz + " ";
+ }
+ }
</ins><span class="cx">
</span><del>- // Avoid setting ret to empty string here
- // so we don't default to auto
- if ( ret == null && style && style[ name ] ) {
- ret = style[ name ];
- }
</del><ins>+ // only assign if different to avoid unneeded rendering.
+ finalValue = jQuery.trim( cur );
+ if ( elem.className !== finalValue ) {
+ elem.className = finalValue;
+ }
+ }
+ }
+ }
</ins><span class="cx">
</span><del>- // From the awesome hack by Dean Edwards
- // http://erik.eae.net/archives/2007/07/27/18.54.15/#comment-102291
</del><ins>+ return this;
+ },
</ins><span class="cx">
</span><del>- // If we're not dealing with a regular pixel number
- // but a number that has a weird ending, we need to convert it to pixels
- // but not position css attributes, as those are proportional to the parent element instead
- // and we can't measure the parent instead because it might trigger a "stacking dolls" problem
- if ( rnumnonpx.test( ret ) && !rposition.test( name ) ) {
</del><ins>+ removeClass: function( value ) {
+ var classes, elem, cur, clazz, j, finalValue,
+ proceed = arguments.length === 0 || typeof value === "string" && value,
+ i = 0,
+ len = this.length;
</ins><span class="cx">
</span><del>- // Remember the original values
- left = style.left;
- rs = elem.runtimeStyle;
- rsLeft = rs && rs.left;
</del><ins>+ if ( jQuery.isFunction( value ) ) {
+ return this.each(function( j ) {
+ jQuery( this ).removeClass( value.call( this, j, this.className ) );
+ });
+ }
+ if ( proceed ) {
+ classes = ( value || "" ).match( rnotwhite ) || [];
</ins><span class="cx">
</span><del>- // Put in the new values to get a computed value out
- if ( rsLeft ) {
- rs.left = elem.currentStyle.left;
- }
- style.left = name === "fontSize" ? "1em" : ret;
- ret = style.pixelLeft + "px";
</del><ins>+ for ( ; i < len; i++ ) {
+ elem = this[ i ];
+ // This expression is here for better compressibility (see addClass)
+ cur = elem.nodeType === 1 && ( elem.className ?
+ ( " " + elem.className + " " ).replace( rclass, " " ) :
+ ""
+ );
</ins><span class="cx">
</span><del>- // Revert the changed values
- style.left = left;
- if ( rsLeft ) {
- rs.left = rsLeft;
- }
- }
</del><ins>+ if ( cur ) {
+ j = 0;
+ while ( (clazz = classes[j++]) ) {
+ // Remove *all* instances
+ while ( cur.indexOf( " " + clazz + " " ) >= 0 ) {
+ cur = cur.replace( " " + clazz + " ", " " );
+ }
+ }
</ins><span class="cx">
</span><del>- return ret === "" ? "auto" : ret;
- };
-}
</del><ins>+ // only assign if different to avoid unneeded rendering.
+ finalValue = value ? jQuery.trim( cur ) : "";
+ if ( elem.className !== finalValue ) {
+ elem.className = finalValue;
+ }
+ }
+ }
+ }
</ins><span class="cx">
</span><del>-function setPositiveNumber( elem, value, subtract ) {
- var matches = rnumsplit.exec( value );
- return matches ?
- // Guard against undefined "subtract", e.g., when used as in cssHooks
- Math.max( 0, matches[ 1 ] - ( subtract || 0 ) ) + ( matches[ 2 ] || "px" ) :
- value;
-}
</del><ins>+ return this;
+ },
</ins><span class="cx">
</span><del>-function augmentWidthOrHeight( elem, name, extra, isBorderBox, styles ) {
- var i = extra === ( isBorderBox ? "border" : "content" ) ?
- // If we already have the right measurement, avoid augmentation
- 4 :
- // Otherwise initialize for horizontal or vertical properties
- name === "width" ? 1 : 0,
</del><ins>+ toggleClass: function( value, stateVal ) {
+ var type = typeof value;
</ins><span class="cx">
</span><del>- val = 0;
</del><ins>+ if ( typeof stateVal === "boolean" && type === "string" ) {
+ return stateVal ? this.addClass( value ) : this.removeClass( value );
+ }
</ins><span class="cx">
</span><del>- for ( ; i < 4; i += 2 ) {
- // both box models exclude margin, so add it if we want it
- if ( extra === "margin" ) {
- val += jQuery.css( elem, extra + cssExpand[ i ], true, styles );
- }
</del><ins>+ if ( jQuery.isFunction( value ) ) {
+ return this.each(function( i ) {
+ jQuery( this ).toggleClass( value.call(this, i, this.className, stateVal), stateVal );
+ });
+ }
</ins><span class="cx">
</span><del>- if ( isBorderBox ) {
- // border-box includes padding, so remove it if we want content
- if ( extra === "content" ) {
- val -= jQuery.css( elem, "padding" + cssExpand[ i ], true, styles );
- }
</del><ins>+ return this.each(function() {
+ if ( type === "string" ) {
+ // toggle individual class names
+ var className,
+ i = 0,
+ self = jQuery( this ),
+ classNames = value.match( rnotwhite ) || [];
</ins><span class="cx">
</span><del>- // at this point, extra isn't border nor margin, so remove border
- if ( extra !== "margin" ) {
- val -= jQuery.css( elem, "border" + cssExpand[ i ] + "Width", true, styles );
- }
- } else {
- // at this point, extra isn't content, so add padding
- val += jQuery.css( elem, "padding" + cssExpand[ i ], true, styles );
</del><ins>+ while ( (className = classNames[ i++ ]) ) {
+ // check each className given, space separated list
+ if ( self.hasClass( className ) ) {
+ self.removeClass( className );
+ } else {
+ self.addClass( className );
+ }
+ }
</ins><span class="cx">
</span><del>- // at this point, extra isn't content nor padding, so add border
- if ( extra !== "padding" ) {
- val += jQuery.css( elem, "border" + cssExpand[ i ] + "Width", true, styles );
</del><ins>+ // Toggle whole class name
+ } else if ( type === strundefined || type === "boolean" ) {
+ if ( this.className ) {
+ // store className if set
+ data_priv.set( this, "__className__", this.className );
+ }
+
+ // If the element has a class name or if we're passed "false",
+ // then remove the whole classname (if there was one, the above saved it).
+ // Otherwise bring back whatever was previously saved (if anything),
+ // falling back to the empty string if nothing was stored.
+ this.className = this.className || value === false ? "" : data_priv.get( this, "__className__" ) || "";
+ }
+ });
+ },
+
+ hasClass: function( selector ) {
+ var className = " " + selector + " ",
+ i = 0,
+ l = this.length;
+ for ( ; i < l; i++ ) {
+ if ( this[i].nodeType === 1 && (" " + this[i].className + " ").replace(rclass, " ").indexOf( className ) >= 0 ) {
+ return true;
+ }
+ }
+
+ return false;
</ins><span class="cx"> }
</span><del>- }
- }
</del><ins>+});
</ins><span class="cx">
</span><del>- return val;
-}
</del><span class="cx">
</span><del>-function getWidthOrHeight( elem, name, extra ) {
</del><span class="cx">
</span><del>- // Start with offset property, which is equivalent to the border-box value
- var valueIsBorderBox = true,
- val = name === "width" ? elem.offsetWidth : elem.offsetHeight,
- styles = getStyles( elem ),
- isBorderBox = jQuery.support.boxSizing && jQuery.css( elem, "boxSizing", false, styles ) === "border-box";
</del><span class="cx">
</span><del>- // some non-html elements return undefined for offsetWidth, so check for null/undefined
- // svg - https://bugzilla.mozilla.org/show_bug.cgi?id=649285
- // MathML - https://bugzilla.mozilla.org/show_bug.cgi?id=491668
- if ( val <= 0 || val == null ) {
- // Fall back to computed then uncomputed css if necessary
- val = curCSS( elem, name, styles );
- if ( val < 0 || val == null ) {
- val = elem.style[ name ];
- }
</del><ins>+var rreturn = /\r/g;
</ins><span class="cx">
</span><del>- // Computed unit is not pixels. Stop here and return.
- if ( rnumnonpx.test(val) ) {
- return val;
- }
</del><ins>+jQuery.fn.extend({
+ val: function( value ) {
+ var hooks, ret, isFunction,
+ elem = this[0];
</ins><span class="cx">
</span><del>- // we need the check for style in case a browser which returns unreliable values
- // for getComputedStyle silently falls back to the reliable elem.style
- valueIsBorderBox = isBorderBox && ( jQuery.support.boxSizingReliable || val === elem.style[ name ] );
</del><ins>+ if ( !arguments.length ) {
+ if ( elem ) {
+ hooks = jQuery.valHooks[ elem.type ] || jQuery.valHooks[ elem.nodeName.toLowerCase() ];
</ins><span class="cx">
</span><del>- // Normalize "", auto, and prepare for extra
- val = parseFloat( val ) || 0;
- }
</del><ins>+ if ( hooks && "get" in hooks && (ret = hooks.get( elem, "value" )) !== undefined ) {
+ return ret;
+ }
</ins><span class="cx">
</span><del>- // use the active box-sizing model to add/subtract irrelevant styles
- return ( val +
- augmentWidthOrHeight(
- elem,
- name,
- extra || ( isBorderBox ? "border" : "content" ),
- valueIsBorderBox,
- styles
- )
- ) + "px";
-}
</del><ins>+ ret = elem.value;
</ins><span class="cx">
</span><del>-// Try to determine the default display value of an element
-function css_defaultDisplay( nodeName ) {
- var doc = document,
- display = elemdisplay[ nodeName ];
</del><ins>+ return typeof ret === "string" ?
+ // handle most common string cases
+ ret.replace(rreturn, "") :
+ // handle cases where value is null/undef or number
+ ret == null ? "" : ret;
+ }
</ins><span class="cx">
</span><del>- if ( !display ) {
- display = actualDisplay( nodeName, doc );
</del><ins>+ return;
+ }
</ins><span class="cx">
</span><del>- // If the simple way fails, read from inside an iframe
- if ( display === "none" || !display ) {
- // Use the already-created iframe if possible
- iframe = ( iframe ||
- jQuery("<iframe frameborder='0' width='0' height='0'/>")
- .css( "cssText", "display:block !important" )
- ).appendTo( doc.documentElement );
</del><ins>+ isFunction = jQuery.isFunction( value );
</ins><span class="cx">
</span><del>- // Always write a new HTML skeleton so Webkit and Firefox don't choke on reuse
- doc = ( iframe[0].contentWindow || iframe[0].contentDocument ).document;
- doc.write("<!doctype html><html><body>");
- doc.close();
</del><ins>+ return this.each(function( i ) {
+ var val;
</ins><span class="cx">
</span><del>- display = actualDisplay( nodeName, doc );
- iframe.detach();
- }
</del><ins>+ if ( this.nodeType !== 1 ) {
+ return;
+ }
</ins><span class="cx">
</span><del>- // Store the correct default display
- elemdisplay[ nodeName ] = display;
- }
</del><ins>+ if ( isFunction ) {
+ val = value.call( this, i, jQuery( this ).val() );
+ } else {
+ val = value;
+ }
</ins><span class="cx">
</span><del>- return display;
-}
</del><ins>+ // Treat null/undefined as ""; convert numbers to string
+ if ( val == null ) {
+ val = "";
</ins><span class="cx">
</span><del>-// Called ONLY from within css_defaultDisplay
-function actualDisplay( name, doc ) {
- var elem = jQuery( doc.createElement( name ) ).appendTo( doc.body ),
- display = jQuery.css( elem[0], "display" );
- elem.remove();
- return display;
-}
</del><ins>+ } else if ( typeof val === "number" ) {
+ val += "";
</ins><span class="cx">
</span><del>-jQuery.each([ "height", "width" ], function( i, name ) {
- jQuery.cssHooks[ name ] = {
- get: function( elem, computed, extra ) {
- if ( computed ) {
- // certain elements can have dimension info if we invisibly show them
- // however, it must have a current display style that would benefit from this
- return elem.offsetWidth === 0 && rdisplayswap.test( jQuery.css( elem, "display" ) ) ?
- jQuery.swap( elem, cssShow, function() {
- return getWidthOrHeight( elem, name, extra );
- }) :
- getWidthOrHeight( elem, name, extra );
- }
- },
</del><ins>+ } else if ( jQuery.isArray( val ) ) {
+ val = jQuery.map( val, function( value ) {
+ return value == null ? "" : value + "";
+ });
+ }
</ins><span class="cx">
</span><del>- set: function( elem, value, extra ) {
- var styles = extra && getStyles( elem );
- return setPositiveNumber( elem, value, extra ?
- augmentWidthOrHeight(
- elem,
- name,
- extra,
- jQuery.support.boxSizing && jQuery.css( elem, "boxSizing", false, styles ) === "border-box",
- styles
- ) : 0
- );
</del><ins>+ hooks = jQuery.valHooks[ this.type ] || jQuery.valHooks[ this.nodeName.toLowerCase() ];
+
+ // If set returns undefined, fall back to normal setting
+ if ( !hooks || !("set" in hooks) || hooks.set( this, val, "value" ) === undefined ) {
+ this.value = val;
+ }
+ });
</ins><span class="cx"> }
</span><del>- };
</del><span class="cx"> });
</span><span class="cx">
</span><del>-if ( !jQuery.support.opacity ) {
- jQuery.cssHooks.opacity = {
- get: function( elem, computed ) {
- // IE uses filters for opacity
- return ropacity.test( (computed && elem.currentStyle ? elem.currentStyle.filter : elem.style.filter) || "" ) ?
- ( 0.01 * parseFloat( RegExp.$1 ) ) + "" :
- computed ? "1" : "";
- },
</del><ins>+jQuery.extend({
+ valHooks: {
+ select: {
+ get: function( elem ) {
+ var value, option,
+ options = elem.options,
+ index = elem.selectedIndex,
+ one = elem.type === "select-one" || index < 0,
+ values = one ? null : [],
+ max = one ? index + 1 : options.length,
+ i = index < 0 ?
+ max :
+ one ? index : 0;
</ins><span class="cx">
</span><del>- set: function( elem, value ) {
- var style = elem.style,
- currentStyle = elem.currentStyle,
- opacity = jQuery.isNumeric( value ) ? "alpha(opacity=" + value * 100 + ")" : "",
- filter = currentStyle && currentStyle.filter || style.filter || "";
</del><ins>+ // Loop through all the selected options
+ for ( ; i < max; i++ ) {
+ option = options[ i ];
</ins><span class="cx">
</span><del>- // IE has trouble with opacity if it does not have layout
- // Force it by setting the zoom level
- style.zoom = 1;
</del><ins>+ // IE6-9 doesn't update selected after form reset (#2551)
+ if ( ( option.selected || i === index ) &&
+ // Don't return options that are disabled or in a disabled optgroup
+ ( support.optDisabled ? !option.disabled : option.getAttribute( "disabled" ) === null ) &&
+ ( !option.parentNode.disabled || !jQuery.nodeName( option.parentNode, "optgroup" ) ) ) {
</ins><span class="cx">
</span><del>- // if setting opacity to 1, and no other filters exist - attempt to remove filter attribute #6652
- // if value === "", then remove inline opacity #12685
- if ( ( value >= 1 || value === "" ) &&
- jQuery.trim( filter.replace( ralpha, "" ) ) === "" &&
- style.removeAttribute ) {
</del><ins>+ // Get the specific value for the option
+ value = jQuery( option ).val();
</ins><span class="cx">
</span><del>- // Setting style.filter to null, "" & " " still leave "filter:" in the cssText
- // if "filter:" is present at all, clearType is disabled, we want to avoid this
- // style.removeAttribute is IE Only, but so apparently is this code path...
- style.removeAttribute( "filter" );
</del><ins>+ // We don't need an array for one selects
+ if ( one ) {
+ return value;
+ }
</ins><span class="cx">
</span><del>- // if there is no filter style applied in a css rule or unset inline opacity, we are done
- if ( value === "" || currentStyle && !currentStyle.filter ) {
- return;
- }
- }
</del><ins>+ // Multi-Selects return an array
+ values.push( value );
+ }
+ }
</ins><span class="cx">
</span><del>- // otherwise, set new filter values
- style.filter = ralpha.test( filter ) ?
- filter.replace( ralpha, opacity ) :
- filter + " " + opacity;
- }
- };
-}
</del><ins>+ return values;
+ },
</ins><span class="cx">
</span><del>-// These hooks cannot be added until DOM ready because the support test
-// for it is not run until after DOM ready
-jQuery(function() {
- if ( !jQuery.support.reliableMarginRight ) {
- jQuery.cssHooks.marginRight = {
- get: function( elem, computed ) {
- if ( computed ) {
- // WebKit Bug 13343 - getComputedStyle returns wrong value for margin-right
- // Work around by temporarily setting element display to inline-block
- return jQuery.swap( elem, { "display": "inline-block" },
- curCSS, [ elem, "marginRight" ] );
- }
- }
- };
- }
</del><ins>+ set: function( elem, value ) {
+ var optionSet, option,
+ options = elem.options,
+ values = jQuery.makeArray( value ),
+ i = options.length;
</ins><span class="cx">
</span><del>- // Webkit bug: https://bugs.webkit.org/show_bug.cgi?id=29084
- // getComputedStyle returns percent when specified for top/left/bottom/right
- // rather than make the css module depend on the offset module, we just check for it here
- if ( !jQuery.support.pixelPosition && jQuery.fn.position ) {
- jQuery.each( [ "top", "left" ], function( i, prop ) {
- jQuery.cssHooks[ prop ] = {
- get: function( elem, computed ) {
- if ( computed ) {
- computed = curCSS( elem, prop );
- // if curCSS returns percentage, fallback to offset
- return rnumnonpx.test( computed ) ?
- jQuery( elem ).position()[ prop ] + "px" :
- computed;
</del><ins>+ while ( i-- ) {
+ option = options[ i ];
+ if ( (option.selected = jQuery.inArray( jQuery(option).val(), values ) >= 0) ) {
+ optionSet = true;
+ }
+ }
+
+ // force browsers to behave consistently when non-matching value is set
+ if ( !optionSet ) {
+ elem.selectedIndex = -1;
+ }
+ return values;
+ }
+ }
</ins><span class="cx"> }
</span><del>- }
</del><ins>+});
+
+// Radios and checkboxes getter/setter
+jQuery.each([ "radio", "checkbox" ], function() {
+ jQuery.valHooks[ this ] = {
+ set: function( elem, value ) {
+ if ( jQuery.isArray( value ) ) {
+ return ( elem.checked = jQuery.inArray( jQuery(elem).val(), value ) >= 0 );
+ }
+ }
</ins><span class="cx"> };
</span><del>- });
</del><ins>+ if ( !support.checkOn ) {
+ jQuery.valHooks[ this ].get = function( elem ) {
+ // Support: Webkit
+ // "" is returned instead of "on" if a value isn't specified
+ return elem.getAttribute("value") === null ? "on" : elem.value;
+ };
</ins><span class="cx"> }
</span><del>-
</del><span class="cx"> });
</span><span class="cx">
</span><del>-if ( jQuery.expr && jQuery.expr.filters ) {
- jQuery.expr.filters.hidden = function( elem ) {
- // Support: Opera <= 12.12
- // Opera reports offsetWidths and offsetHeights less than zero on some elements
- return elem.offsetWidth <= 0 && elem.offsetHeight <= 0 ||
- (!jQuery.support.reliableHiddenOffsets && ((elem.style && elem.style.display) || jQuery.css( elem, "display" )) === "none");
- };
</del><span class="cx">
</span><del>- jQuery.expr.filters.visible = function( elem ) {
- return !jQuery.expr.filters.hidden( elem );
- };
-}
</del><span class="cx">
</span><del>-// These hooks are used by animate to expand properties
-jQuery.each({
- margin: "",
- padding: "",
- border: "Width"
-}, function( prefix, suffix ) {
- jQuery.cssHooks[ prefix + suffix ] = {
- expand: function( value ) {
- var i = 0,
- expanded = {},
</del><span class="cx">
</span><del>- // assumes a single number if not a string
- parts = typeof value === "string" ? value.split(" ") : [ value ];
</del><ins>+// Return jQuery for attributes-only inclusion
</ins><span class="cx">
</span><del>- for ( ; i < 4; i++ ) {
- expanded[ prefix + cssExpand[ i ] + suffix ] =
- parts[ i ] || parts[ i - 2 ] || parts[ 0 ];
- }
</del><span class="cx">
</span><del>- return expanded;
- }
- };
</del><ins>+jQuery.each( ("blur focus focusin focusout load resize scroll unload click dblclick " +
+ "mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave " +
+ "change select submit keydown keypress keyup error contextmenu").split(" "), function( i, name ) {
</ins><span class="cx">
</span><del>- if ( !rmargin.test( prefix ) ) {
- jQuery.cssHooks[ prefix + suffix ].set = setPositiveNumber;
- }
</del><ins>+ // Handle event binding
+ jQuery.fn[ name ] = function( data, fn ) {
+ return arguments.length > 0 ?
+ this.on( name, null, data, fn ) :
+ this.trigger( name );
+ };
</ins><span class="cx"> });
</span><del>-var r20 = /%20/g,
- rbracket = /\[\]$/,
- rCRLF = /\r?\n/g,
- rsubmitterTypes = /^(?:submit|button|image|reset|file)$/i,
- rsubmittable = /^(?:input|select|textarea|keygen)/i;
</del><span class="cx">
</span><span class="cx"> jQuery.fn.extend({
</span><del>- serialize: function() {
- return jQuery.param( this.serializeArray() );
</del><ins>+ hover: function( fnOver, fnOut ) {
+ return this.mouseenter( fnOver ).mouseleave( fnOut || fnOver );
</ins><span class="cx"> },
</span><del>- serializeArray: function() {
- return this.map(function(){
- // Can add propHook for "elements" to filter or add form elements
- var elements = jQuery.prop( this, "elements" );
- return elements ? jQuery.makeArray( elements ) : this;
- })
- .filter(function(){
- var type = this.type;
- // Use .is(":disabled") so that fieldset[disabled] works
- return this.name && !jQuery( this ).is( ":disabled" ) &&
- rsubmittable.test( this.nodeName ) && !rsubmitterTypes.test( type ) &&
- ( this.checked || !manipulation_rcheckableType.test( type ) );
- })
- .map(function( i, elem ){
- var val = jQuery( this ).val();
</del><span class="cx">
</span><del>- return val == null ?
- null :
- jQuery.isArray( val ) ?
- jQuery.map( val, function( val ){
- return { name: elem.name, value: val.replace( rCRLF, "\r\n" ) };
- }) :
- { name: elem.name, value: val.replace( rCRLF, "\r\n" ) };
- }).get();
</del><ins>+ bind: function( types, data, fn ) {
+ return this.on( types, null, data, fn );
+ },
+ unbind: function( types, fn ) {
+ return this.off( types, null, fn );
+ },
+
+ delegate: function( selector, types, data, fn ) {
+ return this.on( types, selector, data, fn );
+ },
+ undelegate: function( selector, types, fn ) {
+ // ( namespace ) or ( selector, types [, fn] )
+ return arguments.length === 1 ? this.off( selector, "**" ) : this.off( types, selector || "**", fn );
</ins><span class="cx"> }
</span><span class="cx"> });
</span><span class="cx">
</span><del>-//Serialize an array of form elements or a set of
-//key/values into a query string
-jQuery.param = function( a, traditional ) {
- var prefix,
- s = [],
- add = function( key, value ) {
- // If value is a function, invoke it and return its value
- value = jQuery.isFunction( value ) ? value() : ( value == null ? "" : value );
- s[ s.length ] = encodeURIComponent( key ) + "=" + encodeURIComponent( value );
- };
</del><span class="cx">
</span><del>- // Set traditional to true for jQuery <= 1.3.2 behavior.
- if ( traditional === undefined ) {
- traditional = jQuery.ajaxSettings && jQuery.ajaxSettings.traditional;
- }
</del><ins>+var nonce = jQuery.now();
</ins><span class="cx">
</span><del>- // If an array was passed in, assume that it is an array of form elements.
- if ( jQuery.isArray( a ) || ( a.jquery && !jQuery.isPlainObject( a ) ) ) {
- // Serialize the form elements
- jQuery.each( a, function() {
- add( this.name, this.value );
- });
</del><ins>+var rquery = (/\?/);
</ins><span class="cx">
</span><del>- } else {
- // If traditional, encode the "old" way (the way 1.3.2 or older
- // did it), otherwise encode params recursively.
- for ( prefix in a ) {
- buildParams( prefix, a[ prefix ], traditional, add );
- }
- }
</del><span class="cx">
</span><del>- // Return the resulting serialization
- return s.join( "&" ).replace( r20, "+" );
</del><ins>+
+// Support: Android 2.3
+// Workaround failure to string-cast null input
+jQuery.parseJSON = function( data ) {
+ return JSON.parse( data + "" );
</ins><span class="cx"> };
</span><span class="cx">
</span><del>-function buildParams( prefix, obj, traditional, add ) {
- var name;
</del><span class="cx">
</span><del>- if ( jQuery.isArray( obj ) ) {
- // Serialize array item.
- jQuery.each( obj, function( i, v ) {
- if ( traditional || rbracket.test( prefix ) ) {
- // Treat each array item as a scalar.
- add( prefix, v );
-
- } else {
- // Item is non-scalar (array or object), encode its numeric index.
- buildParams( prefix + "[" + ( typeof v === "object" ? i : "" ) + "]", v, traditional, add );
</del><ins>+// Cross-browser xml parsing
+jQuery.parseXML = function( data ) {
+ var xml, tmp;
+ if ( !data || typeof data !== "string" ) {
+ return null;
</ins><span class="cx"> }
</span><del>- });
</del><span class="cx">
</span><del>- } else if ( !traditional && jQuery.type( obj ) === "object" ) {
- // Serialize object item.
- for ( name in obj ) {
- buildParams( prefix + "[" + name + "]", obj[ name ], traditional, add );
</del><ins>+ // Support: IE9
+ try {
+ tmp = new DOMParser();
+ xml = tmp.parseFromString( data, "text/xml" );
+ } catch ( e ) {
+ xml = undefined;
</ins><span class="cx"> }
</span><span class="cx">
</span><del>- } else {
- // Serialize scalar item.
- add( prefix, obj );
</del><ins>+ if ( !xml || xml.getElementsByTagName( "parsererror" ).length ) {
+ jQuery.error( "Invalid XML: " + data );
</ins><span class="cx"> }
</span><del>-}
-jQuery.each( ("blur focus focusin focusout load resize scroll unload click dblclick " +
- "mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave " +
- "change select submit keydown keypress keyup error contextmenu").split(" "), function( i, name ) {
</del><ins>+ return xml;
+};
</ins><span class="cx">
</span><del>- // Handle event binding
- jQuery.fn[ name ] = function( data, fn ) {
- return arguments.length > 0 ?
- this.on( name, null, data, fn ) :
- this.trigger( name );
- };
-});
</del><span class="cx">
</span><del>-jQuery.fn.hover = function( fnOver, fnOut ) {
- return this.mouseenter( fnOver ).mouseleave( fnOut || fnOver );
-};
</del><span class="cx"> var
</span><span class="cx"> // Document location
</span><span class="cx"> ajaxLocParts,
</span><span class="cx"> ajaxLocation,
</span><del>- ajax_nonce = jQuery.now(),
</del><span class="cx">
</span><del>- ajax_rquery = /\?/,
</del><span class="cx"> rhash = /#.*$/,
</span><span class="cx"> rts = /([?&])_=[^&]*/,
</span><del>- rheaders = /^(.*?):[ \t]*([^\r\n]*)\r?$/mg, // IE leaves an \r character at EOL
</del><ins>+ rheaders = /^(.*?):[ \t]*([^\r\n]*)$/mg,
</ins><span class="cx"> // #7653, #8125, #8152: local protocol detection
</span><span class="cx"> rlocalProtocol = /^(?:about|app|app-storage|.+-extension|file|res|widget):$/,
</span><span class="cx"> rnoContent = /^(?:GET|HEAD)$/,
</span><span class="cx"> rprotocol = /^\/\//,
</span><del>- rurl = /^([\w.+-]+:)(?:\/\/([^\/?#:]*)(?::(\d+)|)|)/,
</del><ins>+ rurl = /^([\w.+-]+:)(?:\/\/(?:[^\/?#]*@|)([^\/?#:]*)(?::(\d+)|)|)/,
</ins><span class="cx">
</span><del>- // Keep a copy of the old load method
- _load = jQuery.fn.load,
-
</del><span class="cx"> /* Prefilters
</span><span class="cx"> * 1) They are useful to introduce custom dataTypes (see ajax/jsonp.js for an example)
</span><span class="cx"> * 2) These are called:
</span><span class="lines">@@ -7468,29 +7507,29 @@
</span><span class="cx"> // dataTypeExpression is optional and defaults to "*"
</span><span class="cx"> return function( dataTypeExpression, func ) {
</span><span class="cx">
</span><del>- if ( typeof dataTypeExpression !== "string" ) {
- func = dataTypeExpression;
- dataTypeExpression = "*";
- }
</del><ins>+ if ( typeof dataTypeExpression !== "string" ) {
+ func = dataTypeExpression;
+ dataTypeExpression = "*";
+ }
</ins><span class="cx">
</span><del>- var dataType,
- i = 0,
- dataTypes = dataTypeExpression.toLowerCase().match( core_rnotwhite ) || [];
</del><ins>+ var dataType,
+ i = 0,
+ dataTypes = dataTypeExpression.toLowerCase().match( rnotwhite ) || [];
</ins><span class="cx">
</span><del>- if ( jQuery.isFunction( func ) ) {
- // For each dataType in the dataTypeExpression
- while ( (dataType = dataTypes[i++]) ) {
- // Prepend if requested
- if ( dataType[0] === "+" ) {
- dataType = dataType.slice( 1 ) || "*";
- (structure[ dataType ] = structure[ dataType ] || []).unshift( func );
</del><ins>+ if ( jQuery.isFunction( func ) ) {
+ // For each dataType in the dataTypeExpression
+ while ( (dataType = dataTypes[i++]) ) {
+ // Prepend if requested
+ if ( dataType[0] === "+" ) {
+ dataType = dataType.slice( 1 ) || "*";
+ (structure[ dataType ] = structure[ dataType ] || []).unshift( func );
</ins><span class="cx">
</span><del>- // Otherwise append
- } else {
- (structure[ dataType ] = structure[ dataType ] || []).push( func );
- }
- }
- }
</del><ins>+ // Otherwise append
+ } else {
+ (structure[ dataType ] = structure[ dataType ] || []).push( func );
+ }
+ }
+ }
</ins><span class="cx"> };
</span><span class="cx"> }
</span><span class="cx">
</span><span class="lines">@@ -7498,23 +7537,23 @@
</span><span class="cx"> function inspectPrefiltersOrTransports( structure, options, originalOptions, jqXHR ) {
</span><span class="cx">
</span><span class="cx"> var inspected = {},
</span><del>- seekingTransport = ( structure === transports );
</del><ins>+ seekingTransport = ( structure === transports );
</ins><span class="cx">
</span><span class="cx"> function inspect( dataType ) {
</span><del>- var selected;
- inspected[ dataType ] = true;
- jQuery.each( structure[ dataType ] || [], function( _, prefilterOrFactory ) {
- var dataTypeOrTransport = prefilterOrFactory( options, originalOptions, jqXHR );
- if( typeof dataTypeOrTransport === "string" && !seekingTransport && !inspected[ dataTypeOrTransport ] ) {
- options.dataTypes.unshift( dataTypeOrTransport );
- inspect( dataTypeOrTransport );
- return false;
- } else if ( seekingTransport ) {
- return !( selected = dataTypeOrTransport );
</del><ins>+ var selected;
+ inspected[ dataType ] = true;
+ jQuery.each( structure[ dataType ] || [], function( _, prefilterOrFactory ) {
+ var dataTypeOrTransport = prefilterOrFactory( options, originalOptions, jqXHR );
+ if ( typeof dataTypeOrTransport === "string" && !seekingTransport && !inspected[ dataTypeOrTransport ] ) {
+ options.dataTypes.unshift( dataTypeOrTransport );
+ inspect( dataTypeOrTransport );
+ return false;
+ } else if ( seekingTransport ) {
+ return !( selected = dataTypeOrTransport );
+ }
+ });
+ return selected;
</ins><span class="cx"> }
</span><del>- });
- return selected;
- }
</del><span class="cx">
</span><span class="cx"> return inspect( options.dataTypes[ 0 ] ) || !inspected[ "*" ] && inspect( "*" );
</span><span class="cx"> }
</span><span class="lines">@@ -7523,103 +7562,171 @@
</span><span class="cx"> // that takes "flat" options (not to be deep extended)
</span><span class="cx"> // Fixes #9887
</span><span class="cx"> function ajaxExtend( target, src ) {
</span><del>- var deep, key,
- flatOptions = jQuery.ajaxSettings.flatOptions || {};
</del><ins>+ var key, deep,
+ flatOptions = jQuery.ajaxSettings.flatOptions || {};
</ins><span class="cx">
</span><span class="cx"> for ( key in src ) {
</span><del>- if ( src[ key ] !== undefined ) {
- ( flatOptions[ key ] ? target : ( deep || (deep = {}) ) )[ key ] = src[ key ];
</del><ins>+ if ( src[ key ] !== undefined ) {
+ ( flatOptions[ key ] ? target : ( deep || (deep = {}) ) )[ key ] = src[ key ];
+ }
</ins><span class="cx"> }
</span><del>- }
</del><span class="cx"> if ( deep ) {
</span><del>- jQuery.extend( true, target, deep );
</del><ins>+ jQuery.extend( true, target, deep );
</ins><span class="cx"> }
</span><span class="cx">
</span><span class="cx"> return target;
</span><span class="cx"> }
</span><span class="cx">
</span><del>-jQuery.fn.load = function( url, params, callback ) {
- if ( typeof url !== "string" && _load ) {
- return _load.apply( this, arguments );
</del><ins>+/* Handles responses to an ajax request:
+ * - finds the right dataType (mediates between content-type and expected dataType)
+ * - returns the corresponding response
+ */
+function ajaxHandleResponses( s, jqXHR, responses ) {
+
+ var ct, type, finalDataType, firstDataType,
+ contents = s.contents,
+ dataTypes = s.dataTypes;
+
+ // Remove auto dataType and get content-type in the process
+ while ( dataTypes[ 0 ] === "*" ) {
+ dataTypes.shift();
+ if ( ct === undefined ) {
+ ct = s.mimeType || jqXHR.getResponseHeader("Content-Type");
+ }
</ins><span class="cx"> }
</span><span class="cx">
</span><del>- var selector, response, type,
- self = this,
- off = url.indexOf(" ");
</del><ins>+ // Check if we're dealing with a known content-type
+ if ( ct ) {
+ for ( type in contents ) {
+ if ( contents[ type ] && contents[ type ].test( ct ) ) {
+ dataTypes.unshift( type );
+ break;
+ }
+ }
+ }
</ins><span class="cx">
</span><del>- if ( off >= 0 ) {
- selector = url.slice( off, url.length );
- url = url.slice( 0, off );
</del><ins>+ // Check to see if we have a response for the expected dataType
+ if ( dataTypes[ 0 ] in responses ) {
+ finalDataType = dataTypes[ 0 ];
+ } else {
+ // Try convertible dataTypes
+ for ( type in responses ) {
+ if ( !dataTypes[ 0 ] || s.converters[ type + " " + dataTypes[0] ] ) {
+ finalDataType = type;
+ break;
+ }
+ if ( !firstDataType ) {
+ firstDataType = type;
+ }
+ }
+ // Or just use first one
+ finalDataType = finalDataType || firstDataType;
</ins><span class="cx"> }
</span><span class="cx">
</span><del>- // If it's a function
- if ( jQuery.isFunction( params ) ) {
</del><ins>+ // If we found a dataType
+ // We add the dataType to the list if needed
+ // and return the corresponding response
+ if ( finalDataType ) {
+ if ( finalDataType !== dataTypes[ 0 ] ) {
+ dataTypes.unshift( finalDataType );
+ }
+ return responses[ finalDataType ];
+ }
+}
</ins><span class="cx">
</span><del>- // We assume that it's the callback
- callback = params;
- params = undefined;
</del><ins>+/* Chain conversions given the request and the original response
+ * Also sets the responseXXX fields on the jqXHR instance
+ */
+function ajaxConvert( s, response, jqXHR, isSuccess ) {
+ var conv2, current, conv, tmp, prev,
+ converters = {},
+ // Work with a copy of dataTypes in case we need to modify it for conversion
+ dataTypes = s.dataTypes.slice();
</ins><span class="cx">
</span><del>- // Otherwise, build a param string
- } else if ( params && typeof params === "object" ) {
- type = "POST";
</del><ins>+ // Create converters map with lowercased keys
+ if ( dataTypes[ 1 ] ) {
+ for ( conv in s.converters ) {
+ converters[ conv.toLowerCase() ] = s.converters[ conv ];
+ }
</ins><span class="cx"> }
</span><span class="cx">
</span><del>- // If we have elements to modify, make the request
- if ( self.length > 0 ) {
- jQuery.ajax({
- url: url,
</del><ins>+ current = dataTypes.shift();
</ins><span class="cx">
</span><del>- // if "type" variable is undefined, then "GET" method will be used
- type: type,
- dataType: "html",
- data: params
- }).done(function( responseText ) {
</del><ins>+ // Convert to each sequential dataType
+ while ( current ) {
</ins><span class="cx">
</span><del>- // Save response for use in complete callback
- response = arguments;
</del><ins>+ if ( s.responseFields[ current ] ) {
+ jqXHR[ s.responseFields[ current ] ] = response;
+ }
</ins><span class="cx">
</span><del>- self.html( selector ?
</del><ins>+ // Apply the dataFilter if provided
+ if ( !prev && isSuccess && s.dataFilter ) {
+ response = s.dataFilter( response, s.dataType );
+ }
</ins><span class="cx">
</span><del>- // If a selector was specified, locate the right elements in a dummy div
- // Exclude scripts to avoid IE 'Permission Denied' errors
- jQuery("<div>").append( jQuery.parseHTML( responseText ) ).find( selector ) :
</del><ins>+ prev = current;
+ current = dataTypes.shift();
</ins><span class="cx">
</span><del>- // Otherwise use the full result
- responseText );
</del><ins>+ if ( current ) {
</ins><span class="cx">
</span><del>- }).complete( callback && function( jqXHR, status ) {
- self.each( callback, response || [ jqXHR.responseText, status, jqXHR ] );
- });
- }
</del><ins>+ // There's only work to do if current dataType is non-auto
+ if ( current === "*" ) {
</ins><span class="cx">
</span><del>- return this;
-};
</del><ins>+ current = prev;
</ins><span class="cx">
</span><del>-// Attach a bunch of functions for handling common AJAX events
-jQuery.each( [ "ajaxStart", "ajaxStop", "ajaxComplete", "ajaxError", "ajaxSuccess", "ajaxSend" ], function( i, type ){
- jQuery.fn[ type ] = function( fn ){
- return this.on( type, fn );
- };
-});
</del><ins>+ // Convert response if prev dataType is non-auto and differs from current
+ } else if ( prev !== "*" && prev !== current ) {
</ins><span class="cx">
</span><del>-jQuery.each( [ "get", "post" ], function( i, method ) {
- jQuery[ method ] = function( url, data, callback, type ) {
- // shift arguments if data argument was omitted
- if ( jQuery.isFunction( data ) ) {
- type = type || callback;
- callback = data;
- data = undefined;
</del><ins>+ // Seek a direct converter
+ conv = converters[ prev + " " + current ] || converters[ "* " + current ];
+
+ // If none found, seek a pair
+ if ( !conv ) {
+ for ( conv2 in converters ) {
+
+ // If conv2 outputs current
+ tmp = conv2.split( " " );
+ if ( tmp[ 1 ] === current ) {
+
+ // If prev can be converted to accepted input
+ conv = converters[ prev + " " + tmp[ 0 ] ] ||
+ converters[ "* " + tmp[ 0 ] ];
+ if ( conv ) {
+ // Condense equivalence converters
+ if ( conv === true ) {
+ conv = converters[ conv2 ];
+
+ // Otherwise, insert the intermediate dataType
+ } else if ( converters[ conv2 ] !== true ) {
+ current = tmp[ 0 ];
+ dataTypes.unshift( tmp[ 1 ] );
+ }
+ break;
+ }
+ }
+ }
+ }
+
+ // Apply converter (if not an equivalence)
+ if ( conv !== true ) {
+
+ // Unless errors are allowed to bubble, catch and return them
+ if ( conv && s[ "throws" ] ) {
+ response = conv( response );
+ } else {
+ try {
+ response = conv( response );
+ } catch ( e ) {
+ return { state: "parsererror", error: conv ? e : "No conversion from " + prev + " to " + current };
+ }
+ }
+ }
+ }
+ }
</ins><span class="cx"> }
</span><span class="cx">
</span><del>- return jQuery.ajax({
- url: url,
- type: method,
- dataType: type,
- data: data,
- success: callback
- });
- };
-});
</del><ins>+ return { state: "success", data: response };
+}
</ins><span class="cx">
</span><span class="cx"> jQuery.extend({
</span><span class="cx">
</span><span class="lines">@@ -7631,82 +7738,83 @@
</span><span class="cx"> etag: {},
</span><span class="cx">
</span><span class="cx"> ajaxSettings: {
</span><del>- url: ajaxLocation,
- type: "GET",
- isLocal: rlocalProtocol.test( ajaxLocParts[ 1 ] ),
- global: true,
- processData: true,
- async: true,
- contentType: "application/x-www-form-urlencoded; charset=UTF-8",
- /*
- timeout: 0,
- data: null,
- dataType: null,
- username: null,
- password: null,
- cache: null,
- throws: false,
- traditional: false,
- headers: {},
- */
</del><ins>+ url: ajaxLocation,
+ type: "GET",
+ isLocal: rlocalProtocol.test( ajaxLocParts[ 1 ] ),
+ global: true,
+ processData: true,
+ async: true,
+ contentType: "application/x-www-form-urlencoded; charset=UTF-8",
+ /*
+ timeout: 0,
+ data: null,
+ dataType: null,
+ username: null,
+ password: null,
+ cache: null,
+ throws: false,
+ traditional: false,
+ headers: {},
+ */
</ins><span class="cx">
</span><del>- accepts: {
- "*": allTypes,
- text: "text/plain",
- html: "text/html",
- xml: "application/xml, text/xml",
- json: "application/json, text/javascript"
- },
</del><ins>+ accepts: {
+ "*": allTypes,
+ text: "text/plain",
+ html: "text/html",
+ xml: "application/xml, text/xml",
+ json: "application/json, text/javascript"
+ },
</ins><span class="cx">
</span><del>- contents: {
- xml: /xml/,
- html: /html/,
- json: /json/
- },
</del><ins>+ contents: {
+ xml: /xml/,
+ html: /html/,
+ json: /json/
+ },
</ins><span class="cx">
</span><del>- responseFields: {
- xml: "responseXML",
- text: "responseText"
- },
</del><ins>+ responseFields: {
+ xml: "responseXML",
+ text: "responseText",
+ json: "responseJSON"
+ },
</ins><span class="cx">
</span><del>- // Data converters
- // Keys separate source (or catchall "*") and destination types with a single space
- converters: {
</del><ins>+ // Data converters
+ // Keys separate source (or catchall "*") and destination types with a single space
+ converters: {
</ins><span class="cx">
</span><del>- // Convert anything to text
- "* text": window.String,
</del><ins>+ // Convert anything to text
+ "* text": String,
</ins><span class="cx">
</span><del>- // Text to html (true = no transformation)
- "text html": true,
</del><ins>+ // Text to html (true = no transformation)
+ "text html": true,
</ins><span class="cx">
</span><del>- // Evaluate text as a json expression
- "text json": jQuery.parseJSON,
</del><ins>+ // Evaluate text as a json expression
+ "text json": jQuery.parseJSON,
</ins><span class="cx">
</span><del>- // Parse text as xml
- "text xml": jQuery.parseXML
- },
</del><ins>+ // Parse text as xml
+ "text xml": jQuery.parseXML
+ },
</ins><span class="cx">
</span><del>- // For options that shouldn't be deep extended:
- // you can add your own custom options here if
- // and when you create one that shouldn't be
- // deep extended (see ajaxExtend)
- flatOptions: {
- url: true,
- context: true
- }
</del><ins>+ // For options that shouldn't be deep extended:
+ // you can add your own custom options here if
+ // and when you create one that shouldn't be
+ // deep extended (see ajaxExtend)
+ flatOptions: {
+ url: true,
+ context: true
+ }
</ins><span class="cx"> },
</span><span class="cx">
</span><span class="cx"> // Creates a full fledged settings object into target
</span><span class="cx"> // with both ajaxSettings and settings fields.
</span><span class="cx"> // If target is omitted, writes into ajaxSettings.
</span><span class="cx"> ajaxSetup: function( target, settings ) {
</span><del>- return settings ?
</del><ins>+ return settings ?
</ins><span class="cx">
</span><del>- // Building a settings object
- ajaxExtend( ajaxExtend( target, jQuery.ajaxSettings ), settings ) :
</del><ins>+ // Building a settings object
+ ajaxExtend( ajaxExtend( target, jQuery.ajaxSettings ), settings ) :
</ins><span class="cx">
</span><del>- // Extending ajaxSettings
- ajaxExtend( jQuery.ajaxSettings, target );
</del><ins>+ // Extending ajaxSettings
+ ajaxExtend( jQuery.ajaxSettings, target );
</ins><span class="cx"> },
</span><span class="cx">
</span><span class="cx"> ajaxPrefilter: addToPrefiltersOrTransports( prefilters ),
</span><span class="lines">@@ -7715,1883 +7823,1289 @@
</span><span class="cx"> // Main method
</span><span class="cx"> ajax: function( url, options ) {
</span><span class="cx">
</span><del>- // If url is an object, simulate pre-1.5 signature
- if ( typeof url === "object" ) {
- options = url;
- url = undefined;
- }
</del><ins>+ // If url is an object, simulate pre-1.5 signature
+ if ( typeof url === "object" ) {
+ options = url;
+ url = undefined;
+ }
</ins><span class="cx">
</span><del>- // Force options to be an object
- options = options || {};
</del><ins>+ // Force options to be an object
+ options = options || {};
</ins><span class="cx">
</span><del>- var // Cross-domain detection vars
- parts,
- // Loop variable
- i,
- // URL without anti-cache param
- cacheURL,
- // Response headers as string
- responseHeadersString,
- // timeout handle
- timeoutTimer,
</del><ins>+ var transport,
+ // URL without anti-cache param
+ cacheURL,
+ // Response headers
+ responseHeadersString,
+ responseHeaders,
+ // timeout handle
+ timeoutTimer,
+ // Cross-domain detection vars
+ parts,
+ // To know if global events are to be dispatched
+ fireGlobals,
+ // Loop variable
+ i,
+ // Create the final options object
+ s = jQuery.ajaxSetup( {}, options ),
+ // Callbacks context
+ callbackContext = s.context || s,
+ // Context for global events is callbackContext if it is a DOM node or jQuery collection
+ globalEventContext = s.context && ( callbackContext.nodeType || callbackContext.jquery ) ?
+ jQuery( callbackContext ) :
+ jQuery.event,
+ // Deferreds
+ deferred = jQuery.Deferred(),
+ completeDeferred = jQuery.Callbacks("once memory"),
+ // Status-dependent callbacks
+ statusCode = s.statusCode || {},
+ // Headers (they are sent all at once)
+ requestHeaders = {},
+ requestHeadersNames = {},
+ // The jqXHR state
+ state = 0,
+ // Default abort message
+ strAbort = "canceled",
+ // Fake xhr
+ jqXHR = {
+ readyState: 0,
</ins><span class="cx">
</span><del>- // To know if global events are to be dispatched
- fireGlobals,
</del><ins>+ // Builds headers hashtable if needed
+ getResponseHeader: function( key ) {
+ var match;
+ if ( state === 2 ) {
+ if ( !responseHeaders ) {
+ responseHeaders = {};
+ while ( (match = rheaders.exec( responseHeadersString )) ) {
+ responseHeaders[ match[1].toLowerCase() ] = match[ 2 ];
+ }
+ }
+ match = responseHeaders[ key.toLowerCase() ];
+ }
+ return match == null ? null : match;
+ },
</ins><span class="cx">
</span><del>- transport,
- // Response headers
- responseHeaders,
- // Create the final options object
- s = jQuery.ajaxSetup( {}, options ),
- // Callbacks context
- callbackContext = s.context || s,
- // Context for global events is callbackContext if it is a DOM node or jQuery collection
- globalEventContext = s.context && ( callbackContext.nodeType || callbackContext.jquery ) ?
- jQuery( callbackContext ) :
- jQuery.event,
- // Deferreds
- deferred = jQuery.Deferred(),
- completeDeferred = jQuery.Callbacks("once memory"),
- // Status-dependent callbacks
- statusCode = s.statusCode || {},
- // Headers (they are sent all at once)
- requestHeaders = {},
- requestHeadersNames = {},
- // The jqXHR state
- state = 0,
- // Default abort message
- strAbort = "canceled",
- // Fake xhr
- jqXHR = {
- readyState: 0,
</del><ins>+ // Raw string
+ getAllResponseHeaders: function() {
+ return state === 2 ? responseHeadersString : null;
+ },
</ins><span class="cx">
</span><del>- // Builds headers hashtable if needed
- getResponseHeader: function( key ) {
- var match;
- if ( state === 2 ) {
- if ( !responseHeaders ) {
- responseHeaders = {};
- while ( (match = rheaders.exec( responseHeadersString )) ) {
- responseHeaders[ match[1].toLowerCase() ] = match[ 2 ];
- }
- }
- match = responseHeaders[ key.toLowerCase() ];
- }
- return match == null ? null : match;
- },
</del><ins>+ // Caches the header
+ setRequestHeader: function( name, value ) {
+ var lname = name.toLowerCase();
+ if ( !state ) {
+ name = requestHeadersNames[ lname ] = requestHeadersNames[ lname ] || name;
+ requestHeaders[ name ] = value;
+ }
+ return this;
+ },
</ins><span class="cx">
</span><del>- // Raw string
- getAllResponseHeaders: function() {
- return state === 2 ? responseHeadersString : null;
- },
</del><ins>+ // Overrides response content-type header
+ overrideMimeType: function( type ) {
+ if ( !state ) {
+ s.mimeType = type;
+ }
+ return this;
+ },
</ins><span class="cx">
</span><del>- // Caches the header
- setRequestHeader: function( name, value ) {
- var lname = name.toLowerCase();
- if ( !state ) {
- name = requestHeadersNames[ lname ] = requestHeadersNames[ lname ] || name;
- requestHeaders[ name ] = value;
- }
- return this;
- },
</del><ins>+ // Status-dependent callbacks
+ statusCode: function( map ) {
+ var code;
+ if ( map ) {
+ if ( state < 2 ) {
+ for ( code in map ) {
+ // Lazy-add the new callback in a way that preserves old ones
+ statusCode[ code ] = [ statusCode[ code ], map[ code ] ];
+ }
+ } else {
+ // Execute the appropriate callbacks
+ jqXHR.always( map[ jqXHR.status ] );
+ }
+ }
+ return this;
+ },
</ins><span class="cx">
</span><del>- // Overrides response content-type header
- overrideMimeType: function( type ) {
- if ( !state ) {
- s.mimeType = type;
- }
- return this;
- },
</del><ins>+ // Cancel the request
+ abort: function( statusText ) {
+ var finalText = statusText || strAbort;
+ if ( transport ) {
+ transport.abort( finalText );
+ }
+ done( 0, finalText );
+ return this;
+ }
+ };
</ins><span class="cx">
</span><del>- // Status-dependent callbacks
- statusCode: function( map ) {
- var code;
- if ( map ) {
- if ( state < 2 ) {
- for ( code in map ) {
- // Lazy-add the new callback in a way that preserves old ones
- statusCode[ code ] = [ statusCode[ code ], map[ code ] ];
- }
- } else {
- // Execute the appropriate callbacks
- jqXHR.always( map[ jqXHR.status ] );
- }
- }
- return this;
- },
</del><ins>+ // Attach deferreds
+ deferred.promise( jqXHR ).complete = completeDeferred.add;
+ jqXHR.success = jqXHR.done;
+ jqXHR.error = jqXHR.fail;
</ins><span class="cx">
</span><del>- // Cancel the request
- abort: function( statusText ) {
- var finalText = statusText || strAbort;
- if ( transport ) {
- transport.abort( finalText );
- }
- done( 0, finalText );
- return this;
- }
- };
</del><ins>+ // Remove hash character (#7531: and string promotion)
+ // Add protocol if not provided (prefilters might expect it)
+ // Handle falsy url in the settings object (#10093: consistency with old signature)
+ // We also use the url parameter if available
+ s.url = ( ( url || s.url || ajaxLocation ) + "" ).replace( rhash, "" )
+ .replace( rprotocol, ajaxLocParts[ 1 ] + "//" );
</ins><span class="cx">
</span><del>- // Attach deferreds
- deferred.promise( jqXHR ).complete = completeDeferred.add;
- jqXHR.success = jqXHR.done;
- jqXHR.error = jqXHR.fail;
</del><ins>+ // Alias method option to type as per ticket #12004
+ s.type = options.method || options.type || s.method || s.type;
</ins><span class="cx">
</span><del>- // Remove hash character (#7531: and string promotion)
- // Add protocol if not provided (#5866: IE7 issue with protocol-less urls)
- // Handle falsy url in the settings object (#10093: consistency with old signature)
- // We also use the url parameter if available
- s.url = ( ( url || s.url || ajaxLocation ) + "" ).replace( rhash, "" ).replace( rprotocol, ajaxLocParts[ 1 ] + "//" );
</del><ins>+ // Extract dataTypes list
+ s.dataTypes = jQuery.trim( s.dataType || "*" ).toLowerCase().match( rnotwhite ) || [ "" ];
</ins><span class="cx">
</span><del>- // Alias method option to type as per ticket #12004
- s.type = options.method || options.type || s.method || s.type;
</del><ins>+ // A cross-domain request is in order when we have a protocol:host:port mismatch
+ if ( s.crossDomain == null ) {
+ parts = rurl.exec( s.url.toLowerCase() );
+ s.crossDomain = !!( parts &&
+ ( parts[ 1 ] !== ajaxLocParts[ 1 ] || parts[ 2 ] !== ajaxLocParts[ 2 ] ||
+ ( parts[ 3 ] || ( parts[ 1 ] === "http:" ? "80" : "443" ) ) !==
+ ( ajaxLocParts[ 3 ] || ( ajaxLocParts[ 1 ] === "http:" ? "80" : "443" ) ) )
+ );
+ }
</ins><span class="cx">
</span><del>- // Extract dataTypes list
- s.dataTypes = jQuery.trim( s.dataType || "*" ).toLowerCase().match( core_rnotwhite ) || [""];
</del><ins>+ // Convert data if not already a string
+ if ( s.data && s.processData && typeof s.data !== "string" ) {
+ s.data = jQuery.param( s.data, s.traditional );
+ }
</ins><span class="cx">
</span><del>- // A cross-domain request is in order when we have a protocol:host:port mismatch
- if ( s.crossDomain == null ) {
- parts = rurl.exec( s.url.toLowerCase() );
- s.crossDomain = !!( parts &&
- ( parts[ 1 ] !== ajaxLocParts[ 1 ] || parts[ 2 ] !== ajaxLocParts[ 2 ] ||
- ( parts[ 3 ] || ( parts[ 1 ] === "http:" ? 80 : 443 ) ) !=
- ( ajaxLocParts[ 3 ] || ( ajaxLocParts[ 1 ] === "http:" ? 80 : 443 ) ) )
- );
- }
</del><ins>+ // Apply prefilters
+ inspectPrefiltersOrTransports( prefilters, s, options, jqXHR );
</ins><span class="cx">
</span><del>- // Convert data if not already a string
- if ( s.data && s.processData && typeof s.data !== "string" ) {
- s.data = jQuery.param( s.data, s.traditional );
- }
</del><ins>+ // If request was aborted inside a prefilter, stop there
+ if ( state === 2 ) {
+ return jqXHR;
+ }
</ins><span class="cx">
</span><del>- // Apply prefilters
- inspectPrefiltersOrTransports( prefilters, s, options, jqXHR );
</del><ins>+ // We can fire global events as of now if asked to
+ fireGlobals = s.global;
</ins><span class="cx">
</span><del>- // If request was aborted inside a prefilter, stop there
- if ( state === 2 ) {
- return jqXHR;
- }
</del><ins>+ // Watch for a new set of requests
+ if ( fireGlobals && jQuery.active++ === 0 ) {
+ jQuery.event.trigger("ajaxStart");
+ }
</ins><span class="cx">
</span><del>- // We can fire global events as of now if asked to
- fireGlobals = s.global;
</del><ins>+ // Uppercase the type
+ s.type = s.type.toUpperCase();
</ins><span class="cx">
</span><del>- // Watch for a new set of requests
- if ( fireGlobals && jQuery.active++ === 0 ) {
- jQuery.event.trigger("ajaxStart");
- }
</del><ins>+ // Determine if request has content
+ s.hasContent = !rnoContent.test( s.type );
</ins><span class="cx">
</span><del>- // Uppercase the type
- s.type = s.type.toUpperCase();
</del><ins>+ // Save the URL in case we're toying with the If-Modified-Since
+ // and/or If-None-Match header later on
+ cacheURL = s.url;
</ins><span class="cx">
</span><del>- // Determine if request has content
- s.hasContent = !rnoContent.test( s.type );
</del><ins>+ // More options handling for requests with no content
+ if ( !s.hasContent ) {
</ins><span class="cx">
</span><del>- // Save the URL in case we're toying with the If-Modified-Since
- // and/or If-None-Match header later on
- cacheURL = s.url;
</del><ins>+ // If data is available, append data to url
+ if ( s.data ) {
+ cacheURL = ( s.url += ( rquery.test( cacheURL ) ? "&" : "?" ) + s.data );
+ // #9682: remove data so that it's not used in an eventual retry
+ delete s.data;
+ }
</ins><span class="cx">
</span><del>- // More options handling for requests with no content
- if ( !s.hasContent ) {
</del><ins>+ // Add anti-cache in url if needed
+ if ( s.cache === false ) {
+ s.url = rts.test( cacheURL ) ?
</ins><span class="cx">
</span><del>- // If data is available, append data to url
- if ( s.data ) {
- cacheURL = ( s.url += ( ajax_rquery.test( cacheURL ) ? "&" : "?" ) + s.data );
- // #9682: remove data so that it's not used in an eventual retry
- delete s.data;
- }
</del><ins>+ // If there is already a '_' parameter, set its value
+ cacheURL.replace( rts, "$1_=" + nonce++ ) :
</ins><span class="cx">
</span><del>- // Add anti-cache in url if needed
- if ( s.cache === false ) {
- s.url = rts.test( cacheURL ) ?
</del><ins>+ // Otherwise add one to the end
+ cacheURL + ( rquery.test( cacheURL ) ? "&" : "?" ) + "_=" + nonce++;
+ }
+ }
</ins><span class="cx">
</span><del>- // If there is already a '_' parameter, set its value
- cacheURL.replace( rts, "$1_=" + ajax_nonce++ ) :
</del><ins>+ // Set the If-Modified-Since and/or If-None-Match header, if in ifModified mode.
+ if ( s.ifModified ) {
+ if ( jQuery.lastModified[ cacheURL ] ) {
+ jqXHR.setRequestHeader( "If-Modified-Since", jQuery.lastModified[ cacheURL ] );
+ }
+ if ( jQuery.etag[ cacheURL ] ) {
+ jqXHR.setRequestHeader( "If-None-Match", jQuery.etag[ cacheURL ] );
+ }
+ }
</ins><span class="cx">
</span><del>- // Otherwise add one to the end
- cacheURL + ( ajax_rquery.test( cacheURL ) ? "&" : "?" ) + "_=" + ajax_nonce++;
- }
- }
</del><ins>+ // Set the correct header, if data is being sent
+ if ( s.data && s.hasContent && s.contentType !== false || options.contentType ) {
+ jqXHR.setRequestHeader( "Content-Type", s.contentType );
+ }
</ins><span class="cx">
</span><del>- // Set the If-Modified-Since and/or If-None-Match header, if in ifModified mode.
- if ( s.ifModified ) {
- if ( jQuery.lastModified[ cacheURL ] ) {
- jqXHR.setRequestHeader( "If-Modified-Since", jQuery.lastModified[ cacheURL ] );
- }
- if ( jQuery.etag[ cacheURL ] ) {
- jqXHR.setRequestHeader( "If-None-Match", jQuery.etag[ cacheURL ] );
- }
- }
</del><ins>+ // Set the Accepts header for the server, depending on the dataType
+ jqXHR.setRequestHeader(
+ "Accept",
+ s.dataTypes[ 0 ] && s.accepts[ s.dataTypes[0] ] ?
+ s.accepts[ s.dataTypes[0] ] + ( s.dataTypes[ 0 ] !== "*" ? ", " + allTypes + "; q=0.01" : "" ) :
+ s.accepts[ "*" ]
+ );
</ins><span class="cx">
</span><del>- // Set the correct header, if data is being sent
- if ( s.data && s.hasContent && s.contentType !== false || options.contentType ) {
- jqXHR.setRequestHeader( "Content-Type", s.contentType );
- }
</del><ins>+ // Check for headers option
+ for ( i in s.headers ) {
+ jqXHR.setRequestHeader( i, s.headers[ i ] );
+ }
</ins><span class="cx">
</span><del>- // Set the Accepts header for the server, depending on the dataType
- jqXHR.setRequestHeader(
- "Accept",
- s.dataTypes[ 0 ] && s.accepts[ s.dataTypes[0] ] ?
- s.accepts[ s.dataTypes[0] ] + ( s.dataTypes[ 0 ] !== "*" ? ", " + allTypes + "; q=0.01" : "" ) :
- s.accepts[ "*" ]
- );
</del><ins>+ // Allow custom headers/mimetypes and early abort
+ if ( s.beforeSend && ( s.beforeSend.call( callbackContext, jqXHR, s ) === false || state === 2 ) ) {
+ // Abort if not done already and return
+ return jqXHR.abort();
+ }
</ins><span class="cx">
</span><del>- // Check for headers option
- for ( i in s.headers ) {
- jqXHR.setRequestHeader( i, s.headers[ i ] );
- }
</del><ins>+ // aborting is no longer a cancellation
+ strAbort = "abort";
</ins><span class="cx">
</span><del>- // Allow custom headers/mimetypes and early abort
- if ( s.beforeSend && ( s.beforeSend.call( callbackContext, jqXHR, s ) === false || state === 2 ) ) {
- // Abort if not done already and return
- return jqXHR.abort();
- }
</del><ins>+ // Install callbacks on deferreds
+ for ( i in { success: 1, error: 1, complete: 1 } ) {
+ jqXHR[ i ]( s[ i ] );
+ }
</ins><span class="cx">
</span><del>- // aborting is no longer a cancellation
- strAbort = "abort";
</del><ins>+ // Get transport
+ transport = inspectPrefiltersOrTransports( transports, s, options, jqXHR );
</ins><span class="cx">
</span><del>- // Install callbacks on deferreds
- for ( i in { success: 1, error: 1, complete: 1 } ) {
- jqXHR[ i ]( s[ i ] );
- }
</del><ins>+ // If no transport, we auto-abort
+ if ( !transport ) {
+ done( -1, "No Transport" );
+ } else {
+ jqXHR.readyState = 1;
</ins><span class="cx">
</span><del>- // Get transport
- transport = inspectPrefiltersOrTransports( transports, s, options, jqXHR );
</del><ins>+ // Send global event
+ if ( fireGlobals ) {
+ globalEventContext.trigger( "ajaxSend", [ jqXHR, s ] );
+ }
+ // Timeout
+ if ( s.async && s.timeout > 0 ) {
+ timeoutTimer = setTimeout(function() {
+ jqXHR.abort("timeout");
+ }, s.timeout );
+ }
</ins><span class="cx">
</span><del>- // If no transport, we auto-abort
- if ( !transport ) {
- done( -1, "No Transport" );
- } else {
- jqXHR.readyState = 1;
</del><ins>+ try {
+ state = 1;
+ transport.send( requestHeaders, done );
+ } catch ( e ) {
+ // Propagate exception as error if not done
+ if ( state < 2 ) {
+ done( -1, e );
+ // Simply rethrow otherwise
+ } else {
+ throw e;
+ }
+ }
+ }
</ins><span class="cx">
</span><del>- // Send global event
- if ( fireGlobals ) {
- globalEventContext.trigger( "ajaxSend", [ jqXHR, s ] );
- }
- // Timeout
- if ( s.async && s.timeout > 0 ) {
- timeoutTimer = setTimeout(function() {
- jqXHR.abort("timeout");
- }, s.timeout );
- }
</del><ins>+ // Callback for when everything is done
+ function done( status, nativeStatusText, responses, headers ) {
+ var isSuccess, success, error, response, modified,
+ statusText = nativeStatusText;
</ins><span class="cx">
</span><del>- try {
- state = 1;
- transport.send( requestHeaders, done );
- } catch ( e ) {
- // Propagate exception as error if not done
- if ( state < 2 ) {
- done( -1, e );
- // Simply rethrow otherwise
- } else {
- throw e;
- }
- }
- }
</del><ins>+ // Called once
+ if ( state === 2 ) {
+ return;
+ }
</ins><span class="cx">
</span><del>- // Callback for when everything is done
- function done( status, nativeStatusText, responses, headers ) {
- var isSuccess, success, error, response, modified,
- statusText = nativeStatusText;
</del><ins>+ // State is "done" now
+ state = 2;
</ins><span class="cx">
</span><del>- // Called once
- if ( state === 2 ) {
- return;
- }
</del><ins>+ // Clear timeout if it exists
+ if ( timeoutTimer ) {
+ clearTimeout( timeoutTimer );
+ }
</ins><span class="cx">
</span><del>- // State is "done" now
- state = 2;
</del><ins>+ // Dereference transport for early garbage collection
+ // (no matter how long the jqXHR object will be used)
+ transport = undefined;
</ins><span class="cx">
</span><del>- // Clear timeout if it exists
- if ( timeoutTimer ) {
- clearTimeout( timeoutTimer );
- }
</del><ins>+ // Cache response headers
+ responseHeadersString = headers || "";
</ins><span class="cx">
</span><del>- // Dereference transport for early garbage collection
- // (no matter how long the jqXHR object will be used)
- transport = undefined;
</del><ins>+ // Set readyState
+ jqXHR.readyState = status > 0 ? 4 : 0;
</ins><span class="cx">
</span><del>- // Cache response headers
- responseHeadersString = headers || "";
</del><ins>+ // Determine if successful
+ isSuccess = status >= 200 && status < 300 || status === 304;
</ins><span class="cx">
</span><del>- // Set readyState
- jqXHR.readyState = status > 0 ? 4 : 0;
</del><ins>+ // Get response data
+ if ( responses ) {
+ response = ajaxHandleResponses( s, jqXHR, responses );
+ }
</ins><span class="cx">
</span><del>- // Get response data
- if ( responses ) {
- response = ajaxHandleResponses( s, jqXHR, responses );
- }
</del><ins>+ // Convert no matter what (that way responseXXX fields are always set)
+ response = ajaxConvert( s, response, jqXHR, isSuccess );
</ins><span class="cx">
</span><del>- // If successful, handle type chaining
- if ( status >= 200 && status < 300 || status === 304 ) {
</del><ins>+ // If successful, handle type chaining
+ if ( isSuccess ) {
</ins><span class="cx">
</span><del>- // Set the If-Modified-Since and/or If-None-Match header, if in ifModified mode.
- if ( s.ifModified ) {
- modified = jqXHR.getResponseHeader("Last-Modified");
- if ( modified ) {
- jQuery.lastModified[ cacheURL ] = modified;
- }
- modified = jqXHR.getResponseHeader("etag");
- if ( modified ) {
- jQuery.etag[ cacheURL ] = modified;
- }
- }
</del><ins>+ // Set the If-Modified-Since and/or If-None-Match header, if in ifModified mode.
+ if ( s.ifModified ) {
+ modified = jqXHR.getResponseHeader("Last-Modified");
+ if ( modified ) {
+ jQuery.lastModified[ cacheURL ] = modified;
+ }
+ modified = jqXHR.getResponseHeader("etag");
+ if ( modified ) {
+ jQuery.etag[ cacheURL ] = modified;
+ }
+ }
</ins><span class="cx">
</span><del>- // if no content
- if ( status === 204 ) {
- isSuccess = true;
- statusText = "nocontent";
</del><ins>+ // if no content
+ if ( status === 204 || s.type === "HEAD" ) {
+ statusText = "nocontent";
</ins><span class="cx">
</span><del>- // if not modified
- } else if ( status === 304 ) {
- isSuccess = true;
- statusText = "notmodified";
</del><ins>+ // if not modified
+ } else if ( status === 304 ) {
+ statusText = "notmodified";
</ins><span class="cx">
</span><del>- // If we have data, let's convert it
- } else {
- isSuccess = ajaxConvert( s, response );
- statusText = isSuccess.state;
- success = isSuccess.data;
- error = isSuccess.error;
- isSuccess = !error;
- }
- } else {
- // We extract error from statusText
- // then normalize statusText and status for non-aborts
- error = statusText;
- if ( status || !statusText ) {
- statusText = "error";
- if ( status < 0 ) {
- status = 0;
- }
- }
- }
</del><ins>+ // If we have data, let's convert it
+ } else {
+ statusText = response.state;
+ success = response.data;
+ error = response.error;
+ isSuccess = !error;
+ }
+ } else {
+ // We extract error from statusText
+ // then normalize statusText and status for non-aborts
+ error = statusText;
+ if ( status || !statusText ) {
+ statusText = "error";
+ if ( status < 0 ) {
+ status = 0;
+ }
+ }
+ }
</ins><span class="cx">
</span><del>- // Set data for the fake xhr object
- jqXHR.status = status;
- jqXHR.statusText = ( nativeStatusText || statusText ) + "";
</del><ins>+ // Set data for the fake xhr object
+ jqXHR.status = status;
+ jqXHR.statusText = ( nativeStatusText || statusText ) + "";
</ins><span class="cx">
</span><del>- // Success/Error
- if ( isSuccess ) {
- deferred.resolveWith( callbackContext, [ success, statusText, jqXHR ] );
- } else {
- deferred.rejectWith( callbackContext, [ jqXHR, statusText, error ] );
- }
</del><ins>+ // Success/Error
+ if ( isSuccess ) {
+ deferred.resolveWith( callbackContext, [ success, statusText, jqXHR ] );
+ } else {
+ deferred.rejectWith( callbackContext, [ jqXHR, statusText, error ] );
+ }
</ins><span class="cx">
</span><del>- // Status-dependent callbacks
- jqXHR.statusCode( statusCode );
- statusCode = undefined;
</del><ins>+ // Status-dependent callbacks
+ jqXHR.statusCode( statusCode );
+ statusCode = undefined;
</ins><span class="cx">
</span><del>- if ( fireGlobals ) {
- globalEventContext.trigger( isSuccess ? "ajaxSuccess" : "ajaxError",
- [ jqXHR, s, isSuccess ? success : error ] );
- }
</del><ins>+ if ( fireGlobals ) {
+ globalEventContext.trigger( isSuccess ? "ajaxSuccess" : "ajaxError",
+ [ jqXHR, s, isSuccess ? success : error ] );
+ }
</ins><span class="cx">
</span><del>- // Complete
- completeDeferred.fireWith( callbackContext, [ jqXHR, statusText ] );
</del><ins>+ // Complete
+ completeDeferred.fireWith( callbackContext, [ jqXHR, statusText ] );
</ins><span class="cx">
</span><del>- if ( fireGlobals ) {
- globalEventContext.trigger( "ajaxComplete", [ jqXHR, s ] );
- // Handle the global AJAX counter
- if ( !( --jQuery.active ) ) {
- jQuery.event.trigger("ajaxStop");
- }
- }
- }
</del><ins>+ if ( fireGlobals ) {
+ globalEventContext.trigger( "ajaxComplete", [ jqXHR, s ] );
+ // Handle the global AJAX counter
+ if ( !( --jQuery.active ) ) {
+ jQuery.event.trigger("ajaxStop");
+ }
+ }
+ }
</ins><span class="cx">
</span><del>- return jqXHR;
</del><ins>+ return jqXHR;
</ins><span class="cx"> },
</span><span class="cx">
</span><del>- getScript: function( url, callback ) {
- return jQuery.get( url, undefined, callback, "script" );
</del><ins>+ getJSON: function( url, data, callback ) {
+ return jQuery.get( url, data, callback, "json" );
</ins><span class="cx"> },
</span><span class="cx">
</span><del>- getJSON: function( url, data, callback ) {
- return jQuery.get( url, data, callback, "json" );
</del><ins>+ getScript: function( url, callback ) {
+ return jQuery.get( url, undefined, callback, "script" );
</ins><span class="cx"> }
</span><span class="cx"> });
</span><span class="cx">
</span><del>-/* Handles responses to an ajax request:
- * - sets all responseXXX fields accordingly
- * - finds the right dataType (mediates between content-type and expected dataType)
- * - returns the corresponding response
- */
-function ajaxHandleResponses( s, jqXHR, responses ) {
- var firstDataType, ct, finalDataType, type,
- contents = s.contents,
- dataTypes = s.dataTypes,
- responseFields = s.responseFields;
</del><ins>+jQuery.each( [ "get", "post" ], function( i, method ) {
+ jQuery[ method ] = function( url, data, callback, type ) {
+ // shift arguments if data argument was omitted
+ if ( jQuery.isFunction( data ) ) {
+ type = type || callback;
+ callback = data;
+ data = undefined;
+ }
</ins><span class="cx">
</span><del>- // Fill responseXXX fields
- for ( type in responseFields ) {
- if ( type in responses ) {
- jqXHR[ responseFields[type] ] = responses[ type ];
- }
- }
</del><ins>+ return jQuery.ajax({
+ url: url,
+ type: method,
+ dataType: type,
+ data: data,
+ success: callback
+ });
+ };
+});
</ins><span class="cx">
</span><del>- // Remove auto dataType and get content-type in the process
- while( dataTypes[ 0 ] === "*" ) {
- dataTypes.shift();
- if ( ct === undefined ) {
- ct = s.mimeType || jqXHR.getResponseHeader("Content-Type");
- }
- }
</del><ins>+// Attach a bunch of functions for handling common AJAX events
+jQuery.each( [ "ajaxStart", "ajaxStop", "ajaxComplete", "ajaxError", "ajaxSuccess", "ajaxSend" ], function( i, type ) {
+ jQuery.fn[ type ] = function( fn ) {
+ return this.on( type, fn );
+ };
+});
</ins><span class="cx">
</span><del>- // Check if we're dealing with a known content-type
- if ( ct ) {
- for ( type in contents ) {
- if ( contents[ type ] && contents[ type ].test( ct ) ) {
- dataTypes.unshift( type );
- break;
- }
- }
- }
</del><span class="cx">
</span><del>- // Check to see if we have a response for the expected dataType
- if ( dataTypes[ 0 ] in responses ) {
- finalDataType = dataTypes[ 0 ];
- } else {
- // Try convertible dataTypes
- for ( type in responses ) {
- if ( !dataTypes[ 0 ] || s.converters[ type + " " + dataTypes[0] ] ) {
- finalDataType = type;
- break;
- }
- if ( !firstDataType ) {
- firstDataType = type;
- }
- }
- // Or just use first one
- finalDataType = finalDataType || firstDataType;
- }
</del><ins>+jQuery._evalUrl = function( url ) {
+ return jQuery.ajax({
+ url: url,
+ type: "GET",
+ dataType: "script",
+ async: false,
+ global: false,
+ "throws": true
+ });
+};
</ins><span class="cx">
</span><del>- // If we found a dataType
- // We add the dataType to the list if needed
- // and return the corresponding response
- if ( finalDataType ) {
- if ( finalDataType !== dataTypes[ 0 ] ) {
- dataTypes.unshift( finalDataType );
- }
- return responses[ finalDataType ];
- }
-}
</del><span class="cx">
</span><del>-// Chain conversions given the request and the original response
-function ajaxConvert( s, response ) {
- var conv2, current, conv, tmp,
- converters = {},
- i = 0,
- // Work with a copy of dataTypes in case we need to modify it for conversion
- dataTypes = s.dataTypes.slice(),
- prev = dataTypes[ 0 ];
</del><ins>+jQuery.fn.extend({
+ wrapAll: function( html ) {
+ var wrap;
</ins><span class="cx">
</span><del>- // Apply the dataFilter if provided
- if ( s.dataFilter ) {
- response = s.dataFilter( response, s.dataType );
- }
</del><ins>+ if ( jQuery.isFunction( html ) ) {
+ return this.each(function( i ) {
+ jQuery( this ).wrapAll( html.call(this, i) );
+ });
+ }
</ins><span class="cx">
</span><del>- // Create converters map with lowercased keys
- if ( dataTypes[ 1 ] ) {
- for ( conv in s.converters ) {
- converters[ conv.toLowerCase() ] = s.converters[ conv ];
- }
- }
</del><ins>+ if ( this[ 0 ] ) {
</ins><span class="cx">
</span><del>- // Convert to each sequential dataType, tolerating list modification
- for ( ; (current = dataTypes[++i]); ) {
</del><ins>+ // The elements to wrap the target around
+ wrap = jQuery( html, this[ 0 ].ownerDocument ).eq( 0 ).clone( true );
</ins><span class="cx">
</span><del>- // There's only work to do if current dataType is non-auto
- if ( current !== "*" ) {
</del><ins>+ if ( this[ 0 ].parentNode ) {
+ wrap.insertBefore( this[ 0 ] );
+ }
</ins><span class="cx">
</span><del>- // Convert response if prev dataType is non-auto and differs from current
- if ( prev !== "*" && prev !== current ) {
</del><ins>+ wrap.map(function() {
+ var elem = this;
</ins><span class="cx">
</span><del>- // Seek a direct converter
- conv = converters[ prev + " " + current ] || converters[ "* " + current ];
</del><ins>+ while ( elem.firstElementChild ) {
+ elem = elem.firstElementChild;
+ }
</ins><span class="cx">
</span><del>- // If none found, seek a pair
- if ( !conv ) {
- for ( conv2 in converters ) {
</del><ins>+ return elem;
+ }).append( this );
+ }
</ins><span class="cx">
</span><del>- // If conv2 outputs current
- tmp = conv2.split(" ");
- if ( tmp[ 1 ] === current ) {
</del><ins>+ return this;
+ },
</ins><span class="cx">
</span><del>- // If prev can be converted to accepted input
- conv = converters[ prev + " " + tmp[ 0 ] ] ||
- converters[ "* " + tmp[ 0 ] ];
- if ( conv ) {
- // Condense equivalence converters
- if ( conv === true ) {
- conv = converters[ conv2 ];
</del><ins>+ wrapInner: function( html ) {
+ if ( jQuery.isFunction( html ) ) {
+ return this.each(function( i ) {
+ jQuery( this ).wrapInner( html.call(this, i) );
+ });
+ }
</ins><span class="cx">
</span><del>- // Otherwise, insert the intermediate dataType
- } else if ( converters[ conv2 ] !== true ) {
- current = tmp[ 0 ];
- dataTypes.splice( i--, 0, current );
- }
</del><ins>+ return this.each(function() {
+ var self = jQuery( this ),
+ contents = self.contents();
</ins><span class="cx">
</span><del>- break;
- }
- }
- }
- }
</del><ins>+ if ( contents.length ) {
+ contents.wrapAll( html );
</ins><span class="cx">
</span><del>- // Apply converter (if not an equivalence)
- if ( conv !== true ) {
</del><ins>+ } else {
+ self.append( html );
+ }
+ });
+ },
</ins><span class="cx">
</span><del>- // Unless errors are allowed to bubble, catch and return them
- if ( conv && s["throws"] ) {
- response = conv( response );
- } else {
- try {
- response = conv( response );
- } catch ( e ) {
- return { state: "parsererror", error: conv ? e : "No conversion from " + prev + " to " + current };
- }
- }
- }
- }
</del><ins>+ wrap: function( html ) {
+ var isFunction = jQuery.isFunction( html );
</ins><span class="cx">
</span><del>- // Update prev for next iteration
- prev = current;
- }
- }
-
- return { state: "success", data: response };
-}
-// Install script dataType
-jQuery.ajaxSetup({
- accepts: {
- script: "text/javascript, application/javascript, application/ecmascript, application/x-ecmascript"
</del><ins>+ return this.each(function( i ) {
+ jQuery( this ).wrapAll( isFunction ? html.call(this, i) : html );
+ });
</ins><span class="cx"> },
</span><del>- contents: {
- script: /(?:java|ecma)script/
- },
- converters: {
- "text script": function( text ) {
- jQuery.globalEval( text );
- return text;
- }
- }
-});
</del><span class="cx">
</span><del>-// Handle cache's special case and global
-jQuery.ajaxPrefilter( "script", function( s ) {
- if ( s.cache === undefined ) {
- s.cache = false;
</del><ins>+ unwrap: function() {
+ return this.parent().each(function() {
+ if ( !jQuery.nodeName( this, "body" ) ) {
+ jQuery( this ).replaceWith( this.childNodes );
+ }
+ }).end();
</ins><span class="cx"> }
</span><del>- if ( s.crossDomain ) {
- s.type = "GET";
- s.global = false;
- }
</del><span class="cx"> });
</span><span class="cx">
</span><del>-// Bind script tag hack transport
-jQuery.ajaxTransport( "script", function(s) {
</del><span class="cx">
</span><del>- // This transport only deals with cross domain requests
- if ( s.crossDomain ) {
</del><ins>+jQuery.expr.filters.hidden = function( elem ) {
+ // Support: Opera <= 12.12
+ // Opera reports offsetWidths and offsetHeights less than zero on some elements
+ return elem.offsetWidth <= 0 && elem.offsetHeight <= 0;
+};
+jQuery.expr.filters.visible = function( elem ) {
+ return !jQuery.expr.filters.hidden( elem );
+};
</ins><span class="cx">
</span><del>- var script,
- head = document.head || jQuery("head")[0] || document.documentElement;
</del><span class="cx">
</span><del>- return {
</del><span class="cx">
</span><del>- send: function( _, callback ) {
</del><span class="cx">
</span><del>- script = document.createElement("script");
</del><ins>+var r20 = /%20/g,
+ rbracket = /\[\]$/,
+ rCRLF = /\r?\n/g,
+ rsubmitterTypes = /^(?:submit|button|image|reset|file)$/i,
+ rsubmittable = /^(?:input|select|textarea|keygen)/i;
</ins><span class="cx">
</span><del>- script.async = true;
</del><ins>+function buildParams( prefix, obj, traditional, add ) {
+ var name;
</ins><span class="cx">
</span><del>- if ( s.scriptCharset ) {
- script.charset = s.scriptCharset;
- }
</del><ins>+ if ( jQuery.isArray( obj ) ) {
+ // Serialize array item.
+ jQuery.each( obj, function( i, v ) {
+ if ( traditional || rbracket.test( prefix ) ) {
+ // Treat each array item as a scalar.
+ add( prefix, v );
</ins><span class="cx">
</span><del>- script.src = s.url;
</del><ins>+ } else {
+ // Item is non-scalar (array or object), encode its numeric index.
+ buildParams( prefix + "[" + ( typeof v === "object" ? i : "" ) + "]", v, traditional, add );
+ }
+ });
</ins><span class="cx">
</span><del>- // Attach handlers for all browsers
- script.onload = script.onreadystatechange = function( _, isAbort ) {
</del><ins>+ } else if ( !traditional && jQuery.type( obj ) === "object" ) {
+ // Serialize object item.
+ for ( name in obj ) {
+ buildParams( prefix + "[" + name + "]", obj[ name ], traditional, add );
+ }
</ins><span class="cx">
</span><del>- if ( isAbort || !script.readyState || /loaded|complete/.test( script.readyState ) ) {
-
- // Handle memory leak in IE
- script.onload = script.onreadystatechange = null;
-
- // Remove the script
- if ( script.parentNode ) {
- script.parentNode.removeChild( script );
</del><ins>+ } else {
+ // Serialize scalar item.
+ add( prefix, obj );
</ins><span class="cx"> }
</span><ins>+}
</ins><span class="cx">
</span><del>- // Dereference the script
- script = null;
</del><ins>+// Serialize an array of form elements or a set of
+// key/values into a query string
+jQuery.param = function( a, traditional ) {
+ var prefix,
+ s = [],
+ add = function( key, value ) {
+ // If value is a function, invoke it and return its value
+ value = jQuery.isFunction( value ) ? value() : ( value == null ? "" : value );
+ s[ s.length ] = encodeURIComponent( key ) + "=" + encodeURIComponent( value );
+ };
</ins><span class="cx">
</span><del>- // Callback if not abort
- if ( !isAbort ) {
- callback( 200, "success" );
</del><ins>+ // Set traditional to true for jQuery <= 1.3.2 behavior.
+ if ( traditional === undefined ) {
+ traditional = jQuery.ajaxSettings && jQuery.ajaxSettings.traditional;
</ins><span class="cx"> }
</span><del>- }
- };
</del><span class="cx">
</span><del>- // Circumvent IE6 bugs with base elements (#2709 and #4378) by prepending
- // Use native DOM manipulation to avoid our domManip AJAX trickery
- head.insertBefore( script, head.firstChild );
- },
</del><ins>+ // If an array was passed in, assume that it is an array of form elements.
+ if ( jQuery.isArray( a ) || ( a.jquery && !jQuery.isPlainObject( a ) ) ) {
+ // Serialize the form elements
+ jQuery.each( a, function() {
+ add( this.name, this.value );
+ });
</ins><span class="cx">
</span><del>- abort: function() {
- if ( script ) {
- script.onload( undefined, true );
</del><ins>+ } else {
+ // If traditional, encode the "old" way (the way 1.3.2 or older
+ // did it), otherwise encode params recursively.
+ for ( prefix in a ) {
+ buildParams( prefix, a[ prefix ], traditional, add );
+ }
</ins><span class="cx"> }
</span><del>- }
- };
- }
-});
-var oldCallbacks = [],
- rjsonp = /(=)\?(?=&|$)|\?\?/;
</del><span class="cx">
</span><del>-// Default jsonp settings
-jQuery.ajaxSetup({
- jsonp: "callback",
- jsonpCallback: function() {
- var callback = oldCallbacks.pop() || ( jQuery.expando + "_" + ( ajax_nonce++ ) );
- this[ callback ] = true;
- return callback;
- }
-});
</del><ins>+ // Return the resulting serialization
+ return s.join( "&" ).replace( r20, "+" );
+};
</ins><span class="cx">
</span><del>-// Detect, normalize options and install callbacks for jsonp requests
-jQuery.ajaxPrefilter( "json jsonp", function( s, originalSettings, jqXHR ) {
</del><ins>+jQuery.fn.extend({
+ serialize: function() {
+ return jQuery.param( this.serializeArray() );
+ },
+ serializeArray: function() {
+ return this.map(function() {
+ // Can add propHook for "elements" to filter or add form elements
+ var elements = jQuery.prop( this, "elements" );
+ return elements ? jQuery.makeArray( elements ) : this;
+ })
+ .filter(function() {
+ var type = this.type;
</ins><span class="cx">
</span><del>- var callbackName, overwritten, responseContainer,
- jsonProp = s.jsonp !== false && ( rjsonp.test( s.url ) ?
- "url" :
- typeof s.data === "string" && !( s.contentType || "" ).indexOf("application/x-www-form-urlencoded") && rjsonp.test( s.data ) && "data"
- );
</del><ins>+ // Use .is( ":disabled" ) so that fieldset[disabled] works
+ return this.name && !jQuery( this ).is( ":disabled" ) &&
+ rsubmittable.test( this.nodeName ) && !rsubmitterTypes.test( type ) &&
+ ( this.checked || !rcheckableType.test( type ) );
+ })
+ .map(function( i, elem ) {
+ var val = jQuery( this ).val();
</ins><span class="cx">
</span><del>- // Handle iff the expected data type is "jsonp" or we have a parameter to set
- if ( jsonProp || s.dataTypes[ 0 ] === "jsonp" ) {
-
- // Get callback name, remembering preexisting value associated with it
- callbackName = s.jsonpCallback = jQuery.isFunction( s.jsonpCallback ) ?
- s.jsonpCallback() :
- s.jsonpCallback;
-
- // Insert callback into url or form data
- if ( jsonProp ) {
- s[ jsonProp ] = s[ jsonProp ].replace( rjsonp, "$1" + callbackName );
- } else if ( s.jsonp !== false ) {
- s.url += ( ajax_rquery.test( s.url ) ? "&" : "?" ) + s.jsonp + "=" + callbackName;
</del><ins>+ return val == null ?
+ null :
+ jQuery.isArray( val ) ?
+ jQuery.map( val, function( val ) {
+ return { name: elem.name, value: val.replace( rCRLF, "\r\n" ) };
+ }) :
+ { name: elem.name, value: val.replace( rCRLF, "\r\n" ) };
+ }).get();
</ins><span class="cx"> }
</span><del>-
- // Use data converter to retrieve json after script execution
- s.converters["script json"] = function() {
- if ( !responseContainer ) {
- jQuery.error( callbackName + " was not called" );
- }
- return responseContainer[ 0 ];
- };
-
- // force json dataType
- s.dataTypes[ 0 ] = "json";
-
- // Install callback
- overwritten = window[ callbackName ];
- window[ callbackName ] = function() {
- responseContainer = arguments;
- };
-
- // Clean-up function (fires after converters)
- jqXHR.always(function() {
- // Restore preexisting value
- window[ callbackName ] = overwritten;
-
- // Save back as free
- if ( s[ callbackName ] ) {
- // make sure that re-using the options doesn't screw things around
- s.jsonpCallback = originalSettings.jsonpCallback;
-
- // save the callback name for future use
- oldCallbacks.push( callbackName );
- }
-
- // Call if it was a function and we have a response
- if ( responseContainer && jQuery.isFunction( overwritten ) ) {
- overwritten( responseContainer[ 0 ] );
- }
-
- responseContainer = overwritten = undefined;
- });
-
- // Delegate to script
- return "script";
- }
</del><span class="cx"> });
</span><del>-var xhrCallbacks, xhrSupported,
- xhrId = 0,
- // #5280: Internet Explorer will keep connections alive if we don't abort on unload
- xhrOnUnloadAbort = window.ActiveXObject && function() {
- // Abort all pending requests
- var key;
- for ( key in xhrCallbacks ) {
- xhrCallbacks[ key ]( undefined, true );
- }
- };
</del><span class="cx">
</span><del>-// Functions to create xhrs
-function createStandardXHR() {
- try {
- return new window.XMLHttpRequest();
- } catch( e ) {}
-}
</del><span class="cx">
</span><del>-function createActiveXHR() {
</del><ins>+jQuery.ajaxSettings.xhr = function() {
</ins><span class="cx"> try {
</span><del>- return new window.ActiveXObject("Microsoft.XMLHTTP");
</del><ins>+ return new XMLHttpRequest();
</ins><span class="cx"> } catch( e ) {}
</span><del>-}
</del><ins>+};
</ins><span class="cx">
</span><del>-// Create the request object
-// (This is still attached to ajaxSettings for backward compatibility)
-jQuery.ajaxSettings.xhr = window.ActiveXObject ?
- /* Microsoft failed to properly
- * implement the XMLHttpRequest in IE7 (can't request local files),
- * so we use the ActiveXObject when it is available
- * Additionally XMLHttpRequest can be disabled in IE7/IE8 so
- * we need a fallback.
- */
- function() {
- return !this.isLocal && createStandardXHR() || createActiveXHR();
- } :
- // For all other browsers, use the standard XMLHttpRequest object
- createStandardXHR;
</del><ins>+var xhrId = 0,
+ xhrCallbacks = {},
+ xhrSuccessStatus = {
+ // file protocol always yields status code 0, assume 200
+ 0: 200,
+ // Support: IE9
+ // #1450: sometimes IE returns 1223 when it should be 204
+ 1223: 204
+ },
+ xhrSupported = jQuery.ajaxSettings.xhr();
</ins><span class="cx">
</span><del>-// Determine support properties
-xhrSupported = jQuery.ajaxSettings.xhr();
-jQuery.support.cors = !!xhrSupported && ( "withCredentials" in xhrSupported );
-xhrSupported = jQuery.support.ajax = !!xhrSupported;
</del><ins>+// Support: IE9
+// Open requests must be manually aborted on unload (#5280)
+if ( window.ActiveXObject ) {
+ jQuery( window ).on( "unload", function() {
+ for ( var key in xhrCallbacks ) {
+ xhrCallbacks[ key ]();
+ }
+ });
+}
</ins><span class="cx">
</span><del>-// Create transport if the browser can provide an xhr
-if ( xhrSupported ) {
</del><ins>+support.cors = !!xhrSupported && ( "withCredentials" in xhrSupported );
+support.ajax = xhrSupported = !!xhrSupported;
</ins><span class="cx">
</span><del>- jQuery.ajaxTransport(function( s ) {
- // Cross domain only allowed if supported through XMLHttpRequest
- if ( !s.crossDomain || jQuery.support.cors ) {
-
</del><ins>+jQuery.ajaxTransport(function( options ) {
</ins><span class="cx"> var callback;
</span><span class="cx">
</span><del>- return {
- send: function( headers, complete ) {
</del><ins>+ // Cross domain only allowed if supported through XMLHttpRequest
+ if ( support.cors || xhrSupported && !options.crossDomain ) {
+ return {
+ send: function( headers, complete ) {
+ var i,
+ xhr = options.xhr(),
+ id = ++xhrId;
</ins><span class="cx">
</span><del>- // Get a new xhr
- var handle, i,
- xhr = s.xhr();
</del><ins>+ xhr.open( options.type, options.url, options.async, options.username, options.password );
</ins><span class="cx">
</span><del>- // Open the socket
- // Passing null username, generates a login popup on Opera (#2865)
- if ( s.username ) {
- xhr.open( s.type, s.url, s.async, s.username, s.password );
- } else {
- xhr.open( s.type, s.url, s.async );
- }
</del><ins>+ // Apply custom fields if provided
+ if ( options.xhrFields ) {
+ for ( i in options.xhrFields ) {
+ xhr[ i ] = options.xhrFields[ i ];
+ }
+ }
</ins><span class="cx">
</span><del>- // Apply custom fields if provided
- if ( s.xhrFields ) {
- for ( i in s.xhrFields ) {
- xhr[ i ] = s.xhrFields[ i ];
- }
- }
</del><ins>+ // Override mime type if needed
+ if ( options.mimeType && xhr.overrideMimeType ) {
+ xhr.overrideMimeType( options.mimeType );
+ }
</ins><span class="cx">
</span><del>- // Override mime type if needed
- if ( s.mimeType && xhr.overrideMimeType ) {
- xhr.overrideMimeType( s.mimeType );
- }
</del><ins>+ // X-Requested-With header
+ // For cross-domain requests, seeing as conditions for a preflight are
+ // akin to a jigsaw puzzle, we simply never set it to be sure.
+ // (it can always be set on a per-request basis or even using ajaxSetup)
+ // For same-domain requests, won't change header if already provided.
+ if ( !options.crossDomain && !headers["X-Requested-With"] ) {
+ headers["X-Requested-With"] = "XMLHttpRequest";
+ }
</ins><span class="cx">
</span><del>- // X-Requested-With header
- // For cross-domain requests, seeing as conditions for a preflight are
- // akin to a jigsaw puzzle, we simply never set it to be sure.
- // (it can always be set on a per-request basis or even using ajaxSetup)
- // For same-domain requests, won't change header if already provided.
- if ( !s.crossDomain && !headers["X-Requested-With"] ) {
- headers["X-Requested-With"] = "XMLHttpRequest";
- }
</del><ins>+ // Set headers
+ for ( i in headers ) {
+ xhr.setRequestHeader( i, headers[ i ] );
+ }
</ins><span class="cx">
</span><del>- // Need an extra try/catch for cross domain requests in Firefox 3
- try {
- for ( i in headers ) {
- xhr.setRequestHeader( i, headers[ i ] );
- }
- } catch( err ) {}
</del><ins>+ // Callback
+ callback = function( type ) {
+ return function() {
+ if ( callback ) {
+ delete xhrCallbacks[ id ];
+ callback = xhr.onload = xhr.onerror = null;
</ins><span class="cx">
</span><del>- // Do send the request
- // This may raise an exception which is actually
- // handled in jQuery.ajax (so no try/catch here)
- xhr.send( ( s.hasContent && s.data ) || null );
</del><ins>+ if ( type === "abort" ) {
+ xhr.abort();
+ } else if ( type === "error" ) {
+ complete(
+ // file: protocol always yields status 0; see #8605, #14207
+ xhr.status,
+ xhr.statusText
+ );
+ } else {
+ complete(
+ xhrSuccessStatus[ xhr.status ] || xhr.status,
+ xhr.statusText,
+ // Support: IE9
+ // Accessing binary-data responseText throws an exception
+ // (#11426)
+ typeof xhr.responseText === "string" ? {
+ text: xhr.responseText
+ } : undefined,
+ xhr.getAllResponseHeaders()
+ );
+ }
+ }
+ };
+ };
</ins><span class="cx">
</span><del>- // Listener
- callback = function( _, isAbort ) {
- var status, responseHeaders, statusText, responses;
</del><ins>+ // Listen to events
+ xhr.onload = callback();
+ xhr.onerror = callback("error");
</ins><span class="cx">
</span><del>- // Firefox throws exceptions when accessing properties
- // of an xhr when a network error occurred
- // http://helpful.knobs-dials.com/index.php/Component_returned_failure_code:_0x80040111_(NS_ERROR_NOT_AVAILABLE)
- try {
</del><ins>+ // Create the abort callback
+ callback = xhrCallbacks[ id ] = callback("abort");
</ins><span class="cx">
</span><del>- // Was never called and is aborted or complete
- if ( callback && ( isAbort || xhr.readyState === 4 ) ) {
</del><ins>+ // Do send the request
+ // This may raise an exception which is actually
+ // handled in jQuery.ajax (so no try/catch here)
+ xhr.send( options.hasContent && options.data || null );
+ },
</ins><span class="cx">
</span><del>- // Only called once
- callback = undefined;
-
- // Do not keep as active anymore
- if ( handle ) {
- xhr.onreadystatechange = jQuery.noop;
- if ( xhrOnUnloadAbort ) {
- delete xhrCallbacks[ handle ];
</del><ins>+ abort: function() {
+ if ( callback ) {
+ callback();
+ }
+ }
+ };
</ins><span class="cx"> }
</span><del>- }
</del><ins>+});
</ins><span class="cx">
</span><del>- // If it's an abort
- if ( isAbort ) {
- // Abort it manually if needed
- if ( xhr.readyState !== 4 ) {
- xhr.abort();
- }
- } else {
- responses = {};
- status = xhr.status;
- responseHeaders = xhr.getAllResponseHeaders();
</del><span class="cx">
</span><del>- // When requesting binary data, IE6-9 will throw an exception
- // on any attempt to access responseText (#11426)
- if ( typeof xhr.responseText === "string" ) {
- responses.text = xhr.responseText;
- }
</del><span class="cx">
</span><del>- // Firefox throws an exception when accessing
- // statusText for faulty cross-domain requests
- try {
- statusText = xhr.statusText;
- } catch( e ) {
- // We normalize with Webkit giving an empty statusText
- statusText = "";
- }
</del><span class="cx">
</span><del>- // Filter status for non standard behaviors
-
- // If the request is local and we have data: assume a success
- // (success with no data won't get notified, that's the best we
- // can do given current implementations)
- if ( !status && s.isLocal && !s.crossDomain ) {
- status = responses.text ? 200 : 404;
- // IE - #1450: sometimes returns 1223 when it should be 204
- } else if ( status === 1223 ) {
- status = 204;
</del><ins>+// Install script dataType
+jQuery.ajaxSetup({
+ accepts: {
+ script: "text/javascript, application/javascript, application/ecmascript, application/x-ecmascript"
+ },
+ contents: {
+ script: /(?:java|ecma)script/
+ },
+ converters: {
+ "text script": function( text ) {
+ jQuery.globalEval( text );
+ return text;
+ }
</ins><span class="cx"> }
</span><del>- }
- }
- } catch( firefoxAccessException ) {
- if ( !isAbort ) {
- complete( -1, firefoxAccessException );
- }
- }
</del><ins>+});
</ins><span class="cx">
</span><del>- // Call complete if needed
- if ( responses ) {
- complete( status, statusText, responses, responseHeaders );
</del><ins>+// Handle cache's special case and crossDomain
+jQuery.ajaxPrefilter( "script", function( s ) {
+ if ( s.cache === undefined ) {
+ s.cache = false;
</ins><span class="cx"> }
</span><del>- };
-
- if ( !s.async ) {
- // if we're in sync mode we fire the callback
- callback();
- } else if ( xhr.readyState === 4 ) {
- // (IE6 & IE7) if it's in cache and has been
- // retrieved directly we need to fire the callback
- setTimeout( callback );
- } else {
- handle = ++xhrId;
- if ( xhrOnUnloadAbort ) {
- // Create the active xhrs callbacks list if needed
- // and attach the unload handler
- if ( !xhrCallbacks ) {
- xhrCallbacks = {};
- jQuery( window ).unload( xhrOnUnloadAbort );
</del><ins>+ if ( s.crossDomain ) {
+ s.type = "GET";
</ins><span class="cx"> }
</span><del>- // Add to list of active xhrs callbacks
- xhrCallbacks[ handle ] = callback;
- }
- xhr.onreadystatechange = callback;
- }
- },
</del><ins>+});
</ins><span class="cx">
</span><del>- abort: function() {
- if ( callback ) {
- callback( undefined, true );
</del><ins>+// Bind script tag hack transport
+jQuery.ajaxTransport( "script", function( s ) {
+ // This transport only deals with cross domain requests
+ if ( s.crossDomain ) {
+ var script, callback;
+ return {
+ send: function( _, complete ) {
+ script = jQuery("<script>").prop({
+ async: true,
+ charset: s.scriptCharset,
+ src: s.url
+ }).on(
+ "load error",
+ callback = function( evt ) {
+ script.remove();
+ callback = null;
+ if ( evt ) {
+ complete( evt.type === "error" ? 404 : 200, evt.type );
+ }
+ }
+ );
+ document.head.appendChild( script[ 0 ] );
+ },
+ abort: function() {
+ if ( callback ) {
+ callback();
+ }
+ }
+ };
</ins><span class="cx"> }
</span><del>- }
- };
- }
- });
-}
-var fxNow, timerId,
- rfxtypes = /^(?:toggle|show|hide)$/,
- rfxnum = new RegExp( "^(?:([+-])=|)(" + core_pnum + ")([a-z%]*)$", "i" ),
- rrun = /queueHooks$/,
- animationPrefilters = [ defaultPrefilter ],
- tweeners = {
- "*": [function( prop, value ) {
- var end, unit,
- tween = this.createTween( prop, value ),
- parts = rfxnum.exec( value ),
- target = tween.cur(),
- start = +target || 0,
- scale = 1,
- maxIterations = 20;
</del><ins>+});
</ins><span class="cx">
</span><del>- if ( parts ) {
- end = +parts[2];
- unit = parts[3] || ( jQuery.cssNumber[ prop ] ? "" : "px" );
</del><span class="cx">
</span><del>- // We need to compute starting value
- if ( unit !== "px" && start ) {
- // Iteratively approximate from a nonzero starting point
- // Prefer the current property, because this process will be trivial if it uses the same units
- // Fallback to end or a simple constant
- start = jQuery.css( tween.elem, prop, true ) || end || 1;
</del><span class="cx">
</span><del>- do {
- // If previous iteration zeroed out, double until we get *something*
- // Use a string for doubling factor so we don't accidentally see scale as unchanged below
- scale = scale || ".5";
</del><span class="cx">
</span><del>- // Adjust and apply
- start = start / scale;
- jQuery.style( tween.elem, prop, start + unit );
</del><ins>+var oldCallbacks = [],
+ rjsonp = /(=)\?(?=&|$)|\?\?/;
</ins><span class="cx">
</span><del>- // Update scale, tolerating zero or NaN from tween.cur()
- // And breaking the loop if scale is unchanged or perfect, or if we've just had enough
- } while ( scale !== (scale = tween.cur() / target) && scale !== 1 && --maxIterations );
</del><ins>+// Default jsonp settings
+jQuery.ajaxSetup({
+ jsonp: "callback",
+ jsonpCallback: function() {
+ var callback = oldCallbacks.pop() || ( jQuery.expando + "_" + ( nonce++ ) );
+ this[ callback ] = true;
+ return callback;
</ins><span class="cx"> }
</span><ins>+});
</ins><span class="cx">
</span><del>- tween.unit = unit;
- tween.start = start;
- // If a +=/-= token was provided, we're doing a relative animation
- tween.end = parts[1] ? start + ( parts[1] + 1 ) * end : end;
- }
- return tween;
- }]
- };
</del><ins>+// Detect, normalize options and install callbacks for jsonp requests
+jQuery.ajaxPrefilter( "json jsonp", function( s, originalSettings, jqXHR ) {
</ins><span class="cx">
</span><del>-// Animations created synchronously will run synchronously
-function createFxNow() {
- setTimeout(function() {
- fxNow = undefined;
- });
- return ( fxNow = jQuery.now() );
-}
</del><ins>+ var callbackName, overwritten, responseContainer,
+ jsonProp = s.jsonp !== false && ( rjsonp.test( s.url ) ?
+ "url" :
+ typeof s.data === "string" && !( s.contentType || "" ).indexOf("application/x-www-form-urlencoded") && rjsonp.test( s.data ) && "data"
+ );
</ins><span class="cx">
</span><del>-function createTweens( animation, props ) {
- jQuery.each( props, function( prop, value ) {
- var collection = ( tweeners[ prop ] || [] ).concat( tweeners[ "*" ] ),
- index = 0,
- length = collection.length;
- for ( ; index < length; index++ ) {
- if ( collection[ index ].call( animation, prop, value ) ) {
</del><ins>+ // Handle iff the expected data type is "jsonp" or we have a parameter to set
+ if ( jsonProp || s.dataTypes[ 0 ] === "jsonp" ) {
</ins><span class="cx">
</span><del>- // we're done with this property
- return;
- }
- }
- });
-}
</del><ins>+ // Get callback name, remembering preexisting value associated with it
+ callbackName = s.jsonpCallback = jQuery.isFunction( s.jsonpCallback ) ?
+ s.jsonpCallback() :
+ s.jsonpCallback;
</ins><span class="cx">
</span><del>-function Animation( elem, properties, options ) {
- var result,
- stopped,
- index = 0,
- length = animationPrefilters.length,
- deferred = jQuery.Deferred().always( function() {
- // don't match elem in the :animated selector
- delete tick.elem;
- }),
- tick = function() {
- if ( stopped ) {
- return false;
- }
- var currentTime = fxNow || createFxNow(),
- remaining = Math.max( 0, animation.startTime + animation.duration - currentTime ),
- // archaic crash bug won't allow us to use 1 - ( 0.5 || 0 ) (#12497)
- temp = remaining / animation.duration || 0,
- percent = 1 - temp,
- index = 0,
- length = animation.tweens.length;
</del><ins>+ // Insert callback into url or form data
+ if ( jsonProp ) {
+ s[ jsonProp ] = s[ jsonProp ].replace( rjsonp, "$1" + callbackName );
+ } else if ( s.jsonp !== false ) {
+ s.url += ( rquery.test( s.url ) ? "&" : "?" ) + s.jsonp + "=" + callbackName;
+ }
</ins><span class="cx">
</span><del>- for ( ; index < length ; index++ ) {
- animation.tweens[ index ].run( percent );
- }
</del><ins>+ // Use data converter to retrieve json after script execution
+ s.converters["script json"] = function() {
+ if ( !responseContainer ) {
+ jQuery.error( callbackName + " was not called" );
+ }
+ return responseContainer[ 0 ];
+ };
</ins><span class="cx">
</span><del>- deferred.notifyWith( elem, [ animation, percent, remaining ]);
</del><ins>+ // force json dataType
+ s.dataTypes[ 0 ] = "json";
</ins><span class="cx">
</span><del>- if ( percent < 1 && length ) {
- return remaining;
- } else {
- deferred.resolveWith( elem, [ animation ] );
- return false;
- }
- },
- animation = deferred.promise({
- elem: elem,
- props: jQuery.extend( {}, properties ),
- opts: jQuery.extend( true, { specialEasing: {} }, options ),
- originalProperties: properties,
- originalOptions: options,
- startTime: fxNow || createFxNow(),
- duration: options.duration,
- tweens: [],
- createTween: function( prop, end ) {
- var tween = jQuery.Tween( elem, animation.opts, prop, end,
- animation.opts.specialEasing[ prop ] || animation.opts.easing );
- animation.tweens.push( tween );
- return tween;
- },
- stop: function( gotoEnd ) {
- var index = 0,
- // if we are going to the end, we want to run all the tweens
- // otherwise we skip this part
- length = gotoEnd ? animation.tweens.length : 0;
- if ( stopped ) {
- return this;
- }
- stopped = true;
- for ( ; index < length ; index++ ) {
- animation.tweens[ index ].run( 1 );
- }
</del><ins>+ // Install callback
+ overwritten = window[ callbackName ];
+ window[ callbackName ] = function() {
+ responseContainer = arguments;
+ };
</ins><span class="cx">
</span><del>- // resolve when we played the last frame
- // otherwise, reject
- if ( gotoEnd ) {
- deferred.resolveWith( elem, [ animation, gotoEnd ] );
- } else {
- deferred.rejectWith( elem, [ animation, gotoEnd ] );
- }
- return this;
- }
- }),
- props = animation.props;
</del><ins>+ // Clean-up function (fires after converters)
+ jqXHR.always(function() {
+ // Restore preexisting value
+ window[ callbackName ] = overwritten;
</ins><span class="cx">
</span><del>- propFilter( props, animation.opts.specialEasing );
</del><ins>+ // Save back as free
+ if ( s[ callbackName ] ) {
+ // make sure that re-using the options doesn't screw things around
+ s.jsonpCallback = originalSettings.jsonpCallback;
</ins><span class="cx">
</span><del>- for ( ; index < length ; index++ ) {
- result = animationPrefilters[ index ].call( animation, elem, props, animation.opts );
- if ( result ) {
- return result;
- }
- }
</del><ins>+ // save the callback name for future use
+ oldCallbacks.push( callbackName );
+ }
</ins><span class="cx">
</span><del>- createTweens( animation, props );
</del><ins>+ // Call if it was a function and we have a response
+ if ( responseContainer && jQuery.isFunction( overwritten ) ) {
+ overwritten( responseContainer[ 0 ] );
+ }
</ins><span class="cx">
</span><del>- if ( jQuery.isFunction( animation.opts.start ) ) {
- animation.opts.start.call( elem, animation );
</del><ins>+ responseContainer = overwritten = undefined;
+ });
+
+ // Delegate to script
+ return "script";
</ins><span class="cx"> }
</span><ins>+});
</ins><span class="cx">
</span><del>- jQuery.fx.timer(
- jQuery.extend( tick, {
- elem: elem,
- anim: animation,
- queue: animation.opts.queue
- })
- );
</del><span class="cx">
</span><del>- // attach callbacks from options
- return animation.progress( animation.opts.progress )
- .done( animation.opts.done, animation.opts.complete )
- .fail( animation.opts.fail )
- .always( animation.opts.always );
-}
</del><span class="cx">
</span><del>-function propFilter( props, specialEasing ) {
- var value, name, index, easing, hooks;
</del><span class="cx">
</span><del>- // camelCase, specialEasing and expand cssHook pass
- for ( index in props ) {
- name = jQuery.camelCase( index );
- easing = specialEasing[ name ];
- value = props[ index ];
- if ( jQuery.isArray( value ) ) {
- easing = value[ 1 ];
- value = props[ index ] = value[ 0 ];
</del><ins>+// data: string of html
+// context (optional): If specified, the fragment will be created in this context, defaults to document
+// keepScripts (optional): If true, will include scripts passed in the html string
+jQuery.parseHTML = function( data, context, keepScripts ) {
+ if ( !data || typeof data !== "string" ) {
+ return null;
</ins><span class="cx"> }
</span><del>-
- if ( index !== name ) {
- props[ name ] = value;
- delete props[ index ];
</del><ins>+ if ( typeof context === "boolean" ) {
+ keepScripts = context;
+ context = false;
</ins><span class="cx"> }
</span><ins>+ context = context || document;
</ins><span class="cx">
</span><del>- hooks = jQuery.cssHooks[ name ];
- if ( hooks && "expand" in hooks ) {
- value = hooks.expand( value );
- delete props[ name ];
</del><ins>+ var parsed = rsingleTag.exec( data ),
+ scripts = !keepScripts && [];
</ins><span class="cx">
</span><del>- // not quite $.extend, this wont overwrite keys already present.
- // also - reusing 'index' from above because we have the correct "name"
- for ( index in value ) {
- if ( !( index in props ) ) {
- props[ index ] = value[ index ];
- specialEasing[ index ] = easing;
</del><ins>+ // Single tag
+ if ( parsed ) {
+ return [ context.createElement( parsed[1] ) ];
</ins><span class="cx"> }
</span><del>- }
- } else {
- specialEasing[ name ] = easing;
- }
- }
-}
</del><span class="cx">
</span><del>-jQuery.Animation = jQuery.extend( Animation, {
</del><ins>+ parsed = jQuery.buildFragment( [ data ], context, scripts );
</ins><span class="cx">
</span><del>- tweener: function( props, callback ) {
- if ( jQuery.isFunction( props ) ) {
- callback = props;
- props = [ "*" ];
- } else {
- props = props.split(" ");
</del><ins>+ if ( scripts && scripts.length ) {
+ jQuery( scripts ).remove();
</ins><span class="cx"> }
</span><span class="cx">
</span><del>- var prop,
- index = 0,
- length = props.length;
</del><ins>+ return jQuery.merge( [], parsed.childNodes );
+};
</ins><span class="cx">
</span><del>- for ( ; index < length ; index++ ) {
- prop = props[ index ];
- tweeners[ prop ] = tweeners[ prop ] || [];
- tweeners[ prop ].unshift( callback );
- }
- },
</del><span class="cx">
</span><del>- prefilter: function( callback, prepend ) {
- if ( prepend ) {
- animationPrefilters.unshift( callback );
- } else {
- animationPrefilters.push( callback );
- }
- }
-});
</del><ins>+// Keep a copy of the old load method
+var _load = jQuery.fn.load;
</ins><span class="cx">
</span><del>-function defaultPrefilter( elem, props, opts ) {
- /*jshint validthis:true */
- var prop, index, length,
- value, dataShow, toggle,
- tween, hooks, oldfire,
- anim = this,
- style = elem.style,
- orig = {},
- handled = [],
- hidden = elem.nodeType && isHidden( elem );
-
- // handle queue: false promises
- if ( !opts.queue ) {
- hooks = jQuery._queueHooks( elem, "fx" );
- if ( hooks.unqueued == null ) {
- hooks.unqueued = 0;
- oldfire = hooks.empty.fire;
- hooks.empty.fire = function() {
- if ( !hooks.unqueued ) {
- oldfire();
</del><ins>+/**
+ * Load a url into a page
+ */
+jQuery.fn.load = function( url, params, callback ) {
+ if ( typeof url !== "string" && _load ) {
+ return _load.apply( this, arguments );
</ins><span class="cx"> }
</span><del>- };
- }
- hooks.unqueued++;
</del><span class="cx">
</span><del>- anim.always(function() {
- // doing this makes sure that the complete handler will be called
- // before this completes
- anim.always(function() {
- hooks.unqueued--;
- if ( !jQuery.queue( elem, "fx" ).length ) {
- hooks.empty.fire();
- }
- });
- });
- }
</del><ins>+ var selector, type, response,
+ self = this,
+ off = url.indexOf(" ");
</ins><span class="cx">
</span><del>- // height/width overflow pass
- if ( elem.nodeType === 1 && ( "height" in props || "width" in props ) ) {
- // Make sure that nothing sneaks out
- // Record all 3 overflow attributes because IE does not
- // change the overflow attribute when overflowX and
- // overflowY are set to the same value
- opts.overflow = [ style.overflow, style.overflowX, style.overflowY ];
-
- // Set display property to inline-block for height/width
- // animations on inline elements that are having width/height animated
- if ( jQuery.css( elem, "display" ) === "inline" &&
- jQuery.css( elem, "float" ) === "none" ) {
-
- // inline-level elements accept inline-block;
- // block-level elements need to be inline with layout
- if ( !jQuery.support.inlineBlockNeedsLayout || css_defaultDisplay( elem.nodeName ) === "inline" ) {
- style.display = "inline-block";
-
- } else {
- style.zoom = 1;
</del><ins>+ if ( off >= 0 ) {
+ selector = url.slice( off );
+ url = url.slice( 0, off );
</ins><span class="cx"> }
</span><del>- }
- }
</del><span class="cx">
</span><del>- if ( opts.overflow ) {
- style.overflow = "hidden";
- if ( !jQuery.support.shrinkWrapBlocks ) {
- anim.always(function() {
- style.overflow = opts.overflow[ 0 ];
- style.overflowX = opts.overflow[ 1 ];
- style.overflowY = opts.overflow[ 2 ];
- });
- }
- }
</del><ins>+ // If it's a function
+ if ( jQuery.isFunction( params ) ) {
</ins><span class="cx">
</span><ins>+ // We assume that it's the callback
+ callback = params;
+ params = undefined;
</ins><span class="cx">
</span><del>- // show/hide pass
- for ( index in props ) {
- value = props[ index ];
- if ( rfxtypes.exec( value ) ) {
- delete props[ index ];
- toggle = toggle || value === "toggle";
- if ( value === ( hidden ? "hide" : "show" ) ) {
- continue;
</del><ins>+ // Otherwise, build a param string
+ } else if ( params && typeof params === "object" ) {
+ type = "POST";
</ins><span class="cx"> }
</span><del>- handled.push( index );
- }
- }
</del><span class="cx">
</span><del>- length = handled.length;
- if ( length ) {
- dataShow = jQuery._data( elem, "fxshow" ) || jQuery._data( elem, "fxshow", {} );
- if ( "hidden" in dataShow ) {
- hidden = dataShow.hidden;
- }
</del><ins>+ // If we have elements to modify, make the request
+ if ( self.length > 0 ) {
+ jQuery.ajax({
+ url: url,
</ins><span class="cx">
</span><del>- // store state if its toggle - enables .stop().toggle() to "reverse"
- if ( toggle ) {
- dataShow.hidden = !hidden;
- }
- if ( hidden ) {
- jQuery( elem ).show();
- } else {
- anim.done(function() {
- jQuery( elem ).hide();
- });
- }
- anim.done(function() {
- var prop;
- jQuery._removeData( elem, "fxshow" );
- for ( prop in orig ) {
- jQuery.style( elem, prop, orig[ prop ] );
- }
- });
- for ( index = 0 ; index < length ; index++ ) {
- prop = handled[ index ];
- tween = anim.createTween( prop, hidden ? dataShow[ prop ] : 0 );
- orig[ prop ] = dataShow[ prop ] || jQuery.style( elem, prop );
</del><ins>+ // if "type" variable is undefined, then "GET" method will be used
+ type: type,
+ dataType: "html",
+ data: params
+ }).done(function( responseText ) {
</ins><span class="cx">
</span><del>- if ( !( prop in dataShow ) ) {
- dataShow[ prop ] = tween.start;
- if ( hidden ) {
- tween.end = tween.start;
- tween.start = prop === "width" || prop === "height" ? 1 : 0;
- }
- }
- }
- }
-}
</del><ins>+ // Save response for use in complete callback
+ response = arguments;
</ins><span class="cx">
</span><del>-function Tween( elem, options, prop, end, easing ) {
- return new Tween.prototype.init( elem, options, prop, end, easing );
-}
-jQuery.Tween = Tween;
</del><ins>+ self.html( selector ?
</ins><span class="cx">
</span><del>-Tween.prototype = {
- constructor: Tween,
- init: function( elem, options, prop, end, easing, unit ) {
- this.elem = elem;
- this.prop = prop;
- this.easing = easing || "swing";
- this.options = options;
- this.start = this.now = this.cur();
- this.end = end;
- this.unit = unit || ( jQuery.cssNumber[ prop ] ? "" : "px" );
- },
- cur: function() {
- var hooks = Tween.propHooks[ this.prop ];
</del><ins>+ // If a selector was specified, locate the right elements in a dummy div
+ // Exclude scripts to avoid IE 'Permission Denied' errors
+ jQuery("<div>").append( jQuery.parseHTML( responseText ) ).find( selector ) :
</ins><span class="cx">
</span><del>- return hooks && hooks.get ?
- hooks.get( this ) :
- Tween.propHooks._default.get( this );
- },
- run: function( percent ) {
- var eased,
- hooks = Tween.propHooks[ this.prop ];
</del><ins>+ // Otherwise use the full result
+ responseText );
</ins><span class="cx">
</span><del>- if ( this.options.duration ) {
- this.pos = eased = jQuery.easing[ this.easing ](
- percent, this.options.duration * percent, 0, 1, this.options.duration
- );
- } else {
- this.pos = eased = percent;
</del><ins>+ }).complete( callback && function( jqXHR, status ) {
+ self.each( callback, response || [ jqXHR.responseText, status, jqXHR ] );
+ });
</ins><span class="cx"> }
</span><del>- this.now = ( this.end - this.start ) * eased + this.start;
</del><span class="cx">
</span><del>- if ( this.options.step ) {
- this.options.step.call( this.elem, this.now, this );
- }
-
- if ( hooks && hooks.set ) {
- hooks.set( this );
- } else {
- Tween.propHooks._default.set( this );
- }
</del><span class="cx"> return this;
</span><del>- }
</del><span class="cx"> };
</span><span class="cx">
</span><del>-Tween.prototype.init.prototype = Tween.prototype;
</del><span class="cx">
</span><del>-Tween.propHooks = {
- _default: {
- get: function( tween ) {
- var result;
</del><span class="cx">
</span><del>- if ( tween.elem[ tween.prop ] != null &&
- (!tween.elem.style || tween.elem.style[ tween.prop ] == null) ) {
- return tween.elem[ tween.prop ];
- }
</del><span class="cx">
</span><del>- // passing an empty string as a 3rd parameter to .css will automatically
- // attempt a parseFloat and fallback to a string if the parse fails
- // so, simple values such as "10px" are parsed to Float.
- // complex values such as "rotate(1rad)" are returned as is.
- result = jQuery.css( tween.elem, tween.prop, "" );
- // Empty strings, null, undefined and "auto" are converted to 0.
- return !result || result === "auto" ? 0 : result;
- },
- set: function( tween ) {
- // use step hook for back compat - use cssHook if its there - use .style if its
- // available and use plain properties where available
- if ( jQuery.fx.step[ tween.prop ] ) {
- jQuery.fx.step[ tween.prop ]( tween );
- } else if ( tween.elem.style && ( tween.elem.style[ jQuery.cssProps[ tween.prop ] ] != null || jQuery.cssHooks[ tween.prop ] ) ) {
- jQuery.style( tween.elem, tween.prop, tween.now + tween.unit );
- } else {
- tween.elem[ tween.prop ] = tween.now;
- }
- }
- }
</del><ins>+jQuery.expr.filters.animated = function( elem ) {
+ return jQuery.grep(jQuery.timers, function( fn ) {
+ return elem === fn.elem;
+ }).length;
</ins><span class="cx"> };
</span><span class="cx">
</span><del>-// Remove in 2.0 - this supports IE8's panic based approach
-// to setting things on disconnected nodes
</del><span class="cx">
</span><del>-Tween.propHooks.scrollTop = Tween.propHooks.scrollLeft = {
- set: function( tween ) {
- if ( tween.elem.nodeType && tween.elem.parentNode ) {
- tween.elem[ tween.prop ] = tween.now;
- }
- }
-};
</del><span class="cx">
</span><del>-jQuery.each([ "toggle", "show", "hide" ], function( i, name ) {
- var cssFn = jQuery.fn[ name ];
- jQuery.fn[ name ] = function( speed, easing, callback ) {
- return speed == null || typeof speed === "boolean" ?
- cssFn.apply( this, arguments ) :
- this.animate( genFx( name, true ), speed, easing, callback );
- };
-});
</del><span class="cx">
</span><del>-jQuery.fn.extend({
- fadeTo: function( speed, to, easing, callback ) {
</del><ins>+var docElem = window.document.documentElement;
</ins><span class="cx">
</span><del>- // show any hidden elements after setting opacity to 0
- return this.filter( isHidden ).css( "opacity", 0 ).show()
</del><ins>+/**
+ * Gets a window from an element
+ */
+function getWindow( elem ) {
+ return jQuery.isWindow( elem ) ? elem : elem.nodeType === 9 && elem.defaultView;
+}
</ins><span class="cx">
</span><del>- // animate to the value specified
- .end().animate({ opacity: to }, speed, easing, callback );
- },
- animate: function( prop, speed, easing, callback ) {
- var empty = jQuery.isEmptyObject( prop ),
- optall = jQuery.speed( speed, easing, callback ),
- doAnimation = function() {
- // Operate on a copy of prop so per-property easing won't be lost
- var anim = Animation( this, jQuery.extend( {}, prop ), optall );
- doAnimation.finish = function() {
- anim.stop( true );
- };
- // Empty animations, or finishing resolves immediately
- if ( empty || jQuery._data( this, "finish" ) ) {
- anim.stop( true );
- }
- };
- doAnimation.finish = doAnimation;
</del><ins>+jQuery.offset = {
+ setOffset: function( elem, options, i ) {
+ var curPosition, curLeft, curCSSTop, curTop, curOffset, curCSSLeft, calculatePosition,
+ position = jQuery.css( elem, "position" ),
+ curElem = jQuery( elem ),
+ props = {};
</ins><span class="cx">
</span><del>- return empty || optall.queue === false ?
- this.each( doAnimation ) :
- this.queue( optall.queue, doAnimation );
- },
- stop: function( type, clearQueue, gotoEnd ) {
- var stopQueue = function( hooks ) {
- var stop = hooks.stop;
- delete hooks.stop;
- stop( gotoEnd );
- };
</del><ins>+ // Set position first, in-case top/left are set even on static elem
+ if ( position === "static" ) {
+ elem.style.position = "relative";
+ }
</ins><span class="cx">
</span><del>- if ( typeof type !== "string" ) {
- gotoEnd = clearQueue;
- clearQueue = type;
- type = undefined;
- }
- if ( clearQueue && type !== false ) {
- this.queue( type || "fx", [] );
- }
</del><ins>+ curOffset = curElem.offset();
+ curCSSTop = jQuery.css( elem, "top" );
+ curCSSLeft = jQuery.css( elem, "left" );
+ calculatePosition = ( position === "absolute" || position === "fixed" ) &&
+ ( curCSSTop + curCSSLeft ).indexOf("auto") > -1;
</ins><span class="cx">
</span><del>- return this.each(function() {
- var dequeue = true,
- index = type != null && type + "queueHooks",
- timers = jQuery.timers,
- data = jQuery._data( this );
</del><ins>+ // Need to be able to calculate position if either top or left is auto and position is either absolute or fixed
+ if ( calculatePosition ) {
+ curPosition = curElem.position();
+ curTop = curPosition.top;
+ curLeft = curPosition.left;
</ins><span class="cx">
</span><del>- if ( index ) {
- if ( data[ index ] && data[ index ].stop ) {
- stopQueue( data[ index ] );
- }
- } else {
- for ( index in data ) {
- if ( data[ index ] && data[ index ].stop && rrun.test( index ) ) {
- stopQueue( data[ index ] );
- }
- }
- }
</del><ins>+ } else {
+ curTop = parseFloat( curCSSTop ) || 0;
+ curLeft = parseFloat( curCSSLeft ) || 0;
+ }
</ins><span class="cx">
</span><del>- for ( index = timers.length; index--; ) {
- if ( timers[ index ].elem === this && (type == null || timers[ index ].queue === type) ) {
- timers[ index ].anim.stop( gotoEnd );
- dequeue = false;
- timers.splice( index, 1 );
- }
- }
</del><ins>+ if ( jQuery.isFunction( options ) ) {
+ options = options.call( elem, i, curOffset );
+ }
</ins><span class="cx">
</span><del>- // start the next in the queue if the last step wasn't forced
- // timers currently will call their complete callbacks, which will dequeue
- // but only if they were gotoEnd
- if ( dequeue || !gotoEnd ) {
- jQuery.dequeue( this, type );
- }
- });
- },
- finish: function( type ) {
- if ( type !== false ) {
- type = type || "fx";
- }
- return this.each(function() {
- var index,
- data = jQuery._data( this ),
- queue = data[ type + "queue" ],
- hooks = data[ type + "queueHooks" ],
- timers = jQuery.timers,
- length = queue ? queue.length : 0;
</del><ins>+ if ( options.top != null ) {
+ props.top = ( options.top - curOffset.top ) + curTop;
+ }
+ if ( options.left != null ) {
+ props.left = ( options.left - curOffset.left ) + curLeft;
+ }
</ins><span class="cx">
</span><del>- // enable finishing flag on private data
- data.finish = true;
</del><ins>+ if ( "using" in options ) {
+ options.using.call( elem, props );
</ins><span class="cx">
</span><del>- // empty the queue first
- jQuery.queue( this, type, [] );
-
- if ( hooks && hooks.cur && hooks.cur.finish ) {
- hooks.cur.finish.call( this );
</del><ins>+ } else {
+ curElem.css( props );
+ }
</ins><span class="cx"> }
</span><ins>+};
</ins><span class="cx">
</span><del>- // look for any active animations, and finish them
- for ( index = timers.length; index--; ) {
- if ( timers[ index ].elem === this && timers[ index ].queue === type ) {
- timers[ index ].anim.stop( true );
- timers.splice( index, 1 );
- }
- }
</del><ins>+jQuery.fn.extend({
+ offset: function( options ) {
+ if ( arguments.length ) {
+ return options === undefined ?
+ this :
+ this.each(function( i ) {
+ jQuery.offset.setOffset( this, options, i );
+ });
+ }
</ins><span class="cx">
</span><del>- // look for any animations in the old queue and finish them
- for ( index = 0; index < length; index++ ) {
- if ( queue[ index ] && queue[ index ].finish ) {
- queue[ index ].finish.call( this );
- }
- }
</del><ins>+ var docElem, win,
+ elem = this[ 0 ],
+ box = { top: 0, left: 0 },
+ doc = elem && elem.ownerDocument;
</ins><span class="cx">
</span><del>- // turn off finishing flag
- delete data.finish;
- });
- }
-});
</del><ins>+ if ( !doc ) {
+ return;
+ }
</ins><span class="cx">
</span><del>-// Generate parameters to create a standard animation
-function genFx( type, includeWidth ) {
- var which,
- attrs = { height: type },
- i = 0;
</del><ins>+ docElem = doc.documentElement;
</ins><span class="cx">
</span><del>- // if we include width, step value is 1 to do all cssExpand values,
- // if we don't include width, step value is 2 to skip over Left and Right
- includeWidth = includeWidth? 1 : 0;
- for( ; i < 4 ; i += 2 - includeWidth ) {
- which = cssExpand[ i ];
- attrs[ "margin" + which ] = attrs[ "padding" + which ] = type;
- }
</del><ins>+ // Make sure it's not a disconnected DOM node
+ if ( !jQuery.contains( docElem, elem ) ) {
+ return box;
+ }
</ins><span class="cx">
</span><del>- if ( includeWidth ) {
- attrs.opacity = attrs.width = type;
- }
</del><ins>+ // If we don't have gBCR, just use 0,0 rather than error
+ // BlackBerry 5, iOS 3 (original iPhone)
+ if ( typeof elem.getBoundingClientRect !== strundefined ) {
+ box = elem.getBoundingClientRect();
+ }
+ win = getWindow( doc );
+ return {
+ top: box.top + win.pageYOffset - docElem.clientTop,
+ left: box.left + win.pageXOffset - docElem.clientLeft
+ };
+ },
</ins><span class="cx">
</span><del>- return attrs;
-}
</del><ins>+ position: function() {
+ if ( !this[ 0 ] ) {
+ return;
+ }
</ins><span class="cx">
</span><del>-// Generate shortcuts for custom animations
-jQuery.each({
- slideDown: genFx("show"),
- slideUp: genFx("hide"),
- slideToggle: genFx("toggle"),
- fadeIn: { opacity: "show" },
- fadeOut: { opacity: "hide" },
- fadeToggle: { opacity: "toggle" }
-}, function( name, props ) {
- jQuery.fn[ name ] = function( speed, easing, callback ) {
- return this.animate( props, speed, easing, callback );
- };
-});
</del><ins>+ var offsetParent, offset,
+ elem = this[ 0 ],
+ parentOffset = { top: 0, left: 0 };
</ins><span class="cx">
</span><del>-jQuery.speed = function( speed, easing, fn ) {
- var opt = speed && typeof speed === "object" ? jQuery.extend( {}, speed ) : {
- complete: fn || !fn && easing ||
- jQuery.isFunction( speed ) && speed,
- duration: speed,
- easing: fn && easing || easing && !jQuery.isFunction( easing ) && easing
- };
</del><ins>+ // Fixed elements are offset from window (parentOffset = {top:0, left: 0}, because it is its only offset parent
+ if ( jQuery.css( elem, "position" ) === "fixed" ) {
+ // We assume that getBoundingClientRect is available when computed position is fixed
+ offset = elem.getBoundingClientRect();
</ins><span class="cx">
</span><del>- opt.duration = jQuery.fx.off ? 0 : typeof opt.duration === "number" ? opt.duration :
- opt.duration in jQuery.fx.speeds ? jQuery.fx.speeds[ opt.duration ] : jQuery.fx.speeds._default;
</del><ins>+ } else {
+ // Get *real* offsetParent
+ offsetParent = this.offsetParent();
</ins><span class="cx">
</span><del>- // normalize opt.queue - true/undefined/null -> "fx"
- if ( opt.queue == null || opt.queue === true ) {
- opt.queue = "fx";
- }
</del><ins>+ // Get correct offsets
+ offset = this.offset();
+ if ( !jQuery.nodeName( offsetParent[ 0 ], "html" ) ) {
+ parentOffset = offsetParent.offset();
+ }
</ins><span class="cx">
</span><del>- // Queueing
- opt.old = opt.complete;
</del><ins>+ // Add offsetParent borders
+ parentOffset.top += jQuery.css( offsetParent[ 0 ], "borderTopWidth", true );
+ parentOffset.left += jQuery.css( offsetParent[ 0 ], "borderLeftWidth", true );
+ }
</ins><span class="cx">
</span><del>- opt.complete = function() {
- if ( jQuery.isFunction( opt.old ) ) {
- opt.old.call( this );
- }
-
- if ( opt.queue ) {
- jQuery.dequeue( this, opt.queue );
- }
- };
-
- return opt;
-};
-
-jQuery.easing = {
- linear: function( p ) {
- return p;
</del><ins>+ // Subtract parent offsets and element margins
+ return {
+ top: offset.top - parentOffset.top - jQuery.css( elem, "marginTop", true ),
+ left: offset.left - parentOffset.left - jQuery.css( elem, "marginLeft", true )
+ };
</ins><span class="cx"> },
</span><del>- swing: function( p ) {
- return 0.5 - Math.cos( p*Math.PI ) / 2;
- }
-};
</del><span class="cx">
</span><del>-jQuery.timers = [];
-jQuery.fx = Tween.prototype.init;
-jQuery.fx.tick = function() {
- var timer,
- timers = jQuery.timers,
- i = 0;
</del><ins>+ offsetParent: function() {
+ return this.map(function() {
+ var offsetParent = this.offsetParent || docElem;
</ins><span class="cx">
</span><del>- fxNow = jQuery.now();
</del><ins>+ while ( offsetParent && ( !jQuery.nodeName( offsetParent, "html" ) && jQuery.css( offsetParent, "position" ) === "static" ) ) {
+ offsetParent = offsetParent.offsetParent;
+ }
</ins><span class="cx">
</span><del>- for ( ; i < timers.length; i++ ) {
- timer = timers[ i ];
- // Checks the timer has not already been removed
- if ( !timer() && timers[ i ] === timer ) {
- timers.splice( i--, 1 );
</del><ins>+ return offsetParent || docElem;
+ });
</ins><span class="cx"> }
</span><del>- }
</del><ins>+});
</ins><span class="cx">
</span><del>- if ( !timers.length ) {
- jQuery.fx.stop();
- }
- fxNow = undefined;
-};
</del><ins>+// Create scrollLeft and scrollTop methods
+jQuery.each( { scrollLeft: "pageXOffset", scrollTop: "pageYOffset" }, function( method, prop ) {
+ var top = "pageYOffset" === prop;
</ins><span class="cx">
</span><del>-jQuery.fx.timer = function( timer ) {
- if ( timer() && jQuery.timers.push( timer ) ) {
- jQuery.fx.start();
- }
-};
</del><ins>+ jQuery.fn[ method ] = function( val ) {
+ return access( this, function( elem, method, val ) {
+ var win = getWindow( elem );
</ins><span class="cx">
</span><del>-jQuery.fx.interval = 13;
</del><ins>+ if ( val === undefined ) {
+ return win ? win[ prop ] : elem[ method ];
+ }
</ins><span class="cx">
</span><del>-jQuery.fx.start = function() {
- if ( !timerId ) {
- timerId = setInterval( jQuery.fx.tick, jQuery.fx.interval );
- }
-};
</del><ins>+ if ( win ) {
+ win.scrollTo(
+ !top ? val : window.pageXOffset,
+ top ? val : window.pageYOffset
+ );
</ins><span class="cx">
</span><del>-jQuery.fx.stop = function() {
- clearInterval( timerId );
- timerId = null;
-};
-
-jQuery.fx.speeds = {
- slow: 600,
- fast: 200,
- // Default speed
- _default: 400
-};
-
-// Back Compat <1.8 extension point
-jQuery.fx.step = {};
-
-if ( jQuery.expr && jQuery.expr.filters ) {
- jQuery.expr.filters.animated = function( elem ) {
- return jQuery.grep(jQuery.timers, function( fn ) {
- return elem === fn.elem;
- }).length;
</del><ins>+ } else {
+ elem[ method ] = val;
+ }
+ }, method, val, arguments.length, null );
</ins><span class="cx"> };
</span><del>-}
-jQuery.fn.offset = function( options ) {
- if ( arguments.length ) {
- return options === undefined ?
- this :
- this.each(function( i ) {
- jQuery.offset.setOffset( this, options, i );
- });
- }
</del><ins>+});
</ins><span class="cx">
</span><del>- var docElem, win,
- box = { top: 0, left: 0 },
- elem = this[ 0 ],
- doc = elem && elem.ownerDocument;
</del><ins>+// Add the top/left cssHooks using jQuery.fn.position
+// Webkit bug: https://bugs.webkit.org/show_bug.cgi?id=29084
+// getComputedStyle returns percent when specified for top/left/bottom/right
+// rather than make the css module depend on the offset module, we just check for it here
+jQuery.each( [ "top", "left" ], function( i, prop ) {
+ jQuery.cssHooks[ prop ] = addGetHookIf( support.pixelPosition,
+ function( elem, computed ) {
+ if ( computed ) {
+ computed = curCSS( elem, prop );
+ // if curCSS returns percentage, fallback to offset
+ return rnumnonpx.test( computed ) ?
+ jQuery( elem ).position()[ prop ] + "px" :
+ computed;
+ }
+ }
+ );
+});
</ins><span class="cx">
</span><del>- if ( !doc ) {
- return;
- }
</del><span class="cx">
</span><del>- docElem = doc.documentElement;
</del><ins>+// Create innerHeight, innerWidth, height, width, outerHeight and outerWidth methods
+jQuery.each( { Height: "height", Width: "width" }, function( name, type ) {
+ jQuery.each( { padding: "inner" + name, content: type, "": "outer" + name }, function( defaultExtra, funcName ) {
+ // margin is only for outerHeight, outerWidth
+ jQuery.fn[ funcName ] = function( margin, value ) {
+ var chainable = arguments.length && ( defaultExtra || typeof margin !== "boolean" ),
+ extra = defaultExtra || ( margin === true || value === true ? "margin" : "border" );
</ins><span class="cx">
</span><del>- // Make sure it's not a disconnected DOM node
- if ( !jQuery.contains( docElem, elem ) ) {
- return box;
- }
</del><ins>+ return access( this, function( elem, type, value ) {
+ var doc;
</ins><span class="cx">
</span><del>- // If we don't have gBCR, just use 0,0 rather than error
- // BlackBerry 5, iOS 3 (original iPhone)
- if ( typeof elem.getBoundingClientRect !== core_strundefined ) {
- box = elem.getBoundingClientRect();
- }
- win = getWindow( doc );
- return {
- top: box.top + ( win.pageYOffset || docElem.scrollTop ) - ( docElem.clientTop || 0 ),
- left: box.left + ( win.pageXOffset || docElem.scrollLeft ) - ( docElem.clientLeft || 0 )
- };
-};
</del><ins>+ if ( jQuery.isWindow( elem ) ) {
+ // As of 5/8/2012 this will yield incorrect results for Mobile Safari, but there
+ // isn't a whole lot we can do. See pull request at this URL for discussion:
+ // https://github.com/jquery/jquery/pull/764
+ return elem.document.documentElement[ "client" + name ];
+ }
</ins><span class="cx">
</span><del>-jQuery.offset = {
</del><ins>+ // Get document width or height
+ if ( elem.nodeType === 9 ) {
+ doc = elem.documentElement;
</ins><span class="cx">
</span><del>- setOffset: function( elem, options, i ) {
- var position = jQuery.css( elem, "position" );
</del><ins>+ // Either scroll[Width/Height] or offset[Width/Height] or client[Width/Height],
+ // whichever is greatest
+ return Math.max(
+ elem.body[ "scroll" + name ], doc[ "scroll" + name ],
+ elem.body[ "offset" + name ], doc[ "offset" + name ],
+ doc[ "client" + name ]
+ );
+ }
</ins><span class="cx">
</span><del>- // set position first, in-case top/left are set even on static elem
- if ( position === "static" ) {
- elem.style.position = "relative";
- }
</del><ins>+ return value === undefined ?
+ // Get width or height on the element, requesting but not forcing parseFloat
+ jQuery.css( elem, type, extra ) :
</ins><span class="cx">
</span><del>- var curElem = jQuery( elem ),
- curOffset = curElem.offset(),
- curCSSTop = jQuery.css( elem, "top" ),
- curCSSLeft = jQuery.css( elem, "left" ),
- calculatePosition = ( position === "absolute" || position === "fixed" ) && jQuery.inArray("auto", [curCSSTop, curCSSLeft]) > -1,
- props = {}, curPosition = {}, curTop, curLeft;
</del><ins>+ // Set width or height on the element
+ jQuery.style( elem, type, value, extra );
+ }, type, chainable ? margin : undefined, chainable, null );
+ };
+ });
+});
</ins><span class="cx">
</span><del>- // need to be able to calculate position if either top or left is auto and position is either absolute or fixed
- if ( calculatePosition ) {
- curPosition = curElem.position();
- curTop = curPosition.top;
- curLeft = curPosition.left;
- } else {
- curTop = parseFloat( curCSSTop ) || 0;
- curLeft = parseFloat( curCSSLeft ) || 0;
- }
</del><span class="cx">
</span><del>- if ( jQuery.isFunction( options ) ) {
- options = options.call( elem, i, curOffset );
- }
-
- if ( options.top != null ) {
- props.top = ( options.top - curOffset.top ) + curTop;
- }
- if ( options.left != null ) {
- props.left = ( options.left - curOffset.left ) + curLeft;
- }
-
- if ( "using" in options ) {
- options.using.call( elem, props );
- } else {
- curElem.css( props );
- }
- }
</del><ins>+// The number of elements contained in the matched element set
+jQuery.fn.size = function() {
+ return this.length;
</ins><span class="cx"> };
</span><span class="cx">
</span><ins>+jQuery.fn.andSelf = jQuery.fn.addBack;
</ins><span class="cx">
</span><del>-jQuery.fn.extend({
</del><span class="cx">
</span><del>- position: function() {
- if ( !this[ 0 ] ) {
- return;
- }
</del><span class="cx">
</span><del>- var offsetParent, offset,
- parentOffset = { top: 0, left: 0 },
- elem = this[ 0 ];
</del><span class="cx">
</span><del>- // fixed elements are offset from window (parentOffset = {top:0, left: 0}, because it is it's only offset parent
- if ( jQuery.css( elem, "position" ) === "fixed" ) {
- // we assume that getBoundingClientRect is available when computed position is fixed
- offset = elem.getBoundingClientRect();
- } else {
- // Get *real* offsetParent
- offsetParent = this.offsetParent();
-
- // Get correct offsets
- offset = this.offset();
- if ( !jQuery.nodeName( offsetParent[ 0 ], "html" ) ) {
- parentOffset = offsetParent.offset();
- }
-
- // Add offsetParent borders
- parentOffset.top += jQuery.css( offsetParent[ 0 ], "borderTopWidth", true );
- parentOffset.left += jQuery.css( offsetParent[ 0 ], "borderLeftWidth", true );
- }
-
- // Subtract parent offsets and element margins
- // note: when an element has margin: auto the offsetLeft and marginLeft
- // are the same in Safari causing offset.left to incorrectly be 0
- return {
- top: offset.top - parentOffset.top - jQuery.css( elem, "marginTop", true ),
- left: offset.left - parentOffset.left - jQuery.css( elem, "marginLeft", true)
- };
- },
-
- offsetParent: function() {
- return this.map(function() {
- var offsetParent = this.offsetParent || document.documentElement;
- while ( offsetParent && ( !jQuery.nodeName( offsetParent, "html" ) && jQuery.css( offsetParent, "position") === "static" ) ) {
- offsetParent = offsetParent.offsetParent;
- }
- return offsetParent || document.documentElement;
</del><ins>+// Register as a named AMD module, since jQuery can be concatenated with other
+// files that may use define, but not via a proper concatenation script that
+// understands anonymous AMD modules. A named AMD is safest and most robust
+// way to register. Lowercase jquery is used because AMD module names are
+// derived from file names, and jQuery is normally delivered in a lowercase
+// file name. Do this after creating the global so that if an AMD module wants
+// to call noConflict to hide this version of jQuery, it will work.
+if ( typeof define === "function" && define.amd ) {
+ define( "jquery", [], function() {
+ return jQuery;
</ins><span class="cx"> });
</span><del>- }
-});
</del><ins>+}
</ins><span class="cx">
</span><span class="cx">
</span><del>-// Create scrollLeft and scrollTop methods
-jQuery.each( {scrollLeft: "pageXOffset", scrollTop: "pageYOffset"}, function( method, prop ) {
- var top = /Y/.test( prop );
</del><span class="cx">
</span><del>- jQuery.fn[ method ] = function( val ) {
- return jQuery.access( this, function( elem, method, val ) {
- var win = getWindow( elem );
</del><span class="cx">
</span><del>- if ( val === undefined ) {
- return win ? (prop in win) ? win[ prop ] :
- win.document.documentElement[ method ] :
- elem[ method ];
- }
</del><ins>+var
+ // Map over jQuery in case of overwrite
+ _jQuery = window.jQuery,
</ins><span class="cx">
</span><del>- if ( win ) {
- win.scrollTo(
- !top ? val : jQuery( win ).scrollLeft(),
- top ? val : jQuery( win ).scrollTop()
- );
</del><ins>+ // Map over the $ in case of overwrite
+ _$ = window.$;
</ins><span class="cx">
</span><del>- } else {
- elem[ method ] = val;
</del><ins>+jQuery.noConflict = function( deep ) {
+ if ( window.$ === jQuery ) {
+ window.$ = _$;
</ins><span class="cx"> }
</span><del>- }, method, val, arguments.length, null );
- };
-});
</del><span class="cx">
</span><del>-function getWindow( elem ) {
- return jQuery.isWindow( elem ) ?
- elem :
- elem.nodeType === 9 ?
- elem.defaultView || elem.parentWindow :
- false;
-}
-// Create innerHeight, innerWidth, height, width, outerHeight and outerWidth methods
-jQuery.each( { Height: "height", Width: "width" }, function( name, type ) {
- jQuery.each( { padding: "inner" + name, content: type, "": "outer" + name }, function( defaultExtra, funcName ) {
- // margin is only for outerHeight, outerWidth
- jQuery.fn[ funcName ] = function( margin, value ) {
- var chainable = arguments.length && ( defaultExtra || typeof margin !== "boolean" ),
- extra = defaultExtra || ( margin === true || value === true ? "margin" : "border" );
-
- return jQuery.access( this, function( elem, type, value ) {
- var doc;
-
- if ( jQuery.isWindow( elem ) ) {
- // As of 5/8/2012 this will yield incorrect results for Mobile Safari, but there
- // isn't a whole lot we can do. See pull request at this URL for discussion:
- // https://github.com/jquery/jquery/pull/764
- return elem.document.documentElement[ "client" + name ];
</del><ins>+ if ( deep && window.jQuery === jQuery ) {
+ window.jQuery = _jQuery;
</ins><span class="cx"> }
</span><span class="cx">
</span><del>- // Get document width or height
- if ( elem.nodeType === 9 ) {
- doc = elem.documentElement;
</del><ins>+ return jQuery;
+};
</ins><span class="cx">
</span><del>- // Either scroll[Width/Height] or offset[Width/Height] or client[Width/Height], whichever is greatest
- // unfortunately, this causes bug #3838 in IE6/8 only, but there is currently no good, small way to fix it.
- return Math.max(
- elem.body[ "scroll" + name ], doc[ "scroll" + name ],
- elem.body[ "offset" + name ], doc[ "offset" + name ],
- doc[ "client" + name ]
- );
- }
</del><ins>+// Expose jQuery and $ identifiers, even in
+// AMD (#7102#comment:10, https://github.com/jquery/jquery/pull/557)
+// and CommonJS for browser emulators (#13566)
+if ( typeof noGlobal === strundefined ) {
+ window.jQuery = window.$ = jQuery;
+}
</ins><span class="cx">
</span><del>- return value === undefined ?
- // Get width or height on the element, requesting but not forcing parseFloat
- jQuery.css( elem, type, extra ) :
</del><span class="cx">
</span><del>- // Set width or height on the element
- jQuery.style( elem, type, value, extra );
- }, type, chainable ? margin : undefined, chainable, null );
- };
- });
-});
-// Limit scope pollution from any deprecated API
-// (function() {
</del><span class="cx">
</span><del>-// })();
-// Expose jQuery to the global object
-window.jQuery = window.$ = jQuery;
</del><span class="cx">
</span><del>-// Expose jQuery as an AMD module, but only for AMD loaders that
-// understand the issues with loading multiple versions of jQuery
-// in a page that all might call define(). The loader will indicate
-// they have special allowances for multiple jQuery versions by
-// specifying define.amd.jQuery = true. Register as a named module,
-// since jQuery can be concatenated with other files that may use define,
-// but not use a proper concatenation script that understands anonymous
-// AMD modules. A named AMD is safest and most robust way to register.
-// Lowercase jquery is used because AMD module names are derived from
-// file names, and jQuery is normally delivered in a lowercase file name.
-// Do this after creating the global so that if an AMD module wants to call
-// noConflict to hide this version of jQuery, it will work.
-if ( typeof define === "function" && define.amd && define.amd.jQuery ) {
- define( "jquery", [], function () { return jQuery; } );
-}
</del><ins>+return jQuery;
</ins><span class="cx">
</span><del>-})( window );
</del><span class="cx">\ No newline at end of file
</span><ins>+}));
</ins></span></pre></div>
<a id="trunkPerformanceTestsDoYouEvenBenchresourcestodomvcarchitectureexamplesemberjsbower_componentstodomvccommonbasecss"></a>
<div class="modfile"><h4>Modified: trunk/PerformanceTests/DoYouEvenBench/resources/todomvc/architecture-examples/emberjs/bower_components/todomvc-common/base.css (163428 => 163429)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/PerformanceTests/DoYouEvenBench/resources/todomvc/architecture-examples/emberjs/bower_components/todomvc-common/base.css        2014-02-05 05:32:21 UTC (rev 163428)
+++ trunk/PerformanceTests/DoYouEvenBench/resources/todomvc/architecture-examples/emberjs/bower_components/todomvc-common/base.css        2014-02-05 05:36:04 UTC (rev 163429)
</span><span class="lines">@@ -14,7 +14,6 @@
</span><span class="cx"> font-family: inherit;
</span><span class="cx"> color: inherit;
</span><span class="cx"> -webkit-appearance: none;
</span><del>- /*-moz-appearance: none;*/
</del><span class="cx"> -ms-appearance: none;
</span><span class="cx"> -o-appearance: none;
</span><span class="cx"> appearance: none;
</span><span class="lines">@@ -34,6 +33,11 @@
</span><span class="cx"> font-smoothing: antialiased;
</span><span class="cx"> }
</span><span class="cx">
</span><ins>+button,
+input[type="checkbox"] {
+ outline: none;
+}
+
</ins><span class="cx"> #todoapp {
</span><span class="cx"> background: #fff;
</span><span class="cx"> background: rgba(255, 255, 255, 0.9);
</span><span class="lines">@@ -43,7 +47,7 @@
</span><span class="cx"> border-top-left-radius: 2px;
</span><span class="cx"> border-top-right-radius: 2px;
</span><span class="cx"> box-shadow: 0 2px 6px 0 rgba(0, 0, 0, 0.2),
</span><del>- 0 25px 50px 0 rgba(0, 0, 0, 0.15);
</del><ins>+ 0 25px 50px 0 rgba(0, 0, 0, 0.15);
</ins><span class="cx"> }
</span><span class="cx">
</span><span class="cx"> #todoapp:before {
</span><span class="lines">@@ -100,9 +104,6 @@
</span><span class="cx"> background: #8d7d77;
</span><span class="cx"> background: -webkit-gradient(linear, left top, left bottom, from(rgba(132, 110, 100, 0.8)),to(rgba(101, 84, 76, 0.8)));
</span><span class="cx"> background: -webkit-linear-gradient(top, rgba(132, 110, 100, 0.8), rgba(101, 84, 76, 0.8));
</span><del>- background: -moz-linear-gradient(top, rgba(132, 110, 100, 0.8), rgba(101, 84, 76, 0.8));
- background: -o-linear-gradient(top, rgba(132, 110, 100, 0.8), rgba(101, 84, 76, 0.8));
- background: -ms-linear-gradient(top, rgba(132, 110, 100, 0.8), rgba(101, 84, 76, 0.8));
</del><span class="cx"> background: linear-gradient(top, rgba(132, 110, 100, 0.8), rgba(101, 84, 76, 0.8));
</span><span class="cx"> filter: progid:DXImageTransform.Microsoft.gradient(GradientType=0,StartColorStr='#9d8b83', EndColorStr='#847670');
</span><span class="cx"> border-top-left-radius: 1px;
</span><span class="lines">@@ -123,7 +124,6 @@
</span><span class="cx"> padding: 6px;
</span><span class="cx"> border: 1px solid #999;
</span><span class="cx"> box-shadow: inset 0 -1px 5px 0 rgba(0, 0, 0, 0.2);
</span><del>- -webkit-box-sizing: border-box;
</del><span class="cx"> -moz-box-sizing: border-box;
</span><span class="cx"> -ms-box-sizing: border-box;
</span><span class="cx"> -o-box-sizing: border-box;
</span><span class="lines">@@ -159,7 +159,8 @@
</span><span class="cx"> left: -4px;
</span><span class="cx"> width: 40px;
</span><span class="cx"> text-align: center;
</span><del>- border: none; /* Mobile Safari */
</del><ins>+ /* Mobile Safari */
+ border: none;
</ins><span class="cx"> }
</span><span class="cx">
</span><span class="cx"> #toggle-all:before {
</span><span class="lines">@@ -214,9 +215,9 @@
</span><span class="cx"> top: 0;
</span><span class="cx"> bottom: 0;
</span><span class="cx"> margin: auto 0;
</span><del>- border: none; /* Mobile Safari */
</del><ins>+ /* Mobile Safari */
+ border: none;
</ins><span class="cx"> -webkit-appearance: none;
</span><del>- /*-moz-appearance: none;*/
</del><span class="cx"> -ms-appearance: none;
</span><span class="cx"> -o-appearance: none;
</span><span class="cx"> appearance: none;
</span><span class="lines">@@ -224,7 +225,8 @@
</span><span class="cx">
</span><span class="cx"> #todo-list li .toggle:after {
</span><span class="cx"> content: '✔';
</span><del>- line-height: 43px; /* 40 + a couple of pixels visual adjustment */
</del><ins>+ /* 40 + a couple of pixels visual adjustment */
+ line-height: 43px;
</ins><span class="cx"> font-size: 20px;
</span><span class="cx"> color: #d9d9d9;
</span><span class="cx"> text-shadow: 0 -1px 0 #bfbfbf;
</span><span class="lines">@@ -238,15 +240,13 @@
</span><span class="cx"> }
</span><span class="cx">
</span><span class="cx"> #todo-list li label {
</span><ins>+ white-space: pre;
</ins><span class="cx"> word-break: break-word;
</span><del>- padding: 15px;
</del><ins>+ padding: 15px 60px 15px 15px;
</ins><span class="cx"> margin-left: 45px;
</span><span class="cx"> display: block;
</span><span class="cx"> line-height: 1.2;
</span><span class="cx"> -webkit-transition: color 0.4s;
</span><del>- -moz-transition: color 0.4s;
- -ms-transition: color 0.4s;
- -o-transition: color 0.4s;
</del><span class="cx"> transition: color 0.4s;
</span><span class="cx"> }
</span><span class="cx">
</span><span class="lines">@@ -267,19 +267,14 @@
</span><span class="cx"> font-size: 22px;
</span><span class="cx"> color: #a88a8a;
</span><span class="cx"> -webkit-transition: all 0.2s;
</span><del>- -moz-transition: all 0.2s;
- -ms-transition: all 0.2s;
- -o-transition: all 0.2s;
</del><span class="cx"> transition: all 0.2s;
</span><span class="cx"> }
</span><span class="cx">
</span><span class="cx"> #todo-list li .destroy:hover {
</span><span class="cx"> text-shadow: 0 0 1px #000,
</span><del>- 0 0 10px rgba(199, 107, 107, 0.8);
</del><ins>+ 0 0 10px rgba(199, 107, 107, 0.8);
</ins><span class="cx"> -webkit-transform: scale(1.3);
</span><del>- -moz-transform: scale(1.3);
</del><span class="cx"> -ms-transform: scale(1.3);
</span><del>- -o-transform: scale(1.3);
</del><span class="cx"> transform: scale(1.3);
</span><span class="cx"> }
</span><span class="cx">
</span><span class="lines">@@ -320,10 +315,10 @@
</span><span class="cx"> height: 50px;
</span><span class="cx"> z-index: -1;
</span><span class="cx"> box-shadow: 0 1px 1px rgba(0, 0, 0, 0.3),
</span><del>- 0 6px 0 -3px rgba(255, 255, 255, 0.8),
- 0 7px 1px -3px rgba(0, 0, 0, 0.3),
- 0 43px 0 -6px rgba(255, 255, 255, 0.8),
- 0 44px 2px -6px rgba(0, 0, 0, 0.2);
</del><ins>+ 0 6px 0 -3px rgba(255, 255, 255, 0.8),
+ 0 7px 1px -3px rgba(0, 0, 0, 0.3),
+ 0 43px 0 -6px rgba(255, 255, 255, 0.8),
+ 0 44px 2px -6px rgba(0, 0, 0, 0.2);
</ins><span class="cx"> }
</span><span class="cx">
</span><span class="cx"> #todo-count {
</span><span class="lines">@@ -387,30 +382,32 @@
</span><span class="cx"> Hack to remove background from Mobile Safari.
</span><span class="cx"> Can't use it globally since it destroys checkboxes in Firefox and Opera
</span><span class="cx"> */
</span><ins>+
</ins><span class="cx"> @media screen and (-webkit-min-device-pixel-ratio:0) {
</span><span class="cx"> #toggle-all,
</span><span class="cx"> #todo-list li .toggle {
</span><del>- background: none;
</del><ins>+ background: none;
</ins><span class="cx"> }
</span><span class="cx">
</span><span class="cx"> #todo-list li .toggle {
</span><del>- height: 40px;
</del><ins>+ height: 40px;
</ins><span class="cx"> }
</span><span class="cx">
</span><span class="cx"> #toggle-all {
</span><del>- top: -56px;
- left: -15px;
- width: 65px;
- height: 41px;
- -webkit-transform: rotate(90deg);
- transform: rotate(90deg);
- -webkit-appearance: none;
- appearance: none;
</del><ins>+ top: -56px;
+ left: -15px;
+ width: 65px;
+ height: 41px;
+ -webkit-transform: rotate(90deg);
+ -ms-transform: rotate(90deg);
+ transform: rotate(90deg);
+ -webkit-appearance: none;
+ appearance: none;
</ins><span class="cx"> }
</span><span class="cx"> }
</span><span class="cx">
</span><del>-.hidden{
- display:none;
</del><ins>+.hidden {
+ display: none;
</ins><span class="cx"> }
</span><span class="cx">
</span><span class="cx"> hr {
</span><span class="lines">@@ -528,7 +525,7 @@
</span><span class="cx"> border-top-color: rgba(0, 0, 0, .04);
</span><span class="cx"> }
</span><span class="cx">
</span><del>-/**body*/.learn-bar > .learn {
</del><ins>+.learn-bar > .learn {
</ins><span class="cx"> position: absolute;
</span><span class="cx"> width: 272px;
</span><span class="cx"> top: 8px;
</span><span class="lines">@@ -536,20 +533,24 @@
</span><span class="cx"> padding: 10px;
</span><span class="cx"> border-radius: 5px;
</span><span class="cx"> background-color: rgba(255, 255, 255, .6);
</span><ins>+ -webkit-transition-property: left;
</ins><span class="cx"> transition-property: left;
</span><ins>+ -webkit-transition-duration: 500ms;
</ins><span class="cx"> transition-duration: 500ms;
</span><span class="cx"> }
</span><span class="cx">
</span><span class="cx"> @media (min-width: 899px) {
</span><del>- /**body*/.learn-bar {
- width: auto;
- margin: 0 0 0 300px;
</del><ins>+ .learn-bar {
+ width: auto;
+ margin: 0 0 0 300px;
</ins><span class="cx"> }
</span><del>- /**body*/.learn-bar > .learn {
- left: 8px;
</del><ins>+
+ .learn-bar > .learn {
+ left: 8px;
</ins><span class="cx"> }
</span><del>- /**body*/.learn-bar #todoapp {
- width: 550px;
- margin: 130px auto 40px auto;
</del><ins>+
+ .learn-bar #todoapp {
+ width: 550px;
+ margin: 130px auto 40px auto;
</ins><span class="cx"> }
</span><span class="cx"> }
</span></span></pre></div>
<a id="trunkPerformanceTestsDoYouEvenBenchresourcestodomvcarchitectureexamplesemberjsbower_componentstodomvccommonbasejs"></a>
<div class="modfile"><h4>Modified: trunk/PerformanceTests/DoYouEvenBench/resources/todomvc/architecture-examples/emberjs/bower_components/todomvc-common/base.js (163428 => 163429)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/PerformanceTests/DoYouEvenBench/resources/todomvc/architecture-examples/emberjs/bower_components/todomvc-common/base.js        2014-02-05 05:32:21 UTC (rev 163428)
+++ trunk/PerformanceTests/DoYouEvenBench/resources/todomvc/architecture-examples/emberjs/bower_components/todomvc-common/base.js        2014-02-05 05:36:04 UTC (rev 163429)
</span><span class="lines">@@ -4,204 +4,204 @@
</span><span class="cx"> // Underscore's Template Module
</span><span class="cx"> // Courtesy of underscorejs.org
</span><span class="cx"> var _ = (function (_) {
</span><del>- _.defaults = function (object) {
- if (!object) {
- return object;
- }
- for (var argsIndex = 1, argsLength = arguments.length; argsIndex < argsLength; argsIndex++) {
- var iterable = arguments[argsIndex];
- if (iterable) {
- for (var key in iterable) {
- if (object[key] == null) {
- object[key] = iterable[key];
- }
- }
- }
- }
- return object;
- }
</del><ins>+ _.defaults = function (object) {
+ if (!object) {
+ return object;
+ }
+ for (var argsIndex = 1, argsLength = arguments.length; argsIndex < argsLength; argsIndex++) {
+ var iterable = arguments[argsIndex];
+ if (iterable) {
+ for (var key in iterable) {
+ if (object[key] == null) {
+ object[key] = iterable[key];
+ }
+ }
+ }
+ }
+ return object;
+ }
</ins><span class="cx">
</span><del>- // By default, Underscore uses ERB-style template delimiters, change the
- // following template settings to use alternative delimiters.
- _.templateSettings = {
- evaluate : /<%([\s\S]+?)%>/g,
- interpolate : /<%=([\s\S]+?)%>/g,
- escape : /<%-([\s\S]+?)%>/g
- };
</del><ins>+ // By default, Underscore uses ERB-style template delimiters, change the
+ // following template settings to use alternative delimiters.
+ _.templateSettings = {
+ evaluate : /<%([\s\S]+?)%>/g,
+ interpolate : /<%=([\s\S]+?)%>/g,
+ escape : /<%-([\s\S]+?)%>/g
+ };
</ins><span class="cx">
</span><del>- // When customizing `templateSettings`, if you don't want to define an
- // interpolation, evaluation or escaping regex, we need one that is
- // guaranteed not to match.
- var noMatch = /(.)^/;
</del><ins>+ // When customizing `templateSettings`, if you don't want to define an
+ // interpolation, evaluation or escaping regex, we need one that is
+ // guaranteed not to match.
+ var noMatch = /(.)^/;
</ins><span class="cx">
</span><del>- // Certain characters need to be escaped so that they can be put into a
- // string literal.
- var escapes = {
- "'": "'",
- '\\': '\\',
- '\r': 'r',
- '\n': 'n',
- '\t': 't',
- '\u2028': 'u2028',
- '\u2029': 'u2029'
- };
</del><ins>+ // Certain characters need to be escaped so that they can be put into a
+ // string literal.
+ var escapes = {
+ "'": "'",
+ '\\': '\\',
+ '\r': 'r',
+ '\n': 'n',
+ '\t': 't',
+ '\u2028': 'u2028',
+ '\u2029': 'u2029'
+ };
</ins><span class="cx">
</span><del>- var escaper = /\\|'|\r|\n|\t|\u2028|\u2029/g;
</del><ins>+ var escaper = /\\|'|\r|\n|\t|\u2028|\u2029/g;
</ins><span class="cx">
</span><del>- // JavaScript micro-templating, similar to John Resig's implementation.
- // Underscore templating handles arbitrary delimiters, preserves whitespace,
- // and correctly escapes quotes within interpolated code.
- _.template = function(text, data, settings) {
- var render;
- settings = _.defaults({}, settings, _.templateSettings);
</del><ins>+ // JavaScript micro-templating, similar to John Resig's implementation.
+ // Underscore templating handles arbitrary delimiters, preserves whitespace,
+ // and correctly escapes quotes within interpolated code.
+ _.template = function(text, data, settings) {
+ var render;
+ settings = _.defaults({}, settings, _.templateSettings);
</ins><span class="cx">
</span><del>- // Combine delimiters into one regular expression via alternation.
- var matcher = new RegExp([
- (settings.escape || noMatch).source,
- (settings.interpolate || noMatch).source,
- (settings.evaluate || noMatch).source
- ].join('|') + '|$', 'g');
</del><ins>+ // Combine delimiters into one regular expression via alternation.
+ var matcher = new RegExp([
+ (settings.escape || noMatch).source,
+ (settings.interpolate || noMatch).source,
+ (settings.evaluate || noMatch).source
+ ].join('|') + '|$', 'g');
</ins><span class="cx">
</span><del>- // Compile the template source, escaping string literals appropriately.
- var index = 0;
- var source = "__p+='";
- text.replace(matcher, function(match, escape, interpolate, evaluate, offset) {
- source += text.slice(index, offset)
- .replace(escaper, function(match) { return '\\' + escapes[match]; });
</del><ins>+ // Compile the template source, escaping string literals appropriately.
+ var index = 0;
+ var source = "__p+='";
+ text.replace(matcher, function(match, escape, interpolate, evaluate, offset) {
+ source += text.slice(index, offset)
+ .replace(escaper, function(match) { return '\\' + escapes[match]; });
</ins><span class="cx">
</span><del>- if (escape) {
- source += "'+\n((__t=(" + escape + "))==null?'':_.escape(__t))+\n'";
- }
- if (interpolate) {
- source += "'+\n((__t=(" + interpolate + "))==null?'':__t)+\n'";
- }
- if (evaluate) {
- source += "';\n" + evaluate + "\n__p+='";
- }
- index = offset + match.length;
- return match;
- });
- source += "';\n";
</del><ins>+ if (escape) {
+ source += "'+\n((__t=(" + escape + "))==null?'':_.escape(__t))+\n'";
+ }
+ if (interpolate) {
+ source += "'+\n((__t=(" + interpolate + "))==null?'':__t)+\n'";
+ }
+ if (evaluate) {
+ source += "';\n" + evaluate + "\n__p+='";
+ }
+ index = offset + match.length;
+ return match;
+ });
+ source += "';\n";
</ins><span class="cx">
</span><del>- // If a variable is not specified, place data values in local scope.
- if (!settings.variable) source = 'with(obj||{}){\n' + source + '}\n';
</del><ins>+ // If a variable is not specified, place data values in local scope.
+ if (!settings.variable) source = 'with(obj||{}){\n' + source + '}\n';
</ins><span class="cx">
</span><del>- source = "var __t,__p='',__j=Array.prototype.join," +
- "print=function(){__p+=__j.call(arguments,'');};\n" +
- source + "return __p;\n";
</del><ins>+ source = "var __t,__p='',__j=Array.prototype.join," +
+ "print=function(){__p+=__j.call(arguments,'');};\n" +
+ source + "return __p;\n";
</ins><span class="cx">
</span><del>- try {
- render = new Function(settings.variable || 'obj', '_', source);
- } catch (e) {
- e.source = source;
- throw e;
- }
</del><ins>+ try {
+ render = new Function(settings.variable || 'obj', '_', source);
+ } catch (e) {
+ e.source = source;
+ throw e;
+ }
</ins><span class="cx">
</span><del>- if (data) return render(data, _);
- var template = function(data) {
- return render.call(this, data, _);
- };
</del><ins>+ if (data) return render(data, _);
+ var template = function(data) {
+ return render.call(this, data, _);
+ };
</ins><span class="cx">
</span><del>- // Provide the compiled function source as a convenience for precompilation.
- template.source = 'function(' + (settings.variable || 'obj') + '){\n' + source + '}';
</del><ins>+ // Provide the compiled function source as a convenience for precompilation.
+ template.source = 'function(' + (settings.variable || 'obj') + '){\n' + source + '}';
</ins><span class="cx">
</span><del>- return template;
- };
</del><ins>+ return template;
+ };
</ins><span class="cx">
</span><del>- return _;
</del><ins>+ return _;
</ins><span class="cx"> })({});
</span><span class="cx">
</span><span class="cx"> if (location.hostname === 'todomvc.com') {
</span><del>- window._gaq = [['_setAccount','UA-31081062-1'],['_trackPageview']];(function(d,t){var g=d.createElement(t),s=d.getElementsByTagName(t)[0];g.src='//www.google-analytics.com/ga.js';s.parentNode.insertBefore(g,s)}(document,'script'));
</del><ins>+ window._gaq = [['_setAccount','UA-31081062-1'],['_trackPageview']];(function(d,t){var g=d.createElement(t),s=d.getElementsByTagName(t)[0];g.src='//www.google-analytics.com/ga.js';s.parentNode.insertBefore(g,s)}(document,'script'));
</ins><span class="cx"> }
</span><span class="cx">
</span><span class="cx"> function redirect() {
</span><del>- if (location.hostname === 'tastejs.github.io') {
- location.href = location.href.replace('tastejs.github.io/todomvc', 'todomvc.com');
</del><ins>+ if (location.hostname === 'tastejs.github.io') {
+ location.href = location.href.replace('tastejs.github.io/todomvc', 'todomvc.com');
+ }
</ins><span class="cx"> }
</span><del>- }
</del><span class="cx">
</span><span class="cx"> function findRoot() {
</span><del>- var base;
</del><ins>+ var base;
</ins><span class="cx">
</span><del>- [/labs/, /\w*-examples/].forEach(function (href) {
- var match = location.href.match(href);
</del><ins>+ [/labs/, /\w*-examples/].forEach(function (href) {
+ var match = location.href.match(href);
</ins><span class="cx">
</span><del>- if (!base && match) {
- base = location.href.indexOf(match);
- }
- });
</del><ins>+ if (!base && match) {
+ base = location.href.indexOf(match);
+ }
+ });
</ins><span class="cx">
</span><del>- return location.href.substr(0, base);
</del><ins>+ return location.href.substr(0, base);
</ins><span class="cx"> }
</span><span class="cx">
</span><span class="cx"> function getFile(file, callback) {
</span><del>- if (!location.host) {
- return console.info('Miss the info bar? Run TodoMVC from a server to avoid a cross-origin error.');
- }
</del><ins>+ if (!location.host) {
+ return console.info('Miss the info bar? Run TodoMVC from a server to avoid a cross-origin error.');
+ }
</ins><span class="cx">
</span><del>- var xhr = new XMLHttpRequest();
</del><ins>+ var xhr = new XMLHttpRequest();
</ins><span class="cx">
</span><del>- xhr.open('GET', findRoot() + file, true);
- xhr.send();
</del><ins>+ xhr.open('GET', findRoot() + file, true);
+ xhr.send();
</ins><span class="cx">
</span><del>- xhr.onload = function () {
- if (xhr.status === 200 && callback) {
- callback(xhr.responseText);
</del><ins>+ xhr.onload = function () {
+ if (xhr.status === 200 && callback) {
+ callback(xhr.responseText);
+ }
+ };
</ins><span class="cx"> }
</span><del>- };
- }
</del><span class="cx">
</span><span class="cx"> function Learn(learnJSON, config) {
</span><del>- if (!(this instanceof Learn)) {
- return new Learn(learnJSON, config);
- }
</del><ins>+ if (!(this instanceof Learn)) {
+ return new Learn(learnJSON, config);
+ }
</ins><span class="cx">
</span><del>- var template, framework;
</del><ins>+ var template, framework;
</ins><span class="cx">
</span><del>- if (typeof learnJSON !== 'object') {
- try {
- learnJSON = JSON.parse(learnJSON);
- } catch (e) {
- return;
- }
- }
</del><ins>+ if (typeof learnJSON !== 'object') {
+ try {
+ learnJSON = JSON.parse(learnJSON);
+ } catch (e) {
+ return;
+ }
+ }
</ins><span class="cx">
</span><del>- if (config) {
- template = config.template;
- framework = config.framework;
- }
</del><ins>+ if (config) {
+ template = config.template;
+ framework = config.framework;
+ }
</ins><span class="cx">
</span><del>- if (!template && learnJSON.templates) {
- template = learnJSON.templates.todomvc;
- }
</del><ins>+ if (!template && learnJSON.templates) {
+ template = learnJSON.templates.todomvc;
+ }
</ins><span class="cx">
</span><del>- if (!framework && document.querySelector('[data-framework]')) {
- framework = document.querySelector('[data-framework]').getAttribute('data-framework');
- }
</del><ins>+ if (!framework && document.querySelector('[data-framework]')) {
+ framework = document.querySelector('[data-framework]').getAttribute('data-framework');
+ }
</ins><span class="cx">
</span><span class="cx">
</span><del>- if (template && learnJSON[framework]) {
- this.frameworkJSON = learnJSON[framework];
- this.template = template;
</del><ins>+ if (template && learnJSON[framework]) {
+ this.frameworkJSON = learnJSON[framework];
+ this.template = template;
</ins><span class="cx">
</span><del>- this.append();
</del><ins>+ this.append();
+ }
</ins><span class="cx"> }
</span><del>- }
</del><span class="cx">
</span><span class="cx"> Learn.prototype.append = function () {
</span><del>- var aside = document.createElement('aside');
- aside.innerHTML = _.template(this.template, this.frameworkJSON);
- aside.className = 'learn';
</del><ins>+ var aside = document.createElement('aside');
+ aside.innerHTML = _.template(this.template, this.frameworkJSON);
+ aside.className = 'learn';
</ins><span class="cx">
</span><del>- // Localize demo links
- var demoLinks = aside.querySelectorAll('.demo-link');
- Array.prototype.forEach.call(demoLinks, function (demoLink) {
- demoLink.setAttribute('href', findRoot() + demoLink.getAttribute('href'));
- });
</del><ins>+ // Localize demo links
+ var demoLinks = aside.querySelectorAll('.demo-link');
+ Array.prototype.forEach.call(demoLinks, function (demoLink) {
+ demoLink.setAttribute('href', findRoot() + demoLink.getAttribute('href'));
+ });
</ins><span class="cx">
</span><del>- document.body.className = (document.body.className + ' learn-bar').trim();
- document.body.insertAdjacentHTML('afterBegin', aside.outerHTML);
</del><ins>+ document.body.className = (document.body.className + ' learn-bar').trim();
+ document.body.insertAdjacentHTML('afterBegin', aside.outerHTML);
</ins><span class="cx"> };
</span><span class="cx">
</span><span class="cx"> redirect();
</span></span></pre></div>
<a id="trunkPerformanceTestsDoYouEvenBenchresourcestodomvcarchitectureexamplesemberjsindexhtml"></a>
<div class="modfile"><h4>Modified: trunk/PerformanceTests/DoYouEvenBench/resources/todomvc/architecture-examples/emberjs/index.html (163428 => 163429)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/PerformanceTests/DoYouEvenBench/resources/todomvc/architecture-examples/emberjs/index.html        2014-02-05 05:32:21 UTC (rev 163428)
+++ trunk/PerformanceTests/DoYouEvenBench/resources/todomvc/architecture-examples/emberjs/index.html        2014-02-05 05:36:04 UTC (rev 163429)
</span><span class="lines">@@ -1,80 +1,78 @@
</span><span class="cx"> <!doctype html>
</span><span class="cx"> <html lang="en" data-framework="emberjs">
</span><span class="cx"> <head>
</span><del>- <meta charset="utf-8">
- <meta http-equiv="X-UA-Compatible" content="IE=edge">
- <title>ember.js • TodoMVC</title>
- <link rel="stylesheet" href="bower_components/todomvc-common/base.css">
</del><ins>+ <meta charset="utf-8">
+ <title>ember.js • TodoMVC</title>
+ <link rel="stylesheet" href="bower_components/todomvc-common/base.css">
</ins><span class="cx"> </head>
</span><span class="cx"> <body>
</span><del>- <script type="text/x-handlebars" data-template-name="todos">
- <section id="todoapp">
- <header id="header">
- <h1>todos</h1>
- {{view Ember.TextField id="new-todo" placeholder="What needs to be done?"
- valueBinding="newTitle" action="createTodo"}}
- </header>
- {{#if length}}
- <section id="main">
- <ul id="todo-list">
- {{#each filteredTodos itemController="todo"}}
- <li {{bindAttr class="isCompleted:completed isEditing:editing"}}>
- {{#if isEditing}}
- {{view Todos.EditTodoView todoBinding="this"}}
- {{else}}
- {{view Ember.Checkbox checkedBinding="isCompleted" class="toggle"}}
- <label {{action "editTodo" on="doubleClick"}}>{{title}}</label>
- <button {{action "removeTodo"}} class="destroy"></button>
- {{/if}}
- </li>
- {{/each}}
- </ul>
- {{view Ember.Checkbox id="toggle-all" checkedBinding="allAreDone"}}
- </section>
- <footer id="footer">
- <span id="todo-count">{{{remainingFormatted}}}</span>
- <ul id="filters">
- <li>
- {{#linkTo todos.index activeClass="selected"}}All{{/linkTo}}
- </li>
- <li>
- {{#linkTo todos.active activeClass="selected"}}Active{{/linkTo}}
- </li>
- <li>
- {{#linkTo todos.completed activeClass="selected"}}Completed{{/linkTo}}
- </li>
- </ul>
- {{#if hasCompleted}}
- <button id="clear-completed" {{action "clearCompleted"}} {{bindAttr class="buttonClass:hidden"}}>
- Clear completed ({{completed}})
- </button>
- {{/if}}
- </footer>
- {{/if}}
- </section>
- <footer id="info">
- <p>Double-click to edit a todo</p>
- <p>
- Created by
- <a href="http://github.com/tomdale">Tom Dale</a>,
- <a href="http://github.com/addyosmani">Addy Osmani</a>
- </p>
- <p>Part of <a href="http://todomvc.com">TodoMVC</a></p>
- </footer>
- </script>
- <script src="bower_components/todomvc-common/base.js"></script>
- <script src="bower_components/jquery/jquery.js"></script>
- <script src="bower_components/handlebars/handlebars.js"></script>
- <script src="bower_components/ember/ember.js"></script>
- <!-- TODO: change out with a component when a built one is available on Bower -->
- <script src="js/libs/ember-data.js"></script>
- <script src="bower_components/ember-localstorage-adapter/localstorage_adapter.js"></script>
- <script src="js/app.js"></script>
- <script src="js/router.js"></script>
- <script src="js/models/todo.js"></script>
- <script src="js/models/store.js"></script>
- <script src="js/controllers/todos_controller.js"></script>
- <script src="js/controllers/todo_controller.js"></script>
- <script src="js/views/edit_todo_view.js"></script>
</del><ins>+ <script type="text/x-handlebars" data-template-name="todos">
+ <section id="todoapp">
+ <header id="header">
+ <h1>todos</h1>
+ {{input id="new-todo" type="text" value=newTitle action="createTodo" placeholder="What needs to be done?"}}
+ </header>
+ {{#if length}}
+ <section id="main">
+ <ul id="todo-list">
+ {{#each filteredTodos itemController="todo"}}
+ <li {{bind-attr class="isCompleted:completed isEditing:editing"}}>
+ {{#if isEditing}}
+ {{edit-todo class="edit" value=bufferedTitle focus-out="doneEditing" insert-newline="doneEditing" escape-press="cancelEditing"}}
+ {{else}}
+ {{input type="checkbox" class="toggle" checked=isCompleted}}
+ <label {{action "editTodo" on="doubleClick"}}>{{title}}</label>
+ <button {{action "removeTodo"}} class="destroy"></button>
+ {{/if}}
+ </li>
+ {{/each}}
+ </ul>
+ {{input type="checkbox" id="toggle-all" checked=allAreDone}}
+ </section>
+ <footer id="footer">
+ <span id="todo-count"><strong>{{remaining.length}}</strong> {{pluralize 'item' remaining.length}} left</span>
+ <ul id="filters">
+ <li>
+ {{#link-to "todos.index" activeClass="selected"}}All{{/link-to}}
+ </li>
+ <li>
+ {{#link-to "todos.active" activeClass="selected"}}Active{{/link-to}}
+ </li>
+ <li>
+ {{#link-to "todos.completed" activeClass="selected"}}Completed{{/link-to}}
+ </li>
+ </ul>
+ {{#if completed.length}}
+ <button id="clear-completed" {{action "clearCompleted"}}>
+ Clear completed ({{completed.length}})
+ </button>
+ {{/if}}
+ </footer>
+ {{/if}}
+ </section>
+ <footer id="info">
+ <p>Double-click to edit a todo</p>
+ <p>
+ Created by
+ <a href="http://github.com/tomdale">Tom Dale</a>,
+ <a href="http://github.com/addyosmani">Addy Osmani</a>
+ </p>
+ <p>Part of <a href="http://todomvc.com">TodoMVC</a></p>
+ </footer>
+ </script>
+ <script src="bower_components/todomvc-common/base.js"></script>
+ <script src="bower_components/jquery/jquery.js"></script>
+ <script src="bower_components/handlebars/handlebars.js"></script>
+ <script src="bower_components/ember/ember.js"></script>
+ <script src="bower_components/ember-data/ember-data.js"></script>
+ <script src="bower_components/ember-localstorage-adapter/localstorage_adapter.js"></script>
+ <script src="js/app.js"></script>
+ <script src="js/router.js"></script>
+ <script src="js/models/todo.js"></script>
+ <script src="js/controllers/todos_controller.js"></script>
+ <script src="js/controllers/todo_controller.js"></script>
+ <script src="js/views/edit_todo_view.js"></script>
+ <script src="js/views/todos_view.js"></script>
+ <script src="js/helpers/pluralize.js"></script>
</ins><span class="cx"> </body>
</span><span class="cx"> </html>
</span></span></pre></div>
<a id="trunkPerformanceTestsDoYouEvenBenchresourcestodomvcarchitectureexamplesemberjsjsappjs"></a>
<div class="modfile"><h4>Modified: trunk/PerformanceTests/DoYouEvenBench/resources/todomvc/architecture-examples/emberjs/js/app.js (163428 => 163429)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/PerformanceTests/DoYouEvenBench/resources/todomvc/architecture-examples/emberjs/js/app.js        2014-02-05 05:32:21 UTC (rev 163428)
+++ trunk/PerformanceTests/DoYouEvenBench/resources/todomvc/architecture-examples/emberjs/js/app.js        2014-02-05 05:36:04 UTC (rev 163429)
</span><span class="lines">@@ -1,2 +1,6 @@
</span><del>-/*global Ember */
</del><ins>+/*global Ember, DS, Todos:true */
</ins><span class="cx"> window.Todos = Ember.Application.create();
</span><ins>+
+Todos.ApplicationAdapter = DS.LSAdapter.extend({
+ namespace: 'todos-emberjs'
+});
</ins></span></pre></div>
<a id="trunkPerformanceTestsDoYouEvenBenchresourcestodomvcarchitectureexamplesemberjsjscontrollerstodo_controllerjs"></a>
<div class="modfile"><h4>Modified: trunk/PerformanceTests/DoYouEvenBench/resources/todomvc/architecture-examples/emberjs/js/controllers/todo_controller.js (163428 => 163429)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/PerformanceTests/DoYouEvenBench/resources/todomvc/architecture-examples/emberjs/js/controllers/todo_controller.js        2014-02-05 05:32:21 UTC (rev 163428)
+++ trunk/PerformanceTests/DoYouEvenBench/resources/todomvc/architecture-examples/emberjs/js/controllers/todo_controller.js        2014-02-05 05:36:04 UTC (rev 163429)
</span><span class="lines">@@ -1,17 +1,58 @@
</span><del>-/*global Todos Ember */
</del><ins>+/*global Todos, Ember */
</ins><span class="cx"> 'use strict';
</span><span class="cx">
</span><span class="cx"> Todos.TodoController = Ember.ObjectController.extend({
</span><span class="cx"> isEditing: false,
</span><span class="cx">
</span><del>- editTodo: function () {
- this.set('isEditing', true);
</del><ins>+ // We use the bufferedTitle to store the original value of
+ // the model's title so that we can roll it back later in the
+ // `cancelEditing` action.
+ bufferedTitle: Ember.computed.oneWay('title'),
+
+ actions: {
+ editTodo: function () {
+ this.set('isEditing', true);
+ },
+
+ doneEditing: function () {
+ var bufferedTitle = this.get('bufferedTitle').trim();
+
+ if (Ember.isEmpty(bufferedTitle)) {
+ // The `doneEditing` action gets sent twice when the user hits
+ // enter (once via 'insert-newline' and once via 'focus-out').
+ //
+ // We debounce our call to 'removeTodo' so that it only gets
+ // made once.
+ Ember.run.debounce(this, 'removeTodo', 0);
+ } else {
+ var todo = this.get('model');
+ todo.set('title', bufferedTitle);
+ todo.save();
+ }
+
+ // Re-set our newly edited title to persist its trimmed version
+ this.set('bufferedTitle', bufferedTitle);
+ this.set('isEditing', false);
+ },
+
+ cancelEditing: function () {
+ this.set('bufferedTitle', this.get('title'));
+ this.set('isEditing', false);
+ },
+
+ removeTodo: function () {
+ this.removeTodo();
+ }
</ins><span class="cx"> },
</span><span class="cx">
</span><span class="cx"> removeTodo: function () {
</span><del>- var todo = this.get('model');
</del><ins>+ var todo = this.get('model');
</ins><span class="cx">
</span><del>- todo.deleteRecord();
- todo.get('store').commit();
- }
</del><ins>+ todo.deleteRecord();
+ todo.save();
+ },
+
+ saveWhenCompleted: function () {
+ this.get('model').save();
+ }.observes('isCompleted')
</ins><span class="cx"> });
</span></span></pre></div>
<a id="trunkPerformanceTestsDoYouEvenBenchresourcestodomvcarchitectureexamplesemberjsjscontrollerstodos_controllerjs"></a>
<div class="modfile"><h4>Modified: trunk/PerformanceTests/DoYouEvenBench/resources/todomvc/architecture-examples/emberjs/js/controllers/todos_controller.js (163428 => 163429)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/PerformanceTests/DoYouEvenBench/resources/todomvc/architecture-examples/emberjs/js/controllers/todos_controller.js        2014-02-05 05:32:21 UTC (rev 163428)
+++ trunk/PerformanceTests/DoYouEvenBench/resources/todomvc/architecture-examples/emberjs/js/controllers/todos_controller.js        2014-02-05 05:36:04 UTC (rev 163429)
</span><span class="lines">@@ -1,59 +1,49 @@
</span><del>-/*global Todos Ember */
</del><ins>+/*global Todos, Ember */
</ins><span class="cx"> 'use strict';
</span><span class="cx">
</span><span class="cx"> Todos.TodosController = Ember.ArrayController.extend({
</span><del>- createTodo: function () {
- // Get the todo title set by the "New Todo" text field
- var title = this.get('newTitle');
- if (!title.trim()) {
- return;
- }
</del><ins>+ actions: {
+ createTodo: function () {
+ var title, todo;
</ins><span class="cx">
</span><del>- // Create the new Todo model
- Todos.Todo.createRecord({
- title: title,
- isCompleted: false
- });
</del><ins>+ // Get the todo title set by the "New Todo" text field
+ title = this.get('newTitle').trim();
+ if (!title) {
+ return;
+ }
</ins><span class="cx">
</span><del>- // Clear the "New Todo" text field
- this.set('newTitle', '');
</del><ins>+ // Create the new Todo model
+ todo = this.store.createRecord('todo', {
+ title: title,
+ isCompleted: false
+ });
+ todo.save();
</ins><span class="cx">
</span><del>- // Save the new model
- this.get('store').commit();
- },
</del><ins>+ // Clear the "New Todo" text field
+ this.set('newTitle', '');
+ },
</ins><span class="cx">
</span><del>- clearCompleted: function () {
- var completed = this.filterProperty('isCompleted', true);
- completed.invoke('deleteRecord');
-
- this.get('store').commit();
</del><ins>+ clearCompleted: function () {
+ var completed = this.get('completed');
+ completed.invoke('deleteRecord');
+ completed.invoke('save');
+ },
</ins><span class="cx"> },
</span><span class="cx">
</span><del>- remaining: function () {
- return this.filterProperty('isCompleted', false).get('length');
- }.property('@each.isCompleted'),
</del><ins>+ /* properties */
</ins><span class="cx">
</span><del>- remainingFormatted: function () {
- var remaining = this.get('remaining');
- var plural = remaining === 1 ? 'item' : 'items';
- return '<strong>%@</strong> %@ left'.fmt(remaining, plural);
- }.property('remaining'),
</del><ins>+ remaining: Ember.computed.filterBy('content', 'isCompleted', false),
+ completed: Ember.computed.filterBy('content', 'isCompleted', true),
</ins><span class="cx">
</span><del>- completed: function () {
- return this.filterProperty('isCompleted', true).get('length');
- }.property('@each.isCompleted'),
-
- hasCompleted: function () {
- return this.get('completed') > 0;
- }.property('completed'),
-
</del><span class="cx"> allAreDone: function (key, value) {
</span><del>- if (value !== undefined) {
- this.setEach('isCompleted', value);
- return value;
- } else {
- return !!this.get('length') &&
- this.everyProperty('isCompleted', true);
- }
- }.property('@each.isCompleted')
</del><ins>+ if (value !== undefined) {
+ this.setEach('isCompleted', value);
+ return value;
+ } else {
+ var length = this.get('length');
+ var completedLength = this.get('completed.length');
+
+ return length > 0 && length === completedLength;
+ }
+ }.property('length', 'completed.length')
</ins><span class="cx"> });
</span></span></pre></div>
<a id="trunkPerformanceTestsDoYouEvenBenchresourcestodomvcarchitectureexamplesemberjsjshelperspluralizejs"></a>
<div class="addfile"><h4>Added: trunk/PerformanceTests/DoYouEvenBench/resources/todomvc/architecture-examples/emberjs/js/helpers/pluralize.js (0 => 163429)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/PerformanceTests/DoYouEvenBench/resources/todomvc/architecture-examples/emberjs/js/helpers/pluralize.js         (rev 0)
+++ trunk/PerformanceTests/DoYouEvenBench/resources/todomvc/architecture-examples/emberjs/js/helpers/pluralize.js        2014-02-05 05:36:04 UTC (rev 163429)
</span><span class="lines">@@ -0,0 +1,9 @@
</span><ins>+/*global Todos, Ember */
+'use strict';
+
+Ember.Handlebars.helper('pluralize', function (singular, count) {
+ /* From Ember-Data */
+ var inflector = new Ember.Inflector(Ember.Inflector.defaultRules);
+
+ return count === 1 ? singular : inflector.pluralize(singular);
+});
</ins></span></pre></div>
<a id="trunkPerformanceTestsDoYouEvenBenchresourcestodomvcarchitectureexamplesemberjsjsmodelsstorejs"></a>
<div class="delfile"><h4>Deleted: trunk/PerformanceTests/DoYouEvenBench/resources/todomvc/architecture-examples/emberjs/js/models/store.js (163428 => 163429)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/PerformanceTests/DoYouEvenBench/resources/todomvc/architecture-examples/emberjs/js/models/store.js        2014-02-05 05:32:21 UTC (rev 163428)
+++ trunk/PerformanceTests/DoYouEvenBench/resources/todomvc/architecture-examples/emberjs/js/models/store.js        2014-02-05 05:36:04 UTC (rev 163429)
</span><span class="lines">@@ -1,11 +0,0 @@
</span><del>-/*global Todos DS */
-'use strict';
-
-Todos.Store = DS.Store.extend({
- revision: 12,
- adapter: 'Todos.LSAdapter'
-});
-
-Todos.LSAdapter = DS.LSAdapter.extend({
- namespace: 'todos-emberjs'
-});
</del></span></pre></div>
<a id="trunkPerformanceTestsDoYouEvenBenchresourcestodomvcarchitectureexamplesemberjsjsmodelstodojs"></a>
<div class="modfile"><h4>Modified: trunk/PerformanceTests/DoYouEvenBench/resources/todomvc/architecture-examples/emberjs/js/models/todo.js (163428 => 163429)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/PerformanceTests/DoYouEvenBench/resources/todomvc/architecture-examples/emberjs/js/models/todo.js        2014-02-05 05:32:21 UTC (rev 163428)
+++ trunk/PerformanceTests/DoYouEvenBench/resources/todomvc/architecture-examples/emberjs/js/models/todo.js        2014-02-05 05:36:04 UTC (rev 163429)
</span><span class="lines">@@ -1,13 +1,7 @@
</span><del>-/*global Todos DS Ember */
</del><ins>+/*global Todos, DS */
</ins><span class="cx"> 'use strict';
</span><span class="cx">
</span><span class="cx"> Todos.Todo = DS.Model.extend({
</span><span class="cx"> title: DS.attr('string'),
</span><del>- isCompleted: DS.attr('boolean'),
-
- todoDidChange: function () {
- Ember.run.once(this, function () {
- this.get('store').commit();
- });
- }.observes('isCompleted', 'title')
</del><ins>+ isCompleted: DS.attr('boolean')
</ins><span class="cx"> });
</span></span></pre></div>
<a id="trunkPerformanceTestsDoYouEvenBenchresourcestodomvcarchitectureexamplesemberjsjsrouterjs"></a>
<div class="modfile"><h4>Modified: trunk/PerformanceTests/DoYouEvenBench/resources/todomvc/architecture-examples/emberjs/js/router.js (163428 => 163429)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/PerformanceTests/DoYouEvenBench/resources/todomvc/architecture-examples/emberjs/js/router.js        2014-02-05 05:32:21 UTC (rev 163428)
+++ trunk/PerformanceTests/DoYouEvenBench/resources/todomvc/architecture-examples/emberjs/js/router.js        2014-02-05 05:36:04 UTC (rev 163429)
</span><span class="lines">@@ -1,46 +1,41 @@
</span><del>-/*global Todos Ember */
</del><ins>+/*global Ember, Todos */
</ins><span class="cx"> 'use strict';
</span><span class="cx">
</span><span class="cx"> Todos.Router.map(function () {
</span><span class="cx"> this.resource('todos', { path: '/' }, function () {
</span><del>- this.route('active');
- this.route('completed');
</del><ins>+ this.route('active');
+ this.route('completed');
</ins><span class="cx"> });
</span><span class="cx"> });
</span><span class="cx">
</span><span class="cx"> Todos.TodosRoute = Ember.Route.extend({
</span><span class="cx"> model: function () {
</span><del>- return Todos.Todo.find();
</del><ins>+ return this.store.find('todo');
</ins><span class="cx"> }
</span><span class="cx"> });
</span><span class="cx">
</span><span class="cx"> Todos.TodosIndexRoute = Ember.Route.extend({
</span><span class="cx"> setupController: function () {
</span><del>- var todos = Todos.Todo.find();
- this.controllerFor('todos').set('filteredTodos', todos);
</del><ins>+ this.controllerFor('todos').set('filteredTodos', this.modelFor('todos'));
</ins><span class="cx"> }
</span><span class="cx"> });
</span><span class="cx">
</span><span class="cx"> Todos.TodosActiveRoute = Ember.Route.extend({
</span><span class="cx"> setupController: function () {
</span><del>- var todos = Todos.Todo.filter(function (todo) {
- if (!todo.get('isCompleted')) {
- return true;
- }
- });
</del><ins>+ var todos = this.store.filter('todo', function (todo) {
+ return !todo.get('isCompleted');
+ });
</ins><span class="cx">
</span><del>- this.controllerFor('todos').set('filteredTodos', todos);
</del><ins>+ this.controllerFor('todos').set('filteredTodos', todos);
</ins><span class="cx"> }
</span><span class="cx"> });
</span><span class="cx">
</span><span class="cx"> Todos.TodosCompletedRoute = Ember.Route.extend({
</span><span class="cx"> setupController: function () {
</span><del>- var todos = Todos.Todo.filter(function (todo) {
- if (todo.get('isCompleted')) {
- return true;
- }
- });
</del><ins>+ var todos = this.store.filter('todo', function (todo) {
+ return todo.get('isCompleted');
+ });
</ins><span class="cx">
</span><del>- this.controllerFor('todos').set('filteredTodos', todos);
</del><ins>+ this.controllerFor('todos').set('filteredTodos', todos);
</ins><span class="cx"> }
</span><span class="cx"> });
</span></span></pre></div>
<a id="trunkPerformanceTestsDoYouEvenBenchresourcestodomvcarchitectureexamplesemberjsjsviewsedit_todo_viewjs"></a>
<div class="modfile"><h4>Modified: trunk/PerformanceTests/DoYouEvenBench/resources/todomvc/architecture-examples/emberjs/js/views/edit_todo_view.js (163428 => 163429)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/PerformanceTests/DoYouEvenBench/resources/todomvc/architecture-examples/emberjs/js/views/edit_todo_view.js        2014-02-05 05:32:21 UTC (rev 163428)
+++ trunk/PerformanceTests/DoYouEvenBench/resources/todomvc/architecture-examples/emberjs/js/views/edit_todo_view.js        2014-02-05 05:36:04 UTC (rev 163429)
</span><span class="lines">@@ -1,28 +1,12 @@
</span><del>-/*global Todos Ember */
</del><ins>+/*global Todos, Ember */
</ins><span class="cx"> 'use strict';
</span><span class="cx">
</span><span class="cx"> Todos.EditTodoView = Ember.TextField.extend({
</span><del>- classNames: ['edit'],
-
- valueBinding: 'todo.title',
-
- change: function () {
- var value = this.get('value');
-
- if (Ember.isEmpty(value)) {
- this.get('controller').removeTodo();
- }
- },
-
- focusOut: function () {
- this.set('controller.isEditing', false);
- },
-
- insertNewline: function () {
- this.set('controller.isEditing', false);
- },
-
- didInsertElement: function () {
- this.$().focus();
- }
</del><ins>+ focusOnInsert: function () {
+ // Re-set input value to get rid of a reduntant text selection
+ this.$().val(this.$().val());
+ this.$().focus();
+ }.on('didInsertElement')
</ins><span class="cx"> });
</span><ins>+
+Ember.Handlebars.helper('edit-todo', Todos.EditTodoView);
</ins></span></pre></div>
<a id="trunkPerformanceTestsDoYouEvenBenchresourcestodomvcarchitectureexamplesemberjsjsviewstodos_viewjs"></a>
<div class="addfile"><h4>Added: trunk/PerformanceTests/DoYouEvenBench/resources/todomvc/architecture-examples/emberjs/js/views/todos_view.js (0 => 163429)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/PerformanceTests/DoYouEvenBench/resources/todomvc/architecture-examples/emberjs/js/views/todos_view.js         (rev 0)
+++ trunk/PerformanceTests/DoYouEvenBench/resources/todomvc/architecture-examples/emberjs/js/views/todos_view.js        2014-02-05 05:36:04 UTC (rev 163429)
</span><span class="lines">@@ -0,0 +1,8 @@
</span><ins>+/*global Todos, Ember */
+'use strict';
+
+Todos.TodosView = Ember.View.extend({
+ focusInput: function () {
+ this.$('#new-todo').focus();
+ }.on('didInsertElement')
+});
</ins></span></pre></div>
<a id="trunkPerformanceTestsDoYouEvenBenchresourcestodomvcarchitectureexamplesemberjsreadmemd"></a>
<div class="modfile"><h4>Modified: trunk/PerformanceTests/DoYouEvenBench/resources/todomvc/architecture-examples/emberjs/readme.md (163428 => 163429)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/PerformanceTests/DoYouEvenBench/resources/todomvc/architecture-examples/emberjs/readme.md        2014-02-05 05:32:21 UTC (rev 163428)
+++ trunk/PerformanceTests/DoYouEvenBench/resources/todomvc/architecture-examples/emberjs/readme.md        2014-02-05 05:36:04 UTC (rev 163429)
</span><span class="lines">@@ -21,6 +21,7 @@
</span><span class="cx">
</span><span class="cx"> * [Getting Into Ember.js](http://net.tutsplus.com/tutorials/javascript-ajax/getting-into-ember-js)
</span><span class="cx"> * [EmberWatch](http://emberwatch.com)
</span><ins>+* [CodeSchool course Warming Up With Ember.js](https://www.codeschool.com/courses/warming-up-with-emberjs)
</ins><span class="cx">
</span><span class="cx"> Get help from other Ember.js users:
</span><span class="cx">
</span></span></pre></div>
<a id="trunkPerformanceTestsDoYouEvenBenchresourcestodomvcarchitectureexamplesemberjstesthtml"></a>
<div class="modfile"><h4>Modified: trunk/PerformanceTests/DoYouEvenBench/resources/todomvc/architecture-examples/emberjs/test.html (163428 => 163429)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/PerformanceTests/DoYouEvenBench/resources/todomvc/architecture-examples/emberjs/test.html        2014-02-05 05:32:21 UTC (rev 163428)
+++ trunk/PerformanceTests/DoYouEvenBench/resources/todomvc/architecture-examples/emberjs/test.html        2014-02-05 05:36:04 UTC (rev 163429)
</span><span class="lines">@@ -9,49 +9,49 @@
</span><span class="cx"> title: 'SimpleEmberJS',
</span><span class="cx"> url: 'index.html',
</span><span class="cx"> prepare: function (contentWindow, contentDocument) {
</span><del>- contentWindow.Todos.Store = contentWindow.DS.Store.extend({
- revision: 12,
- adapter: 'Todos.LSAdapter',
- commit: function () { }
- });
</del><ins>+ contentWindow.Todos.Store = contentWindow.DS.Store.extend({
+ revision: 12,
+ adapter: 'Todos.LSAdapter',
+ commit: function () { }
+ });
</ins><span class="cx">
</span><del>- var promise = new SimplePromise;
</del><ins>+ var promise = new SimplePromise;
</ins><span class="cx">
</span><del>- function resolveIfReady() {
- if (contentDocument.querySelector('#new-todo'))
- return promise.resolve();
- setTimeout(resolveIfReady, 10);
- }
- resolveIfReady();
</del><ins>+ function resolveIfReady() {
+ if (contentDocument.querySelector('#new-todo'))
+ return promise.resolve();
+ setTimeout(resolveIfReady, 10);
+ }
+ resolveIfReady();
</ins><span class="cx">
</span><del>- return promise;
</del><ins>+ return promise;
</ins><span class="cx"> },
</span><span class="cx"> testSteps: function (contentWindow, contentDocument) {
</span><del>- contentDocument.querySelector('#new-todo').focus();
- var views = contentWindow.Ember.View.views;
- var emberRun = contentWindow.Ember.run;
- var numberOfItemsToAdd = 100;
- return [
- ['Adding' + numberOfItemsToAdd + 'Items', function () {
- for (var i = 0; i < numberOfItemsToAdd; i++) {
- emberRun(function () { views["new-todo"].set('value', 'Something to do'); });
- emberRun(function () { views["new-todo"].insertNewline(document.createEvent('Event')); });
</del><ins>+ contentDocument.querySelector('#new-todo').focus();
+ var views = contentWindow.Ember.View.views;
+ var emberRun = contentWindow.Ember.run;
+ var numberOfItemsToAdd = 100;
+ return [
+ ['Adding' + numberOfItemsToAdd + 'Items', function () {
+ for (var i = 0; i < numberOfItemsToAdd; i++) {
+ emberRun(function () { views["new-todo"].set('value', 'Something to do'); });
+ emberRun(function () { views["new-todo"].insertNewline(document.createEvent('Event')); });
+ }
+ }],
+ ['CompletingAllItems', function () {
+ var checkboxes = contentDocument.querySelectorAll('.ember-checkbox');
+ for (var i = 0; i < checkboxes.length; i++) {
+ var view = views[checkboxes[i].id];
+ emberRun(function () { view.set('checked', true); });
+ }
+ }],
+ ['DeletingItems', function () {
+ var deleteButtons = contentDocument.querySelectorAll('.destroy');
+ for (var i = 0; i < deleteButtons.length; i++)
+ emberRun(function () { deleteButtons[i].click(); });
+ }],
+ ];
</ins><span class="cx"> }
</span><del>- }],
- ['CompletingAllItems', function () {
- var checkboxes = contentDocument.querySelectorAll('.ember-checkbox');
- for (var i = 0; i < checkboxes.length; i++) {
- var view = views[checkboxes[i].id];
- emberRun(function () { view.set('checked', true); });
- }
- }],
- ['DeletingItems', function () {
- var deleteButtons = contentDocument.querySelectorAll('.destroy');
- for (var i = 0; i < deleteButtons.length; i++)
- emberRun(function () { deleteButtons[i].click(); });
- }],
- ];
- }
</del><span class="cx"> });
</span><span class="cx">
</span><span class="cx"> </script>
</span></span></pre>
</div>
</div>
</body>
</html>