Sandbox-commits
Threads by month
- ----- 2026 -----
- June
- May
- April
- March
- February
- January
- ----- 2025 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2024 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2023 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2022 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2021 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2020 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2019 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2018 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2017 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2016 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2015 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2014 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2013 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2012 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2011 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2010 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2009 -----
- December
- November
- October
- September
- August
April 2014
- 3 participants
- 9 discussions
Author: dcosse
Date: 2014-04-24 10:17:45 +0200 (Thu, 24 Apr 2014)
New Revision: 706
Url: http://forge.nuiton.org/projects/sandbox/repository/revisions/706
Log:
add bug explanation on readme
Modified:
hibernate-list-idx-bug/README.txt
Modified: hibernate-list-idx-bug/README.txt
===================================================================
--- hibernate-list-idx-bug/README.txt 2014-04-23 16:24:25 UTC (rev 705)
+++ hibernate-list-idx-bug/README.txt 2014-04-24 08:17:45 UTC (rev 706)
@@ -5,7 +5,25 @@
Run with
$ mvn clean package
-The unit test is expecting an exception which is not thrown in Hibernate 4.3.5.Final
+example model:
+ Building
+ | 1
+ |
+ |
+ v 0..*
+ Flat <<abstract>>
+ ^
+ |
+ ----------
+ | |
+ Loft Suite
+
+
+The unit test will throw an exception:
+
ERROR: Table "FLAT" not found; SQL statement:
update Flat set building=?, building_idx=? where flatId=? [42102-177]
+
+This is due to used of union-subclass, is that case indexes have to be created on concrete entities tables as Loft and Suite
+and not Flat for this example.
1
0
r705 - in hibernate-list-idx-bug: . src/main/java/org/nuiton/entities src/main/resources/org/nuiton/entities src/test/java/org/nuiton/entities src/test/resources
by dcosse@users.nuiton.org 23 Apr '14
by dcosse@users.nuiton.org 23 Apr '14
23 Apr '14
Author: dcosse
Date: 2014-04-23 18:24:25 +0200 (Wed, 23 Apr 2014)
New Revision: 705
Url: http://forge.nuiton.org/projects/sandbox/repository/revisions/705
Log:
cleaning
Modified:
hibernate-list-idx-bug/LICENSE.txt
hibernate-list-idx-bug/README.txt
hibernate-list-idx-bug/changelog.txt
hibernate-list-idx-bug/pom.xml
hibernate-list-idx-bug/src/main/java/org/nuiton/entities/Building.java
hibernate-list-idx-bug/src/main/java/org/nuiton/entities/Flat.java
hibernate-list-idx-bug/src/main/java/org/nuiton/entities/Loft.java
hibernate-list-idx-bug/src/main/java/org/nuiton/entities/Suite.java
hibernate-list-idx-bug/src/main/resources/org/nuiton/entities/Building.hbm.xml
hibernate-list-idx-bug/src/main/resources/org/nuiton/entities/Flat.hbm.xml
hibernate-list-idx-bug/src/main/resources/org/nuiton/entities/Loft.hbm.xml
hibernate-list-idx-bug/src/main/resources/org/nuiton/entities/Suite.hbm.xml
hibernate-list-idx-bug/src/test/java/org/nuiton/entities/AppTest.java
hibernate-list-idx-bug/src/test/resources/hibernate.cfg.xml
Property changes on: hibernate-list-idx-bug/LICENSE.txt
___________________________________________________________________
Added: svn:keywords
+ Author Date Id Revision HeadURL
Added: svn:eol-style
+ native
Modified: hibernate-list-idx-bug/README.txt
===================================================================
--- hibernate-list-idx-bug/README.txt 2014-04-23 15:53:09 UTC (rev 704)
+++ hibernate-list-idx-bug/README.txt 2014-04-23 16:24:25 UTC (rev 705)
@@ -1,5 +1,4 @@
-Test case for https://hibernate.atlassian.net/browse/HHH-8109
-
+Test case for bug on Hibernate union-subclass
HOWTO
-----
Property changes on: hibernate-list-idx-bug/README.txt
___________________________________________________________________
Added: svn:keywords
+ Author Date Id Revision HeadURL
Added: svn:eol-style
+ native
Property changes on: hibernate-list-idx-bug/changelog.txt
___________________________________________________________________
Added: svn:keywords
+ Author Date Id Revision HeadURL
Added: svn:eol-style
+ native
Modified: hibernate-list-idx-bug/pom.xml
===================================================================
--- hibernate-list-idx-bug/pom.xml 2014-04-23 15:53:09 UTC (rev 704)
+++ hibernate-list-idx-bug/pom.xml 2014-04-23 16:24:25 UTC (rev 705)
@@ -22,7 +22,7 @@
<!-- *** Project Information ************************************* -->
<!-- ************************************************************* -->
- <name>Test case for ???</name>
+ <name>Test case for bug on Hibernate union-subclass</name>
<inceptionYear>2014</inceptionYear>
<licenses>
@@ -96,12 +96,6 @@
<version>${hibernateVersion}</version>
</dependency>
- <dependency>
- <groupId>org.hibernate</groupId>
- <artifactId>hibernate-entitymanager</artifactId>
- <version>${hibernateVersion}</version>
- </dependency>
-
<!-- Other libraries -->
<dependency>
<groupId>org.slf4j</groupId>
@@ -109,17 +103,6 @@
<version>1.7.6</version>
</dependency>
- <dependency>
- <groupId>javassist</groupId>
- <artifactId>javassist</artifactId>
- <version>3.12.1.GA</version>
- </dependency>
-
- <dependency>
- <groupId>com.google.guava</groupId>
- <artifactId>guava</artifactId>
- </dependency>
-
<!-- Tests -->
<dependency>
<groupId>junit</groupId>
Property changes on: hibernate-list-idx-bug/pom.xml
___________________________________________________________________
Added: svn:keywords
+ Author Date Id Revision HeadURL
Added: svn:eol-style
+ native
Modified: hibernate-list-idx-bug/src/main/java/org/nuiton/entities/Building.java
===================================================================
--- hibernate-list-idx-bug/src/main/java/org/nuiton/entities/Building.java 2014-04-23 15:53:09 UTC (rev 704)
+++ hibernate-list-idx-bug/src/main/java/org/nuiton/entities/Building.java 2014-04-23 16:24:25 UTC (rev 705)
@@ -1,5 +1,29 @@
package org.nuiton.entities;
+/*
+ * #%L
+ * Test case for bug on Hibernate union-subclass
+ * $Id$
+ * $HeadURL$
+ * %%
+ * Copyright (C) 2014 Code Lutin
+ * %%
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Lesser Public License for more details.
+ *
+ * You should have received a copy of the GNU General Lesser Public
+ * License along with this program. If not, see
+ * <http://www.gnu.org/licenses/lgpl-3.0.html>.
+ * #L%
+ */
+
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.Id;
Property changes on: hibernate-list-idx-bug/src/main/java/org/nuiton/entities/Building.java
___________________________________________________________________
Added: svn:keywords
+ Author Date Id Revision HeadURL
Added: svn:eol-style
+ native
Modified: hibernate-list-idx-bug/src/main/java/org/nuiton/entities/Flat.java
===================================================================
--- hibernate-list-idx-bug/src/main/java/org/nuiton/entities/Flat.java 2014-04-23 15:53:09 UTC (rev 704)
+++ hibernate-list-idx-bug/src/main/java/org/nuiton/entities/Flat.java 2014-04-23 16:24:25 UTC (rev 705)
@@ -1,5 +1,29 @@
package org.nuiton.entities;
+/*
+ * #%L
+ * Test case for bug on Hibernate union-subclass
+ * $Id$
+ * $HeadURL$
+ * %%
+ * Copyright (C) 2014 Code Lutin
+ * %%
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Lesser Public License for more details.
+ *
+ * You should have received a copy of the GNU General Lesser Public
+ * License along with this program. If not, see
+ * <http://www.gnu.org/licenses/lgpl-3.0.html>.
+ * #L%
+ */
+
import java.io.Serializable;
/**
Property changes on: hibernate-list-idx-bug/src/main/java/org/nuiton/entities/Flat.java
___________________________________________________________________
Added: svn:keywords
+ Author Date Id Revision HeadURL
Added: svn:eol-style
+ native
Modified: hibernate-list-idx-bug/src/main/java/org/nuiton/entities/Loft.java
===================================================================
--- hibernate-list-idx-bug/src/main/java/org/nuiton/entities/Loft.java 2014-04-23 15:53:09 UTC (rev 704)
+++ hibernate-list-idx-bug/src/main/java/org/nuiton/entities/Loft.java 2014-04-23 16:24:25 UTC (rev 705)
@@ -1,4 +1,28 @@
package org.nuiton.entities;
+
+/*
+ * #%L
+ * Test case for bug on Hibernate union-subclass
+ * $Id$
+ * $HeadURL$
+ * %%
+ * Copyright (C) 2014 Code Lutin
+ * %%
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Lesser Public License for more details.
+ *
+ * You should have received a copy of the GNU General Lesser Public
+ * License along with this program. If not, see
+ * <http://www.gnu.org/licenses/lgpl-3.0.html>.
+ * #L%
+ */
import java.io.Serializable;
/**
Property changes on: hibernate-list-idx-bug/src/main/java/org/nuiton/entities/Loft.java
___________________________________________________________________
Added: svn:keywords
+ Author Date Id Revision HeadURL
Added: svn:eol-style
+ native
Modified: hibernate-list-idx-bug/src/main/java/org/nuiton/entities/Suite.java
===================================================================
--- hibernate-list-idx-bug/src/main/java/org/nuiton/entities/Suite.java 2014-04-23 15:53:09 UTC (rev 704)
+++ hibernate-list-idx-bug/src/main/java/org/nuiton/entities/Suite.java 2014-04-23 16:24:25 UTC (rev 705)
@@ -1,5 +1,29 @@
package org.nuiton.entities;
+/*
+ * #%L
+ * Test case for bug on Hibernate union-subclass
+ * $Id$
+ * $HeadURL$
+ * %%
+ * Copyright (C) 2014 Code Lutin
+ * %%
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Lesser Public License for more details.
+ *
+ * You should have received a copy of the GNU General Lesser Public
+ * License along with this program. If not, see
+ * <http://www.gnu.org/licenses/lgpl-3.0.html>.
+ * #L%
+ */
+
import java.io.Serializable;
/**
Property changes on: hibernate-list-idx-bug/src/main/java/org/nuiton/entities/Suite.java
___________________________________________________________________
Added: svn:keywords
+ Author Date Id Revision HeadURL
Added: svn:eol-style
+ native
Modified: hibernate-list-idx-bug/src/main/resources/org/nuiton/entities/Building.hbm.xml
===================================================================
--- hibernate-list-idx-bug/src/main/resources/org/nuiton/entities/Building.hbm.xml 2014-04-23 15:53:09 UTC (rev 704)
+++ hibernate-list-idx-bug/src/main/resources/org/nuiton/entities/Building.hbm.xml 2014-04-23 16:24:25 UTC (rev 705)
@@ -1,4 +1,28 @@
<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ #%L
+ Test case for bug on Hibernate union-subclass
+ $Id$
+ $HeadURL$
+ %%
+ Copyright (C) 2014 Code Lutin
+ %%
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU Lesser General Public License as
+ published by the Free Software Foundation, either version 3 of the
+ License, or (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Lesser Public License for more details.
+
+ You should have received a copy of the GNU General Lesser Public
+ License along with this program. If not, see
+ <http://www.gnu.org/licenses/lgpl-3.0.html>.
+ #L%
+ -->
+
<hibernate-mapping xmlns="http://www.hibernate.org/xsd/hibernate-mapping"
xsi:schemaLocation="http://www.hibernate.org/xsd/hibernate-mapping classpath://org/hibernate/hibernate-mapping-4.0.xsd"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
Property changes on: hibernate-list-idx-bug/src/main/resources/org/nuiton/entities/Building.hbm.xml
___________________________________________________________________
Added: svn:keywords
+ Author Date Id Revision HeadURL
Added: svn:eol-style
+ native
Modified: hibernate-list-idx-bug/src/main/resources/org/nuiton/entities/Flat.hbm.xml
===================================================================
--- hibernate-list-idx-bug/src/main/resources/org/nuiton/entities/Flat.hbm.xml 2014-04-23 15:53:09 UTC (rev 704)
+++ hibernate-list-idx-bug/src/main/resources/org/nuiton/entities/Flat.hbm.xml 2014-04-23 16:24:25 UTC (rev 705)
@@ -1,4 +1,28 @@
<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ #%L
+ Test case for bug on Hibernate union-subclass
+ $Id$
+ $HeadURL$
+ %%
+ Copyright (C) 2014 Code Lutin
+ %%
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU Lesser General Public License as
+ published by the Free Software Foundation, either version 3 of the
+ License, or (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Lesser Public License for more details.
+
+ You should have received a copy of the GNU General Lesser Public
+ License along with this program. If not, see
+ <http://www.gnu.org/licenses/lgpl-3.0.html>.
+ #L%
+ -->
+
<hibernate-mapping xmlns="http://www.hibernate.org/xsd/hibernate-mapping"
xsi:schemaLocation="http://www.hibernate.org/xsd/hibernate-mapping classpath://org/hibernate/hibernate-mapping-4.0.xsd"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
Property changes on: hibernate-list-idx-bug/src/main/resources/org/nuiton/entities/Flat.hbm.xml
___________________________________________________________________
Added: svn:keywords
+ Author Date Id Revision HeadURL
Added: svn:eol-style
+ native
Modified: hibernate-list-idx-bug/src/main/resources/org/nuiton/entities/Loft.hbm.xml
===================================================================
--- hibernate-list-idx-bug/src/main/resources/org/nuiton/entities/Loft.hbm.xml 2014-04-23 15:53:09 UTC (rev 704)
+++ hibernate-list-idx-bug/src/main/resources/org/nuiton/entities/Loft.hbm.xml 2014-04-23 16:24:25 UTC (rev 705)
@@ -1,4 +1,28 @@
<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ #%L
+ Test case for bug on Hibernate union-subclass
+ $Id$
+ $HeadURL$
+ %%
+ Copyright (C) 2014 Code Lutin
+ %%
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU Lesser General Public License as
+ published by the Free Software Foundation, either version 3 of the
+ License, or (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Lesser Public License for more details.
+
+ You should have received a copy of the GNU General Lesser Public
+ License along with this program. If not, see
+ <http://www.gnu.org/licenses/lgpl-3.0.html>.
+ #L%
+ -->
+
<hibernate-mapping xmlns="http://www.hibernate.org/xsd/hibernate-mapping"
xsi:schemaLocation="http://www.hibernate.org/xsd/hibernate-mapping classpath://org/hibernate/hibernate-mapping-4.0.xsd"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
Property changes on: hibernate-list-idx-bug/src/main/resources/org/nuiton/entities/Loft.hbm.xml
___________________________________________________________________
Added: svn:keywords
+ Author Date Id Revision HeadURL
Added: svn:eol-style
+ native
Modified: hibernate-list-idx-bug/src/main/resources/org/nuiton/entities/Suite.hbm.xml
===================================================================
--- hibernate-list-idx-bug/src/main/resources/org/nuiton/entities/Suite.hbm.xml 2014-04-23 15:53:09 UTC (rev 704)
+++ hibernate-list-idx-bug/src/main/resources/org/nuiton/entities/Suite.hbm.xml 2014-04-23 16:24:25 UTC (rev 705)
@@ -1,4 +1,28 @@
<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ #%L
+ Test case for bug on Hibernate union-subclass
+ $Id$
+ $HeadURL$
+ %%
+ Copyright (C) 2014 Code Lutin
+ %%
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU Lesser General Public License as
+ published by the Free Software Foundation, either version 3 of the
+ License, or (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Lesser Public License for more details.
+
+ You should have received a copy of the GNU General Lesser Public
+ License along with this program. If not, see
+ <http://www.gnu.org/licenses/lgpl-3.0.html>.
+ #L%
+ -->
+
<hibernate-mapping xmlns="http://www.hibernate.org/xsd/hibernate-mapping"
xsi:schemaLocation="http://www.hibernate.org/xsd/hibernate-mapping classpath://org/hibernate/hibernate-mapping-4.0.xsd"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
Property changes on: hibernate-list-idx-bug/src/main/resources/org/nuiton/entities/Suite.hbm.xml
___________________________________________________________________
Added: svn:keywords
+ Author Date Id Revision HeadURL
Added: svn:eol-style
+ native
Modified: hibernate-list-idx-bug/src/test/java/org/nuiton/entities/AppTest.java
===================================================================
--- hibernate-list-idx-bug/src/test/java/org/nuiton/entities/AppTest.java 2014-04-23 15:53:09 UTC (rev 704)
+++ hibernate-list-idx-bug/src/test/java/org/nuiton/entities/AppTest.java 2014-04-23 16:24:25 UTC (rev 705)
@@ -15,24 +15,23 @@
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Lesser Public License for more details.
- *
+ *
* You should have received a copy of the GNU General Lesser Public
* License along with this program. If not, see
* <http://www.gnu.org/licenses/lgpl-3.0.html>.
* #L%
*/
-import com.google.common.collect.Lists;
import org.hibernate.SessionFactory;
import org.hibernate.Transaction;
import org.hibernate.boot.registry.StandardServiceRegistryBuilder;
import org.hibernate.cfg.Configuration;
import org.hibernate.service.ServiceRegistry;
-import org.hibernate.service.ServiceRegistryBuilder;
import org.junit.Assert;
import org.junit.Test;
import javax.persistence.PersistenceException;
+import java.util.ArrayList;
import java.util.List;
public class AppTest {
@@ -46,41 +45,40 @@
ServiceRegistry serviceRegistry = serviceRegistryBuilder.applySettings(
configuration.getProperties()).build();
SessionFactory sessionFactory = configuration.buildSessionFactory(serviceRegistry);
- org.hibernate.Session sess = sessionFactory.openSession();
+ org.hibernate.Session session = sessionFactory.openSession();
Loft loft = new Loft();
loft.setName("L-A");
// Create and persist a suite instance
- Transaction tx = sess.beginTransaction();
- Long loftKey = (Long)sess.save(loft);
+ Transaction tx = session.beginTransaction();
+ Long loftKey = (Long) session.save(loft);
System.out.println("loftKey:" + loftKey);
Suite suite = new Suite();
suite.setName("S-A");
// Create and persist a suite instance
- Long suiteKey = (Long)sess.save(suite);
+ Long suiteKey = (Long) session.save(suite);
System.out.println("suiteKey:" + suiteKey);
tx.commit();
- tx = sess.beginTransaction();
+ tx = session.beginTransaction();
Building building = new Building();
building.setName("B-A");
- List<Flat> flats = Lists.newArrayList();
+ List<Flat> flats = new ArrayList();
building.setFlats(flats);
flats.add(loft);
flats.add(suite);
- Long buildingKey = (Long)sess.save(building);
+ Long buildingKey = (Long) session.save(building);
System.out.println("buildingKey:" + buildingKey);
-
try {
tx.commit();
Assert.fail("Failed but should not");
} catch (PersistenceException pe) {
- sess.close();
+ session.close();
}
}
Property changes on: hibernate-list-idx-bug/src/test/java/org/nuiton/entities/AppTest.java
___________________________________________________________________
Added: svn:keywords
+ Author Date Id Revision HeadURL
Added: svn:eol-style
+ native
Modified: hibernate-list-idx-bug/src/test/resources/hibernate.cfg.xml
===================================================================
--- hibernate-list-idx-bug/src/test/resources/hibernate.cfg.xml 2014-04-23 15:53:09 UTC (rev 704)
+++ hibernate-list-idx-bug/src/test/resources/hibernate.cfg.xml 2014-04-23 16:24:25 UTC (rev 705)
@@ -1,4 +1,28 @@
<?xml version='1.0' encoding='utf-8'?>
+<!--
+ #%L
+ Test case for bug on Hibernate union-subclass
+ $Id$
+ $HeadURL$
+ %%
+ Copyright (C) 2014 Code Lutin
+ %%
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU Lesser General Public License as
+ published by the Free Software Foundation, either version 3 of the
+ License, or (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Lesser Public License for more details.
+
+ You should have received a copy of the GNU General Lesser Public
+ License along with this program. If not, see
+ <http://www.gnu.org/licenses/lgpl-3.0.html>.
+ #L%
+ -->
+
<!DOCTYPE hibernate-configuration PUBLIC
"-//Hibernate/Hibernate Configuration DTD//EN"
"http://hibernate.sourceforge.net/hibernate-configuration-3.0.dtd">
@@ -6,15 +30,17 @@
<hibernate-configuration>
<session-factory>
+ <!-- Database connection settings -->
<property name="hibernate.connection.driver_class">org.h2.Driver</property>
<property name="hibernate.connection.url">jdbc:h2:mem:db1;DB_CLOSE_DELAY=-1;MVCC=TRUE</property>
<property name="hibernate.connection.username">sa</property>
<property name="hibernate.connection.password"></property>
+ <!-- Echo all executed SQL to stdout -->
<property name="show_sql">true</property>
<property name="hbm2ddl.auto">update</property>
- <!--Liste des fichiers de Mapping hbm -->
+ <!--Mappings hbm -->
<mapping resource="org/nuiton/entities/Building.hbm.xml"/>
<mapping resource="org/nuiton/entities/Flat.hbm.xml"/>
<mapping resource="org/nuiton/entities/Loft.hbm.xml"/>
Property changes on: hibernate-list-idx-bug/src/test/resources/hibernate.cfg.xml
___________________________________________________________________
Added: svn:keywords
+ Author Date Id Revision HeadURL
Added: svn:eol-style
+ native
1
0
Author: dcosse
Date: 2014-04-23 17:53:09 +0200 (Wed, 23 Apr 2014)
New Revision: 704
Url: http://forge.nuiton.org/projects/sandbox/repository/revisions/704
Log:
export vers repository
Removed:
hibernate-list-idx-bug/.idea/
hibernate-list-idx-bug/target/
Modified:
hibernate-list-idx-bug/
Property changes on: hibernate-list-idx-bug
___________________________________________________________________
Added: svn:ignore
+ target
.idea
1
0
Author: dcosse
Date: 2014-04-23 17:39:47 +0200 (Wed, 23 Apr 2014)
New Revision: 703
Url: http://forge.nuiton.org/projects/sandbox/repository/revisions/703
Log:
export sur svn du projet
Added:
hibernate-list-idx-bug/.idea/
hibernate-list-idx-bug/.idea/.name
hibernate-list-idx-bug/.idea/compiler.xml
hibernate-list-idx-bug/.idea/copyright/
hibernate-list-idx-bug/.idea/copyright/profiles_settings.xml
hibernate-list-idx-bug/.idea/encodings.xml
hibernate-list-idx-bug/.idea/libraries/
hibernate-list-idx-bug/.idea/libraries/Maven__antlr_antlr_2_7_7.xml
hibernate-list-idx-bug/.idea/libraries/Maven__com_google_guava_guava_14_0_1.xml
hibernate-list-idx-bug/.idea/libraries/Maven__com_h2database_h2_1_4_177.xml
hibernate-list-idx-bug/.idea/libraries/Maven__dom4j_dom4j_1_6_1.xml
hibernate-list-idx-bug/.idea/libraries/Maven__javassist_javassist_3_12_1_GA.xml
hibernate-list-idx-bug/.idea/libraries/Maven__junit_junit_4_11.xml
hibernate-list-idx-bug/.idea/libraries/Maven__org_hamcrest_hamcrest_core_1_3.xml
hibernate-list-idx-bug/.idea/libraries/Maven__org_hibernate_common_hibernate_commons_annotations_4_0_4_Final.xml
hibernate-list-idx-bug/.idea/libraries/Maven__org_hibernate_hibernate_core_4_3_5_Final.xml
hibernate-list-idx-bug/.idea/libraries/Maven__org_hibernate_hibernate_entitymanager_4_3_5_Final.xml
hibernate-list-idx-bug/.idea/libraries/Maven__org_hibernate_javax_persistence_hibernate_jpa_2_1_api_1_0_0_Final.xml
hibernate-list-idx-bug/.idea/libraries/Maven__org_javassist_javassist_3_18_1_GA.xml
hibernate-list-idx-bug/.idea/libraries/Maven__org_jboss_jandex_1_1_0_Final.xml
hibernate-list-idx-bug/.idea/libraries/Maven__org_jboss_logging_jboss_logging_3_1_3_GA.xml
hibernate-list-idx-bug/.idea/libraries/Maven__org_jboss_logging_jboss_logging_annotations_1_2_0_Beta1.xml
hibernate-list-idx-bug/.idea/libraries/Maven__org_jboss_spec_javax_transaction_jboss_transaction_api_1_2_spec_1_0_0_Final.xml
hibernate-list-idx-bug/.idea/libraries/Maven__org_slf4j_slf4j_api_1_7_6.xml
hibernate-list-idx-bug/.idea/libraries/Maven__org_slf4j_slf4j_simple_1_7_6.xml
hibernate-list-idx-bug/.idea/libraries/Maven__xml_apis_xml_apis_1_0_b2.xml
hibernate-list-idx-bug/.idea/misc.xml
hibernate-list-idx-bug/.idea/modules.xml
hibernate-list-idx-bug/.idea/scopes/
hibernate-list-idx-bug/.idea/scopes/scope_settings.xml
hibernate-list-idx-bug/.idea/uiDesigner.xml
hibernate-list-idx-bug/.idea/vcs.xml
hibernate-list-idx-bug/.idea/workspace.xml
hibernate-list-idx-bug/LICENSE.txt
hibernate-list-idx-bug/README.txt
hibernate-list-idx-bug/changelog.txt
hibernate-list-idx-bug/pom.xml
hibernate-list-idx-bug/src/
hibernate-list-idx-bug/src/main/
hibernate-list-idx-bug/src/main/java/
hibernate-list-idx-bug/src/main/java/org/
hibernate-list-idx-bug/src/main/java/org/nuiton/
hibernate-list-idx-bug/src/main/java/org/nuiton/entities/
hibernate-list-idx-bug/src/main/java/org/nuiton/entities/Building.java
hibernate-list-idx-bug/src/main/java/org/nuiton/entities/Flat.java
hibernate-list-idx-bug/src/main/java/org/nuiton/entities/Loft.java
hibernate-list-idx-bug/src/main/java/org/nuiton/entities/Suite.java
hibernate-list-idx-bug/src/main/resources/
hibernate-list-idx-bug/src/main/resources/org/
hibernate-list-idx-bug/src/main/resources/org/nuiton/
hibernate-list-idx-bug/src/main/resources/org/nuiton/entities/
hibernate-list-idx-bug/src/main/resources/org/nuiton/entities/Building.hbm.xml
hibernate-list-idx-bug/src/main/resources/org/nuiton/entities/Flat.hbm.xml
hibernate-list-idx-bug/src/main/resources/org/nuiton/entities/Loft.hbm.xml
hibernate-list-idx-bug/src/main/resources/org/nuiton/entities/Suite.hbm.xml
hibernate-list-idx-bug/src/test/
hibernate-list-idx-bug/src/test/java/
hibernate-list-idx-bug/src/test/java/org/
hibernate-list-idx-bug/src/test/java/org/nuiton/
hibernate-list-idx-bug/src/test/java/org/nuiton/entities/
hibernate-list-idx-bug/src/test/java/org/nuiton/entities/AppTest.java
hibernate-list-idx-bug/src/test/resources/
hibernate-list-idx-bug/src/test/resources/META-INF/
hibernate-list-idx-bug/src/test/resources/hibernate.cfg.xml
hibernate-list-idx-bug/target/
hibernate-list-idx-bug/target/antrun/
hibernate-list-idx-bug/target/antrun/build-main.xml
hibernate-list-idx-bug/target/classes/
hibernate-list-idx-bug/target/classes/org/
hibernate-list-idx-bug/target/classes/org/nuiton/
hibernate-list-idx-bug/target/classes/org/nuiton/entities/
hibernate-list-idx-bug/target/classes/org/nuiton/entities/Building.class
hibernate-list-idx-bug/target/classes/org/nuiton/entities/Building.hbm.xml
hibernate-list-idx-bug/target/classes/org/nuiton/entities/Flat.class
hibernate-list-idx-bug/target/classes/org/nuiton/entities/Flat.hbm.xml
hibernate-list-idx-bug/target/classes/org/nuiton/entities/Loft.class
hibernate-list-idx-bug/target/classes/org/nuiton/entities/Loft.hbm.xml
hibernate-list-idx-bug/target/classes/org/nuiton/entities/Suite.class
hibernate-list-idx-bug/target/classes/org/nuiton/entities/Suite.hbm.xml
hibernate-list-idx-bug/target/generated-sources/
hibernate-list-idx-bug/target/generated-sources/annotations/
hibernate-list-idx-bug/target/generated-test-sources/
hibernate-list-idx-bug/target/generated-test-sources/test-annotations/
hibernate-list-idx-bug/target/surefire-reports/
hibernate-list-idx-bug/target/surefire-reports/TEST-org.nuiton.entities.AppTest.xml
hibernate-list-idx-bug/target/surefire-reports/org.nuiton.entities.AppTest.txt
hibernate-list-idx-bug/target/surefire-workdir/
hibernate-list-idx-bug/target/test-classes/
hibernate-list-idx-bug/target/test-classes/hibernate.cfg.xml
hibernate-list-idx-bug/target/test-classes/org/
hibernate-list-idx-bug/target/test-classes/org/nuiton/
hibernate-list-idx-bug/target/test-classes/org/nuiton/entities/
hibernate-list-idx-bug/target/test-classes/org/nuiton/entities/AppTest.class
Added: hibernate-list-idx-bug/.idea/.name
===================================================================
--- hibernate-list-idx-bug/.idea/.name (rev 0)
+++ hibernate-list-idx-bug/.idea/.name 2014-04-23 15:39:47 UTC (rev 703)
@@ -0,0 +1 @@
+hhh8109
\ No newline at end of file
Added: hibernate-list-idx-bug/.idea/compiler.xml
===================================================================
--- hibernate-list-idx-bug/.idea/compiler.xml (rev 0)
+++ hibernate-list-idx-bug/.idea/compiler.xml 2014-04-23 15:39:47 UTC (rev 703)
@@ -0,0 +1,35 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project version="4">
+ <component name="CompilerConfiguration">
+ <option name="DEFAULT_COMPILER" value="Javac" />
+ <resourceExtensions />
+ <wildcardResourcePatterns>
+ <entry name="!?*.java" />
+ <entry name="!?*.form" />
+ <entry name="!?*.class" />
+ <entry name="!?*.groovy" />
+ <entry name="!?*.scala" />
+ <entry name="!?*.flex" />
+ <entry name="!?*.kt" />
+ <entry name="!?*.clj" />
+ </wildcardResourcePatterns>
+ <annotationProcessing>
+ <profile default="true" name="Default" enabled="false">
+ <processorPath useClasspath="true" />
+ </profile>
+ <profile default="false" name="Maven default annotation processors profile" enabled="true">
+ <sourceOutputDir name="target/generated-sources/annotations" />
+ <sourceTestOutputDir name="target/generated-test-sources/test-annotations" />
+ <outputRelativeToContentRoot value="true" />
+ <processorPath useClasspath="true" />
+ <module name="hhh0000" />
+ <module name="hhh8109" />
+ </profile>
+ </annotationProcessing>
+ <bytecodeTargetLevel>
+ <module name="hhh0000" target="1.6" />
+ <module name="hhh8109" target="1.6" />
+ </bytecodeTargetLevel>
+ </component>
+</project>
+
Added: hibernate-list-idx-bug/.idea/copyright/profiles_settings.xml
===================================================================
--- hibernate-list-idx-bug/.idea/copyright/profiles_settings.xml (rev 0)
+++ hibernate-list-idx-bug/.idea/copyright/profiles_settings.xml 2014-04-23 15:39:47 UTC (rev 703)
@@ -0,0 +1,3 @@
+<component name="CopyrightManager">
+ <settings default="" />
+</component>
\ No newline at end of file
Added: hibernate-list-idx-bug/.idea/encodings.xml
===================================================================
--- hibernate-list-idx-bug/.idea/encodings.xml (rev 0)
+++ hibernate-list-idx-bug/.idea/encodings.xml 2014-04-23 15:39:47 UTC (rev 703)
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project version="4">
+ <component name="Encoding" useUTFGuessing="true" native2AsciiForPropertiesFiles="false">
+ <file url="file://$PROJECT_DIR$" charset="UTF-8" />
+ </component>
+</project>
+
Added: hibernate-list-idx-bug/.idea/libraries/Maven__antlr_antlr_2_7_7.xml
===================================================================
--- hibernate-list-idx-bug/.idea/libraries/Maven__antlr_antlr_2_7_7.xml (rev 0)
+++ hibernate-list-idx-bug/.idea/libraries/Maven__antlr_antlr_2_7_7.xml 2014-04-23 15:39:47 UTC (rev 703)
@@ -0,0 +1,13 @@
+<component name="libraryTable">
+ <library name="Maven: antlr:antlr:2.7.7">
+ <CLASSES>
+ <root url="jar://$MAVEN_REPOSITORY$/antlr/antlr/2.7.7/antlr-2.7.7.jar!/" />
+ </CLASSES>
+ <JAVADOC>
+ <root url="jar://$MAVEN_REPOSITORY$/antlr/antlr/2.7.7/antlr-2.7.7-javadoc.jar!/" />
+ </JAVADOC>
+ <SOURCES>
+ <root url="jar://$MAVEN_REPOSITORY$/antlr/antlr/2.7.7/antlr-2.7.7-sources.jar!/" />
+ </SOURCES>
+ </library>
+</component>
\ No newline at end of file
Added: hibernate-list-idx-bug/.idea/libraries/Maven__com_google_guava_guava_14_0_1.xml
===================================================================
--- hibernate-list-idx-bug/.idea/libraries/Maven__com_google_guava_guava_14_0_1.xml (rev 0)
+++ hibernate-list-idx-bug/.idea/libraries/Maven__com_google_guava_guava_14_0_1.xml 2014-04-23 15:39:47 UTC (rev 703)
@@ -0,0 +1,13 @@
+<component name="libraryTable">
+ <library name="Maven: com.google.guava:guava:14.0.1">
+ <CLASSES>
+ <root url="jar://$MAVEN_REPOSITORY$/com/google/guava/guava/14.0.1/guava-14.0.1.jar!/" />
+ </CLASSES>
+ <JAVADOC>
+ <root url="jar://$MAVEN_REPOSITORY$/com/google/guava/guava/14.0.1/guava-14.0.1-javadoc.jar!/" />
+ </JAVADOC>
+ <SOURCES>
+ <root url="jar://$MAVEN_REPOSITORY$/com/google/guava/guava/14.0.1/guava-14.0.1-sources.jar!/" />
+ </SOURCES>
+ </library>
+</component>
\ No newline at end of file
Added: hibernate-list-idx-bug/.idea/libraries/Maven__com_h2database_h2_1_4_177.xml
===================================================================
--- hibernate-list-idx-bug/.idea/libraries/Maven__com_h2database_h2_1_4_177.xml (rev 0)
+++ hibernate-list-idx-bug/.idea/libraries/Maven__com_h2database_h2_1_4_177.xml 2014-04-23 15:39:47 UTC (rev 703)
@@ -0,0 +1,13 @@
+<component name="libraryTable">
+ <library name="Maven: com.h2database:h2:1.4.177">
+ <CLASSES>
+ <root url="jar://$MAVEN_REPOSITORY$/com/h2database/h2/1.4.177/h2-1.4.177.jar!/" />
+ </CLASSES>
+ <JAVADOC>
+ <root url="jar://$MAVEN_REPOSITORY$/com/h2database/h2/1.4.177/h2-1.4.177-javadoc.jar!/" />
+ </JAVADOC>
+ <SOURCES>
+ <root url="jar://$MAVEN_REPOSITORY$/com/h2database/h2/1.4.177/h2-1.4.177-sources.jar!/" />
+ </SOURCES>
+ </library>
+</component>
\ No newline at end of file
Added: hibernate-list-idx-bug/.idea/libraries/Maven__dom4j_dom4j_1_6_1.xml
===================================================================
--- hibernate-list-idx-bug/.idea/libraries/Maven__dom4j_dom4j_1_6_1.xml (rev 0)
+++ hibernate-list-idx-bug/.idea/libraries/Maven__dom4j_dom4j_1_6_1.xml 2014-04-23 15:39:47 UTC (rev 703)
@@ -0,0 +1,13 @@
+<component name="libraryTable">
+ <library name="Maven: dom4j:dom4j:1.6.1">
+ <CLASSES>
+ <root url="jar://$MAVEN_REPOSITORY$/dom4j/dom4j/1.6.1/dom4j-1.6.1.jar!/" />
+ </CLASSES>
+ <JAVADOC>
+ <root url="jar://$MAVEN_REPOSITORY$/dom4j/dom4j/1.6.1/dom4j-1.6.1-javadoc.jar!/" />
+ </JAVADOC>
+ <SOURCES>
+ <root url="jar://$MAVEN_REPOSITORY$/dom4j/dom4j/1.6.1/dom4j-1.6.1-sources.jar!/" />
+ </SOURCES>
+ </library>
+</component>
\ No newline at end of file
Added: hibernate-list-idx-bug/.idea/libraries/Maven__javassist_javassist_3_12_1_GA.xml
===================================================================
--- hibernate-list-idx-bug/.idea/libraries/Maven__javassist_javassist_3_12_1_GA.xml (rev 0)
+++ hibernate-list-idx-bug/.idea/libraries/Maven__javassist_javassist_3_12_1_GA.xml 2014-04-23 15:39:47 UTC (rev 703)
@@ -0,0 +1,13 @@
+<component name="libraryTable">
+ <library name="Maven: javassist:javassist:3.12.1.GA">
+ <CLASSES>
+ <root url="jar://$MAVEN_REPOSITORY$/javassist/javassist/3.12.1.GA/javassist-3.12.1.GA.jar!/" />
+ </CLASSES>
+ <JAVADOC>
+ <root url="jar://$MAVEN_REPOSITORY$/javassist/javassist/3.12.1.GA/javassist-3.12.1.GA-javadoc.jar!/" />
+ </JAVADOC>
+ <SOURCES>
+ <root url="jar://$MAVEN_REPOSITORY$/javassist/javassist/3.12.1.GA/javassist-3.12.1.GA-sources.jar!/" />
+ </SOURCES>
+ </library>
+</component>
\ No newline at end of file
Added: hibernate-list-idx-bug/.idea/libraries/Maven__junit_junit_4_11.xml
===================================================================
--- hibernate-list-idx-bug/.idea/libraries/Maven__junit_junit_4_11.xml (rev 0)
+++ hibernate-list-idx-bug/.idea/libraries/Maven__junit_junit_4_11.xml 2014-04-23 15:39:47 UTC (rev 703)
@@ -0,0 +1,13 @@
+<component name="libraryTable">
+ <library name="Maven: junit:junit:4.11">
+ <CLASSES>
+ <root url="jar://$MAVEN_REPOSITORY$/junit/junit/4.11/junit-4.11.jar!/" />
+ </CLASSES>
+ <JAVADOC>
+ <root url="jar://$MAVEN_REPOSITORY$/junit/junit/4.11/junit-4.11-javadoc.jar!/" />
+ </JAVADOC>
+ <SOURCES>
+ <root url="jar://$MAVEN_REPOSITORY$/junit/junit/4.11/junit-4.11-sources.jar!/" />
+ </SOURCES>
+ </library>
+</component>
\ No newline at end of file
Added: hibernate-list-idx-bug/.idea/libraries/Maven__org_hamcrest_hamcrest_core_1_3.xml
===================================================================
--- hibernate-list-idx-bug/.idea/libraries/Maven__org_hamcrest_hamcrest_core_1_3.xml (rev 0)
+++ hibernate-list-idx-bug/.idea/libraries/Maven__org_hamcrest_hamcrest_core_1_3.xml 2014-04-23 15:39:47 UTC (rev 703)
@@ -0,0 +1,13 @@
+<component name="libraryTable">
+ <library name="Maven: org.hamcrest:hamcrest-core:1.3">
+ <CLASSES>
+ <root url="jar://$MAVEN_REPOSITORY$/org/hamcrest/hamcrest-core/1.3/hamcrest-core-1.3.jar!/" />
+ </CLASSES>
+ <JAVADOC>
+ <root url="jar://$MAVEN_REPOSITORY$/org/hamcrest/hamcrest-core/1.3/hamcrest-core-1.3-javadoc.jar!/" />
+ </JAVADOC>
+ <SOURCES>
+ <root url="jar://$MAVEN_REPOSITORY$/org/hamcrest/hamcrest-core/1.3/hamcrest-core-1.3-sources.jar!/" />
+ </SOURCES>
+ </library>
+</component>
\ No newline at end of file
Added: hibernate-list-idx-bug/.idea/libraries/Maven__org_hibernate_common_hibernate_commons_annotations_4_0_4_Final.xml
===================================================================
--- hibernate-list-idx-bug/.idea/libraries/Maven__org_hibernate_common_hibernate_commons_annotations_4_0_4_Final.xml (rev 0)
+++ hibernate-list-idx-bug/.idea/libraries/Maven__org_hibernate_common_hibernate_commons_annotations_4_0_4_Final.xml 2014-04-23 15:39:47 UTC (rev 703)
@@ -0,0 +1,13 @@
+<component name="libraryTable">
+ <library name="Maven: org.hibernate.common:hibernate-commons-annotations:4.0.4.Final">
+ <CLASSES>
+ <root url="jar://$MAVEN_REPOSITORY$/org/hibernate/common/hibernate-commons-annotations/4.0.4.Final/hibernate-commons-annotations-4.0.4.Final.jar!/" />
+ </CLASSES>
+ <JAVADOC>
+ <root url="jar://$MAVEN_REPOSITORY$/org/hibernate/common/hibernate-commons-annotations/4.0.4.Final/hibernate-commons-annotations-4.0.4.Final-javadoc.jar!/" />
+ </JAVADOC>
+ <SOURCES>
+ <root url="jar://$MAVEN_REPOSITORY$/org/hibernate/common/hibernate-commons-annotations/4.0.4.Final/hibernate-commons-annotations-4.0.4.Final-sources.jar!/" />
+ </SOURCES>
+ </library>
+</component>
\ No newline at end of file
Added: hibernate-list-idx-bug/.idea/libraries/Maven__org_hibernate_hibernate_core_4_3_5_Final.xml
===================================================================
--- hibernate-list-idx-bug/.idea/libraries/Maven__org_hibernate_hibernate_core_4_3_5_Final.xml (rev 0)
+++ hibernate-list-idx-bug/.idea/libraries/Maven__org_hibernate_hibernate_core_4_3_5_Final.xml 2014-04-23 15:39:47 UTC (rev 703)
@@ -0,0 +1,13 @@
+<component name="libraryTable">
+ <library name="Maven: org.hibernate:hibernate-core:4.3.5.Final">
+ <CLASSES>
+ <root url="jar://$MAVEN_REPOSITORY$/org/hibernate/hibernate-core/4.3.5.Final/hibernate-core-4.3.5.Final.jar!/" />
+ </CLASSES>
+ <JAVADOC>
+ <root url="jar://$MAVEN_REPOSITORY$/org/hibernate/hibernate-core/4.3.5.Final/hibernate-core-4.3.5.Final-javadoc.jar!/" />
+ </JAVADOC>
+ <SOURCES>
+ <root url="jar://$MAVEN_REPOSITORY$/org/hibernate/hibernate-core/4.3.5.Final/hibernate-core-4.3.5.Final-sources.jar!/" />
+ </SOURCES>
+ </library>
+</component>
\ No newline at end of file
Added: hibernate-list-idx-bug/.idea/libraries/Maven__org_hibernate_hibernate_entitymanager_4_3_5_Final.xml
===================================================================
--- hibernate-list-idx-bug/.idea/libraries/Maven__org_hibernate_hibernate_entitymanager_4_3_5_Final.xml (rev 0)
+++ hibernate-list-idx-bug/.idea/libraries/Maven__org_hibernate_hibernate_entitymanager_4_3_5_Final.xml 2014-04-23 15:39:47 UTC (rev 703)
@@ -0,0 +1,13 @@
+<component name="libraryTable">
+ <library name="Maven: org.hibernate:hibernate-entitymanager:4.3.5.Final">
+ <CLASSES>
+ <root url="jar://$MAVEN_REPOSITORY$/org/hibernate/hibernate-entitymanager/4.3.5.Final/hibernate-entitymanager-4.3.5.Final.jar!/" />
+ </CLASSES>
+ <JAVADOC>
+ <root url="jar://$MAVEN_REPOSITORY$/org/hibernate/hibernate-entitymanager/4.3.5.Final/hibernate-entitymanager-4.3.5.Final-javadoc.jar!/" />
+ </JAVADOC>
+ <SOURCES>
+ <root url="jar://$MAVEN_REPOSITORY$/org/hibernate/hibernate-entitymanager/4.3.5.Final/hibernate-entitymanager-4.3.5.Final-sources.jar!/" />
+ </SOURCES>
+ </library>
+</component>
\ No newline at end of file
Added: hibernate-list-idx-bug/.idea/libraries/Maven__org_hibernate_javax_persistence_hibernate_jpa_2_1_api_1_0_0_Final.xml
===================================================================
--- hibernate-list-idx-bug/.idea/libraries/Maven__org_hibernate_javax_persistence_hibernate_jpa_2_1_api_1_0_0_Final.xml (rev 0)
+++ hibernate-list-idx-bug/.idea/libraries/Maven__org_hibernate_javax_persistence_hibernate_jpa_2_1_api_1_0_0_Final.xml 2014-04-23 15:39:47 UTC (rev 703)
@@ -0,0 +1,13 @@
+<component name="libraryTable">
+ <library name="Maven: org.hibernate.javax.persistence:hibernate-jpa-2.1-api:1.0.0.Final">
+ <CLASSES>
+ <root url="jar://$MAVEN_REPOSITORY$/org/hibernate/javax/persistence/hibernate-jpa-2.1-api/1.0.0.Final/hibernate-jpa-2.1-api-1.0.0.Final.jar!/" />
+ </CLASSES>
+ <JAVADOC>
+ <root url="jar://$MAVEN_REPOSITORY$/org/hibernate/javax/persistence/hibernate-jpa-2.1-api/1.0.0.Final/hibernate-jpa-2.1-api-1.0.0.Final-javadoc.jar!/" />
+ </JAVADOC>
+ <SOURCES>
+ <root url="jar://$MAVEN_REPOSITORY$/org/hibernate/javax/persistence/hibernate-jpa-2.1-api/1.0.0.Final/hibernate-jpa-2.1-api-1.0.0.Final-sources.jar!/" />
+ </SOURCES>
+ </library>
+</component>
\ No newline at end of file
Added: hibernate-list-idx-bug/.idea/libraries/Maven__org_javassist_javassist_3_18_1_GA.xml
===================================================================
--- hibernate-list-idx-bug/.idea/libraries/Maven__org_javassist_javassist_3_18_1_GA.xml (rev 0)
+++ hibernate-list-idx-bug/.idea/libraries/Maven__org_javassist_javassist_3_18_1_GA.xml 2014-04-23 15:39:47 UTC (rev 703)
@@ -0,0 +1,13 @@
+<component name="libraryTable">
+ <library name="Maven: org.javassist:javassist:3.18.1-GA">
+ <CLASSES>
+ <root url="jar://$MAVEN_REPOSITORY$/org/javassist/javassist/3.18.1-GA/javassist-3.18.1-GA.jar!/" />
+ </CLASSES>
+ <JAVADOC>
+ <root url="jar://$MAVEN_REPOSITORY$/org/javassist/javassist/3.18.1-GA/javassist-3.18.1-GA-javadoc.jar!/" />
+ </JAVADOC>
+ <SOURCES>
+ <root url="jar://$MAVEN_REPOSITORY$/org/javassist/javassist/3.18.1-GA/javassist-3.18.1-GA-sources.jar!/" />
+ </SOURCES>
+ </library>
+</component>
\ No newline at end of file
Added: hibernate-list-idx-bug/.idea/libraries/Maven__org_jboss_jandex_1_1_0_Final.xml
===================================================================
--- hibernate-list-idx-bug/.idea/libraries/Maven__org_jboss_jandex_1_1_0_Final.xml (rev 0)
+++ hibernate-list-idx-bug/.idea/libraries/Maven__org_jboss_jandex_1_1_0_Final.xml 2014-04-23 15:39:47 UTC (rev 703)
@@ -0,0 +1,13 @@
+<component name="libraryTable">
+ <library name="Maven: org.jboss:jandex:1.1.0.Final">
+ <CLASSES>
+ <root url="jar://$MAVEN_REPOSITORY$/org/jboss/jandex/1.1.0.Final/jandex-1.1.0.Final.jar!/" />
+ </CLASSES>
+ <JAVADOC>
+ <root url="jar://$MAVEN_REPOSITORY$/org/jboss/jandex/1.1.0.Final/jandex-1.1.0.Final-javadoc.jar!/" />
+ </JAVADOC>
+ <SOURCES>
+ <root url="jar://$MAVEN_REPOSITORY$/org/jboss/jandex/1.1.0.Final/jandex-1.1.0.Final-sources.jar!/" />
+ </SOURCES>
+ </library>
+</component>
\ No newline at end of file
Added: hibernate-list-idx-bug/.idea/libraries/Maven__org_jboss_logging_jboss_logging_3_1_3_GA.xml
===================================================================
--- hibernate-list-idx-bug/.idea/libraries/Maven__org_jboss_logging_jboss_logging_3_1_3_GA.xml (rev 0)
+++ hibernate-list-idx-bug/.idea/libraries/Maven__org_jboss_logging_jboss_logging_3_1_3_GA.xml 2014-04-23 15:39:47 UTC (rev 703)
@@ -0,0 +1,13 @@
+<component name="libraryTable">
+ <library name="Maven: org.jboss.logging:jboss-logging:3.1.3.GA">
+ <CLASSES>
+ <root url="jar://$MAVEN_REPOSITORY$/org/jboss/logging/jboss-logging/3.1.3.GA/jboss-logging-3.1.3.GA.jar!/" />
+ </CLASSES>
+ <JAVADOC>
+ <root url="jar://$MAVEN_REPOSITORY$/org/jboss/logging/jboss-logging/3.1.3.GA/jboss-logging-3.1.3.GA-javadoc.jar!/" />
+ </JAVADOC>
+ <SOURCES>
+ <root url="jar://$MAVEN_REPOSITORY$/org/jboss/logging/jboss-logging/3.1.3.GA/jboss-logging-3.1.3.GA-sources.jar!/" />
+ </SOURCES>
+ </library>
+</component>
\ No newline at end of file
Added: hibernate-list-idx-bug/.idea/libraries/Maven__org_jboss_logging_jboss_logging_annotations_1_2_0_Beta1.xml
===================================================================
--- hibernate-list-idx-bug/.idea/libraries/Maven__org_jboss_logging_jboss_logging_annotations_1_2_0_Beta1.xml (rev 0)
+++ hibernate-list-idx-bug/.idea/libraries/Maven__org_jboss_logging_jboss_logging_annotations_1_2_0_Beta1.xml 2014-04-23 15:39:47 UTC (rev 703)
@@ -0,0 +1,13 @@
+<component name="libraryTable">
+ <library name="Maven: org.jboss.logging:jboss-logging-annotations:1.2.0.Beta1">
+ <CLASSES>
+ <root url="jar://$MAVEN_REPOSITORY$/org/jboss/logging/jboss-logging-annotations/1.2.0.Beta1/jboss-logging-annotations-1.2.0.Beta1.jar!/" />
+ </CLASSES>
+ <JAVADOC>
+ <root url="jar://$MAVEN_REPOSITORY$/org/jboss/logging/jboss-logging-annotations/1.2.0.Beta1/jboss-logging-annotations-1.2.0.Beta1-javadoc.jar!/" />
+ </JAVADOC>
+ <SOURCES>
+ <root url="jar://$MAVEN_REPOSITORY$/org/jboss/logging/jboss-logging-annotations/1.2.0.Beta1/jboss-logging-annotations-1.2.0.Beta1-sources.jar!/" />
+ </SOURCES>
+ </library>
+</component>
\ No newline at end of file
Added: hibernate-list-idx-bug/.idea/libraries/Maven__org_jboss_spec_javax_transaction_jboss_transaction_api_1_2_spec_1_0_0_Final.xml
===================================================================
--- hibernate-list-idx-bug/.idea/libraries/Maven__org_jboss_spec_javax_transaction_jboss_transaction_api_1_2_spec_1_0_0_Final.xml (rev 0)
+++ hibernate-list-idx-bug/.idea/libraries/Maven__org_jboss_spec_javax_transaction_jboss_transaction_api_1_2_spec_1_0_0_Final.xml 2014-04-23 15:39:47 UTC (rev 703)
@@ -0,0 +1,13 @@
+<component name="libraryTable">
+ <library name="Maven: org.jboss.spec.javax.transaction:jboss-transaction-api_1.2_spec:1.0.0.Final">
+ <CLASSES>
+ <root url="jar://$MAVEN_REPOSITORY$/org/jboss/spec/javax/transaction/jboss-transaction-api_1.2_spec/1.0.0.Final/jboss-transaction-api_1.2_spec-1.0.0.Final.jar!/" />
+ </CLASSES>
+ <JAVADOC>
+ <root url="jar://$MAVEN_REPOSITORY$/org/jboss/spec/javax/transaction/jboss-transaction-api_1.2_spec/1.0.0.Final/jboss-transaction-api_1.2_spec-1.0.0.Final-javadoc.jar!/" />
+ </JAVADOC>
+ <SOURCES>
+ <root url="jar://$MAVEN_REPOSITORY$/org/jboss/spec/javax/transaction/jboss-transaction-api_1.2_spec/1.0.0.Final/jboss-transaction-api_1.2_spec-1.0.0.Final-sources.jar!/" />
+ </SOURCES>
+ </library>
+</component>
\ No newline at end of file
Added: hibernate-list-idx-bug/.idea/libraries/Maven__org_slf4j_slf4j_api_1_7_6.xml
===================================================================
--- hibernate-list-idx-bug/.idea/libraries/Maven__org_slf4j_slf4j_api_1_7_6.xml (rev 0)
+++ hibernate-list-idx-bug/.idea/libraries/Maven__org_slf4j_slf4j_api_1_7_6.xml 2014-04-23 15:39:47 UTC (rev 703)
@@ -0,0 +1,13 @@
+<component name="libraryTable">
+ <library name="Maven: org.slf4j:slf4j-api:1.7.6">
+ <CLASSES>
+ <root url="jar://$MAVEN_REPOSITORY$/org/slf4j/slf4j-api/1.7.6/slf4j-api-1.7.6.jar!/" />
+ </CLASSES>
+ <JAVADOC>
+ <root url="jar://$MAVEN_REPOSITORY$/org/slf4j/slf4j-api/1.7.6/slf4j-api-1.7.6-javadoc.jar!/" />
+ </JAVADOC>
+ <SOURCES>
+ <root url="jar://$MAVEN_REPOSITORY$/org/slf4j/slf4j-api/1.7.6/slf4j-api-1.7.6-sources.jar!/" />
+ </SOURCES>
+ </library>
+</component>
\ No newline at end of file
Added: hibernate-list-idx-bug/.idea/libraries/Maven__org_slf4j_slf4j_simple_1_7_6.xml
===================================================================
--- hibernate-list-idx-bug/.idea/libraries/Maven__org_slf4j_slf4j_simple_1_7_6.xml (rev 0)
+++ hibernate-list-idx-bug/.idea/libraries/Maven__org_slf4j_slf4j_simple_1_7_6.xml 2014-04-23 15:39:47 UTC (rev 703)
@@ -0,0 +1,13 @@
+<component name="libraryTable">
+ <library name="Maven: org.slf4j:slf4j-simple:1.7.6">
+ <CLASSES>
+ <root url="jar://$MAVEN_REPOSITORY$/org/slf4j/slf4j-simple/1.7.6/slf4j-simple-1.7.6.jar!/" />
+ </CLASSES>
+ <JAVADOC>
+ <root url="jar://$MAVEN_REPOSITORY$/org/slf4j/slf4j-simple/1.7.6/slf4j-simple-1.7.6-javadoc.jar!/" />
+ </JAVADOC>
+ <SOURCES>
+ <root url="jar://$MAVEN_REPOSITORY$/org/slf4j/slf4j-simple/1.7.6/slf4j-simple-1.7.6-sources.jar!/" />
+ </SOURCES>
+ </library>
+</component>
\ No newline at end of file
Added: hibernate-list-idx-bug/.idea/libraries/Maven__xml_apis_xml_apis_1_0_b2.xml
===================================================================
--- hibernate-list-idx-bug/.idea/libraries/Maven__xml_apis_xml_apis_1_0_b2.xml (rev 0)
+++ hibernate-list-idx-bug/.idea/libraries/Maven__xml_apis_xml_apis_1_0_b2.xml 2014-04-23 15:39:47 UTC (rev 703)
@@ -0,0 +1,13 @@
+<component name="libraryTable">
+ <library name="Maven: xml-apis:xml-apis:1.0.b2">
+ <CLASSES>
+ <root url="jar://$MAVEN_REPOSITORY$/xml-apis/xml-apis/1.0.b2/xml-apis-1.0.b2.jar!/" />
+ </CLASSES>
+ <JAVADOC>
+ <root url="jar://$MAVEN_REPOSITORY$/xml-apis/xml-apis/1.0.b2/xml-apis-1.0.b2-javadoc.jar!/" />
+ </JAVADOC>
+ <SOURCES>
+ <root url="jar://$MAVEN_REPOSITORY$/xml-apis/xml-apis/1.0.b2/xml-apis-1.0.b2-sources.jar!/" />
+ </SOURCES>
+ </library>
+</component>
\ No newline at end of file
Added: hibernate-list-idx-bug/.idea/misc.xml
===================================================================
--- hibernate-list-idx-bug/.idea/misc.xml (rev 0)
+++ hibernate-list-idx-bug/.idea/misc.xml 2014-04-23 15:39:47 UTC (rev 703)
@@ -0,0 +1,36 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project version="4">
+ <component name="EntryPointsManager">
+ <entry_points version="2.0" />
+ </component>
+ <component name="MavenProjectsManager">
+ <option name="originalFiles">
+ <list>
+ <option value="$PROJECT_DIR$/pom.xml" />
+ </list>
+ </option>
+ </component>
+ <component name="ProjectRootManager" version="2" languageLevel="JDK_1_6" assert-keyword="true" jdk-15="true" project-jdk-name="1.7" project-jdk-type="JavaSDK">
+ <output url="file://$PROJECT_DIR$/classes" />
+ </component>
+ <component name="SvnBranchConfigurationManager">
+ <option name="mySupportsUserInfoFilter" value="true" />
+ </component>
+ <component name="masterDetails">
+ <states>
+ <state key="ProjectJDKs.UI">
+ <settings>
+ <last-edited>1.7</last-edited>
+ <splitter-proportions>
+ <option name="proportions">
+ <list>
+ <option value="0.2" />
+ </list>
+ </option>
+ </splitter-proportions>
+ </settings>
+ </state>
+ </states>
+ </component>
+</project>
+
Added: hibernate-list-idx-bug/.idea/modules.xml
===================================================================
--- hibernate-list-idx-bug/.idea/modules.xml (rev 0)
+++ hibernate-list-idx-bug/.idea/modules.xml 2014-04-23 15:39:47 UTC (rev 703)
@@ -0,0 +1,9 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project version="4">
+ <component name="ProjectModuleManager">
+ <modules>
+ <module fileurl="file://$PROJECT_DIR$/hhh8109.iml" filepath="$PROJECT_DIR$/hhh8109.iml" />
+ </modules>
+ </component>
+</project>
+
Added: hibernate-list-idx-bug/.idea/scopes/scope_settings.xml
===================================================================
--- hibernate-list-idx-bug/.idea/scopes/scope_settings.xml (rev 0)
+++ hibernate-list-idx-bug/.idea/scopes/scope_settings.xml 2014-04-23 15:39:47 UTC (rev 703)
@@ -0,0 +1,5 @@
+<component name="DependencyValidationManager">
+ <state>
+ <option name="SKIP_IMPORT_STATEMENTS" value="false" />
+ </state>
+</component>
\ No newline at end of file
Added: hibernate-list-idx-bug/.idea/uiDesigner.xml
===================================================================
--- hibernate-list-idx-bug/.idea/uiDesigner.xml (rev 0)
+++ hibernate-list-idx-bug/.idea/uiDesigner.xml 2014-04-23 15:39:47 UTC (rev 703)
@@ -0,0 +1,125 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project version="4">
+ <component name="Palette2">
+ <group name="Swing">
+ <item class="com.intellij.uiDesigner.HSpacer" tooltip-text="Horizontal Spacer" icon="/com/intellij/uiDesigner/icons/hspacer.png" removable="false" auto-create-binding="false" can-attach-label="false">
+ <default-constraints vsize-policy="1" hsize-policy="6" anchor="0" fill="1" />
+ </item>
+ <item class="com.intellij.uiDesigner.VSpacer" tooltip-text="Vertical Spacer" icon="/com/intellij/uiDesigner/icons/vspacer.png" removable="false" auto-create-binding="false" can-attach-label="false">
+ <default-constraints vsize-policy="6" hsize-policy="1" anchor="0" fill="2" />
+ </item>
+ <item class="javax.swing.JPanel" icon="/com/intellij/uiDesigner/icons/panel.png" removable="false" auto-create-binding="false" can-attach-label="false">
+ <default-constraints vsize-policy="3" hsize-policy="3" anchor="0" fill="3" />
+ </item>
+ <item class="javax.swing.JScrollPane" icon="/com/intellij/uiDesigner/icons/scrollPane.png" removable="false" auto-create-binding="false" can-attach-label="true">
+ <default-constraints vsize-policy="7" hsize-policy="7" anchor="0" fill="3" />
+ </item>
+ <item class="javax.swing.JButton" icon="/com/intellij/uiDesigner/icons/button.png" removable="false" auto-create-binding="true" can-attach-label="false">
+ <default-constraints vsize-policy="0" hsize-policy="3" anchor="0" fill="1" />
+ <initial-values>
+ <property name="text" value="Button" />
+ </initial-values>
+ </item>
+ <item class="javax.swing.JRadioButton" icon="/com/intellij/uiDesigner/icons/radioButton.png" removable="false" auto-create-binding="true" can-attach-label="false">
+ <default-constraints vsize-policy="0" hsize-policy="3" anchor="8" fill="0" />
+ <initial-values>
+ <property name="text" value="RadioButton" />
+ </initial-values>
+ </item>
+ <item class="javax.swing.JCheckBox" icon="/com/intellij/uiDesigner/icons/checkBox.png" removable="false" auto-create-binding="true" can-attach-label="false">
+ <default-constraints vsize-policy="0" hsize-policy="3" anchor="8" fill="0" />
+ <initial-values>
+ <property name="text" value="CheckBox" />
+ </initial-values>
+ </item>
+ <item class="javax.swing.JLabel" icon="/com/intellij/uiDesigner/icons/label.png" removable="false" auto-create-binding="false" can-attach-label="false">
+ <default-constraints vsize-policy="0" hsize-policy="0" anchor="8" fill="0" />
+ <initial-values>
+ <property name="text" value="Label" />
+ </initial-values>
+ </item>
+ <item class="javax.swing.JTextField" icon="/com/intellij/uiDesigner/icons/textField.png" removable="false" auto-create-binding="true" can-attach-label="true">
+ <default-constraints vsize-policy="0" hsize-policy="6" anchor="8" fill="1">
+ <preferred-size width="150" height="-1" />
+ </default-constraints>
+ </item>
+ <item class="javax.swing.JPasswordField" icon="/com/intellij/uiDesigner/icons/passwordField.png" removable="false" auto-create-binding="true" can-attach-label="true">
+ <default-constraints vsize-policy="0" hsize-policy="6" anchor="8" fill="1">
+ <preferred-size width="150" height="-1" />
+ </default-constraints>
+ </item>
+ <item class="javax.swing.JFormattedTextField" icon="/com/intellij/uiDesigner/icons/formattedTextField.png" removable="false" auto-create-binding="true" can-attach-label="true">
+ <default-constraints vsize-policy="0" hsize-policy="6" anchor="8" fill="1">
+ <preferred-size width="150" height="-1" />
+ </default-constraints>
+ </item>
+ <item class="javax.swing.JTextArea" icon="/com/intellij/uiDesigner/icons/textArea.png" removable="false" auto-create-binding="true" can-attach-label="true">
+ <default-constraints vsize-policy="6" hsize-policy="6" anchor="0" fill="3">
+ <preferred-size width="150" height="50" />
+ </default-constraints>
+ </item>
+ <item class="javax.swing.JTextPane" icon="/com/intellij/uiDesigner/icons/textPane.png" removable="false" auto-create-binding="true" can-attach-label="true">
+ <default-constraints vsize-policy="6" hsize-policy="6" anchor="0" fill="3">
+ <preferred-size width="150" height="50" />
+ </default-constraints>
+ </item>
+ <item class="javax.swing.JEditorPane" icon="/com/intellij/uiDesigner/icons/editorPane.png" removable="false" auto-create-binding="true" can-attach-label="true">
+ <default-constraints vsize-policy="6" hsize-policy="6" anchor="0" fill="3">
+ <preferred-size width="150" height="50" />
+ </default-constraints>
+ </item>
+ <item class="javax.swing.JComboBox" icon="/com/intellij/uiDesigner/icons/comboBox.png" removable="false" auto-create-binding="true" can-attach-label="true">
+ <default-constraints vsize-policy="0" hsize-policy="2" anchor="8" fill="1" />
+ </item>
+ <item class="javax.swing.JTable" icon="/com/intellij/uiDesigner/icons/table.png" removable="false" auto-create-binding="true" can-attach-label="false">
+ <default-constraints vsize-policy="6" hsize-policy="6" anchor="0" fill="3">
+ <preferred-size width="150" height="50" />
+ </default-constraints>
+ </item>
+ <item class="javax.swing.JList" icon="/com/intellij/uiDesigner/icons/list.png" removable="false" auto-create-binding="true" can-attach-label="false">
+ <default-constraints vsize-policy="6" hsize-policy="2" anchor="0" fill="3">
+ <preferred-size width="150" height="50" />
+ </default-constraints>
+ </item>
+ <item class="javax.swing.JTree" icon="/com/intellij/uiDesigner/icons/tree.png" removable="false" auto-create-binding="true" can-attach-label="false">
+ <default-constraints vsize-policy="6" hsize-policy="6" anchor="0" fill="3">
+ <preferred-size width="150" height="50" />
+ </default-constraints>
+ </item>
+ <item class="javax.swing.JTabbedPane" icon="/com/intellij/uiDesigner/icons/tabbedPane.png" removable="false" auto-create-binding="true" can-attach-label="false">
+ <default-constraints vsize-policy="3" hsize-policy="3" anchor="0" fill="3">
+ <preferred-size width="200" height="200" />
+ </default-constraints>
+ </item>
+ <item class="javax.swing.JSplitPane" icon="/com/intellij/uiDesigner/icons/splitPane.png" removable="false" auto-create-binding="false" can-attach-label="false">
+ <default-constraints vsize-policy="3" hsize-policy="3" anchor="0" fill="3">
+ <preferred-size width="200" height="200" />
+ </default-constraints>
+ </item>
+ <item class="javax.swing.JSpinner" icon="/com/intellij/uiDesigner/icons/spinner.png" removable="false" auto-create-binding="true" can-attach-label="true">
+ <default-constraints vsize-policy="0" hsize-policy="6" anchor="8" fill="1" />
+ </item>
+ <item class="javax.swing.JSlider" icon="/com/intellij/uiDesigner/icons/slider.png" removable="false" auto-create-binding="true" can-attach-label="false">
+ <default-constraints vsize-policy="0" hsize-policy="6" anchor="8" fill="1" />
+ </item>
+ <item class="javax.swing.JSeparator" icon="/com/intellij/uiDesigner/icons/separator.png" removable="false" auto-create-binding="false" can-attach-label="false">
+ <default-constraints vsize-policy="6" hsize-policy="6" anchor="0" fill="3" />
+ </item>
+ <item class="javax.swing.JProgressBar" icon="/com/intellij/uiDesigner/icons/progressbar.png" removable="false" auto-create-binding="true" can-attach-label="false">
+ <default-constraints vsize-policy="0" hsize-policy="6" anchor="0" fill="1" />
+ </item>
+ <item class="javax.swing.JToolBar" icon="/com/intellij/uiDesigner/icons/toolbar.png" removable="false" auto-create-binding="false" can-attach-label="false">
+ <default-constraints vsize-policy="0" hsize-policy="6" anchor="0" fill="1">
+ <preferred-size width="-1" height="20" />
+ </default-constraints>
+ </item>
+ <item class="javax.swing.JToolBar$Separator" icon="/com/intellij/uiDesigner/icons/toolbarSeparator.png" removable="false" auto-create-binding="false" can-attach-label="false">
+ <default-constraints vsize-policy="0" hsize-policy="0" anchor="0" fill="1" />
+ </item>
+ <item class="javax.swing.JScrollBar" icon="/com/intellij/uiDesigner/icons/scrollbar.png" removable="false" auto-create-binding="true" can-attach-label="false">
+ <default-constraints vsize-policy="6" hsize-policy="0" anchor="0" fill="2" />
+ </item>
+ </group>
+ </component>
+</project>
+
Added: hibernate-list-idx-bug/.idea/vcs.xml
===================================================================
--- hibernate-list-idx-bug/.idea/vcs.xml (rev 0)
+++ hibernate-list-idx-bug/.idea/vcs.xml 2014-04-23 15:39:47 UTC (rev 703)
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project version="4">
+ <component name="VcsDirectoryMappings">
+ <mapping directory="" vcs="svn" />
+ </component>
+</project>
+
Added: hibernate-list-idx-bug/.idea/workspace.xml
===================================================================
--- hibernate-list-idx-bug/.idea/workspace.xml (rev 0)
+++ hibernate-list-idx-bug/.idea/workspace.xml 2014-04-23 15:39:47 UTC (rev 703)
@@ -0,0 +1,1262 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project version="4">
+ <component name="ChangeListManager">
+ <list default="true" readonly="true" id="37108273-977e-4138-bb28-38ae04e78df0" name="Default" comment="">
+ <change type="NEW" beforePath="" afterPath="$PROJECT_DIR$/src/main/resources/org/nuiton/entities" />
+ <change type="NEW" beforePath="" afterPath="$PROJECT_DIR$/src/main/resources/org/nuiton" />
+ <change type="NEW" beforePath="" afterPath="$PROJECT_DIR$/src/main/resources/org" />
+ <change type="NEW" beforePath="" afterPath="$PROJECT_DIR$/src/main/resources" />
+ <change type="DELETED" beforePath="$PROJECT_DIR$/src/main/java/org/nuiton/hhh8109/Thread.java" afterPath="" />
+ <change type="DELETED" beforePath="$PROJECT_DIR$/src/main/java/org/nuiton/hhh8109/ThreadUserAssociation.java" afterPath="" />
+ <change type="DELETED" beforePath="$PROJECT_DIR$/src/main/java/org/nuiton/hhh8109/User.java" afterPath="" />
+ <change type="DELETED" beforePath="$PROJECT_DIR$/src/test/java/org/nuiton/hhh8109/HHH8109Test.java" afterPath="" />
+ <change type="DELETED" beforePath="$PROJECT_DIR$/src/test/resources/META-INF/persistence.xml" afterPath="" />
+ <change type="DELETED" beforePath="$PROJECT_DIR$/src/main/java/org/nuiton/hhh8109" afterPath="" />
+ <change type="DELETED" beforePath="$PROJECT_DIR$/src/test/java/org/nuiton/hhh8109" afterPath="" />
+ <change type="MODIFICATION" beforePath="$PROJECT_DIR$/README.txt" afterPath="$PROJECT_DIR$/README.txt" />
+ <change type="MODIFICATION" beforePath="$PROJECT_DIR$/pom.xml" afterPath="$PROJECT_DIR$/pom.xml" />
+ </list>
+ <ignored path="hhh8109.iws" />
+ <ignored path=".idea/workspace.xml" />
+ <option name="TRACKING_ENABLED" value="true" />
+ <option name="SHOW_DIALOG" value="false" />
+ <option name="HIGHLIGHT_CONFLICTS" value="true" />
+ <option name="HIGHLIGHT_NON_ACTIVE_CHANGELIST" value="false" />
+ <option name="LAST_RESOLUTION" value="IGNORE" />
+ </component>
+ <component name="ChangesViewManager" flattened_view="true" show_ignored="false" />
+ <component name="CreatePatchCommitExecutor">
+ <option name="PATCH_PATH" value="" />
+ </component>
+ <component name="DaemonCodeAnalyzer">
+ <disable_hints />
+ </component>
+ <component name="DebuggerManager">
+ <breakpoint_any converted="true">
+ <breakpoint>
+ <option name="NOTIFY_CAUGHT" value="true" />
+ <option name="NOTIFY_UNCAUGHT" value="true" />
+ <option name="ENABLED" value="false" />
+ <option name="LOG_ENABLED" value="false" />
+ <option name="LOG_EXPRESSION_ENABLED" value="false" />
+ <option name="REMOVE_AFTER_HIT" value="false" />
+ <option name="SUSPEND_POLICY" value="SuspendAll" />
+ <option name="SUSPEND" value="true" />
+ <option name="COUNT_FILTER_ENABLED" value="false" />
+ <option name="COUNT_FILTER" value="0" />
+ <option name="CONDITION_ENABLED" value="true" />
+ <option name="CLASS_FILTERS_ENABLED" value="false" />
+ <option name="INSTANCE_FILTERS_ENABLED" value="false" />
+ <option name="CONDITION" value="" />
+ <option name="LOG_MESSAGE" value="" />
+ </breakpoint>
+ <breakpoint>
+ <option name="NOTIFY_CAUGHT" value="true" />
+ <option name="NOTIFY_UNCAUGHT" value="true" />
+ <option name="ENABLED" value="false" />
+ <option name="LOG_ENABLED" value="false" />
+ <option name="LOG_EXPRESSION_ENABLED" value="false" />
+ <option name="REMOVE_AFTER_HIT" value="false" />
+ <option name="SUSPEND_POLICY" value="SuspendAll" />
+ <option name="SUSPEND" value="true" />
+ <option name="COUNT_FILTER_ENABLED" value="false" />
+ <option name="COUNT_FILTER" value="0" />
+ <option name="CONDITION_ENABLED" value="true" />
+ <option name="CLASS_FILTERS_ENABLED" value="false" />
+ <option name="INSTANCE_FILTERS_ENABLED" value="false" />
+ <option name="CONDITION" value="" />
+ <option name="LOG_MESSAGE" value="" />
+ </breakpoint>
+ </breakpoint_any>
+ <ui_properties converted="true" />
+ <breakpoint_rules converted="true" />
+ </component>
+ <component name="ExecutionTargetManager" SELECTED_TARGET="default_target" />
+ <component name="FavoritesManager">
+ <favorites_list name="hhh8109" />
+ </component>
+ <component name="FileEditorManager">
+ <leaf>
+ <file leaf-file-name="Building.java" pinned="false" current="false" current-in-tab="false">
+ <entry file="file://$PROJECT_DIR$/src/main/java/org/nuiton/entities/Building.java">
+ <provider selected="true" editor-type-id="text-editor">
+ <state vertical-scroll-proportion="-7.6666665" vertical-offset="108" max-vertical-offset="555">
+ <caret line="26" column="28" selection-start-line="26" selection-start-column="28" selection-end-line="26" selection-end-column="28" />
+ <folding>
+ <element signature="e#401#402#0" expanded="true" />
+ <element signature="e#434#435#0" expanded="true" />
+ <element signature="e#484#485#0" expanded="true" />
+ <element signature="e#528#529#0" expanded="true" />
+ </folding>
+ </state>
+ </provider>
+ </entry>
+ </file>
+ <file leaf-file-name="Flat.java" pinned="false" current="false" current-in-tab="false">
+ <entry file="file://$PROJECT_DIR$/src/main/java/org/nuiton/entities/Flat.java">
+ <provider selected="true" editor-type-id="text-editor">
+ <state vertical-scroll-proportion="0.0" vertical-offset="0" max-vertical-offset="375">
+ <caret line="9" column="25" selection-start-line="9" selection-start-column="19" selection-end-line="9" selection-end-column="25" />
+ <folding>
+ <element signature="e#215#216#0" expanded="true" />
+ <element signature="e#244#245#0" expanded="true" />
+ <element signature="e#286#287#0" expanded="true" />
+ <element signature="e#322#323#0" expanded="true" />
+ </folding>
+ </state>
+ </provider>
+ </entry>
+ </file>
+ <file leaf-file-name="AppTest.java" pinned="false" current="false" current-in-tab="false">
+ <entry file="file://$PROJECT_DIR$/src/test/java/org/nuiton/entities/AppTest.java">
+ <provider selected="true" editor-type-id="text-editor">
+ <state vertical-scroll-proportion="0.0" vertical-offset="665" max-vertical-offset="1410">
+ <caret line="65" column="37" selection-start-line="65" selection-start-column="37" selection-end-line="65" selection-end-column="37" />
+ <folding>
+ <element signature="imports" expanded="true" />
+ </folding>
+ </state>
+ </provider>
+ </entry>
+ </file>
+ <file leaf-file-name="Session.java" pinned="false" current="false" current-in-tab="false">
+ <entry file="jar://$MAVEN_REPOSITORY$/org/hibernate/hibernate-core/4.3.5.Final/hibernate-core-4.3.5.Final-sources.jar!/org/hibernate/Session.java">
+ <provider selected="true" editor-type-id="text-editor">
+ <state vertical-scroll-proportion="0.0" vertical-offset="5428" max-vertical-offset="16305">
+ <caret line="401" column="0" selection-start-line="401" selection-start-column="0" selection-end-line="401" selection-end-column="0" />
+ <folding />
+ </state>
+ </provider>
+ </entry>
+ </file>
+ <file leaf-file-name="hibernate.cfg.xml" pinned="false" current="false" current-in-tab="false">
+ <entry file="file://$PROJECT_DIR$/src/test/resources/hibernate.cfg.xml">
+ <provider selected="true" editor-type-id="text-editor">
+ <state vertical-scroll-proportion="0.0" vertical-offset="0" max-vertical-offset="465">
+ <caret line="6" column="18" selection-start-line="6" selection-start-column="18" selection-end-line="6" selection-end-column="18" />
+ <folding />
+ </state>
+ </provider>
+ </entry>
+ </file>
+ <file leaf-file-name="changelog.txt" pinned="false" current="false" current-in-tab="false">
+ <entry file="file://$PROJECT_DIR$/changelog.txt">
+ <provider selected="true" editor-type-id="text-editor">
+ <state vertical-scroll-proportion="0.0" vertical-offset="0" max-vertical-offset="90">
+ <caret line="0" column="0" selection-start-line="0" selection-start-column="0" selection-end-line="0" selection-end-column="0" />
+ <folding />
+ </state>
+ </provider>
+ </entry>
+ </file>
+ <file leaf-file-name="LICENSE.txt" pinned="false" current="false" current-in-tab="false">
+ <entry file="file://$PROJECT_DIR$/LICENSE.txt">
+ <provider selected="true" editor-type-id="text-editor">
+ <state vertical-scroll-proportion="0.0" vertical-offset="0" max-vertical-offset="2580">
+ <caret line="0" column="0" selection-start-line="0" selection-start-column="0" selection-end-line="0" selection-end-column="0" />
+ <folding />
+ </state>
+ </provider>
+ </entry>
+ </file>
+ <file leaf-file-name="pom.xml" pinned="false" current="false" current-in-tab="false">
+ <entry file="file://$PROJECT_DIR$/pom.xml">
+ <provider selected="true" editor-type-id="text-editor">
+ <state vertical-scroll-proportion="0.0" vertical-offset="0" max-vertical-offset="2490">
+ <caret line="36" column="0" selection-start-line="36" selection-start-column="0" selection-end-line="36" selection-end-column="0" />
+ <folding />
+ </state>
+ </provider>
+ </entry>
+ </file>
+ <file leaf-file-name="README.txt" pinned="false" current="true" current-in-tab="true">
+ <entry file="file://$PROJECT_DIR$/README.txt">
+ <provider selected="true" editor-type-id="text-editor">
+ <state vertical-scroll-proportion="0.0" vertical-offset="0" max-vertical-offset="335">
+ <caret line="0" column="53" selection-start-line="0" selection-start-column="53" selection-end-line="0" selection-end-column="61" />
+ <folding />
+ </state>
+ </provider>
+ </entry>
+ </file>
+ <file leaf-file-name="Loft.java" pinned="false" current="false" current-in-tab="false">
+ <entry file="file://$PROJECT_DIR$/src/main/java/org/nuiton/entities/Loft.java">
+ <provider selected="true" editor-type-id="text-editor">
+ <state vertical-scroll-proportion="0.0" vertical-offset="0" max-vertical-offset="315">
+ <caret line="11" column="22" selection-start-line="11" selection-start-column="22" selection-end-line="11" selection-end-column="22" />
+ <folding />
+ </state>
+ </provider>
+ </entry>
+ </file>
+ <file leaf-file-name="Suite.java" pinned="false" current="false" current-in-tab="false">
+ <entry file="file://$PROJECT_DIR$/src/main/java/org/nuiton/entities/Suite.java">
+ <provider selected="true" editor-type-id="text-editor">
+ <state vertical-scroll-proportion="0.0" vertical-offset="0" max-vertical-offset="315">
+ <caret line="9" column="26" selection-start-line="9" selection-start-column="26" selection-end-line="9" selection-end-column="26" />
+ <folding />
+ </state>
+ </provider>
+ </entry>
+ </file>
+ <file leaf-file-name="Building.hbm.xml" pinned="false" current="false" current-in-tab="false">
+ <entry file="file://$PROJECT_DIR$/src/main/resources/org/nuiton/entities/Building.hbm.xml">
+ <provider selected="true" editor-type-id="text-editor">
+ <state vertical-scroll-proportion="0.0" vertical-offset="0" max-vertical-offset="330">
+ <caret line="5" column="61" selection-start-line="5" selection-start-column="61" selection-end-line="5" selection-end-column="61" />
+ <folding />
+ </state>
+ </provider>
+ </entry>
+ </file>
+ <file leaf-file-name="Flat.hbm.xml" pinned="false" current="false" current-in-tab="false">
+ <entry file="file://$PROJECT_DIR$/src/main/resources/org/nuiton/entities/Flat.hbm.xml">
+ <provider selected="true" editor-type-id="text-editor">
+ <state vertical-scroll-proportion="0.0" vertical-offset="0" max-vertical-offset="255">
+ <caret line="5" column="41" selection-start-line="5" selection-start-column="41" selection-end-line="5" selection-end-column="41" />
+ <folding />
+ </state>
+ </provider>
+ </entry>
+ </file>
+ <file leaf-file-name="Loft.hbm.xml" pinned="false" current="false" current-in-tab="false">
+ <entry file="file://$PROJECT_DIR$/src/main/resources/org/nuiton/entities/Loft.hbm.xml">
+ <provider selected="true" editor-type-id="text-editor">
+ <state vertical-scroll-proportion="0.0" vertical-offset="0" max-vertical-offset="210">
+ <caret line="5" column="85" selection-start-line="5" selection-start-column="85" selection-end-line="5" selection-end-column="85" />
+ <folding />
+ </state>
+ </provider>
+ </entry>
+ </file>
+ <file leaf-file-name="Suite.hbm.xml" pinned="false" current="false" current-in-tab="false">
+ <entry file="file://$PROJECT_DIR$/src/main/resources/org/nuiton/entities/Suite.hbm.xml">
+ <provider selected="true" editor-type-id="text-editor">
+ <state vertical-scroll-proportion="0.0" vertical-offset="0" max-vertical-offset="210">
+ <caret line="5" column="103" selection-start-line="5" selection-start-column="103" selection-end-line="5" selection-end-column="103" />
+ <folding />
+ </state>
+ </provider>
+ </entry>
+ </file>
+ </leaf>
+ </component>
+ <component name="FindManager">
+ <FindUsagesManager>
+ <setting name="OPEN_NEW_TAB" value="true" />
+ </FindUsagesManager>
+ </component>
+ <component name="IdeDocumentHistory">
+ <option name="changedFiles">
+ <list>
+ <option value="$PROJECT_DIR$/src/main/java/org/nuiton/hhh8109/Loft.java" />
+ <option value="$PROJECT_DIR$/src/main/java/org/nuiton/hhh8109/Suite.java" />
+ <option value="$PROJECT_DIR$/src/main/java/org/nuiton/hhh8109/Building.java" />
+ <option value="$PROJECT_DIR$/src/main/java/org/nuiton/hhh8109/Flat.java" />
+ <option value="$PROJECT_DIR$/src/test/java/org/nuiton/hhh8109/HHH000Test.java" />
+ <option value="$PROJECT_DIR$/src/test/java/org/nuiton/hhh8109/HHH0000Test.java" />
+ <option value="$PROJECT_DIR$/src/test/java/org/nuiton/hhh0000/HHH0000Test.java" />
+ <option value="$PROJECT_DIR$/src/main/java/org/nuiton/hhh0000/Building.java" />
+ <option value="$PROJECT_DIR$/src/main/java/org/nuiton/hhh0000/Suite.java" />
+ <option value="$PROJECT_DIR$/src/main/java/org/nuiton/hhh0000/Flat.java" />
+ <option value="$PROJECT_DIR$/src/main/java/org/nuiton/hhh0000/Loft.java" />
+ <option value="$PROJECT_DIR$/pom.xml" />
+ <option value="$PROJECT_DIR$/src/main/java/org/nuiton/entities/Suite.java" />
+ <option value="$PROJECT_DIR$/src/main/java/org/nuiton/entities/Flat.java" />
+ <option value="$PROJECT_DIR$/src/main/java/org/nuiton/entities/Loft.java" />
+ <option value="$PROJECT_DIR$/hhh0000.iml" />
+ <option value="$PROJECT_DIR$/src/test/resources/META-INF/persistence.xml" />
+ <option value="$PROJECT_DIR$/src/main/java/org/nuiton/entities/Building.java" />
+ <option value="$PROJECT_DIR$/src/main/java/org/nuiton/entities/hibernate.cfg.xml" />
+ <option value="$PROJECT_DIR$/src/test/java/org/nuiton/entities/hibernate.cfg.xml" />
+ <option value="$PROJECT_DIR$/src/main/webapp/WEB-INF/hibernate.cfg.xml" />
+ <option value="$PROJECT_DIR$/src/test/resources/WEB-INF/hibernate.cfg.xml" />
+ <option value="$PROJECT_DIR$/src/test/resources/hibernate.cfg.xml" />
+ <option value="$PROJECT_DIR$/src/test/java/org/nuiton/entities/AppTest.java" />
+ <option value="$PROJECT_DIR$/src/main/resources/org/nuiton/entities/Loft.hbm.xml" />
+ <option value="$PROJECT_DIR$/src/main/resources/org/nuiton/entities/Suite.hbm.xml" />
+ <option value="$PROJECT_DIR$/src/main/resources/org/nuiton/entities/Flat.hbm.xml" />
+ <option value="$PROJECT_DIR$/src/main/resources/org/nuiton/entities/Building.hbm.xml" />
+ <option value="$PROJECT_DIR$/README.txt" />
+ </list>
+ </option>
+ </component>
+ <component name="MavenImportPreferences">
+ <option name="importingSettings">
+ <MavenImportingSettings>
+ <option name="importAutomatically" value="true" />
+ </MavenImportingSettings>
+ </option>
+ </component>
+ <component name="ProjectFrameBounds">
+ <option name="y" value="-4" />
+ <option name="width" value="1280" />
+ <option name="height" value="804" />
+ </component>
+ <component name="ProjectLevelVcsManager" settingsEditedManually="false">
+ <OptionsSetting value="true" id="Add" />
+ <OptionsSetting value="true" id="Remove" />
+ <OptionsSetting value="true" id="Checkout" />
+ <OptionsSetting value="true" id="Update" />
+ <OptionsSetting value="true" id="Status" />
+ <OptionsSetting value="true" id="Edit" />
+ <ConfirmationsSetting value="0" id="Add" />
+ <ConfirmationsSetting value="0" id="Remove" />
+ </component>
+ <component name="ProjectReloadState">
+ <option name="STATE" value="0" />
+ </component>
+ <component name="ProjectView">
+ <navigator currentView="ProjectPane" proportions="" version="1">
+ <flattenPackages />
+ <showMembers />
+ <showModules />
+ <showLibraryContents />
+ <hideEmptyPackages />
+ <abbreviatePackageNames />
+ <autoscrollToSource />
+ <autoscrollFromSource />
+ <sortByType />
+ </navigator>
+ <panes>
+ <pane id="PackagesPane" />
+ <pane id="Scope" />
+ <pane id="ProjectPane">
+ <subPane>
+ <PATH>
+ <PATH_ELEMENT>
+ <option name="myItemId" value="hhh8109" />
+ <option name="myItemType" value="com.intellij.ide.projectView.impl.nodes.ProjectViewProjectNode" />
+ </PATH_ELEMENT>
+ </PATH>
+ <PATH>
+ <PATH_ELEMENT>
+ <option name="myItemId" value="hhh8109" />
+ <option name="myItemType" value="com.intellij.ide.projectView.impl.nodes.ProjectViewProjectNode" />
+ </PATH_ELEMENT>
+ <PATH_ELEMENT>
+ <option name="myItemId" value="hibernate-abstract-idx" />
+ <option name="myItemType" value="com.android.tools.idea.gradle.projectView.AndroidPsiDirectoryNode" />
+ </PATH_ELEMENT>
+ </PATH>
+ <PATH>
+ <PATH_ELEMENT>
+ <option name="myItemId" value="hhh8109" />
+ <option name="myItemType" value="com.intellij.ide.projectView.impl.nodes.ProjectViewProjectNode" />
+ </PATH_ELEMENT>
+ <PATH_ELEMENT>
+ <option name="myItemId" value="hibernate-abstract-idx" />
+ <option name="myItemType" value="com.android.tools.idea.gradle.projectView.AndroidPsiDirectoryNode" />
+ </PATH_ELEMENT>
+ <PATH_ELEMENT>
+ <option name="myItemId" value="src" />
+ <option name="myItemType" value="com.android.tools.idea.gradle.projectView.AndroidPsiDirectoryNode" />
+ </PATH_ELEMENT>
+ <PATH_ELEMENT>
+ <option name="myItemId" value="test" />
+ <option name="myItemType" value="com.android.tools.idea.gradle.projectView.AndroidPsiDirectoryNode" />
+ </PATH_ELEMENT>
+ <PATH_ELEMENT>
+ <option name="myItemId" value="resources" />
+ <option name="myItemType" value="com.android.tools.idea.gradle.projectView.AndroidPsiDirectoryNode" />
+ </PATH_ELEMENT>
+ </PATH>
+ <PATH>
+ <PATH_ELEMENT>
+ <option name="myItemId" value="hhh8109" />
+ <option name="myItemType" value="com.intellij.ide.projectView.impl.nodes.ProjectViewProjectNode" />
+ </PATH_ELEMENT>
+ <PATH_ELEMENT>
+ <option name="myItemId" value="hibernate-abstract-idx" />
+ <option name="myItemType" value="com.android.tools.idea.gradle.projectView.AndroidPsiDirectoryNode" />
+ </PATH_ELEMENT>
+ <PATH_ELEMENT>
+ <option name="myItemId" value="src" />
+ <option name="myItemType" value="com.android.tools.idea.gradle.projectView.AndroidPsiDirectoryNode" />
+ </PATH_ELEMENT>
+ <PATH_ELEMENT>
+ <option name="myItemId" value="test" />
+ <option name="myItemType" value="com.android.tools.idea.gradle.projectView.AndroidPsiDirectoryNode" />
+ </PATH_ELEMENT>
+ <PATH_ELEMENT>
+ <option name="myItemId" value="java" />
+ <option name="myItemType" value="com.android.tools.idea.gradle.projectView.AndroidPsiDirectoryNode" />
+ </PATH_ELEMENT>
+ <PATH_ELEMENT>
+ <option name="myItemId" value="entities" />
+ <option name="myItemType" value="com.android.tools.idea.gradle.projectView.AndroidPsiDirectoryNode" />
+ </PATH_ELEMENT>
+ </PATH>
+ <PATH>
+ <PATH_ELEMENT>
+ <option name="myItemId" value="hhh8109" />
+ <option name="myItemType" value="com.intellij.ide.projectView.impl.nodes.ProjectViewProjectNode" />
+ </PATH_ELEMENT>
+ <PATH_ELEMENT>
+ <option name="myItemId" value="hibernate-abstract-idx" />
+ <option name="myItemType" value="com.android.tools.idea.gradle.projectView.AndroidPsiDirectoryNode" />
+ </PATH_ELEMENT>
+ <PATH_ELEMENT>
+ <option name="myItemId" value="src" />
+ <option name="myItemType" value="com.android.tools.idea.gradle.projectView.AndroidPsiDirectoryNode" />
+ </PATH_ELEMENT>
+ <PATH_ELEMENT>
+ <option name="myItemId" value="main" />
+ <option name="myItemType" value="com.android.tools.idea.gradle.projectView.AndroidPsiDirectoryNode" />
+ </PATH_ELEMENT>
+ <PATH_ELEMENT>
+ <option name="myItemId" value="resources" />
+ <option name="myItemType" value="com.android.tools.idea.gradle.projectView.AndroidPsiDirectoryNode" />
+ </PATH_ELEMENT>
+ <PATH_ELEMENT>
+ <option name="myItemId" value="entities" />
+ <option name="myItemType" value="com.android.tools.idea.gradle.projectView.AndroidPsiDirectoryNode" />
+ </PATH_ELEMENT>
+ </PATH>
+ <PATH>
+ <PATH_ELEMENT>
+ <option name="myItemId" value="hhh8109" />
+ <option name="myItemType" value="com.intellij.ide.projectView.impl.nodes.ProjectViewProjectNode" />
+ </PATH_ELEMENT>
+ <PATH_ELEMENT>
+ <option name="myItemId" value="hibernate-abstract-idx" />
+ <option name="myItemType" value="com.android.tools.idea.gradle.projectView.AndroidPsiDirectoryNode" />
+ </PATH_ELEMENT>
+ <PATH_ELEMENT>
+ <option name="myItemId" value="src" />
+ <option name="myItemType" value="com.android.tools.idea.gradle.projectView.AndroidPsiDirectoryNode" />
+ </PATH_ELEMENT>
+ <PATH_ELEMENT>
+ <option name="myItemId" value="main" />
+ <option name="myItemType" value="com.android.tools.idea.gradle.projectView.AndroidPsiDirectoryNode" />
+ </PATH_ELEMENT>
+ <PATH_ELEMENT>
+ <option name="myItemId" value="java" />
+ <option name="myItemType" value="com.android.tools.idea.gradle.projectView.AndroidPsiDirectoryNode" />
+ </PATH_ELEMENT>
+ <PATH_ELEMENT>
+ <option name="myItemId" value="entities" />
+ <option name="myItemType" value="com.android.tools.idea.gradle.projectView.AndroidPsiDirectoryNode" />
+ </PATH_ELEMENT>
+ </PATH>
+ </subPane>
+ </pane>
+ </panes>
+ </component>
+ <component name="PropertiesComponent">
+ <property name="GoToClass.includeLibraries" value="false" />
+ <property name="GoToClass.toSaveIncludeLibraries" value="false" />
+ <property name="GoToFile.includeJavaFiles" value="false" />
+ <property name="MemberChooser.sorted" value="false" />
+ <property name="MemberChooser.showClasses" value="true" />
+ <property name="MemberChooser.copyJavadoc" value="false" />
+ <property name="last_opened_file_path" value="$PROJECT_DIR$" />
+ <property name="recentsLimit" value="5" />
+ <property name="dynamic.classpath" value="false" />
+ <property name="FileHistory.org.jetbrains.idea.svn.history.SvnHistoryProvider_flatWidth0" value="64" />
+ <property name="FileHistory.org.jetbrains.idea.svn.history.SvnHistoryProvider_flatOrder0" value="0" />
+ <property name="FileHistory.org.jetbrains.idea.svn.history.SvnHistoryProvider_flatWidth1" value="72" />
+ <property name="FileHistory.org.jetbrains.idea.svn.history.SvnHistoryProvider_flatOrder1" value="1" />
+ <property name="FileHistory.org.jetbrains.idea.svn.history.SvnHistoryProvider_flatWidth2" value="76" />
+ <property name="FileHistory.org.jetbrains.idea.svn.history.SvnHistoryProvider_flatOrder2" value="2" />
+ <property name="FileHistory.org.jetbrains.idea.svn.history.SvnHistoryProvider_flatWidth3" value="64" />
+ <property name="FileHistory.org.jetbrains.idea.svn.history.SvnHistoryProvider_flatOrder3" value="3" />
+ <property name="FileHistory.org.jetbrains.idea.svn.history.SvnHistoryProvider_flatWidth4" value="111" />
+ <property name="FileHistory.org.jetbrains.idea.svn.history.SvnHistoryProvider_flatOrder4" value="4" />
+ <property name="FileHistory.org.jetbrains.idea.svn.history.SvnHistoryProvider_flatWidth5" value="821" />
+ <property name="FileHistory.org.jetbrains.idea.svn.history.SvnHistoryProvider_flatOrder5" value="5" />
+ <property name="FileHistory.org.jetbrains.idea.svn.history.SvnHistoryProvider_treeWidth0" value="66" />
+ <property name="FileHistory.org.jetbrains.idea.svn.history.SvnHistoryProvider_treeOrder0" value="0" />
+ <property name="FileHistory.org.jetbrains.idea.svn.history.SvnHistoryProvider_treeWidth1" value="75" />
+ <property name="FileHistory.org.jetbrains.idea.svn.history.SvnHistoryProvider_treeOrder1" value="1" />
+ <property name="FileHistory.org.jetbrains.idea.svn.history.SvnHistoryProvider_treeWidth2" value="79" />
+ <property name="FileHistory.org.jetbrains.idea.svn.history.SvnHistoryProvider_treeOrder2" value="2" />
+ <property name="FileHistory.org.jetbrains.idea.svn.history.SvnHistoryProvider_treeWidth3" value="66" />
+ <property name="FileHistory.org.jetbrains.idea.svn.history.SvnHistoryProvider_treeOrder3" value="3" />
+ <property name="FileHistory.org.jetbrains.idea.svn.history.SvnHistoryProvider_treeWidth4" value="66" />
+ <property name="FileHistory.org.jetbrains.idea.svn.history.SvnHistoryProvider_treeOrder4" value="4" />
+ <property name="FileHistory.org.jetbrains.idea.svn.history.SvnHistoryProvider_treeWidth5" value="856" />
+ <property name="FileHistory.org.jetbrains.idea.svn.history.SvnHistoryProvider_treeOrder5" value="5" />
+ <property name="FullScreen" value="false" />
+ </component>
+ <component name="RecentsManager">
+ <key name="MoveFile.RECENT_KEYS">
+ <recent name="$PROJECT_DIR$/src/main/resources/org/nuiton/entities" />
+ <recent name="$PROJECT_DIR$/src/main/java/org/nuiton/entities" />
+ <recent name="$PROJECT_DIR$/src/test/java/org/nuiton/entities" />
+ <recent name="$PROJECT_DIR$/src/test/resources" />
+ <recent name="$PROJECT_DIR$/src" />
+ </key>
+ </component>
+ <component name="RunManager">
+ <configuration default="false" name="AppTest" type="JUnit" factoryName="JUnit" temporary="true" nameIsGenerated="true">
+ <module name="hhh8109" />
+ <option name="ALTERNATIVE_JRE_PATH_ENABLED" value="false" />
+ <option name="ALTERNATIVE_JRE_PATH" />
+ <option name="PACKAGE_NAME" value="org.nuiton.entities" />
+ <option name="MAIN_CLASS_NAME" value="org.nuiton.entities.AppTest" />
+ <option name="METHOD_NAME" />
+ <option name="TEST_OBJECT" value="class" />
+ <option name="VM_PARAMETERS" value="-ea" />
+ <option name="PARAMETERS" />
+ <option name="WORKING_DIRECTORY" value="$PROJECT_DIR$" />
+ <option name="ENV_VARIABLES" />
+ <option name="PASS_PARENT_ENVS" value="true" />
+ <option name="TEST_SEARCH_SCOPE">
+ <value defaultName="moduleWithDependencies" />
+ </option>
+ <envs />
+ <patterns />
+ <RunnerSettings RunnerId="Run" />
+ <ConfigurationWrapper RunnerId="Run" />
+ <method />
+ </configuration>
+ <configuration default="false" name="AppTest.test" type="JUnit" factoryName="JUnit" temporary="true" nameIsGenerated="true">
+ <module name="hhh8109" />
+ <option name="ALTERNATIVE_JRE_PATH_ENABLED" value="false" />
+ <option name="ALTERNATIVE_JRE_PATH" />
+ <option name="PACKAGE_NAME" value="org.nuiton.entities" />
+ <option name="MAIN_CLASS_NAME" value="org.nuiton.entities.AppTest" />
+ <option name="METHOD_NAME" value="test" />
+ <option name="TEST_OBJECT" value="method" />
+ <option name="VM_PARAMETERS" value="-ea" />
+ <option name="PARAMETERS" />
+ <option name="WORKING_DIRECTORY" value="$PROJECT_DIR$" />
+ <option name="ENV_VARIABLES" />
+ <option name="PASS_PARENT_ENVS" value="true" />
+ <option name="TEST_SEARCH_SCOPE">
+ <value defaultName="moduleWithDependencies" />
+ </option>
+ <envs />
+ <patterns />
+ <RunnerSettings RunnerId="Debug">
+ <option name="DEBUG_PORT" value="" />
+ <option name="TRANSPORT" value="0" />
+ <option name="LOCAL" value="true" />
+ </RunnerSettings>
+ <RunnerSettings RunnerId="Run" />
+ <ConfigurationWrapper RunnerId="Debug" />
+ <ConfigurationWrapper RunnerId="Run" />
+ <method />
+ </configuration>
+ <configuration default="true" type="#org.jetbrains.idea.devkit.run.PluginConfigurationType" factoryName="Plugin">
+ <module name="" />
+ <option name="VM_PARAMETERS" value="-Xmx512m -Xms256m -XX:MaxPermSize=250m -ea" />
+ <option name="PROGRAM_PARAMETERS" />
+ <method />
+ </configuration>
+ <configuration default="true" type="AndroidTestRunConfigurationType" factoryName="Android Tests">
+ <module name="" />
+ <option name="TESTING_TYPE" value="0" />
+ <option name="INSTRUMENTATION_RUNNER_CLASS" value="" />
+ <option name="METHOD_NAME" value="" />
+ <option name="CLASS_NAME" value="" />
+ <option name="PACKAGE_NAME" value="" />
+ <option name="TARGET_SELECTION_MODE" value="EMULATOR" />
+ <option name="USE_LAST_SELECTED_DEVICE" value="false" />
+ <option name="PREFERRED_AVD" value="" />
+ <option name="USE_COMMAND_LINE" value="true" />
+ <option name="COMMAND_LINE" value="" />
+ <option name="WIPE_USER_DATA" value="false" />
+ <option name="DISABLE_BOOT_ANIMATION" value="false" />
+ <option name="NETWORK_SPEED" value="full" />
+ <option name="NETWORK_LATENCY" value="none" />
+ <option name="CLEAR_LOGCAT" value="false" />
+ <option name="SHOW_LOGCAT_AUTOMATICALLY" value="true" />
+ <option name="FILTER_LOGCAT_AUTOMATICALLY" value="true" />
+ <method />
+ </configuration>
+ <configuration default="true" type="Remote" factoryName="Remote">
+ <option name="USE_SOCKET_TRANSPORT" value="true" />
+ <option name="SERVER_MODE" value="false" />
+ <option name="SHMEM_ADDRESS" value="javadebug" />
+ <option name="HOST" value="localhost" />
+ <option name="PORT" value="5005" />
+ <method />
+ </configuration>
+ <configuration default="true" type="Applet" factoryName="Applet">
+ <module name="" />
+ <option name="MAIN_CLASS_NAME" />
+ <option name="HTML_FILE_NAME" />
+ <option name="HTML_USED" value="false" />
+ <option name="WIDTH" value="400" />
+ <option name="HEIGHT" value="300" />
+ <option name="POLICY_FILE" value="$APPLICATION_HOME_DIR$/bin/appletviewer.policy" />
+ <option name="VM_PARAMETERS" />
+ <option name="ALTERNATIVE_JRE_PATH_ENABLED" value="false" />
+ <option name="ALTERNATIVE_JRE_PATH" />
+ <method />
+ </configuration>
+ <configuration default="true" type="TestNG" factoryName="TestNG">
+ <module name="" />
+ <option name="ALTERNATIVE_JRE_PATH_ENABLED" value="false" />
+ <option name="ALTERNATIVE_JRE_PATH" />
+ <option name="SUITE_NAME" />
+ <option name="PACKAGE_NAME" />
+ <option name="MAIN_CLASS_NAME" />
+ <option name="METHOD_NAME" />
+ <option name="GROUP_NAME" />
+ <option name="TEST_OBJECT" value="CLASS" />
+ <option name="VM_PARAMETERS" value="-ea" />
+ <option name="PARAMETERS" />
+ <option name="WORKING_DIRECTORY" value="$PROJECT_DIR$" />
+ <option name="OUTPUT_DIRECTORY" />
+ <option name="ANNOTATION_TYPE" />
+ <option name="ENV_VARIABLES" />
+ <option name="PASS_PARENT_ENVS" value="true" />
+ <option name="TEST_SEARCH_SCOPE">
+ <value defaultName="moduleWithDependencies" />
+ </option>
+ <option name="USE_DEFAULT_REPORTERS" value="false" />
+ <option name="PROPERTIES_FILE" />
+ <envs />
+ <properties />
+ <listeners />
+ <method />
+ </configuration>
+ <configuration default="true" type="Application" factoryName="Application">
+ <option name="MAIN_CLASS_NAME" />
+ <option name="VM_PARAMETERS" />
+ <option name="PROGRAM_PARAMETERS" />
+ <option name="WORKING_DIRECTORY" value="$PROJECT_DIR$" />
+ <option name="ALTERNATIVE_JRE_PATH_ENABLED" value="false" />
+ <option name="ALTERNATIVE_JRE_PATH" />
+ <option name="ENABLE_SWING_INSPECTOR" value="false" />
+ <option name="ENV_VARIABLES" />
+ <option name="PASS_PARENT_ENVS" value="true" />
+ <module name="" />
+ <envs />
+ <method />
+ </configuration>
+ <configuration default="true" type="JUnit" factoryName="JUnit">
+ <module name="" />
+ <option name="ALTERNATIVE_JRE_PATH_ENABLED" value="false" />
+ <option name="ALTERNATIVE_JRE_PATH" />
+ <option name="PACKAGE_NAME" />
+ <option name="MAIN_CLASS_NAME" />
+ <option name="METHOD_NAME" />
+ <option name="TEST_OBJECT" value="class" />
+ <option name="VM_PARAMETERS" value="-ea" />
+ <option name="PARAMETERS" />
+ <option name="WORKING_DIRECTORY" value="$PROJECT_DIR$" />
+ <option name="ENV_VARIABLES" />
+ <option name="PASS_PARENT_ENVS" value="true" />
+ <option name="TEST_SEARCH_SCOPE">
+ <value defaultName="moduleWithDependencies" />
+ </option>
+ <envs />
+ <patterns />
+ <method />
+ </configuration>
+ <configuration default="true" type="AndroidRunConfigurationType" factoryName="Android Application">
+ <module name="" />
+ <option name="ACTIVITY_CLASS" value="" />
+ <option name="MODE" value="default_activity" />
+ <option name="DEPLOY" value="true" />
+ <option name="ARTIFACT_NAME" value="" />
+ <option name="TARGET_SELECTION_MODE" value="EMULATOR" />
+ <option name="USE_LAST_SELECTED_DEVICE" value="false" />
+ <option name="PREFERRED_AVD" value="" />
+ <option name="USE_COMMAND_LINE" value="true" />
+ <option name="COMMAND_LINE" value="" />
+ <option name="WIPE_USER_DATA" value="false" />
+ <option name="DISABLE_BOOT_ANIMATION" value="false" />
+ <option name="NETWORK_SPEED" value="full" />
+ <option name="NETWORK_LATENCY" value="none" />
+ <option name="CLEAR_LOGCAT" value="false" />
+ <option name="SHOW_LOGCAT_AUTOMATICALLY" value="true" />
+ <option name="FILTER_LOGCAT_AUTOMATICALLY" value="true" />
+ <method />
+ </configuration>
+ <list size="2">
+ <item index="0" class="java.lang.String" itemvalue="JUnit.AppTest" />
+ <item index="1" class="java.lang.String" itemvalue="JUnit.AppTest.test" />
+ </list>
+ <recent_temporary>
+ <list size="2">
+ <item index="0" class="java.lang.String" itemvalue="JUnit.AppTest" />
+ <item index="1" class="java.lang.String" itemvalue="JUnit.AppTest.test" />
+ </list>
+ </recent_temporary>
+ <configuration name="<template>" type="WebApp" default="true" selected="false">
+ <Host>localhost</Host>
+ <Port>5050</Port>
+ </configuration>
+ </component>
+ <component name="ShelveChangesManager" show_recycled="false" />
+ <component name="SvnConfiguration" myUseAcceleration="nothing" cleanupOnStartRun="true">
+ <configuration useDefault="false">$USER_HOME$/.subversion</configuration>
+ <supportedVersion>125</supportedVersion>
+ </component>
+ <component name="SvnFileUrlMappingImpl">
+ <option name="myMappingRoots">
+ <list>
+ <SvnCopyRootSimple>
+ <option name="myVcsRoot" value="$PROJECT_DIR$" />
+ <option name="myCopyRoot" value="$PROJECT_DIR$" />
+ </SvnCopyRootSimple>
+ </list>
+ </option>
+ <option name="myMoreRealMappingRoots">
+ <list>
+ <SvnCopyRootSimple>
+ <option name="myVcsRoot" value="$PROJECT_DIR$" />
+ <option name="myCopyRoot" value="$PROJECT_DIR$" />
+ </SvnCopyRootSimple>
+ </list>
+ </option>
+ </component>
+ <component name="TaskManager">
+ <task active="true" id="Default" summary="Default task">
+ <changelist id="37108273-977e-4138-bb28-38ae04e78df0" name="Default" comment="" />
+ <created>1397662837579</created>
+ <updated>1397662837579</updated>
+ </task>
+ <servers />
+ </component>
+ <component name="ToolWindowManager">
+ <frame x="0" y="-4" width="1280" height="804" extended-state="6" />
+ <editor active="true" />
+ <layout>
+ <window_info id="Palette	" active="false" anchor="left" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" weight="0.33" sideWeight="0.5" order="2" side_tool="false" content_ui="tabs" />
+ <window_info id="Changes" active="false" anchor="bottom" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" weight="0.18209408" sideWeight="0.5" order="7" side_tool="false" content_ui="tabs" />
+ <window_info id="Designer" active="false" anchor="right" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" weight="0.33" sideWeight="0.5" order="3" side_tool="false" content_ui="tabs" />
+ <window_info id="Palette" active="false" anchor="right" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" weight="0.33" sideWeight="0.5" order="5" side_tool="false" content_ui="tabs" />
+ <window_info id="Terminal" active="false" anchor="bottom" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" weight="0.33" sideWeight="0.5" order="13" side_tool="false" content_ui="tabs" />
+ <window_info id="Find" active="false" anchor="bottom" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" weight="0.3262519" sideWeight="0.5" order="1" side_tool="false" content_ui="tabs" />
+ <window_info id="Ant Build" active="false" anchor="right" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" weight="0.25" sideWeight="0.5" order="1" side_tool="false" content_ui="tabs" />
+ <window_info id="Debug" active="false" anchor="bottom" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" weight="0.39908952" sideWeight="0.5" order="3" side_tool="false" content_ui="tabs" />
+ <window_info id="Event Log" active="false" anchor="bottom" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" weight="0.33" sideWeight="0.5" order="8" side_tool="true" content_ui="tabs" />
+ <window_info id="Favorites" active="false" anchor="left" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" weight="0.33" sideWeight="0.5" order="3" side_tool="true" content_ui="tabs" />
+ <window_info id="Version Control" active="false" anchor="bottom" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" weight="0.23065251" sideWeight="0.5" order="9" side_tool="false" content_ui="tabs" />
+ <window_info id="Messages" active="false" anchor="bottom" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" weight="0.12139606" sideWeight="0.5" order="10" side_tool="false" content_ui="tabs" />
+ <window_info id="TODO" active="false" anchor="bottom" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" weight="0.33" sideWeight="0.5" order="6" side_tool="false" content_ui="tabs" />
+ <window_info id="Structure" active="false" anchor="left" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" weight="0.25" sideWeight="0.5" order="1" side_tool="false" content_ui="tabs" />
+ <window_info id="Maven Projects" active="false" anchor="right" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" weight="0.33" sideWeight="0.5" order="4" side_tool="false" content_ui="tabs" />
+ <window_info id="Commander" active="false" anchor="right" auto_hide="false" internal_type="SLIDING" type="SLIDING" visible="false" weight="0.4" sideWeight="0.5" order="0" side_tool="false" content_ui="tabs" />
+ <window_info id="Application Servers" active="false" anchor="bottom" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" weight="0.33" sideWeight="0.5" order="12" side_tool="false" content_ui="tabs" />
+ <window_info id="Project" active="false" anchor="left" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="true" weight="0.2746365" sideWeight="0.5" order="0" side_tool="false" content_ui="combo" />
+ <window_info id="Run" active="false" anchor="bottom" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="true" weight="0.3505311" sideWeight="0.5" order="2" side_tool="false" content_ui="tabs" />
+ <window_info id="Cvs" active="false" anchor="bottom" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" weight="0.25" sideWeight="0.5" order="4" side_tool="false" content_ui="tabs" />
+ <window_info id="SVN Properties" active="true" anchor="bottom" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="true" weight="0.32928678" sideWeight="0.5" order="11" side_tool="false" content_ui="tabs" />
+ <window_info id="Message" active="false" anchor="bottom" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" weight="0.33" sideWeight="0.5" order="0" side_tool="false" content_ui="tabs" />
+ <window_info id="Inspection" active="false" anchor="bottom" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" weight="0.4" sideWeight="0.5" order="5" side_tool="false" content_ui="tabs" />
+ <window_info id="Hierarchy" active="false" anchor="right" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" weight="0.25" sideWeight="0.5" order="2" side_tool="false" content_ui="combo" />
+ </layout>
+ </component>
+ <component name="Vcs.Log.UiProperties">
+ <option name="RECENTLY_FILTERED_USER_GROUPS">
+ <collection />
+ </option>
+ <option name="RECENTLY_FILTERED_BRANCH_GROUPS">
+ <collection />
+ </option>
+ </component>
+ <component name="VcsContentAnnotationSettings">
+ <option name="myLimit" value="2678400000" />
+ </component>
+ <component name="VcsManagerConfiguration">
+ <option name="myTodoPanelSettings">
+ <TodoPanelSettings />
+ </option>
+ </component>
+ <component name="XDebuggerManager">
+ <breakpoint-manager>
+ <option name="time" value="1" />
+ </breakpoint-manager>
+ </component>
+ <component name="antWorkspaceConfiguration">
+ <option name="IS_AUTOSCROLL_TO_SOURCE" value="false" />
+ <option name="FILTER_TARGETS" value="false" />
+ </component>
+ <component name="editorHistoryManager">
+ <entry file="file://$PROJECT_DIR$/src/main/java/org/nuiton/entities/Suite.java">
+ <provider selected="true" editor-type-id="text-editor">
+ <state vertical-scroll-proportion="0.0" vertical-offset="420" max-vertical-offset="540">
+ <caret line="28" column="5" selection-start-line="28" selection-start-column="5" selection-end-line="28" selection-end-column="5" />
+ <folding />
+ </state>
+ </provider>
+ </entry>
+ <entry file="file://$PROJECT_DIR$/src/main/java/org/nuiton/entities/Building.java">
+ <provider selected="true" editor-type-id="text-editor">
+ <state vertical-scroll-proportion="0.0" vertical-offset="330" max-vertical-offset="810">
+ <caret line="22" column="18" selection-start-line="22" selection-start-column="18" selection-end-line="22" selection-end-column="18" />
+ <folding>
+ <element signature="e#401#402#0" expanded="true" />
+ <element signature="e#434#435#0" expanded="true" />
+ <element signature="e#484#485#0" expanded="true" />
+ <element signature="e#528#529#0" expanded="true" />
+ </folding>
+ </state>
+ </provider>
+ </entry>
+ <entry file="file://$PROJECT_DIR$/src/main/java/org/nuiton/entities/Flat.java">
+ <provider selected="true" editor-type-id="text-editor">
+ <state vertical-scroll-proportion="0.0" vertical-offset="195" max-vertical-offset="525">
+ <caret line="13" column="9" selection-start-line="13" selection-start-column="9" selection-end-line="13" selection-end-column="9" />
+ <folding>
+ <element signature="e#215#216#0" expanded="true" />
+ <element signature="e#244#245#0" expanded="true" />
+ <element signature="e#286#287#0" expanded="true" />
+ <element signature="e#322#323#0" expanded="true" />
+ </folding>
+ </state>
+ </provider>
+ </entry>
+ <entry file="file://$PROJECT_DIR$/src/test/java/org/nuiton/entities/AppTest.java">
+ <provider selected="true" editor-type-id="text-editor">
+ <state vertical-scroll-proportion="0.0" vertical-offset="780" max-vertical-offset="1485">
+ <caret line="52" column="19" selection-start-line="52" selection-start-column="19" selection-end-line="52" selection-end-column="19" />
+ <folding>
+ <element signature="imports" expanded="true" />
+ </folding>
+ </state>
+ </provider>
+ </entry>
+ <entry file="file://$PROJECT_DIR$/src/main/java/org/nuiton/entities/Loft.java">
+ <provider selected="true" editor-type-id="text-editor">
+ <state vertical-scroll-proportion="0.0" vertical-offset="45" max-vertical-offset="465">
+ <caret line="27" column="5" selection-start-line="27" selection-start-column="5" selection-end-line="27" selection-end-column="5" />
+ <folding />
+ </state>
+ </provider>
+ </entry>
+ <entry file="file://$PROJECT_DIR$/src/main/java/org/nuiton/entities/Suite.java">
+ <provider selected="true" editor-type-id="text-editor">
+ <state vertical-scroll-proportion="0.0" vertical-offset="420" max-vertical-offset="540">
+ <caret line="28" column="5" selection-start-line="28" selection-start-column="5" selection-end-line="28" selection-end-column="5" />
+ <folding />
+ </state>
+ </provider>
+ </entry>
+ <entry file="file://$PROJECT_DIR$/src/main/java/org/nuiton/entities/Building.java">
+ <provider selected="true" editor-type-id="text-editor">
+ <state vertical-scroll-proportion="0.0" vertical-offset="315" max-vertical-offset="810">
+ <caret line="21" column="34" selection-start-line="21" selection-start-column="34" selection-end-line="21" selection-end-column="34" />
+ <folding>
+ <element signature="e#401#402#0" expanded="true" />
+ <element signature="e#434#435#0" expanded="true" />
+ <element signature="e#484#485#0" expanded="true" />
+ <element signature="e#528#529#0" expanded="true" />
+ </folding>
+ </state>
+ </provider>
+ </entry>
+ <entry file="file://$PROJECT_DIR$/src/main/java/org/nuiton/entities/Flat.java">
+ <provider selected="true" editor-type-id="text-editor">
+ <state vertical-scroll-proportion="0.0" vertical-offset="435" max-vertical-offset="555">
+ <caret line="29" column="5" selection-start-line="29" selection-start-column="5" selection-end-line="29" selection-end-column="5" />
+ <folding>
+ <element signature="e#215#216#0" expanded="true" />
+ <element signature="e#244#245#0" expanded="true" />
+ <element signature="e#286#287#0" expanded="true" />
+ <element signature="e#322#323#0" expanded="true" />
+ </folding>
+ </state>
+ </provider>
+ </entry>
+ <entry file="file://$PROJECT_DIR$/src/test/java/org/nuiton/entities/AppTest.java">
+ <provider selected="true" editor-type-id="text-editor">
+ <state vertical-scroll-proportion="0.0" vertical-offset="1020" max-vertical-offset="1485">
+ <caret line="68" column="28" selection-start-line="68" selection-start-column="28" selection-end-line="68" selection-end-column="28" />
+ <folding>
+ <element signature="imports" expanded="true" />
+ </folding>
+ </state>
+ </provider>
+ </entry>
+ <entry file="file://$PROJECT_DIR$/src/main/java/org/nuiton/entities/Loft.java">
+ <provider selected="true" editor-type-id="text-editor">
+ <state vertical-scroll-proportion="0.0" vertical-offset="390" max-vertical-offset="480">
+ <caret line="30" column="0" selection-start-line="30" selection-start-column="0" selection-end-line="30" selection-end-column="0" />
+ <folding />
+ </state>
+ </provider>
+ </entry>
+ <entry file="file://$PROJECT_DIR$/src/main/java/org/nuiton/entities/Suite.java">
+ <provider selected="true" editor-type-id="text-editor">
+ <state vertical-scroll-proportion="0.0" vertical-offset="285" max-vertical-offset="540">
+ <caret line="19" column="18" selection-start-line="19" selection-start-column="18" selection-end-line="19" selection-end-column="18" />
+ <folding />
+ </state>
+ </provider>
+ </entry>
+ <entry file="file://$PROJECT_DIR$/src/main/java/org/nuiton/entities/Building.java">
+ <provider selected="true" editor-type-id="text-editor">
+ <state vertical-scroll-proportion="0.0" vertical-offset="0" max-vertical-offset="810">
+ <caret line="21" column="34" selection-start-line="21" selection-start-column="34" selection-end-line="21" selection-end-column="34" />
+ <folding>
+ <element signature="e#401#402#0" expanded="true" />
+ <element signature="e#434#435#0" expanded="true" />
+ <element signature="e#484#485#0" expanded="true" />
+ <element signature="e#528#529#0" expanded="true" />
+ </folding>
+ </state>
+ </provider>
+ </entry>
+ <entry file="file://$PROJECT_DIR$/src/main/java/org/nuiton/entities/Flat.java">
+ <provider selected="true" editor-type-id="text-editor">
+ <state vertical-scroll-proportion="0.0" vertical-offset="435" max-vertical-offset="555">
+ <caret line="29" column="5" selection-start-line="29" selection-start-column="5" selection-end-line="29" selection-end-column="5" />
+ <folding>
+ <element signature="e#215#216#0" expanded="true" />
+ <element signature="e#244#245#0" expanded="true" />
+ <element signature="e#286#287#0" expanded="true" />
+ <element signature="e#322#323#0" expanded="true" />
+ </folding>
+ </state>
+ </provider>
+ </entry>
+ <entry file="file://$PROJECT_DIR$/src/test/java/org/nuiton/entities/AppTest.java">
+ <provider selected="true" editor-type-id="text-editor">
+ <state vertical-scroll-proportion="0.0" vertical-offset="1020" max-vertical-offset="1485">
+ <caret line="68" column="28" selection-start-line="68" selection-start-column="28" selection-end-line="68" selection-end-column="28" />
+ <folding>
+ <element signature="imports" expanded="true" />
+ </folding>
+ </state>
+ </provider>
+ </entry>
+ <entry file="file://$PROJECT_DIR$/src/main/java/org/nuiton/entities/Loft.java">
+ <provider selected="true" editor-type-id="text-editor">
+ <state vertical-scroll-proportion="0.0" vertical-offset="390" max-vertical-offset="480">
+ <caret line="30" column="0" selection-start-line="30" selection-start-column="0" selection-end-line="30" selection-end-column="0" />
+ <folding />
+ </state>
+ </provider>
+ </entry>
+ <entry file="file://$PROJECT_DIR$/src/main/java/org/nuiton/entities/Suite.java">
+ <provider selected="true" editor-type-id="text-editor">
+ <state vertical-scroll-proportion="0.0" vertical-offset="285" max-vertical-offset="540">
+ <caret line="19" column="18" selection-start-line="19" selection-start-column="18" selection-end-line="19" selection-end-column="18" />
+ <folding />
+ </state>
+ </provider>
+ </entry>
+ <entry file="file://$PROJECT_DIR$/src/main/java/org/nuiton/entities/Building.java">
+ <provider selected="true" editor-type-id="text-editor">
+ <state vertical-scroll-proportion="0.0" vertical-offset="0" max-vertical-offset="810">
+ <caret line="21" column="34" selection-start-line="21" selection-start-column="34" selection-end-line="21" selection-end-column="34" />
+ <folding>
+ <element signature="e#401#402#0" expanded="true" />
+ <element signature="e#434#435#0" expanded="true" />
+ <element signature="e#484#485#0" expanded="true" />
+ <element signature="e#528#529#0" expanded="true" />
+ </folding>
+ </state>
+ </provider>
+ </entry>
+ <entry file="file://$PROJECT_DIR$/src/main/java/org/nuiton/entities/Flat.java">
+ <provider selected="true" editor-type-id="text-editor">
+ <state vertical-scroll-proportion="0.0" vertical-offset="435" max-vertical-offset="555">
+ <caret line="29" column="5" selection-start-line="29" selection-start-column="5" selection-end-line="29" selection-end-column="5" />
+ <folding>
+ <element signature="e#215#216#0" expanded="true" />
+ <element signature="e#244#245#0" expanded="true" />
+ <element signature="e#286#287#0" expanded="true" />
+ <element signature="e#322#323#0" expanded="true" />
+ </folding>
+ </state>
+ </provider>
+ </entry>
+ <entry file="file://$PROJECT_DIR$/src/test/java/org/nuiton/entities/AppTest.java">
+ <provider selected="true" editor-type-id="text-editor">
+ <state vertical-scroll-proportion="0.0" vertical-offset="1020" max-vertical-offset="1485">
+ <caret line="68" column="28" selection-start-line="68" selection-start-column="28" selection-end-line="68" selection-end-column="28" />
+ <folding>
+ <element signature="imports" expanded="true" />
+ </folding>
+ </state>
+ </provider>
+ </entry>
+ <entry file="file://$PROJECT_DIR$/src/main/java/org/nuiton/entities/Loft.java">
+ <provider selected="true" editor-type-id="text-editor">
+ <state vertical-scroll-proportion="0.0" vertical-offset="45" max-vertical-offset="480">
+ <caret line="3" column="32" selection-start-line="3" selection-start-column="32" selection-end-line="3" selection-end-column="32" />
+ <folding />
+ </state>
+ </provider>
+ </entry>
+ <entry file="file://$PROJECT_DIR$/src/main/java/org/nuiton/entities/Suite.java">
+ <provider selected="true" editor-type-id="text-editor">
+ <state vertical-scroll-proportion="0.0" vertical-offset="285" max-vertical-offset="540">
+ <caret line="19" column="18" selection-start-line="19" selection-start-column="18" selection-end-line="19" selection-end-column="18" />
+ <folding />
+ </state>
+ </provider>
+ </entry>
+ <entry file="file://$PROJECT_DIR$/src/main/java/org/nuiton/entities/Building.java">
+ <provider selected="true" editor-type-id="text-editor">
+ <state vertical-scroll-proportion="0.0" vertical-offset="0" max-vertical-offset="810">
+ <caret line="21" column="34" selection-start-line="21" selection-start-column="34" selection-end-line="21" selection-end-column="34" />
+ <folding>
+ <element signature="e#401#402#0" expanded="true" />
+ <element signature="e#434#435#0" expanded="true" />
+ <element signature="e#484#485#0" expanded="true" />
+ <element signature="e#528#529#0" expanded="true" />
+ </folding>
+ </state>
+ </provider>
+ </entry>
+ <entry file="file://$PROJECT_DIR$/src/main/java/org/nuiton/entities/Flat.java">
+ <provider selected="true" editor-type-id="text-editor">
+ <state vertical-scroll-proportion="0.0" vertical-offset="435" max-vertical-offset="555">
+ <caret line="29" column="5" selection-start-line="29" selection-start-column="5" selection-end-line="29" selection-end-column="5" />
+ <folding>
+ <element signature="e#215#216#0" expanded="true" />
+ <element signature="e#244#245#0" expanded="true" />
+ <element signature="e#286#287#0" expanded="true" />
+ <element signature="e#322#323#0" expanded="true" />
+ </folding>
+ </state>
+ </provider>
+ </entry>
+ <entry file="file://$PROJECT_DIR$/src/test/java/org/nuiton/entities/AppTest.java">
+ <provider selected="true" editor-type-id="text-editor">
+ <state vertical-scroll-proportion="0.0" vertical-offset="1020" max-vertical-offset="1485">
+ <caret line="68" column="28" selection-start-line="68" selection-start-column="28" selection-end-line="68" selection-end-column="28" />
+ <folding>
+ <element signature="imports" expanded="true" />
+ </folding>
+ </state>
+ </provider>
+ </entry>
+ <entry file="file://$PROJECT_DIR$/src/main/java/org/nuiton/entities/Loft.java">
+ <provider selected="true" editor-type-id="text-editor">
+ <state vertical-scroll-proportion="0.0" vertical-offset="45" max-vertical-offset="480">
+ <caret line="3" column="32" selection-start-line="3" selection-start-column="32" selection-end-line="3" selection-end-column="32" />
+ <folding />
+ </state>
+ </provider>
+ </entry>
+ <entry file="file://$PROJECT_DIR$/src/main/java/org/nuiton/entities/Suite.java">
+ <provider selected="true" editor-type-id="text-editor">
+ <state vertical-scroll-proportion="0.0" vertical-offset="285" max-vertical-offset="540">
+ <caret line="19" column="18" selection-start-line="19" selection-start-column="18" selection-end-line="19" selection-end-column="18" />
+ <folding />
+ </state>
+ </provider>
+ </entry>
+ <entry file="file://$PROJECT_DIR$/src/main/java/org/nuiton/entities/Flat.java">
+ <provider selected="true" editor-type-id="text-editor">
+ <state vertical-scroll-proportion="0.0" vertical-offset="0" max-vertical-offset="555">
+ <caret line="15" column="17" selection-start-line="15" selection-start-column="17" selection-end-line="15" selection-end-column="17" />
+ <folding>
+ <element signature="e#215#216#0" expanded="true" />
+ <element signature="e#244#245#0" expanded="true" />
+ <element signature="e#286#287#0" expanded="true" />
+ <element signature="e#322#323#0" expanded="true" />
+ </folding>
+ </state>
+ </provider>
+ </entry>
+ <entry file="file://$PROJECT_DIR$/src/main/java/org/nuiton/entities/Building.java">
+ <provider selected="true" editor-type-id="text-editor">
+ <state vertical-scroll-proportion="0.0" vertical-offset="330" max-vertical-offset="810">
+ <caret line="22" column="30" selection-start-line="22" selection-start-column="25" selection-end-line="22" selection-end-column="30" />
+ <folding>
+ <element signature="e#401#402#0" expanded="true" />
+ <element signature="e#434#435#0" expanded="true" />
+ <element signature="e#484#485#0" expanded="true" />
+ <element signature="e#528#529#0" expanded="true" />
+ </folding>
+ </state>
+ </provider>
+ </entry>
+ <entry file="file://$PROJECT_DIR$/src/main/java/org/nuiton/entities/Loft.java">
+ <provider selected="true" editor-type-id="text-editor">
+ <state vertical-scroll-proportion="0.0" vertical-offset="285" max-vertical-offset="510">
+ <caret line="19" column="0" selection-start-line="19" selection-start-column="0" selection-end-line="19" selection-end-column="0" />
+ <folding />
+ </state>
+ </provider>
+ </entry>
+ <entry file="file://$PROJECT_DIR$/src/main/java/org/nuiton/entities/Suite.java">
+ <provider selected="true" editor-type-id="text-editor">
+ <state vertical-scroll-proportion="0.0" vertical-offset="225" max-vertical-offset="510">
+ <caret line="15" column="47" selection-start-line="15" selection-start-column="47" selection-end-line="15" selection-end-column="47" />
+ <folding />
+ </state>
+ </provider>
+ </entry>
+ <entry file="jar://$MAVEN_REPOSITORY$/org/hibernate/javax/persistence/hibernate-jpa-2.0-api/1.0.1.Final/hibernate-jpa-2.0-api-1.0.1.Final.jar!/javax/persistence/EntityManager.class">
+ <provider selected="true" editor-type-id="text-editor">
+ <state vertical-scroll-proportion="0.08455468" vertical-offset="0" max-vertical-offset="1365">
+ <caret line="6" column="17" selection-start-line="6" selection-start-column="17" selection-end-line="6" selection-end-column="17" />
+ </state>
+ </provider>
+ </entry>
+ <entry file="jar://$MAVEN_REPOSITORY$/org/hibernate/javax/persistence/hibernate-jpa-2.1-api/1.0.0.Final/hibernate-jpa-2.1-api-1.0.0.Final.jar!/javax/persistence/InheritanceType.class">
+ <provider selected="true" editor-type-id="text-editor">
+ <state vertical-scroll-proportion="-3.6" vertical-offset="0" max-vertical-offset="285">
+ <caret line="7" column="33" selection-start-line="7" selection-start-column="18" selection-end-line="7" selection-end-column="33" />
+ </state>
+ </provider>
+ </entry>
+ <entry file="file://$PROJECT_DIR$/.idea/workspace.xml">
+ <provider selected="true" editor-type-id="text-editor">
+ <state vertical-scroll-proportion="0.0" vertical-offset="0" max-vertical-offset="11685">
+ <caret line="0" column="0" selection-start-line="0" selection-start-column="0" selection-end-line="0" selection-end-column="0" />
+ </state>
+ </provider>
+ </entry>
+ <entry file="jar://$MAVEN_REPOSITORY$/org/hibernate/hibernate-core/4.3.5.Final/hibernate-core-4.3.5.Final.jar!/org/hibernate/service/ServiceRegistryBuilder.class">
+ <provider selected="true" editor-type-id="text-editor">
+ <state vertical-scroll-proportion="0.35674158" vertical-offset="23" max-vertical-offset="525">
+ <caret line="11" column="11" selection-start-line="11" selection-start-column="11" selection-end-line="11" selection-end-column="11" />
+ <folding />
+ </state>
+ </provider>
+ </entry>
+ <entry file="jar://$MAVEN_REPOSITORY$/org/hibernate/hibernate-core/4.3.5.Final/hibernate-core-4.3.5.Final.jar!/org/hibernate/cfg/Configuration.class">
+ <provider selected="true" editor-type-id="text-editor">
+ <state vertical-scroll-proportion="0.11792453" vertical-offset="0" max-vertical-offset="10110">
+ <caret line="6" column="13" selection-start-line="6" selection-start-column="13" selection-end-line="6" selection-end-column="13" />
+ <folding />
+ </state>
+ </provider>
+ </entry>
+ <entry file="file://$PROJECT_DIR$/src/main/java/org/nuiton/entities/Loft.java">
+ <provider selected="true" editor-type-id="text-editor">
+ <state vertical-scroll-proportion="0.0" vertical-offset="0" max-vertical-offset="315">
+ <caret line="11" column="22" selection-start-line="11" selection-start-column="22" selection-end-line="11" selection-end-column="22" />
+ <folding />
+ </state>
+ </provider>
+ </entry>
+ <entry file="jar://$MAVEN_REPOSITORY$/org/hibernate/hibernate-core/4.3.5.Final/hibernate-core-4.3.5.Final.jar!/org/hibernate/Session.class">
+ <provider selected="true" editor-type-id="text-editor">
+ <state vertical-scroll-proportion="0.0" vertical-offset="868" max-vertical-offset="3240">
+ <caret line="67" column="25" selection-start-line="67" selection-start-column="25" selection-end-line="67" selection-end-column="25" />
+ <folding />
+ </state>
+ </provider>
+ </entry>
+ <entry file="jar://$MAVEN_REPOSITORY$/org/hibernate/hibernate-core/4.3.5.Final/hibernate-core-4.3.5.Final-sources.jar!/org/hibernate/Session.java">
+ <provider selected="true" editor-type-id="text-editor">
+ <state vertical-scroll-proportion="0.0" vertical-offset="5428" max-vertical-offset="16305">
+ <caret line="401" column="0" selection-start-line="401" selection-start-column="0" selection-end-line="401" selection-end-column="0" />
+ <folding />
+ </state>
+ </provider>
+ </entry>
+ <entry file="file://$PROJECT_DIR$/src/main/java/org/nuiton/entities/Flat.java">
+ <provider selected="true" editor-type-id="text-editor">
+ <state vertical-scroll-proportion="0.0" vertical-offset="0" max-vertical-offset="375">
+ <caret line="9" column="25" selection-start-line="9" selection-start-column="19" selection-end-line="9" selection-end-column="25" />
+ <folding>
+ <element signature="e#215#216#0" expanded="true" />
+ <element signature="e#244#245#0" expanded="true" />
+ <element signature="e#286#287#0" expanded="true" />
+ <element signature="e#322#323#0" expanded="true" />
+ </folding>
+ </state>
+ </provider>
+ </entry>
+ <entry file="file://$PROJECT_DIR$/src/main/java/org/nuiton/entities/Building.java">
+ <provider selected="true" editor-type-id="text-editor">
+ <state vertical-scroll-proportion="-7.6666665" vertical-offset="108" max-vertical-offset="555">
+ <caret line="26" column="28" selection-start-line="26" selection-start-column="28" selection-end-line="26" selection-end-column="28" />
+ <folding>
+ <element signature="e#401#402#0" expanded="true" />
+ <element signature="e#434#435#0" expanded="true" />
+ <element signature="e#484#485#0" expanded="true" />
+ <element signature="e#528#529#0" expanded="true" />
+ </folding>
+ </state>
+ </provider>
+ </entry>
+ <entry file="file://$PROJECT_DIR$/src/test/java/org/nuiton/entities/AppTest.java">
+ <provider selected="true" editor-type-id="text-editor">
+ <state vertical-scroll-proportion="0.0" vertical-offset="665" max-vertical-offset="1410">
+ <caret line="65" column="37" selection-start-line="65" selection-start-column="37" selection-end-line="65" selection-end-column="37" />
+ <folding>
+ <element signature="imports" expanded="true" />
+ </folding>
+ </state>
+ </provider>
+ </entry>
+ <entry file="file://$PROJECT_DIR$/src/main/java/org/nuiton/entities/Suite.java">
+ <provider selected="true" editor-type-id="text-editor">
+ <state vertical-scroll-proportion="0.0" vertical-offset="0" max-vertical-offset="315">
+ <caret line="9" column="26" selection-start-line="9" selection-start-column="26" selection-end-line="9" selection-end-column="26" />
+ <folding />
+ </state>
+ </provider>
+ </entry>
+ <entry file="file://$PROJECT_DIR$/src/main/resources/org/nuiton/entities/Building.hbm.xml">
+ <provider selected="true" editor-type-id="text-editor">
+ <state vertical-scroll-proportion="0.0" vertical-offset="0" max-vertical-offset="330">
+ <caret line="5" column="61" selection-start-line="5" selection-start-column="61" selection-end-line="5" selection-end-column="61" />
+ <folding />
+ </state>
+ </provider>
+ </entry>
+ <entry file="file://$PROJECT_DIR$/src/main/resources/org/nuiton/entities/Flat.hbm.xml">
+ <provider selected="true" editor-type-id="text-editor">
+ <state vertical-scroll-proportion="0.0" vertical-offset="0" max-vertical-offset="255">
+ <caret line="5" column="41" selection-start-line="5" selection-start-column="41" selection-end-line="5" selection-end-column="41" />
+ <folding />
+ </state>
+ </provider>
+ </entry>
+ <entry file="file://$PROJECT_DIR$/src/main/resources/org/nuiton/entities/Loft.hbm.xml">
+ <provider selected="true" editor-type-id="text-editor">
+ <state vertical-scroll-proportion="0.0" vertical-offset="0" max-vertical-offset="210">
+ <caret line="5" column="85" selection-start-line="5" selection-start-column="85" selection-end-line="5" selection-end-column="85" />
+ <folding />
+ </state>
+ </provider>
+ </entry>
+ <entry file="file://$PROJECT_DIR$/src/main/resources/org/nuiton/entities/Suite.hbm.xml">
+ <provider selected="true" editor-type-id="text-editor">
+ <state vertical-scroll-proportion="0.0" vertical-offset="0" max-vertical-offset="210">
+ <caret line="5" column="103" selection-start-line="5" selection-start-column="103" selection-end-line="5" selection-end-column="103" />
+ <folding />
+ </state>
+ </provider>
+ </entry>
+ <entry file="file://$PROJECT_DIR$/src/test/resources/hibernate.cfg.xml">
+ <provider selected="true" editor-type-id="text-editor">
+ <state vertical-scroll-proportion="0.0" vertical-offset="0" max-vertical-offset="465">
+ <caret line="6" column="18" selection-start-line="6" selection-start-column="18" selection-end-line="6" selection-end-column="18" />
+ <folding />
+ </state>
+ </provider>
+ </entry>
+ <entry file="file://$PROJECT_DIR$/changelog.txt">
+ <provider selected="true" editor-type-id="text-editor">
+ <state vertical-scroll-proportion="0.0" vertical-offset="0" max-vertical-offset="90">
+ <caret line="0" column="0" selection-start-line="0" selection-start-column="0" selection-end-line="0" selection-end-column="0" />
+ <folding />
+ </state>
+ </provider>
+ </entry>
+ <entry file="file://$PROJECT_DIR$/LICENSE.txt">
+ <provider selected="true" editor-type-id="text-editor">
+ <state vertical-scroll-proportion="0.0" vertical-offset="0" max-vertical-offset="2580">
+ <caret line="0" column="0" selection-start-line="0" selection-start-column="0" selection-end-line="0" selection-end-column="0" />
+ <folding />
+ </state>
+ </provider>
+ </entry>
+ <entry file="file://$PROJECT_DIR$/pom.xml">
+ <provider selected="true" editor-type-id="text-editor">
+ <state vertical-scroll-proportion="0.0" vertical-offset="0" max-vertical-offset="2490">
+ <caret line="36" column="0" selection-start-line="36" selection-start-column="0" selection-end-line="36" selection-end-column="0" />
+ <folding />
+ </state>
+ </provider>
+ </entry>
+ <entry file="file://$PROJECT_DIR$/README.txt">
+ <provider selected="true" editor-type-id="text-editor">
+ <state vertical-scroll-proportion="0.0" vertical-offset="0" max-vertical-offset="335">
+ <caret line="0" column="53" selection-start-line="0" selection-start-column="53" selection-end-line="0" selection-end-column="61" />
+ <folding />
+ </state>
+ </provider>
+ </entry>
+ </component>
+</project>
+
Added: hibernate-list-idx-bug/LICENSE.txt
===================================================================
--- hibernate-list-idx-bug/LICENSE.txt (rev 0)
+++ hibernate-list-idx-bug/LICENSE.txt 2014-04-23 15:39:47 UTC (rev 703)
@@ -0,0 +1,166 @@
+ GNU LESSER GENERAL PUBLIC LICENSE
+ Version 3, 29 June 2007
+
+ Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+
+ This version of the GNU Lesser General Public License incorporates
+the terms and conditions of version 3 of the GNU General Public
+License, supplemented by the additional permissions listed below.
+
+ 0. Additional Definitions.
+
+ As used herein, "this License" refers to version 3 of the GNU Lesser
+General Public License, and the "GNU GPL" refers to version 3 of the GNU
+General Public License.
+
+ "The Library" refers to a covered work governed by this License,
+other than an Application or a Combined Work as defined below.
+
+ An "Application" is any work that makes use of an interface provided
+by the Library, but which is not otherwise based on the Library.
+Defining a subclass of a class defined by the Library is deemed a mode
+of using an interface provided by the Library.
+
+ A "Combined Work" is a work produced by combining or linking an
+Application with the Library. The particular version of the Library
+with which the Combined Work was made is also called the "Linked
+Version".
+
+ The "Minimal Corresponding Source" for a Combined Work means the
+Corresponding Source for the Combined Work, excluding any source code
+for portions of the Combined Work that, considered in isolation, are
+based on the Application, and not on the Linked Version.
+
+ The "Corresponding Application Code" for a Combined Work means the
+object code and/or source code for the Application, including any data
+and utility programs needed for reproducing the Combined Work from the
+Application, but excluding the System Libraries of the Combined Work.
+
+ 1. Exception to Section 3 of the GNU GPL.
+
+ You may convey a covered work under sections 3 and 4 of this License
+without being bound by section 3 of the GNU GPL.
+
+ 2. Conveying Modified Versions.
+
+ If you modify a copy of the Library, and, in your modifications, a
+facility refers to a function or data to be supplied by an Application
+that uses the facility (other than as an argument passed when the
+facility is invoked), then you may convey a copy of the modified
+version:
+
+ a) under this License, provided that you make a good faith effort to
+ ensure that, in the event an Application does not supply the
+ function or data, the facility still operates, and performs
+ whatever part of its purpose remains meaningful, or
+
+ b) under the GNU GPL, with none of the additional permissions of
+ this License applicable to that copy.
+
+ 3. Object Code Incorporating Material from Library Header Files.
+
+ The object code form of an Application may incorporate material from
+a header file that is part of the Library. You may convey such object
+code under terms of your choice, provided that, if the incorporated
+material is not limited to numerical parameters, data structure
+layouts and accessors, or small macros, inline functions and templates
+(ten or fewer lines in length), you do both of the following:
+
+ a) Give prominent notice with each copy of the object code that the
+ Library is used in it and that the Library and its use are
+ covered by this License.
+
+ b) Accompany the object code with a copy of the GNU GPL and this license
+ document.
+
+ 4. Combined Works.
+
+ You may convey a Combined Work under terms of your choice that,
+taken together, effectively do not restrict modification of the
+portions of the Library contained in the Combined Work and reverse
+engineering for debugging such modifications, if you also do each of
+the following:
+
+ a) Give prominent notice with each copy of the Combined Work that
+ the Library is used in it and that the Library and its use are
+ covered by this License.
+
+ b) Accompany the Combined Work with a copy of the GNU GPL and this license
+ document.
+
+ c) For a Combined Work that displays copyright notices during
+ execution, include the copyright notice for the Library among
+ these notices, as well as a reference directing the user to the
+ copies of the GNU GPL and this license document.
+
+ d) Do one of the following:
+
+ 0) Convey the Minimal Corresponding Source under the terms of this
+ License, and the Corresponding Application Code in a form
+ suitable for, and under terms that permit, the user to
+ recombine or relink the Application with a modified version of
+ the Linked Version to produce a modified Combined Work, in the
+ manner specified by section 6 of the GNU GPL for conveying
+ Corresponding Source.
+
+ 1) Use a suitable shared library mechanism for linking with the
+ Library. A suitable mechanism is one that (a) uses at run time
+ a copy of the Library already present on the user's computer
+ system, and (b) will operate properly with a modified version
+ of the Library that is interface-compatible with the Linked
+ Version.
+
+ e) Provide Installation Information, but only if you would otherwise
+ be required to provide such information under section 6 of the
+ GNU GPL, and only to the extent that such information is
+ necessary to install and execute a modified version of the
+ Combined Work produced by recombining or relinking the
+ Application with a modified version of the Linked Version. (If
+ you use option 4d0, the Installation Information must accompany
+ the Minimal Corresponding Source and Corresponding Application
+ Code. If you use option 4d1, you must provide the Installation
+ Information in the manner specified by section 6 of the GNU GPL
+ for conveying Corresponding Source.)
+
+ 5. Combined Libraries.
+
+ You may place library facilities that are a work based on the
+Library side by side in a single library together with other library
+facilities that are not Applications and are not covered by this
+License, and convey such a combined library under terms of your
+choice, if you do both of the following:
+
+ a) Accompany the combined library with a copy of the same work based
+ on the Library, uncombined with any other library facilities,
+ conveyed under the terms of this License.
+
+ b) Give prominent notice with the combined library that part of it
+ is a work based on the Library, and explaining where to find the
+ accompanying uncombined form of the same work.
+
+ 6. Revised Versions of the GNU Lesser General Public License.
+
+ The Free Software Foundation may publish revised and/or new versions
+of the GNU Lesser General Public License from time to time. Such new
+versions will be similar in spirit to the present version, but may
+differ in detail to address new problems or concerns.
+
+ Each version is given a distinguishing version number. If the
+Library as you received it specifies that a certain numbered version
+of the GNU Lesser General Public License "or any later version"
+applies to it, you have the option of following the terms and
+conditions either of that published version or of any later version
+published by the Free Software Foundation. If the Library as you
+received it does not specify a version number of the GNU Lesser
+General Public License, you may choose any version of the GNU Lesser
+General Public License ever published by the Free Software Foundation.
+
+ If the Library as you received it specifies that a proxy can decide
+whether future versions of the GNU Lesser General Public License shall
+apply, that proxy's public statement of acceptance of any version is
+permanent authorization for you to choose that version for the
+Library.
+
Added: hibernate-list-idx-bug/README.txt
===================================================================
--- hibernate-list-idx-bug/README.txt (rev 0)
+++ hibernate-list-idx-bug/README.txt 2014-04-23 15:39:47 UTC (rev 703)
@@ -0,0 +1,12 @@
+Test case for https://hibernate.atlassian.net/browse/HHH-8109
+
+HOWTO
+-----
+
+Run with
+$ mvn clean package
+
+The unit test is expecting an exception which is not thrown in Hibernate 4.3.5.Final
+
+ERROR: Table "FLAT" not found; SQL statement:
+update Flat set building=?, building_idx=? where flatId=? [42102-177]
Added: hibernate-list-idx-bug/changelog.txt
===================================================================
Added: hibernate-list-idx-bug/pom.xml
===================================================================
--- hibernate-list-idx-bug/pom.xml (rev 0)
+++ hibernate-list-idx-bug/pom.xml 2014-04-23 15:39:47 UTC (rev 703)
@@ -0,0 +1,160 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+
+ <modelVersion>4.0.0</modelVersion>
+
+ <!-- ************************************************************* -->
+ <!-- *** POM Relationships *************************************** -->
+ <!-- ************************************************************* -->
+
+ <parent>
+ <groupId>org.nuiton</groupId>
+ <artifactId>mavenpom</artifactId>
+ <version>3.4.9</version>
+ </parent>
+
+ <groupId>org.nuiton</groupId>
+ <artifactId>test</artifactId>
+ <version>0.1-SNAPSHOT</version>
+
+ <!-- ************************************************************* -->
+ <!-- *** Project Information ************************************* -->
+ <!-- ************************************************************* -->
+
+ <name>Test case for ???</name>
+ <inceptionYear>2014</inceptionYear>
+
+ <licenses>
+ <license>
+ <name>Lesser General Public License (LGPL) v 3.0</name>
+ <url>http://www.gnu.org/licenses/lgpl-3.0.txt</url>
+ <distribution>repo</distribution>
+ </license>
+ </licenses>
+
+ <developers>
+
+ <developer>
+ <name>Arnaud Thimel</name>
+ <id>athimel</id>
+ <email>thimel(a)codelutin.com</email>
+ <organization>Code Lutin</organization>
+ <timezone>+2</timezone>
+ <roles>
+ <role>developer</role>
+ </roles>
+ </developer>
+
+ <developer>
+ <name>David Cossé</name>
+ <id>cosse</id>
+ <email>cosse(a)codelutin.com</email>
+ <organization>Code Lutin</organization>
+ <timezone>+2</timezone>
+ <roles>
+ <role>developer</role>
+ </roles>
+ </developer>
+
+ </developers>
+
+ <organization>
+ <name>Code Lutin</name>
+ <url>http://www.codelutin.com/</url>
+ </organization>
+
+ <!-- ************************************************************* -->
+ <!-- *** Build Environment ************************************** -->
+ <!-- ************************************************************* -->
+
+ <issueManagement/>
+ <ciManagement/>
+ <mailingLists/>
+ <scm/>
+
+ <!-- ************************************************************* -->
+ <!-- *** Build Settings ****************************************** -->
+ <!-- ************************************************************* -->
+
+ <properties>
+ <license.licenseName>lgpl_v3</license.licenseName>
+
+ <h2Version>1.4.177</h2Version>
+
+ <hibernateVersion>4.3.5.Final</hibernateVersion>
+ </properties>
+
+ <packaging>jar</packaging>
+
+ <dependencies>
+
+ <!-- Hibernate -->
+ <dependency>
+ <groupId>org.hibernate</groupId>
+ <artifactId>hibernate-core</artifactId>
+ <version>${hibernateVersion}</version>
+ </dependency>
+
+ <dependency>
+ <groupId>org.hibernate</groupId>
+ <artifactId>hibernate-entitymanager</artifactId>
+ <version>${hibernateVersion}</version>
+ </dependency>
+
+ <!-- Other libraries -->
+ <dependency>
+ <groupId>org.slf4j</groupId>
+ <artifactId>slf4j-simple</artifactId>
+ <version>1.7.6</version>
+ </dependency>
+
+ <dependency>
+ <groupId>javassist</groupId>
+ <artifactId>javassist</artifactId>
+ <version>3.12.1.GA</version>
+ </dependency>
+
+ <dependency>
+ <groupId>com.google.guava</groupId>
+ <artifactId>guava</artifactId>
+ </dependency>
+
+ <!-- Tests -->
+ <dependency>
+ <groupId>junit</groupId>
+ <artifactId>junit</artifactId>
+ </dependency>
+
+ <dependency>
+ <groupId>com.h2database</groupId>
+ <artifactId>h2</artifactId>
+ <version>${h2Version}</version>
+ <scope>test</scope>
+ </dependency>
+
+ </dependencies>
+
+ <repositories/>
+
+ <build>
+ <resources/>
+ <extensions/>
+ <plugins/>
+ </build>
+
+ <reporting/>
+
+ <!-- ************************************************************* -->
+ <!-- *** Maven Environment *************************************** -->
+ <!-- ************************************************************* -->
+
+ <prerequisites>
+ <maven>3.0</maven>
+ </prerequisites>
+
+ <distributionManagement/>
+ <profiles/>
+
+</project>
+
Added: hibernate-list-idx-bug/src/main/java/org/nuiton/entities/Building.java
===================================================================
--- hibernate-list-idx-bug/src/main/java/org/nuiton/entities/Building.java (rev 0)
+++ hibernate-list-idx-bug/src/main/java/org/nuiton/entities/Building.java 2014-04-23 15:39:47 UTC (rev 703)
@@ -0,0 +1,44 @@
+package org.nuiton.entities;
+
+import javax.persistence.Column;
+import javax.persistence.Entity;
+import javax.persistence.Id;
+import javax.persistence.OneToMany;
+import java.io.Serializable;
+import java.util.List;
+
+/**
+ * Created by davidcosse on 16/04/14.
+ */
+public class Building implements Serializable {
+
+ protected Long buildingId;
+
+ protected String name;
+
+ public Long getBuildingId() {
+ return buildingId;
+ }
+
+ public void setBuildingId(Long buildingId) {
+ this.buildingId = buildingId;
+ }
+
+ protected List<Flat> flats;
+
+ public String getName() {
+ return name;
+ }
+
+ public void setName(String name) {
+ this.name = name;
+ }
+
+ public List<Flat> getFlats() {
+ return flats;
+ }
+
+ public void setFlats(List<Flat> flats) {
+ this.flats = flats;
+ }
+}
Added: hibernate-list-idx-bug/src/main/java/org/nuiton/entities/Flat.java
===================================================================
--- hibernate-list-idx-bug/src/main/java/org/nuiton/entities/Flat.java (rev 0)
+++ hibernate-list-idx-bug/src/main/java/org/nuiton/entities/Flat.java 2014-04-23 15:39:47 UTC (rev 703)
@@ -0,0 +1,19 @@
+package org.nuiton.entities;
+
+import java.io.Serializable;
+
+/**
+ * Created by davidcosse on 16/04/14.
+ */
+public abstract class Flat implements Serializable{
+
+ protected Long flatId;
+
+ public Long getFlatId() {
+ return flatId;
+ }
+
+ public void setFlatId(Long flatId) {
+ this.flatId = flatId;
+ }
+}
Added: hibernate-list-idx-bug/src/main/java/org/nuiton/entities/Loft.java
===================================================================
--- hibernate-list-idx-bug/src/main/java/org/nuiton/entities/Loft.java (rev 0)
+++ hibernate-list-idx-bug/src/main/java/org/nuiton/entities/Loft.java 2014-04-23 15:39:47 UTC (rev 703)
@@ -0,0 +1,19 @@
+package org.nuiton.entities;
+import java.io.Serializable;
+
+/**
+ * Created by davidcosse on 16/04/14.
+ */
+public class Loft extends Flat implements Serializable {
+
+ //(a)hibernate.union-subclass
+ protected String name;
+
+ public String getName() {
+ return name;
+ }
+
+ public void setName(String name) {
+ this.name = name;
+ }
+}
Added: hibernate-list-idx-bug/src/main/java/org/nuiton/entities/Suite.java
===================================================================
--- hibernate-list-idx-bug/src/main/java/org/nuiton/entities/Suite.java (rev 0)
+++ hibernate-list-idx-bug/src/main/java/org/nuiton/entities/Suite.java 2014-04-23 15:39:47 UTC (rev 703)
@@ -0,0 +1,19 @@
+package org.nuiton.entities;
+
+import java.io.Serializable;
+
+/**
+ * Created by davidcosse on 16/04/14.
+ */
+public class Suite extends Flat implements Serializable {
+
+ protected String name;
+
+ public String getName() {
+ return name;
+ }
+
+ public void setName(String name) {
+ this.name = name;
+ }
+}
Added: hibernate-list-idx-bug/src/main/resources/org/nuiton/entities/Building.hbm.xml
===================================================================
--- hibernate-list-idx-bug/src/main/resources/org/nuiton/entities/Building.hbm.xml (rev 0)
+++ hibernate-list-idx-bug/src/main/resources/org/nuiton/entities/Building.hbm.xml 2014-04-23 15:39:47 UTC (rev 703)
@@ -0,0 +1,17 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<hibernate-mapping xmlns="http://www.hibernate.org/xsd/hibernate-mapping"
+ xsi:schemaLocation="http://www.hibernate.org/xsd/hibernate-mapping classpath://org/hibernate/hibernate-mapping-4.0.xsd"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ default-access="field" auto-import="true" package="org.nuiton.entities">
+ <class name="org.nuiton.entities.Building" abstract="false" >
+ <id name="buildingId" type="long">
+ <generator class="increment"/>
+ </id>
+ <property name="name" access="field" type="text" column="name"/>
+ <list name="flats" lazy="true" cascade="all,delete-orphan">
+ <key column="building"/>
+ <list-index column="building_idx"/>
+ <one-to-many class="org.nuiton.entities.Flat"/>
+ </list>
+ </class>
+</hibernate-mapping>
\ No newline at end of file
Added: hibernate-list-idx-bug/src/main/resources/org/nuiton/entities/Flat.hbm.xml
===================================================================
--- hibernate-list-idx-bug/src/main/resources/org/nuiton/entities/Flat.hbm.xml (rev 0)
+++ hibernate-list-idx-bug/src/main/resources/org/nuiton/entities/Flat.hbm.xml 2014-04-23 15:39:47 UTC (rev 703)
@@ -0,0 +1,12 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<hibernate-mapping xmlns="http://www.hibernate.org/xsd/hibernate-mapping"
+ xsi:schemaLocation="http://www.hibernate.org/xsd/hibernate-mapping classpath://org/hibernate/hibernate-mapping-4.0.xsd"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ default-access="field" auto-import="true" package="org.nuiton.entities">
+ <class name="org.nuiton.entities.Flat" abstract="true">
+ <id name="flatId" type="long">
+ <generator class="increment"/>
+ </id>
+
+ </class>
+</hibernate-mapping>
\ No newline at end of file
Added: hibernate-list-idx-bug/src/main/resources/org/nuiton/entities/Loft.hbm.xml
===================================================================
--- hibernate-list-idx-bug/src/main/resources/org/nuiton/entities/Loft.hbm.xml (rev 0)
+++ hibernate-list-idx-bug/src/main/resources/org/nuiton/entities/Loft.hbm.xml 2014-04-23 15:39:47 UTC (rev 703)
@@ -0,0 +1,9 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<hibernate-mapping xmlns="http://www.hibernate.org/xsd/hibernate-mapping"
+ xsi:schemaLocation="http://www.hibernate.org/xsd/hibernate-mapping classpath://org/hibernate/hibernate-mapping-4.0.xsd"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ default-access="field" auto-import="true" package="org.nuiton.entities">
+ <union-subclass name="org.nuiton.entities.Loft" extends="org.nuiton.entities.Flat" abstract="false">
+ <property name="name" access="field" type="text" column="name"/>
+ </union-subclass>
+</hibernate-mapping>
\ No newline at end of file
Added: hibernate-list-idx-bug/src/main/resources/org/nuiton/entities/Suite.hbm.xml
===================================================================
--- hibernate-list-idx-bug/src/main/resources/org/nuiton/entities/Suite.hbm.xml (rev 0)
+++ hibernate-list-idx-bug/src/main/resources/org/nuiton/entities/Suite.hbm.xml 2014-04-23 15:39:47 UTC (rev 703)
@@ -0,0 +1,9 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<hibernate-mapping xmlns="http://www.hibernate.org/xsd/hibernate-mapping"
+ xsi:schemaLocation="http://www.hibernate.org/xsd/hibernate-mapping classpath://org/hibernate/hibernate-mapping-4.0.xsd"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ default-access="field" auto-import="true" package="fr.inra.agrosyst.api.entities.measure">
+ <union-subclass name="org.nuiton.entities.Suite" extends="org.nuiton.entities.Flat" abstract="false" >
+ <property name="name" access="field" type="text" column="name"/>
+ </union-subclass>
+</hibernate-mapping>
\ No newline at end of file
Added: hibernate-list-idx-bug/src/test/java/org/nuiton/entities/AppTest.java
===================================================================
--- hibernate-list-idx-bug/src/test/java/org/nuiton/entities/AppTest.java (rev 0)
+++ hibernate-list-idx-bug/src/test/java/org/nuiton/entities/AppTest.java 2014-04-23 15:39:47 UTC (rev 703)
@@ -0,0 +1,88 @@
+package org.nuiton.entities;
+
+/*
+ * #%L
+ * Test case for HHH-8109
+ * %%
+ * Copyright (C) 2013 Code Lutin
+ * %%
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Lesser Public License for more details.
+ *
+ * You should have received a copy of the GNU General Lesser Public
+ * License along with this program. If not, see
+ * <http://www.gnu.org/licenses/lgpl-3.0.html>.
+ * #L%
+ */
+
+import com.google.common.collect.Lists;
+import org.hibernate.SessionFactory;
+import org.hibernate.Transaction;
+import org.hibernate.boot.registry.StandardServiceRegistryBuilder;
+import org.hibernate.cfg.Configuration;
+import org.hibernate.service.ServiceRegistry;
+import org.hibernate.service.ServiceRegistryBuilder;
+import org.junit.Assert;
+import org.junit.Test;
+
+import javax.persistence.PersistenceException;
+import java.util.List;
+
+public class AppTest {
+
+ @Test
+ public void test() {
+
+ Configuration configuration = new Configuration();
+ configuration.configure();
+ StandardServiceRegistryBuilder serviceRegistryBuilder = new StandardServiceRegistryBuilder();
+ ServiceRegistry serviceRegistry = serviceRegistryBuilder.applySettings(
+ configuration.getProperties()).build();
+ SessionFactory sessionFactory = configuration.buildSessionFactory(serviceRegistry);
+ org.hibernate.Session sess = sessionFactory.openSession();
+
+ Loft loft = new Loft();
+ loft.setName("L-A");
+
+ // Create and persist a suite instance
+ Transaction tx = sess.beginTransaction();
+ Long loftKey = (Long)sess.save(loft);
+ System.out.println("loftKey:" + loftKey);
+
+ Suite suite = new Suite();
+ suite.setName("S-A");
+
+ // Create and persist a suite instance
+ Long suiteKey = (Long)sess.save(suite);
+ System.out.println("suiteKey:" + suiteKey);
+ tx.commit();
+ tx = sess.beginTransaction();
+
+ Building building = new Building();
+ building.setName("B-A");
+ List<Flat> flats = Lists.newArrayList();
+ building.setFlats(flats);
+ flats.add(loft);
+ flats.add(suite);
+
+ Long buildingKey = (Long)sess.save(building);
+ System.out.println("buildingKey:" + buildingKey);
+
+
+ try {
+ tx.commit();
+ Assert.fail("Failed but should not");
+ } catch (PersistenceException pe) {
+ sess.close();
+ }
+
+ }
+
+}
Added: hibernate-list-idx-bug/src/test/resources/hibernate.cfg.xml
===================================================================
--- hibernate-list-idx-bug/src/test/resources/hibernate.cfg.xml (rev 0)
+++ hibernate-list-idx-bug/src/test/resources/hibernate.cfg.xml 2014-04-23 15:39:47 UTC (rev 703)
@@ -0,0 +1,25 @@
+<?xml version='1.0' encoding='utf-8'?>
+<!DOCTYPE hibernate-configuration PUBLIC
+ "-//Hibernate/Hibernate Configuration DTD//EN"
+ "http://hibernate.sourceforge.net/hibernate-configuration-3.0.dtd">
+
+<hibernate-configuration>
+ <session-factory>
+
+ <property name="hibernate.connection.driver_class">org.h2.Driver</property>
+ <property name="hibernate.connection.url">jdbc:h2:mem:db1;DB_CLOSE_DELAY=-1;MVCC=TRUE</property>
+ <property name="hibernate.connection.username">sa</property>
+ <property name="hibernate.connection.password"></property>
+
+ <property name="show_sql">true</property>
+ <property name="hbm2ddl.auto">update</property>
+
+ <!--Liste des fichiers de Mapping hbm -->
+ <mapping resource="org/nuiton/entities/Building.hbm.xml"/>
+ <mapping resource="org/nuiton/entities/Flat.hbm.xml"/>
+ <mapping resource="org/nuiton/entities/Loft.hbm.xml"/>
+ <mapping resource="org/nuiton/entities/Suite.hbm.xml"/>
+
+ </session-factory>
+</hibernate-configuration>
+
Added: hibernate-list-idx-bug/target/antrun/build-main.xml
===================================================================
--- hibernate-list-idx-bug/target/antrun/build-main.xml (rev 0)
+++ hibernate-list-idx-bug/target/antrun/build-main.xml 2014-04-23 15:39:47 UTC (rev 703)
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<project name="maven-antrun-" default="main" >
+<target name="main">
+ <mkdir dir="/home/davidcosse/Documents/CodeLutin/workspace/hibernate-bug-report/hibernate-abstract-idx/target/surefire-workdir"/>
+</target>
+</project>
\ No newline at end of file
Added: hibernate-list-idx-bug/target/classes/org/nuiton/entities/Building.class
===================================================================
(Binary files differ)
Property changes on: hibernate-list-idx-bug/target/classes/org/nuiton/entities/Building.class
___________________________________________________________________
Added: svn:mime-type
+ application/octet-stream
Added: hibernate-list-idx-bug/target/classes/org/nuiton/entities/Building.hbm.xml
===================================================================
--- hibernate-list-idx-bug/target/classes/org/nuiton/entities/Building.hbm.xml (rev 0)
+++ hibernate-list-idx-bug/target/classes/org/nuiton/entities/Building.hbm.xml 2014-04-23 15:39:47 UTC (rev 703)
@@ -0,0 +1,17 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<hibernate-mapping xmlns="http://www.hibernate.org/xsd/hibernate-mapping"
+ xsi:schemaLocation="http://www.hibernate.org/xsd/hibernate-mapping classpath://org/hibernate/hibernate-mapping-4.0.xsd"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ default-access="field" auto-import="true" package="org.nuiton.entities">
+ <class name="org.nuiton.entities.Building" abstract="false" >
+ <id name="buildingId" type="long">
+ <generator class="increment"/>
+ </id>
+ <property name="name" access="field" type="text" column="name"/>
+ <list name="flats" lazy="true" cascade="all,delete-orphan">
+ <key column="building"/>
+ <list-index column="building_idx"/>
+ <one-to-many class="org.nuiton.entities.Flat"/>
+ </list>
+ </class>
+</hibernate-mapping>
\ No newline at end of file
Added: hibernate-list-idx-bug/target/classes/org/nuiton/entities/Flat.class
===================================================================
(Binary files differ)
Property changes on: hibernate-list-idx-bug/target/classes/org/nuiton/entities/Flat.class
___________________________________________________________________
Added: svn:mime-type
+ application/octet-stream
Added: hibernate-list-idx-bug/target/classes/org/nuiton/entities/Flat.hbm.xml
===================================================================
--- hibernate-list-idx-bug/target/classes/org/nuiton/entities/Flat.hbm.xml (rev 0)
+++ hibernate-list-idx-bug/target/classes/org/nuiton/entities/Flat.hbm.xml 2014-04-23 15:39:47 UTC (rev 703)
@@ -0,0 +1,12 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<hibernate-mapping xmlns="http://www.hibernate.org/xsd/hibernate-mapping"
+ xsi:schemaLocation="http://www.hibernate.org/xsd/hibernate-mapping classpath://org/hibernate/hibernate-mapping-4.0.xsd"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ default-access="field" auto-import="true" package="org.nuiton.entities">
+ <class name="org.nuiton.entities.Flat" abstract="true">
+ <id name="flatId" type="long">
+ <generator class="increment"/>
+ </id>
+
+ </class>
+</hibernate-mapping>
\ No newline at end of file
Added: hibernate-list-idx-bug/target/classes/org/nuiton/entities/Loft.class
===================================================================
(Binary files differ)
Property changes on: hibernate-list-idx-bug/target/classes/org/nuiton/entities/Loft.class
___________________________________________________________________
Added: svn:mime-type
+ application/octet-stream
Added: hibernate-list-idx-bug/target/classes/org/nuiton/entities/Loft.hbm.xml
===================================================================
--- hibernate-list-idx-bug/target/classes/org/nuiton/entities/Loft.hbm.xml (rev 0)
+++ hibernate-list-idx-bug/target/classes/org/nuiton/entities/Loft.hbm.xml 2014-04-23 15:39:47 UTC (rev 703)
@@ -0,0 +1,9 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<hibernate-mapping xmlns="http://www.hibernate.org/xsd/hibernate-mapping"
+ xsi:schemaLocation="http://www.hibernate.org/xsd/hibernate-mapping classpath://org/hibernate/hibernate-mapping-4.0.xsd"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ default-access="field" auto-import="true" package="org.nuiton.entities">
+ <union-subclass name="org.nuiton.entities.Loft" extends="org.nuiton.entities.Flat" abstract="false">
+ <property name="name" access="field" type="text" column="name"/>
+ </union-subclass>
+</hibernate-mapping>
\ No newline at end of file
Added: hibernate-list-idx-bug/target/classes/org/nuiton/entities/Suite.class
===================================================================
(Binary files differ)
Property changes on: hibernate-list-idx-bug/target/classes/org/nuiton/entities/Suite.class
___________________________________________________________________
Added: svn:mime-type
+ application/octet-stream
Added: hibernate-list-idx-bug/target/classes/org/nuiton/entities/Suite.hbm.xml
===================================================================
--- hibernate-list-idx-bug/target/classes/org/nuiton/entities/Suite.hbm.xml (rev 0)
+++ hibernate-list-idx-bug/target/classes/org/nuiton/entities/Suite.hbm.xml 2014-04-23 15:39:47 UTC (rev 703)
@@ -0,0 +1,9 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<hibernate-mapping xmlns="http://www.hibernate.org/xsd/hibernate-mapping"
+ xsi:schemaLocation="http://www.hibernate.org/xsd/hibernate-mapping classpath://org/hibernate/hibernate-mapping-4.0.xsd"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ default-access="field" auto-import="true" package="fr.inra.agrosyst.api.entities.measure">
+ <union-subclass name="org.nuiton.entities.Suite" extends="org.nuiton.entities.Flat" abstract="false" >
+ <property name="name" access="field" type="text" column="name"/>
+ </union-subclass>
+</hibernate-mapping>
\ No newline at end of file
Added: hibernate-list-idx-bug/target/surefire-reports/TEST-org.nuiton.entities.AppTest.xml
===================================================================
--- hibernate-list-idx-bug/target/surefire-reports/TEST-org.nuiton.entities.AppTest.xml (rev 0)
+++ hibernate-list-idx-bug/target/surefire-reports/TEST-org.nuiton.entities.AppTest.xml 2014-04-23 15:39:47 UTC (rev 703)
@@ -0,0 +1,180 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<testsuite name="org.nuiton.entities.AppTest" time="2.191" tests="1" errors="1" skipped="0" failures="0">
+ <properties>
+ <property name="java.runtime.name" value="Java(TM) SE Runtime Environment"/>
+ <property name="sun.boot.library.path" value="/opt/java/jre/lib/amd64"/>
+ <property name="java.vm.version" value="24.45-b08"/>
+ <property name="java.vm.vendor" value="Oracle Corporation"/>
+ <property name="java.vendor.url" value="http://java.oracle.com/"/>
+ <property name="path.separator" value=":"/>
+ <property name="guice.disable.misplaced.annotation.check" value="true"/>
+ <property name="java.vm.name" value="Java HotSpot(TM) 64-Bit Server VM"/>
+ <property name="file.encoding.pkg" value="sun.io"/>
+ <property name="user.country" value="FR"/>
+ <property name="sun.java.launcher" value="SUN_STANDARD"/>
+ <property name="sun.os.patch.level" value="unknown"/>
+ <property name="java.vm.specification.name" value="Java Virtual Machine Specification"/>
+ <property name="user.dir" value="/home/davidcosse/Documents/CodeLutin/workspace/hibernate-bug-report/hibernate-abstract-idx"/>
+ <property name="java.runtime.version" value="1.7.0_45-b18"/>
+ <property name="java.awt.graphicsenv" value="sun.awt.X11GraphicsEnvironment"/>
+ <property name="java.endorsed.dirs" value="/opt/java/jre/lib/endorsed"/>
+ <property name="os.arch" value="amd64"/>
+ <property name="java.io.tmpdir" value="/tmp"/>
+ <property name="line.separator" value=" "/>
+ <property name="java.vm.specification.vendor" value="Oracle Corporation"/>
+ <property name="os.name" value="Linux"/>
+ <property name="classworlds.conf" value="/usr/share/maven/bin/m2.conf"/>
+ <property name="sun.jnu.encoding" value="UTF-8"/>
+ <property name="java.library.path" value="/usr/java/packages/lib/amd64:/usr/lib64:/lib64:/lib:/usr/lib"/>
+ <property name="java.specification.name" value="Java Platform API Specification"/>
+ <property name="java.class.version" value="51.0"/>
+ <property name="sun.management.compiler" value="HotSpot 64-Bit Tiered Compilers"/>
+ <property name="os.version" value="3.13.0-24-generic"/>
+ <property name="user.home" value="/home/davidcosse"/>
+ <property name="user.timezone" value="Europe/Paris"/>
+ <property name="java.awt.printerjob" value="sun.print.PSPrinterJob"/>
+ <property name="file.encoding" value="UTF-8"/>
+ <property name="java.specification.version" value="1.7"/>
+ <property name="user.name" value="davidcosse"/>
+ <property name="java.class.path" value="/usr/share/maven/boot/plexus-classworlds-2.x.jar"/>
+ <property name="java.vm.specification.version" value="1.7"/>
+ <property name="sun.arch.data.model" value="64"/>
+ <property name="java.home" value="/opt/java/jre"/>
+ <property name="sun.java.command" value="org.codehaus.plexus.classworlds.launcher.Launcher -U clean install"/>
+ <property name="java.specification.vendor" value="Oracle Corporation"/>
+ <property name="user.language" value="fr"/>
+ <property name="awt.toolkit" value="sun.awt.X11.XToolkit"/>
+ <property name="java.vm.info" value="mixed mode"/>
+ <property name="java.version" value="1.7.0_45"/>
+ <property name="java.ext.dirs" value="/opt/java/jre/lib/ext:/usr/java/packages/lib/ext"/>
+ <property name="securerandom.source" value="file:/dev/./urandom"/>
+ <property name="sun.boot.class.path" value="/opt/java/jre/lib/resources.jar:/opt/java/jre/lib/rt.jar:/opt/java/jre/lib/sunrsasign.jar:/opt/java/jre/lib/jsse.jar:/opt/java/jre/lib/jce.jar:/opt/java/jre/lib/charsets.jar:/opt/java/jre/lib/jfr.jar:/opt/java/jre/classes"/>
+ <property name="java.vendor" value="Oracle Corporation"/>
+ <property name="maven.home" value="/usr/share/maven"/>
+ <property name="file.separator" value="/"/>
+ <property name="java.vendor.url.bug" value="http://bugreport.sun.com/bugreport/"/>
+ <property name="sun.cpu.endian" value="little"/>
+ <property name="sun.io.unicode.encoding" value="UnicodeLittle"/>
+ <property name="sun.desktop" value="gnome"/>
+ <property name="sun.cpu.isalist" value=""/>
+ </properties>
+ <testcase name="test" classname="org.nuiton.entities.AppTest" time="2.191">
+ <error message="could not prepare statement" type="org.hibernate.exception.SQLGrammarException"><![CDATA[org.hibernate.exception.SQLGrammarException: could not prepare statement
+ at org.h2.message.DbException.getJdbcSQLException(DbException.java:344)
+ at org.h2.message.DbException.get(DbException.java:178)
+ at org.h2.message.DbException.get(DbException.java:154)
+ at org.h2.command.Parser.readTableOrView(Parser.java:5213)
+ at org.h2.command.Parser.readTableOrView(Parser.java:5190)
+ at org.h2.command.Parser.readSimpleTableFilter(Parser.java:758)
+ at org.h2.command.Parser.parseUpdate(Parser.java:707)
+ at org.h2.command.Parser.parsePrepared(Parser.java:456)
+ at org.h2.command.Parser.parse(Parser.java:306)
+ at org.h2.command.Parser.parse(Parser.java:278)
+ at org.h2.command.Parser.prepareCommand(Parser.java:243)
+ at org.h2.engine.Session.prepareLocal(Session.java:442)
+ at org.h2.engine.Session.prepareCommand(Session.java:384)
+ at org.h2.jdbc.JdbcConnection.prepareCommand(JdbcConnection.java:1188)
+ at org.h2.jdbc.JdbcPreparedStatement.<init>(JdbcPreparedStatement.java:73)
+ at org.h2.jdbc.JdbcConnection.prepareStatement(JdbcConnection.java:276)
+ at org.hibernate.engine.jdbc.internal.StatementPreparerImpl$1.doPrepare(StatementPreparerImpl.java:103)
+ at org.hibernate.engine.jdbc.internal.StatementPreparerImpl$StatementPreparationTemplate.prepareStatement(StatementPreparerImpl.java:186)
+ at org.hibernate.engine.jdbc.internal.StatementPreparerImpl.prepareStatement(StatementPreparerImpl.java:96)
+ at org.hibernate.engine.jdbc.batch.internal.AbstractBatchImpl.buildBatchStatement(AbstractBatchImpl.java:152)
+ at org.hibernate.engine.jdbc.batch.internal.AbstractBatchImpl.getBatchStatement(AbstractBatchImpl.java:141)
+ at org.hibernate.persister.collection.AbstractCollectionPersister.recreate(AbstractCollectionPersister.java:1285)
+ at org.hibernate.persister.collection.OneToManyPersister.recreate(OneToManyPersister.java:184)
+ at org.hibernate.action.internal.CollectionRecreateAction.execute(CollectionRecreateAction.java:67)
+ at org.hibernate.engine.spi.ActionQueue.executeActions(ActionQueue.java:463)
+ at org.hibernate.engine.spi.ActionQueue.executeActions(ActionQueue.java:349)
+ at org.hibernate.event.internal.AbstractFlushingEventListener.performExecutions(AbstractFlushingEventListener.java:350)
+ at org.hibernate.event.internal.DefaultFlushEventListener.onFlush(DefaultFlushEventListener.java:56)
+ at org.hibernate.internal.SessionImpl.flush(SessionImpl.java:1222)
+ at org.hibernate.internal.SessionImpl.managedFlush(SessionImpl.java:425)
+ at org.hibernate.engine.transaction.internal.jdbc.JdbcTransaction.beforeTransactionCommit(JdbcTransaction.java:101)
+ at org.hibernate.engine.transaction.spi.AbstractTransactionImpl.commit(AbstractTransactionImpl.java:177)
+ at org.nuiton.entities.AppTest.test(AppTest.java:80)
+]]></error>
+ <system-out>Hibernate: select max(ids_.mx) from ( select max(flatId) as mx from Loft union select max(flatId) as mx from Suite ) ids_
+loftKey:1
+suiteKey:2
+Hibernate: insert into Loft (name, flatId) values (?, ?)
+Hibernate: insert into Suite (name, flatId) values (?, ?)
+Hibernate: select max(buildingId) from Building
+buildingKey:1
+Hibernate: insert into Building (name, buildingId) values (?, ?)
+Hibernate: update Flat set building=?, building_idx=? where flatId=?
+</system-out>
+ <system-err><![CDATA[avr. 23, 2014 5:18:42 PM org.hibernate.annotations.common.reflection.java.JavaReflectionManager <clinit>
+INFO: HCANN000001: Hibernate Commons Annotations {4.0.4.Final}
+avr. 23, 2014 5:18:42 PM org.hibernate.Version logVersion
+INFO: HHH000412: Hibernate Core {4.3.5.Final}
+avr. 23, 2014 5:18:42 PM org.hibernate.cfg.Environment <clinit>
+INFO: HHH000206: hibernate.properties not found
+avr. 23, 2014 5:18:42 PM org.hibernate.cfg.Environment buildBytecodeProvider
+INFO: HHH000021: Bytecode provider name : javassist
+avr. 23, 2014 5:18:42 PM org.hibernate.cfg.Configuration configure
+INFO: HHH000043: Configuring from resource: /hibernate.cfg.xml
+avr. 23, 2014 5:18:42 PM org.hibernate.cfg.Configuration getConfigurationInputStream
+INFO: HHH000040: Configuration resource: /hibernate.cfg.xml
+avr. 23, 2014 5:18:42 PM org.hibernate.internal.util.xml.DTDEntityResolver resolveEntity
+WARN: HHH000223: Recognized obsolete hibernate namespace http://hibernate.sourceforge.net/. Use namespace http://www.hibernate.org/dtd/ instead. Refer to Hibernate 3.6 Migration Guide!
+avr. 23, 2014 5:18:42 PM org.hibernate.cfg.Configuration addResource
+INFO: HHH000221: Reading mappings from resource: org/nuiton/entities/Building.hbm.xml
+avr. 23, 2014 5:18:43 PM org.hibernate.cfg.Configuration addResource
+INFO: HHH000221: Reading mappings from resource: org/nuiton/entities/Flat.hbm.xml
+avr. 23, 2014 5:18:43 PM org.hibernate.cfg.Configuration addResource
+INFO: HHH000221: Reading mappings from resource: org/nuiton/entities/Loft.hbm.xml
+avr. 23, 2014 5:18:43 PM org.hibernate.cfg.Configuration addResource
+INFO: HHH000221: Reading mappings from resource: org/nuiton/entities/Suite.hbm.xml
+avr. 23, 2014 5:18:43 PM org.hibernate.cfg.Configuration doConfigure
+INFO: HHH000041: Configured SessionFactory: null
+avr. 23, 2014 5:18:43 PM org.hibernate.engine.jdbc.connections.internal.DriverManagerConnectionProviderImpl configure
+WARN: HHH000402: Using Hibernate built-in connection pool (not for production use!)
+avr. 23, 2014 5:18:43 PM org.hibernate.engine.jdbc.connections.internal.DriverManagerConnectionProviderImpl buildCreator
+INFO: HHH000401: using driver [org.h2.Driver] at URL [jdbc:h2:mem:db1;DB_CLOSE_DELAY=-1;MVCC=TRUE]
+avr. 23, 2014 5:18:43 PM org.hibernate.engine.jdbc.connections.internal.DriverManagerConnectionProviderImpl buildCreator
+INFO: HHH000046: Connection properties: {user=sa, password=****}
+avr. 23, 2014 5:18:43 PM org.hibernate.engine.jdbc.connections.internal.DriverManagerConnectionProviderImpl buildCreator
+INFO: HHH000006: Autocommit mode: false
+avr. 23, 2014 5:18:43 PM org.hibernate.engine.jdbc.connections.internal.DriverManagerConnectionProviderImpl configure
+INFO: HHH000115: Hibernate connection pool size: 20 (min=1)
+avr. 23, 2014 5:18:43 PM org.hibernate.dialect.Dialect <init>
+INFO: HHH000400: Using dialect: org.hibernate.dialect.H2Dialect
+avr. 23, 2014 5:18:44 PM org.hibernate.engine.transaction.internal.TransactionFactoryInitiator initiateService
+INFO: HHH000399: Using default transaction strategy (direct JDBC transactions)
+avr. 23, 2014 5:18:44 PM org.hibernate.hql.internal.ast.ASTQueryTranslatorFactory <init>
+INFO: HHH000397: Using ASTQueryTranslatorFactory
+avr. 23, 2014 5:18:44 PM org.hibernate.tool.hbm2ddl.SchemaUpdate execute
+INFO: HHH000228: Running hbm2ddl schema update
+avr. 23, 2014 5:18:44 PM org.hibernate.tool.hbm2ddl.SchemaUpdate execute
+INFO: HHH000102: Fetching database metadata
+avr. 23, 2014 5:18:44 PM org.hibernate.tool.hbm2ddl.SchemaUpdate execute
+INFO: HHH000396: Updating schema
+avr. 23, 2014 5:18:44 PM org.hibernate.tool.hbm2ddl.DatabaseMetadata getTableMetadata
+INFO: HHH000262: Table not found: Building
+avr. 23, 2014 5:18:44 PM org.hibernate.tool.hbm2ddl.DatabaseMetadata getTableMetadata
+INFO: HHH000262: Table not found: Loft
+avr. 23, 2014 5:18:44 PM org.hibernate.tool.hbm2ddl.DatabaseMetadata getTableMetadata
+INFO: HHH000262: Table not found: Suite
+avr. 23, 2014 5:18:44 PM org.hibernate.tool.hbm2ddl.DatabaseMetadata getTableMetadata
+INFO: HHH000262: Table not found: Building
+avr. 23, 2014 5:18:44 PM org.hibernate.tool.hbm2ddl.DatabaseMetadata getTableMetadata
+INFO: HHH000262: Table not found: Loft
+avr. 23, 2014 5:18:44 PM org.hibernate.tool.hbm2ddl.DatabaseMetadata getTableMetadata
+INFO: HHH000262: Table not found: Suite
+avr. 23, 2014 5:18:44 PM org.hibernate.tool.hbm2ddl.DatabaseMetadata getTableMetadata
+INFO: HHH000262: Table not found: Building
+avr. 23, 2014 5:18:44 PM org.hibernate.tool.hbm2ddl.DatabaseMetadata getTableMetadata
+INFO: HHH000262: Table not found: Loft
+avr. 23, 2014 5:18:44 PM org.hibernate.tool.hbm2ddl.DatabaseMetadata getTableMetadata
+INFO: HHH000262: Table not found: Suite
+avr. 23, 2014 5:18:44 PM org.hibernate.tool.hbm2ddl.SchemaUpdate execute
+INFO: HHH000232: Schema update complete
+avr. 23, 2014 5:18:44 PM org.hibernate.engine.jdbc.spi.SqlExceptionHelper logExceptions
+WARN: SQL Error: 42102, SQLState: 42S02
+avr. 23, 2014 5:18:44 PM org.hibernate.engine.jdbc.spi.SqlExceptionHelper logExceptions
+ERROR: Table "FLAT" not found; SQL statement:
+update Flat set building=?, building_idx=? where flatId=? [42102-177]
+]]></system-err>
+ </testcase>
+</testsuite>
\ No newline at end of file
Added: hibernate-list-idx-bug/target/surefire-reports/org.nuiton.entities.AppTest.txt
===================================================================
--- hibernate-list-idx-bug/target/surefire-reports/org.nuiton.entities.AppTest.txt (rev 0)
+++ hibernate-list-idx-bug/target/surefire-reports/org.nuiton.entities.AppTest.txt 2014-04-23 15:39:47 UTC (rev 703)
@@ -0,0 +1,40 @@
+-------------------------------------------------------------------------------
+Test set: org.nuiton.entities.AppTest
+-------------------------------------------------------------------------------
+Tests run: 1, Failures: 0, Errors: 1, Skipped: 0, Time elapsed: 2.275 sec <<< FAILURE!
+test(org.nuiton.entities.AppTest) Time elapsed: 2.191 sec <<< ERROR!
+org.hibernate.exception.SQLGrammarException: could not prepare statement
+ at org.h2.message.DbException.getJdbcSQLException(DbException.java:344)
+ at org.h2.message.DbException.get(DbException.java:178)
+ at org.h2.message.DbException.get(DbException.java:154)
+ at org.h2.command.Parser.readTableOrView(Parser.java:5213)
+ at org.h2.command.Parser.readTableOrView(Parser.java:5190)
+ at org.h2.command.Parser.readSimpleTableFilter(Parser.java:758)
+ at org.h2.command.Parser.parseUpdate(Parser.java:707)
+ at org.h2.command.Parser.parsePrepared(Parser.java:456)
+ at org.h2.command.Parser.parse(Parser.java:306)
+ at org.h2.command.Parser.parse(Parser.java:278)
+ at org.h2.command.Parser.prepareCommand(Parser.java:243)
+ at org.h2.engine.Session.prepareLocal(Session.java:442)
+ at org.h2.engine.Session.prepareCommand(Session.java:384)
+ at org.h2.jdbc.JdbcConnection.prepareCommand(JdbcConnection.java:1188)
+ at org.h2.jdbc.JdbcPreparedStatement.<init>(JdbcPreparedStatement.java:73)
+ at org.h2.jdbc.JdbcConnection.prepareStatement(JdbcConnection.java:276)
+ at org.hibernate.engine.jdbc.internal.StatementPreparerImpl$1.doPrepare(StatementPreparerImpl.java:103)
+ at org.hibernate.engine.jdbc.internal.StatementPreparerImpl$StatementPreparationTemplate.prepareStatement(StatementPreparerImpl.java:186)
+ at org.hibernate.engine.jdbc.internal.StatementPreparerImpl.prepareStatement(StatementPreparerImpl.java:96)
+ at org.hibernate.engine.jdbc.batch.internal.AbstractBatchImpl.buildBatchStatement(AbstractBatchImpl.java:152)
+ at org.hibernate.engine.jdbc.batch.internal.AbstractBatchImpl.getBatchStatement(AbstractBatchImpl.java:141)
+ at org.hibernate.persister.collection.AbstractCollectionPersister.recreate(AbstractCollectionPersister.java:1285)
+ at org.hibernate.persister.collection.OneToManyPersister.recreate(OneToManyPersister.java:184)
+ at org.hibernate.action.internal.CollectionRecreateAction.execute(CollectionRecreateAction.java:67)
+ at org.hibernate.engine.spi.ActionQueue.executeActions(ActionQueue.java:463)
+ at org.hibernate.engine.spi.ActionQueue.executeActions(ActionQueue.java:349)
+ at org.hibernate.event.internal.AbstractFlushingEventListener.performExecutions(AbstractFlushingEventListener.java:350)
+ at org.hibernate.event.internal.DefaultFlushEventListener.onFlush(DefaultFlushEventListener.java:56)
+ at org.hibernate.internal.SessionImpl.flush(SessionImpl.java:1222)
+ at org.hibernate.internal.SessionImpl.managedFlush(SessionImpl.java:425)
+ at org.hibernate.engine.transaction.internal.jdbc.JdbcTransaction.beforeTransactionCommit(JdbcTransaction.java:101)
+ at org.hibernate.engine.transaction.spi.AbstractTransactionImpl.commit(AbstractTransactionImpl.java:177)
+ at org.nuiton.entities.AppTest.test(AppTest.java:80)
+
Added: hibernate-list-idx-bug/target/test-classes/hibernate.cfg.xml
===================================================================
--- hibernate-list-idx-bug/target/test-classes/hibernate.cfg.xml (rev 0)
+++ hibernate-list-idx-bug/target/test-classes/hibernate.cfg.xml 2014-04-23 15:39:47 UTC (rev 703)
@@ -0,0 +1,25 @@
+<?xml version='1.0' encoding='utf-8'?>
+<!DOCTYPE hibernate-configuration PUBLIC
+ "-//Hibernate/Hibernate Configuration DTD//EN"
+ "http://hibernate.sourceforge.net/hibernate-configuration-3.0.dtd">
+
+<hibernate-configuration>
+ <session-factory>
+
+ <property name="hibernate.connection.driver_class">org.h2.Driver</property>
+ <property name="hibernate.connection.url">jdbc:h2:mem:db1;DB_CLOSE_DELAY=-1;MVCC=TRUE</property>
+ <property name="hibernate.connection.username">sa</property>
+ <property name="hibernate.connection.password"></property>
+
+ <property name="show_sql">true</property>
+ <property name="hbm2ddl.auto">update</property>
+
+ <!--Liste des fichiers de Mapping hbm -->
+ <mapping resource="org/nuiton/entities/Building.hbm.xml"/>
+ <mapping resource="org/nuiton/entities/Flat.hbm.xml"/>
+ <mapping resource="org/nuiton/entities/Loft.hbm.xml"/>
+ <mapping resource="org/nuiton/entities/Suite.hbm.xml"/>
+
+ </session-factory>
+</hibernate-configuration>
+
Added: hibernate-list-idx-bug/target/test-classes/org/nuiton/entities/AppTest.class
===================================================================
(Binary files differ)
Property changes on: hibernate-list-idx-bug/target/test-classes/org/nuiton/entities/AppTest.class
___________________________________________________________________
Added: svn:mime-type
+ application/octet-stream
1
0
Author: dcosse
Date: 2014-04-23 17:31:50 +0200 (Wed, 23 Apr 2014)
New Revision: 702
Url: http://forge.nuiton.org/projects/sandbox/repository/revisions/702
Log:
Directory for hibernate list idx bug
Added:
hibernate-list-idx-bug/
1
0
r701 - in test-js-frameworks: . emberJS emberJS/js emberJS/js/libs
by dralagen@users.nuiton.org 17 Apr '14
by dralagen@users.nuiton.org 17 Apr '14
17 Apr '14
Author: dralagen
Date: 2014-04-17 11:35:38 +0200 (Thu, 17 Apr 2014)
New Revision: 701
Url: http://forge.nuiton.org/projects/sandbox/repository/revisions/701
Log:
Add test EmberJS without add choice
Added:
test-js-frameworks/emberJS/
test-js-frameworks/emberJS/index.html
test-js-frameworks/emberJS/js/
test-js-frameworks/emberJS/js/app.js
test-js-frameworks/emberJS/js/libs/
test-js-frameworks/emberJS/js/libs/ember-1.5.0.js
test-js-frameworks/emberJS/js/libs/handlebars-1.1.2.js
test-js-frameworks/emberJS/js/libs/jquery-1.10.2.js
Added: test-js-frameworks/emberJS/index.html
===================================================================
--- test-js-frameworks/emberJS/index.html (rev 0)
+++ test-js-frameworks/emberJS/index.html 2014-04-17 09:35:38 UTC (rev 701)
@@ -0,0 +1,30 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <meta charset="utf-8">
+ <title>Test Ember</title>
+ </head>
+ <body>
+ <script type="text/x-handlebars">
+ title : {{input type="text" value=title}}
+
+ {{#each model.choices}}
+ <div>
+ {{_view.contentIndex}} {{input type="text" value=value}}
+ </div>
+ {{/each}}
+ <h2>{{title}}</h2>
+
+ {{#each model.choices}}
+ <input type="radio" name="choice" {{bind-attr value=_view.contentIndex}}" /> {{value}}<br/>
+ {{/each}}
+ {{outlet}}
+ </script>
+
+ <script src="js/libs/jquery-1.10.2.js"></script>
+ <script src="js/libs/handlebars-1.1.2.js"></script>
+ <script src="js/libs/ember-1.5.0.js"></script>
+ <script src="js/app.js"></script>
+ </body>
+</html>
+
Added: test-js-frameworks/emberJS/js/app.js
===================================================================
--- test-js-frameworks/emberJS/js/app.js (rev 0)
+++ test-js-frameworks/emberJS/js/app.js 2014-04-17 09:35:38 UTC (rev 701)
@@ -0,0 +1,6 @@
+App = Ember.Application.create();
+
+App.Route = Ember.Route.extend({
+ model: function () { return {choices:[{},{}]};},
+});
+
Added: test-js-frameworks/emberJS/js/libs/ember-1.5.0.js
===================================================================
--- test-js-frameworks/emberJS/js/libs/ember-1.5.0.js (rev 0)
+++ test-js-frameworks/emberJS/js/libs/ember-1.5.0.js 2014-04-17 09:35:38 UTC (rev 701)
@@ -0,0 +1,44093 @@
+/*!
+ * @overview Ember - JavaScript Application Framework
+ * @copyright Copyright 2011-2014 Tilde Inc. and contributors
+ * Portions Copyright 2006-2011 Strobe Inc.
+ * Portions Copyright 2008-2011 Apple Inc. All rights reserved.
+ * @license Licensed under MIT license
+ * See https://raw.github.com/emberjs/ember.js/master/LICENSE
+ * @version 1.5.0
+ */
+
+
+(function() {
+/*global __fail__*/
+
+/**
+Ember Debug
+
+@module ember
+@submodule ember-debug
+*/
+
+/**
+@class Ember
+*/
+
+if ('undefined' === typeof Ember) {
+ Ember = {};
+
+ if ('undefined' !== typeof window) {
+ window.Em = window.Ember = Em = Ember;
+ }
+}
+
+// This needs to be kept in sync with the logic in
+// `packages/ember-metal/lib/core.js`.
+//
+// This is duplicated here to ensure that `Ember.ENV`
+// is setup even if `Ember` is not loaded yet.
+if (Ember.ENV) {
+ // do nothing if Ember.ENV is already setup
+} else if ('undefined' !== typeof EmberENV) {
+ Ember.ENV = EmberENV;
+} else if('undefined' !== typeof ENV) {
+ Ember.ENV = ENV;
+} else {
+ Ember.ENV = {};
+}
+
+if (!('MANDATORY_SETTER' in Ember.ENV)) {
+ Ember.ENV.MANDATORY_SETTER = true; // default to true for debug dist
+}
+
+/**
+ Define an assertion that will throw an exception if the condition is not
+ met. Ember build tools will remove any calls to `Ember.assert()` when
+ doing a production build. Example:
+
+ ```javascript
+ // Test for truthiness
+ Ember.assert('Must pass a valid object', obj);
+ // Fail unconditionally
+ Ember.assert('This code path should never be run')
+ ```
+
+ @method assert
+ @param {String} desc A description of the assertion. This will become
+ the text of the Error thrown if the assertion fails.
+ @param {Boolean} test Must be truthy for the assertion to pass. If
+ falsy, an exception will be thrown.
+*/
+Ember.assert = function(desc, test) {
+ if (!test) {
+ throw new Ember.Error("Assertion Failed: " + desc);
+ }
+};
+
+
+/**
+ Display a warning with the provided message. Ember build tools will
+ remove any calls to `Ember.warn()` when doing a production build.
+
+ @method warn
+ @param {String} message A warning to display.
+ @param {Boolean} test An optional boolean. If falsy, the warning
+ will be displayed.
+*/
+Ember.warn = function(message, test) {
+ if (!test) {
+ Ember.Logger.warn("WARNING: "+message);
+ if ('trace' in Ember.Logger) Ember.Logger.trace();
+ }
+};
+
+/**
+ Display a debug notice. Ember build tools will remove any calls to
+ `Ember.debug()` when doing a production build.
+
+ ```javascript
+ Ember.debug("I'm a debug notice!");
+ ```
+
+ @method debug
+ @param {String} message A debug message to display.
+*/
+Ember.debug = function(message) {
+ Ember.Logger.debug("DEBUG: "+message);
+};
+
+/**
+ Display a deprecation warning with the provided message and a stack trace
+ (Chrome and Firefox only). Ember build tools will remove any calls to
+ `Ember.deprecate()` when doing a production build.
+
+ @method deprecate
+ @param {String} message A description of the deprecation.
+ @param {Boolean} test An optional boolean. If falsy, the deprecation
+ will be displayed.
+*/
+Ember.deprecate = function(message, test) {
+ if (test) { return; }
+
+ if (Ember.ENV.RAISE_ON_DEPRECATION) { throw new Ember.Error(message); }
+
+ var error;
+
+ // When using new Error, we can't do the arguments check for Chrome. Alternatives are welcome
+ try { __fail__.fail(); } catch (e) { error = e; }
+
+ if (Ember.LOG_STACKTRACE_ON_DEPRECATION && error.stack) {
+ var stack, stackStr = '';
+ if (error['arguments']) {
+ // Chrome
+ stack = error.stack.replace(/^\s+at\s+/gm, '').
+ replace(/^([^\(]+?)([\n$])/gm, '{anonymous}($1)$2').
+ replace(/^Object.<anonymous>\s*\(([^\)]+)\)/gm, '{anonymous}($1)').split('\n');
+ stack.shift();
+ } else {
+ // Firefox
+ stack = error.stack.replace(/(?:\n@:0)?\s+$/m, '').
+ replace(/^\(/gm, '{anonymous}(').split('\n');
+ }
+
+ stackStr = "\n " + stack.slice(2).join("\n ");
+ message = message + stackStr;
+ }
+
+ Ember.Logger.warn("DEPRECATION: "+message);
+};
+
+
+
+/**
+ Alias an old, deprecated method with its new counterpart.
+
+ Display a deprecation warning with the provided message and a stack trace
+ (Chrome and Firefox only) when the assigned method is called.
+
+ Ember build tools will not remove calls to `Ember.deprecateFunc()`, though
+ no warnings will be shown in production.
+
+ ```javascript
+ Ember.oldMethod = Ember.deprecateFunc("Please use the new, updated method", Ember.newMethod);
+ ```
+
+ @method deprecateFunc
+ @param {String} message A description of the deprecation.
+ @param {Function} func The new function called to replace its deprecated counterpart.
+ @return {Function} a new function that wrapped the original function with a deprecation warning
+*/
+Ember.deprecateFunc = function(message, func) {
+ return function() {
+ Ember.deprecate(message);
+ return func.apply(this, arguments);
+ };
+};
+
+
+/**
+ Run a function meant for debugging. Ember build tools will remove any calls to
+ `Ember.runInDebug()` when doing a production build.
+
+ ```javascript
+ Ember.runInDebug( function() {
+ Ember.Handlebars.EachView.reopen({
+ didInsertElement: function() {
+ console.log("I'm happy");
+ }
+ });
+ });
+ ```
+
+ @method runInDebug
+ @param {Function} func The function to be executed.
+*/
+Ember.runInDebug = function(func) {
+ func()
+};
+
+// Inform the developer about the Ember Inspector if not installed.
+if (!Ember.testing) {
+ var isFirefox = typeof InstallTrigger !== 'undefined';
+ var isChrome = !!window.chrome && !window.opera;
+
+ if (typeof window !== 'undefined' && (isFirefox || isChrome) && window.addEventListener) {
+ window.addEventListener("load", function() {
+ if (document.documentElement && document.documentElement.dataset && !document.documentElement.dataset.emberExtension) {
+ var downloadURL;
+
+ if(isChrome) {
+ downloadURL = 'https://chrome.google.com/webstore/detail/ember-inspector/bmdblncegkenkacie…';
+ } else if(isFirefox) {
+ downloadURL = 'https://addons.mozilla.org/en-US/firefox/addon/ember-inspector/';
+ }
+
+ Ember.debug('For more advanced debugging, install the Ember Inspector from ' + downloadURL);
+ }
+ }, false);
+ }
+}
+
+})();
+
+/*!
+ * @overview Ember - JavaScript Application Framework
+ * @copyright Copyright 2011-2014 Tilde Inc. and contributors
+ * Portions Copyright 2006-2011 Strobe Inc.
+ * Portions Copyright 2008-2011 Apple Inc. All rights reserved.
+ * @license Licensed under MIT license
+ * See https://raw.github.com/emberjs/ember.js/master/LICENSE
+ * @version 1.5.0
+ */
+
+
+(function() {
+var define, requireModule, require, requirejs;
+
+(function() {
+ var registry = {}, seen = {};
+
+ define = function(name, deps, callback) {
+ registry[name] = { deps: deps, callback: callback };
+ };
+
+ requirejs = require = requireModule = function(name) {
+ requirejs._eak_seen = registry;
+
+ if (seen[name]) { return seen[name]; }
+ seen[name] = {};
+
+ if (!registry[name]) {
+ throw new Error("Could not find module " + name);
+ }
+
+ var mod = registry[name],
+ deps = mod.deps,
+ callback = mod.callback,
+ reified = [],
+ exports;
+
+ for (var i=0, l=deps.length; i<l; i++) {
+ if (deps[i] === 'exports') {
+ reified.push(exports = {});
+ } else {
+ reified.push(requireModule(resolve(deps[i])));
+ }
+ }
+
+ var value = callback.apply(this, reified);
+ return seen[name] = exports || value;
+
+ 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("/");
+ }
+ };
+})();
+(function() {
+/*globals Em:true ENV EmberENV MetamorphENV:true */
+
+/**
+@module ember
+@submodule ember-metal
+*/
+
+/**
+ All Ember methods and functions are defined inside of this namespace. You
+ generally should not add new properties to this namespace as it may be
+ overwritten by future versions of Ember.
+
+ You can also use the shorthand `Em` instead of `Ember`.
+
+ Ember-Runtime is a framework that provides core functions for Ember including
+ cross-platform functions, support for property observing and objects. Its
+ focus is on small size and performance. You can use this in place of or
+ along-side other cross-platform libraries such as jQuery.
+
+ The core Runtime framework is based on the jQuery API with a number of
+ performance optimizations.
+
+ @class Ember
+ @static
+ @version 1.5.0
+*/
+
+if ('undefined' === typeof Ember) {
+ // Create core object. Make it act like an instance of Ember.Namespace so that
+ // objects assigned to it are given a sane string representation.
+ Ember = {};
+}
+
+// Default imports, exports and lookup to the global object;
+var imports = Ember.imports = Ember.imports || this;
+var exports = Ember.exports = Ember.exports || this;
+var lookup = Ember.lookup = Ember.lookup || this;
+
+// aliases needed to keep minifiers from removing the global context
+exports.Em = exports.Ember = Em = Ember;
+
+// Make sure these are set whether Ember was already defined or not
+
+Ember.isNamespace = true;
+
+Ember.toString = function() { return "Ember"; };
+
+
+/**
+ @property VERSION
+ @type String
+ @default '1.5.0'
+ @static
+*/
+Ember.VERSION = '1.5.0';
+
+/**
+ Standard environmental variables. You can define these in a global `EmberENV`
+ variable before loading Ember to control various configuration settings.
+
+ For backwards compatibility with earlier versions of Ember the global `ENV`
+ variable will be used if `EmberENV` is not defined.
+
+ @property ENV
+ @type Hash
+*/
+
+// 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 = {};
+}
+
+Ember.config = Ember.config || {};
+
+// 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;
+ }
+};
+
+// ..........................................................
+// BOOTSTRAP
+//
+
+/**
+ Determines whether Ember should enhances some built-in object prototypes to
+ provide a more friendly API. If enabled, a few methods will be added to
+ `Function`, `String`, and `Array`. `Object.prototype` will not be enhanced,
+ which is the one that causes most trouble for people.
+
+ In general we recommend leaving this option set to true since it rarely
+ conflicts with other code. If you need to turn it off however, you can
+ define an `ENV.EXTEND_PROTOTYPES` config to disable it.
+
+ @property EXTEND_PROTOTYPES
+ @type Boolean
+ @default true
+*/
+Ember.EXTEND_PROTOTYPES = Ember.ENV.EXTEND_PROTOTYPES;
+
+if (typeof Ember.EXTEND_PROTOTYPES === 'undefined') {
+ Ember.EXTEND_PROTOTYPES = true;
+}
+
+/**
+ Determines whether Ember logs a full stack trace during deprecation warnings
+
+ @property LOG_STACKTRACE_ON_DEPRECATION
+ @type Boolean
+ @default true
+*/
+Ember.LOG_STACKTRACE_ON_DEPRECATION = (Ember.ENV.LOG_STACKTRACE_ON_DEPRECATION !== false);
+
+/**
+ Determines whether Ember should add ECMAScript 5 shims to older browsers.
+
+ @property SHIM_ES5
+ @type Boolean
+ @default Ember.EXTEND_PROTOTYPES
+*/
+Ember.SHIM_ES5 = (Ember.ENV.SHIM_ES5 === false) ? false : Ember.EXTEND_PROTOTYPES;
+
+/**
+ Determines whether Ember logs info about version of used libraries
+
+ @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`.
+
+ @method K
+ @private
+ @return {Object}
+*/
+Ember.K = function() { return this; };
+
+
+// Stub out the methods defined by the ember-debug package in case it's not loaded
+
+if ('undefined' === typeof Ember.assert) { Ember.assert = Ember.K; }
+if ('undefined' === typeof Ember.warn) { Ember.warn = Ember.K; }
+if ('undefined' === typeof Ember.debug) { Ember.debug = Ember.K; }
+if ('undefined' === typeof Ember.runInDebug) { Ember.runInDebug = Ember.K; }
+if ('undefined' === typeof Ember.deprecate) { Ember.deprecate = Ember.K; }
+if ('undefined' === typeof Ember.deprecateFunc) {
+ Ember.deprecateFunc = function(_, func) { return func; };
+}
+
+/**
+ Previously we used `Ember.$.uuid`, however `$.uuid` has been removed from
+ jQuery master. We'll just bootstrap our own uuid now.
+
+ @property uuid
+ @type Number
+ @private
+*/
+Ember.uuid = 0;
+
+/**
+ Merge the contents of two objects together into the first object.
+
+ ```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'}
+ ```
+
+ @method merge
+ @for Ember
+ @param {Object} original The object to merge into
+ @param {Object} updates The object to copy properties from
+ @return {Object}
+*/
+Ember.merge = function(original, updates) {
+ for (var prop in updates) {
+ if (!updates.hasOwnProperty(prop)) { continue; }
+ original[prop] = updates[prop];
+ }
+ return original;
+};
+
+/**
+ 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 Ember.isNone(obj) || (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);
+
+
+ /**
+ A value is blank if it is empty or a whitespace string.
+
+ ```javascript
+ Ember.isBlank(); // true
+ Ember.isBlank(null); // true
+ Ember.isBlank(undefined); // true
+ Ember.isBlank(''); // true
+ Ember.isBlank([]); // true
+ Ember.isBlank('\n\t'); // true
+ Ember.isBlank(' '); // true
+ Ember.isBlank({}); // false
+ Ember.isBlank('\n\t Hello'); // false
+ Ember.isBlank('Hello world'); // false
+ Ember.isBlank([1,2,3]); // false
+ ```
+
+ @method isBlank
+ @for Ember
+ @param {Object} obj Value to test
+ @return {Boolean}
+ */
+ Ember.isBlank = function(obj) {
+ return Ember.isEmpty(obj) || (typeof obj === 'string' && obj.match(/\S/) === null);
+ };
+
+
+})();
+
+
+
+(function() {
+/*globals Node */
+/**
+@module ember-metal
+*/
+
+/**
+ Platform specific methods and feature detectors needed by the framework.
+
+ @class platform
+ @namespace Ember
+ @static
+*/
+var platform = Ember.platform = {};
+
+
+/**
+ Identical to `Object.create()`. Implements if not available natively.
+
+ @method create
+ @for Ember
+*/
+Ember.create = Object.create;
+
+// 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;
+ }
+}
+
+// STUB_OBJECT_CREATE allows us to override other libraries that stub
+// Object.create different than we would prefer
+if (!Ember.create || Ember.ENV.STUB_OBJECT_CREATE) {
+ var K = function() {};
+
+ Ember.create = function(obj, props) {
+ K.prototype = obj;
+ obj = new K();
+ if (props) {
+ K.prototype = obj;
+ for (var prop in props) {
+ K.prototype[prop] = props[prop].value;
+ }
+ obj = new K();
+ }
+ K.prototype = null;
+
+ return obj;
+ };
+
+ Ember.create.isSimulated = true;
+}
+
+var defineProperty = Object.defineProperty;
+var canRedefineProperties, canDefinePropertyOnDOM;
+
+// Catch IE8 where Object.defineProperty exists but only works on DOM elements
+if (defineProperty) {
+ try {
+ defineProperty({}, 'a',{get:function() {}});
+ } catch (e) {
+ defineProperty = null;
+ }
+}
+
+if (defineProperty) {
+ // Detects a bug in Android <3.2 where you cannot redefine a property using
+ // Object.defineProperty once accessors have already been set.
+ canRedefineProperties = (function() {
+ var obj = {};
+
+ defineProperty(obj, 'a', {
+ configurable: true,
+ enumerable: true,
+ get: function() { },
+ set: function() { }
+ });
+
+ defineProperty(obj, 'a', {
+ configurable: true,
+ enumerable: true,
+ writable: true,
+ value: true
+ });
+
+ return obj.a === true;
+ })();
+
+ // This is for Safari 5.0, which supports Object.defineProperty, but not
+ // on DOM nodes.
+ canDefinePropertyOnDOM = (function() {
+ try {
+ defineProperty(document.createElement('div'), 'definePropertyOnDOM', {});
+ return true;
+ } catch(e) { }
+
+ return false;
+ })();
+
+ if (!canRedefineProperties) {
+ defineProperty = null;
+ } else if (!canDefinePropertyOnDOM) {
+ defineProperty = function(obj, keyName, desc) {
+ var isNode;
+
+ if (typeof Node === "object") {
+ isNode = obj instanceof Node;
+ } else {
+ isNode = typeof obj === "object" && typeof obj.nodeType === "number" && typeof obj.nodeName === "string";
+ }
+
+ if (isNode) {
+ // TODO: Should we have a warning here?
+ return (obj[keyName] = desc.value);
+ } else {
+ return Object.defineProperty(obj, keyName, desc);
+ }
+ };
+ }
+}
+
+/**
+@class platform
+@namespace Ember
+*/
+
+/**
+ Identical to `Object.defineProperty()`. Implements as much functionality
+ as possible if not available natively.
+
+ @method defineProperty
+ @param {Object} obj The object to modify
+ @param {String} keyName property name to modify
+ @param {Object} desc descriptor hash
+ @return {void}
+*/
+platform.defineProperty = defineProperty;
+
+/**
+ Set to true if the platform supports native getters and setters.
+
+ @property hasPropertyAccessors
+ @final
+*/
+platform.hasPropertyAccessors = true;
+
+if (!platform.defineProperty) {
+ platform.hasPropertyAccessors = false;
+
+ platform.defineProperty = function(obj, keyName, desc) {
+ if (!desc.get) { obj[keyName] = desc.value; }
+ };
+
+ platform.defineProperty.isSimulated = true;
+}
+
+if (Ember.ENV.MANDATORY_SETTER && !platform.hasPropertyAccessors) {
+ Ember.ENV.MANDATORY_SETTER = false;
+}
+
+})();
+
+
+
+(function() {
+/*jshint newcap:false*/
+/**
+@module ember-metal
+*/
+
+// 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
+
+// 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/…
+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/…
+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;
+};
+
+var arrayFilter = isNativeFunc(Array.prototype.filter) ? Array.prototype.filter : function (fn, context) {
+ var i,
+ value,
+ result = [],
+ length = this.length;
+
+ for (i = 0; i < length; i++) {
+ if (this.hasOwnProperty(i)) {
+ value = this[i];
+ if (fn.call(context, value, i, this)) {
+ result.push(value);
+ }
+ }
+ }
+ return result;
+};
+
+/**
+ Array polyfills to support ES5 features in older browsers.
+
+ @namespace Ember
+ @property ArrayPolyfills
+*/
+Ember.ArrayPolyfills = {
+ map: arrayMap,
+ forEach: arrayForEach,
+ filter: arrayFilter,
+ 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.filter) {
+ Array.prototype.filter = arrayFilter;
+ }
+
+ 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;
+
+})();
+
+
+
+(function() {
+/**
+@module ember-metal
+*/
+
+/**
+ Prefix used for guids through out Ember.
+ @private
+*/
+Ember.GUID_PREFIX = 'ember';
+
+
+var o_defineProperty = Ember.platform.defineProperty,
+ o_create = Ember.create,
+ // Used for guid generation...
+ GUID_KEY = '__ember'+ (+ new Date()),
+ numberCache = [],
+ stringCache = {},
+ uuid = 0;
+
+var MANDATORY_SETTER = Ember.ENV.MANDATORY_SETTER;
+
+/**
+ A unique key used to assign guids and other private metadata to objects.
+ If you inspect an object in your browser debugger you will often see these.
+ They can be safely ignored.
+
+ On browsers that support it, these properties are added with enumeration
+ disabled so they won't show up when you iterate over your properties.
+
+ @private
+ @property GUID_KEY
+ @for Ember
+ @type String
+ @final
+*/
+Ember.GUID_KEY = GUID_KEY;
+
+var GUID_DESC = {
+ writable: false,
+ configurable: false,
+ enumerable: false,
+ value: null
+};
+
+/**
+ Generates a new guid, optionally saving the guid to the object that you
+ pass in. You will rarely need to use this method. Instead you should
+ call `Ember.guidFor(obj)`, which return an existing guid if available.
+
+ @private
+ @method generateGuid
+ @for Ember
+ @param {Object} [obj] Object the guid will be used for. If passed in, the guid will
+ be saved on the object and reused whenever you pass the same object
+ again.
+
+ If no object is passed, just generate a new guid.
+ @param {String} [prefix] Prefix to place in front of the guid. Useful when you want to
+ separate the guid into separate namespaces.
+ @return {String} the guid
+*/
+Ember.generateGuid = function generateGuid(obj, prefix) {
+ if (!prefix) prefix = Ember.GUID_PREFIX;
+ var ret = (prefix + (uuid++));
+ if (obj) {
+ if (obj[GUID_KEY] === null) {
+ obj[GUID_KEY] = ret;
+ } else {
+ GUID_DESC.value = ret;
+ o_defineProperty(obj, GUID_KEY, GUID_DESC);
+ }
+ }
+ return ret;
+};
+
+/**
+ Returns a unique id for the object. If the object does not yet have a guid,
+ one will be assigned to it. You can call this on any object,
+ `Ember.Object`-based or not, but be aware that it will add a `_guid`
+ property.
+
+ You can also use this method on DOM Element objects.
+
+ @private
+ @method guidFor
+ @for Ember
+ @param {Object} obj any object, string, number, Element, or primitive
+ @return {String} the unique guid for this instance.
+*/
+Ember.guidFor = function guidFor(obj) {
+
+ // special cases where we don't want to add a key to object
+ if (obj === undefined) return "(undefined)";
+ if (obj === null) return "(null)";
+
+ var ret;
+ var type = typeof obj;
+
+ // Don't allow prototype changes to String etc. to change the guidFor
+ switch(type) {
+ case 'number':
+ ret = numberCache[obj];
+ if (!ret) ret = numberCache[obj] = 'nu'+obj;
+ return ret;
+
+ case 'string':
+ ret = stringCache[obj];
+ if (!ret) ret = stringCache[obj] = 'st'+(uuid++);
+ return ret;
+
+ case 'boolean':
+ return obj ? '(true)' : '(false)';
+
+ default:
+ if (obj[GUID_KEY]) return obj[GUID_KEY];
+ if (obj === Object) return '(Object)';
+ if (obj === Array) return '(Array)';
+ ret = 'ember' + (uuid++);
+
+ if (obj[GUID_KEY] === null) {
+ obj[GUID_KEY] = ret;
+ } else {
+ GUID_DESC.value = ret;
+ o_defineProperty(obj, GUID_KEY, GUID_DESC);
+ }
+ return ret;
+ }
+};
+
+// ..........................................................
+// META
+//
+
+var META_DESC = Ember.META_DESC = {
+ writable: true,
+ configurable: false,
+ enumerable: false,
+ value: null
+};
+
+var META_KEY = Ember.GUID_KEY+'_meta';
+
+/**
+ The key used to store meta information on object for property observing.
+
+ @property META_KEY
+ @for Ember
+ @private
+ @final
+ @type String
+*/
+Ember.META_KEY = META_KEY;
+
+var isDefinePropertySimulated = Ember.platform.defineProperty.isSimulated;
+
+function Meta(obj) {
+ this.descs = {};
+ this.watching = {};
+ this.cache = {};
+ this.cacheMeta = {};
+ this.source = obj;
+}
+
+Meta.prototype = {
+ descs: null,
+ deps: null,
+ watching: null,
+ listeners: null,
+ cache: null,
+ cacheMeta: null,
+ source: null,
+ mixins: null,
+ bindings: null,
+ chains: null,
+ chainWatchers: null,
+ values: null,
+ proto: null
+};
+
+if (isDefinePropertySimulated) {
+ // on platforms that don't support enumerable false
+ // make meta fail jQuery.isPlainObject() to hide from
+ // jQuery.extend() by having a property that fails
+ // hasOwnProperty check.
+ Meta.prototype.__preventPlainObject__ = true;
+
+ // Without non-enumerable properties, meta objects will be output in JSON
+ // unless explicitly suppressed
+ Meta.prototype.toJSON = function () { };
+}
+
+// Placeholder for non-writable metas.
+var EMPTY_META = new Meta(null);
+
+if (MANDATORY_SETTER) { EMPTY_META.values = {}; }
+
+Ember.EMPTY_META = EMPTY_META;
+
+/**
+ Retrieves the meta hash for an object. If `writable` is true ensures the
+ hash is writable for this object as well.
+
+ The meta object contains information about computed property descriptors as
+ well as any watched properties and other information. You generally will
+ not access this information directly but instead work with higher level
+ methods that manipulate this hash indirectly.
+
+ @method meta
+ @for Ember
+ @private
+
+ @param {Object} obj The object to retrieve meta for
+ @param {Boolean} [writable=true] Pass `false` if you do not intend to modify
+ the meta hash, allowing the method to avoid making an unnecessary copy.
+ @return {Object} the meta hash for an object
+*/
+Ember.meta = function meta(obj, writable) {
+
+ var ret = obj[META_KEY];
+ if (writable===false) return ret || EMPTY_META;
+
+ if (!ret) {
+ if (!isDefinePropertySimulated) o_defineProperty(obj, META_KEY, META_DESC);
+
+ ret = new Meta(obj);
+
+ if (MANDATORY_SETTER) { ret.values = {}; }
+
+ obj[META_KEY] = ret;
+
+ // make sure we don't accidentally try to create constructor like desc
+ ret.descs.constructor = null;
+
+ } else if (ret.source !== obj) {
+ if (!isDefinePropertySimulated) o_defineProperty(obj, META_KEY, META_DESC);
+
+ ret = o_create(ret);
+ ret.descs = o_create(ret.descs);
+ ret.watching = o_create(ret.watching);
+ ret.cache = {};
+ ret.cacheMeta = {};
+ ret.source = obj;
+
+ if (MANDATORY_SETTER) { ret.values = o_create(ret.values); }
+
+ obj[META_KEY] = ret;
+ }
+ return ret;
+};
+
+Ember.getMeta = function getMeta(obj, property) {
+ var meta = Ember.meta(obj, false);
+ return meta[property];
+};
+
+Ember.setMeta = function setMeta(obj, property, value) {
+ var meta = Ember.meta(obj, true);
+ meta[property] = value;
+ return value;
+};
+
+/**
+ @deprecated
+ @private
+
+ In order to store defaults for a class, a prototype may need to create
+ a default meta object, which will be inherited by any objects instantiated
+ from the class's constructor.
+
+ However, the properties of that meta object are only shallow-cloned,
+ so if a property is a hash (like the event system's `listeners` hash),
+ it will by default be shared across all instances of that class.
+
+ This method allows extensions to deeply clone a series of nested hashes or
+ other complex objects. For instance, the event system might pass
+ `['listeners', 'foo:change', 'ember157']` to `prepareMetaPath`, which will
+ walk down the keys provided.
+
+ For each key, if the key does not exist, it is created. If it already
+ exists and it was inherited from its constructor, the constructor's
+ key is cloned.
+
+ You can also pass false for `writable`, which will simply return
+ undefined if `prepareMetaPath` discovers any part of the path that
+ shared or undefined.
+
+ @method metaPath
+ @for Ember
+ @param {Object} obj The object whose meta we are examining
+ @param {Array} path An array of keys to walk down
+ @param {Boolean} writable whether or not to create a new meta
+ (or meta property) if one does not already exist or if it's
+ shared with its constructor
+*/
+Ember.metaPath = function metaPath(obj, path, writable) {
+ Ember.deprecate("Ember.metaPath is deprecated and will be removed from future releases.");
+ var meta = Ember.meta(obj, writable), keyName, value;
+
+ for (var i=0, l=path.length; i<l; i++) {
+ keyName = path[i];
+ value = meta[keyName];
+
+ if (!value) {
+ if (!writable) { return undefined; }
+ value = meta[keyName] = { __ember_source__: obj };
+ } else if (value.__ember_source__ !== obj) {
+ if (!writable) { return undefined; }
+ value = meta[keyName] = o_create(value);
+ value.__ember_source__ = obj;
+ }
+
+ meta = value;
+ }
+
+ return value;
+};
+
+/**
+ Wraps the passed function so that `this._super` will point to the superFunc
+ when the function is invoked. This is the primitive we use to implement
+ calls to super.
+
+ @private
+ @method wrap
+ @for Ember
+ @param {Function} func The function to call
+ @param {Function} superFunc The super function.
+ @return {Function} wrapped function.
+*/
+Ember.wrap = function(func, superFunc) {
+ function superWrapper() {
+ var ret, sup = this.__nextSuper;
+ this.__nextSuper = superFunc;
+ ret = func.apply(this, arguments);
+ this.__nextSuper = sup;
+ return ret;
+ }
+
+ superWrapper.wrappedFunction = func;
+ superWrapper.__ember_observes__ = func.__ember_observes__;
+ superWrapper.__ember_observesBefore__ = func.__ember_observesBefore__;
+ superWrapper.__ember_listens__ = func.__ember_listens__;
+
+ return superWrapper;
+};
+
+/**
+ Returns true if the passed object is an array or Array-like.
+
+ Ember Array Protocol:
+
+ - the object has an objectAt property
+ - the object is a native Array
+ - the object is an Object, and has a length property
+
+ Unlike `Ember.typeOf` this method returns true even if the passed object is
+ not formally array but appears to be array-like (i.e. implements `Ember.Array`)
+
+ ```javascript
+ Ember.isArray(); // false
+ Ember.isArray([]); // true
+ Ember.isArray( Ember.ArrayProxy.create({ content: [] }) ); // true
+ ```
+
+ @method isArray
+ @for Ember
+ @param {Object} obj The object to test
+ @return {Boolean} true if the passed object is an array or Array-like
+*/
+Ember.isArray = function(obj) {
+ if (!obj || obj.setInterval) { return false; }
+ if (Array.isArray && Array.isArray(obj)) { return true; }
+ if (Ember.Array && Ember.Array.detect(obj)) { return true; }
+ if ((obj.length !== undefined) && 'object'===typeof obj) { return true; }
+ return false;
+};
+
+/**
+ Forces the passed object to be part of an array. If the object is already
+ an array or array-like, returns the object. Otherwise adds the object to
+ an array. If obj is `null` or `undefined`, returns an empty array.
+
+ ```javascript
+ Ember.makeArray(); // []
+ Ember.makeArray(null); // []
+ Ember.makeArray(undefined); // []
+ Ember.makeArray('lindsay'); // ['lindsay']
+ Ember.makeArray([1,2,42]); // [1,2,42]
+
+ var controller = Ember.ArrayProxy.create({ content: [] });
+ Ember.makeArray(controller) === controller; // true
+ ```
+
+ @method makeArray
+ @for Ember
+ @param {Object} obj the object
+ @return {Array}
+*/
+Ember.makeArray = function(obj) {
+ if (obj === null || obj === undefined) { return []; }
+ return Ember.isArray(obj) ? obj : [obj];
+};
+
+function canInvoke(obj, methodName) {
+ return !!(obj && typeof obj[methodName] === 'function');
+}
+
+/**
+ Checks to see if the `methodName` exists on the `obj`.
+
+ ```javascript
+ var foo = {bar: Ember.K, baz: null};
+ Ember.canInvoke(foo, 'bar'); // true
+ Ember.canInvoke(foo, 'baz'); // false
+ Ember.canInvoke(foo, 'bat'); // false
+ ```
+
+ @method canInvoke
+ @for Ember
+ @param {Object} obj The object to check for the method
+ @param {String} methodName The method name to check for
+ @return {Boolean}
+*/
+Ember.canInvoke = canInvoke;
+
+/**
+ Checks to see if the `methodName` exists on the `obj`,
+ and if it does, invokes it with the arguments passed.
+
+ ```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
+ ```
+
+ @method tryInvoke
+ @for Ember
+ @param {Object} obj The object to check for the method
+ @param {String} methodName The method name to check for
+ @param {Array} [args] The arguments to pass to the method
+ @return {*} the return value of the invoked method or undefined if it cannot be invoked
+*/
+Ember.tryInvoke = function(obj, methodName, args) {
+ if (canInvoke(obj, methodName)) {
+ return obj[methodName].apply(obj, args || []);
+ }
+};
+
+// https://github.com/emberjs/ember.js/pull/1617
+var needsFinallyFix = (function() {
+ var count = 0;
+ try{
+ try { }
+ finally {
+ count++;
+ throw new Error('needsFinallyFixTest');
+ }
+ } catch (e) {}
+
+ return count !== 1;
+})();
+
+/**
+ Provides try { } finally { } functionality, while working
+ around Safari's double finally bug.
+
+ ```javascript
+ var tryable = function() {
+ someResource.lock();
+ runCallback(); // May throw error.
+ };
+ var finalizer = function() {
+ someResource.unlock();
+ };
+ Ember.tryFinally(tryable, finalizer);
+ ```
+
+ @method tryFinally
+ @for Ember
+ @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
+ of the tryable
+*/
+
+if (needsFinallyFix) {
+ Ember.tryFinally = function(tryable, finalizer, binding) {
+ var result, finalResult, finalError;
+
+ binding = binding || this;
+
+ try {
+ result = tryable.call(binding);
+ } finally {
+ try {
+ finalResult = finalizer.call(binding);
+ } catch (e) {
+ finalError = e;
+ }
+ }
+
+ if (finalError) { throw finalError; }
+
+ return (finalResult === undefined) ? result : finalResult;
+ };
+} else {
+ Ember.tryFinally = function(tryable, finalizer, binding) {
+ var result, finalResult;
+
+ binding = binding || this;
+
+ try {
+ result = tryable.call(binding);
+ } finally {
+ finalResult = finalizer.call(binding);
+ }
+
+ return (finalResult === undefined) ? result : finalResult;
+ };
+}
+
+/**
+ Provides try { } catch finally { } functionality, while working
+ around Safari's double finally bug.
+
+ ```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);
+ ```
+
+ @method tryCatchFinally
+ @for Ember
+ @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,
+ unless that value is undefined, in which case it is the return value
+ of the tryable.
+*/
+if (needsFinallyFix) {
+ Ember.tryCatchFinally = function(tryable, catchable, finalizer, binding) {
+ var result, finalResult, finalError;
+
+ binding = binding || this;
+
+ try {
+ result = tryable.call(binding);
+ } catch(error) {
+ result = catchable.call(binding, error);
+ } finally {
+ try {
+ finalResult = finalizer.call(binding);
+ } catch (e) {
+ finalError = e;
+ }
+ }
+
+ if (finalError) { throw finalError; }
+
+ return (finalResult === undefined) ? result : finalResult;
+ };
+} else {
+ Ember.tryCatchFinally = function(tryable, catchable, finalizer, binding) {
+ var result, finalResult;
+
+ binding = binding || this;
+
+ try {
+ result = tryable.call(binding);
+ } catch(error) {
+ result = catchable.call(binding, error);
+ } finally {
+ finalResult = finalizer.call(binding);
+ }
+
+ return (finalResult === undefined) ? result : finalResult;
+ };
+}
+
+// ........................................
+// 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;
+};
+
+/**
+ Convenience method to inspect an object. This method will attempt to
+ convert the object into a useful string description.
+
+ It is a pretty simple implementation. If you want something more robust,
+ use something like JSDump: https://github.com/NV/jsDump
+
+ @method inspect
+ @for Ember
+ @param {Object} obj The object you want to inspect.
+ @return {String} A description of the object
+*/
+Ember.inspect = function(obj) {
+ var type = Ember.typeOf(obj);
+ if (type === 'array') {
+ return '[' + obj + ']';
+ }
+ if (type !== 'object') {
+ return obj + '';
+ }
+
+ var v, ret = [];
+ for(var key in obj) {
+ if (obj.hasOwnProperty(key)) {
+ v = obj[key];
+ if (v === 'toString') { continue; } // ignore useless items
+ if (Ember.typeOf(v) === 'function') { v = "function() { ... }"; }
+ ret.push(key + ": " + v);
+ }
+ }
+ return "{" + ret.join(", ") + "}";
+};
+
+
+
+})();
+
+
+
+(function() {
+// Ember.tryCatchFinally
+
+/**
+ The purpose of the Ember Instrumentation module is
+ to provide efficient, general-purpose instrumentation
+ for Ember.
+
+ Subscribe to a listener by using `Ember.subscribe`:
+
+ ```javascript
+ Ember.subscribe("render", {
+ before: function(name, timestamp, payload) {
+
+ },
+
+ after: function(name, timestamp, payload) {
+
+ }
+ });
+ ```
+
+ If you return a value from the `before` callback, that same
+ value will be passed as a fourth parameter to the `after`
+ callback.
+
+ Instrument a block of code by using `Ember.instrument`:
+
+ ```javascript
+ Ember.instrument("render.handlebars", payload, function() {
+ // rendering logic
+ }, binding);
+ ```
+
+ Event names passed to `Ember.instrument` are namespaced
+ by periods, from more general to more specific. Subscribers
+ can listen for events by whatever level of granularity they
+ are interested in.
+
+ In the above example, the event is `render.handlebars`,
+ and the subscriber listened for all events beginning with
+ `render`. It would receive callbacks for events named
+ `render`, `render.handlebars`, `render.container`, or
+ even `render.handlebars.layout`.
+
+ @class Instrumentation
+ @namespace Ember
+ @static
+*/
+Ember.Instrumentation = {};
+
+var subscribers = [], cache = {};
+
+var populateListeners = function(name) {
+ var listeners = [], subscriber;
+
+ for (var i=0, l=subscribers.length; i<l; i++) {
+ subscriber = subscribers[i];
+ if (subscriber.regex.test(name)) {
+ listeners.push(subscriber.object);
+ }
+ }
+
+ cache[name] = listeners;
+ return listeners;
+};
+
+var time = (function() {
+ 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(); };
+})();
+
+/**
+ Notifies event's subscribers, calls `before` and `after` hooks.
+
+ @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.
+*/
+Ember.Instrumentation.instrument = function(name, payload, callback, binding) {
+ var listeners = cache[name], timeName, ret;
+
+ if (Ember.STRUCTURED_PROFILE) {
+ timeName = name + ": " + payload.object;
+ console.time(timeName);
+ }
+
+ if (!listeners) {
+ listeners = populateListeners(name);
+ }
+
+ if (listeners.length === 0) {
+ ret = callback.call(binding);
+ if (Ember.STRUCTURED_PROFILE) { console.timeEnd(timeName); }
+ return ret;
+ }
+
+ var beforeValues = [], listener, i, l;
+
+ function tryable() {
+ for (i=0, l=listeners.length; i<l; i++) {
+ listener = listeners[i];
+ beforeValues[i] = listener.before(name, time(), payload);
+ }
+
+ return callback.call(binding);
+ }
+
+ function catchable(e) {
+ payload = payload || {};
+ payload.exception = e;
+ }
+
+ function finalizer() {
+ for (i=0, l=listeners.length; i<l; i++) {
+ listener = listeners[i];
+ listener.after(name, time(), payload, beforeValues[i]);
+ }
+
+ if (Ember.STRUCTURED_PROFILE) {
+ console.timeEnd(timeName);
+ }
+ }
+
+ return Ember.tryCatchFinally(tryable, catchable, finalizer);
+};
+
+/**
+ 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}
+*/
+Ember.Instrumentation.subscribe = function(pattern, object) {
+ var paths = pattern.split("."), path, regex = [];
+
+ for (var i=0, l=paths.length; i<l; i++) {
+ path = paths[i];
+ if (path === "*") {
+ regex.push("[^\\.]*");
+ } else {
+ regex.push(path);
+ }
+ }
+
+ regex = regex.join("\\.");
+ regex = regex + "(\\..*)?";
+
+ var subscriber = {
+ pattern: pattern,
+ regex: new RegExp("^" + regex + "$"),
+ object: object
+ };
+
+ subscribers.push(subscriber);
+ cache = {};
+
+ return subscriber;
+};
+
+/**
+ Unsubscribes from a particular event or instrumented block of code.
+
+ @method unsubscribe
+ @namespace Ember.Instrumentation
+
+ @param {Object} [subscriber]
+*/
+Ember.Instrumentation.unsubscribe = function(subscriber) {
+ var index;
+
+ for (var i=0, l=subscribers.length; i<l; i++) {
+ if (subscribers[i] === subscriber) {
+ index = i;
+ }
+ }
+
+ subscribers.splice(index, 1);
+ cache = {};
+};
+
+/**
+ Resets `Ember.Instrumentation` by flushing list of subscribers.
+
+ @method reset
+ @namespace Ember.Instrumentation
+*/
+Ember.Instrumentation.reset = function() {
+ subscribers = [];
+ cache = {};
+};
+
+Ember.instrument = Ember.Instrumentation.instrument;
+Ember.subscribe = Ember.Instrumentation.subscribe;
+})();
+
+
+
+(function() {
+var map, forEach, indexOf, splice, filter;
+map = Array.prototype.map || Ember.ArrayPolyfills.map;
+forEach = Array.prototype.forEach || Ember.ArrayPolyfills.forEach;
+indexOf = Array.prototype.indexOf || Ember.ArrayPolyfills.indexOf;
+filter = Array.prototype.filter || Ember.ArrayPolyfills.filter;
+splice = Array.prototype.splice;
+
+/**
+ * Defines some convenience methods for working with Enumerables.
+ * `Ember.EnumerableUtils` uses `Ember.ArrayPolyfills` when necessary.
+ *
+ * @class EnumerableUtils
+ * @namespace Ember
+ * @static
+ * */
+var utils = Ember.EnumerableUtils = {
+ /**
+ * Calls the map function on the passed object with a specified callback. This
+ * uses `Ember.ArrayPolyfill`'s-map method when necessary.
+ *
+ * @method map
+ * @param {Object} obj The object that should be mapped
+ * @param {Function} callback The callback to execute
+ * @param {Object} thisArg Value to use as this when executing *callback*
+ *
+ * @return {Array} An array of mapped values.
+ */
+ map: function(obj, callback, thisArg) {
+ return obj.map ? obj.map.call(obj, callback, thisArg) : map.call(obj, callback, thisArg);
+ },
+
+ /**
+ * Calls the forEach function on the passed object with a specified callback. This
+ * uses `Ember.ArrayPolyfill`'s-forEach method when necessary.
+ *
+ * @method forEach
+ * @param {Object} obj The object to call forEach on
+ * @param {Function} callback The callback to execute
+ * @param {Object} thisArg Value to use as this when executing *callback*
+ *
+ */
+ forEach: function(obj, callback, thisArg) {
+ return obj.forEach ? obj.forEach.call(obj, callback, thisArg) : forEach.call(obj, callback, thisArg);
+ },
+
+ /**
+ * Calls the filter function on the passed object with a specified callback. This
+ * uses `Ember.ArrayPolyfill`'s-filter method when necessary.
+ *
+ * @method filter
+ * @param {Object} obj The object to call filter on
+ * @param {Function} callback The callback to execute
+ * @param {Object} thisArg Value to use as this when executing *callback*
+ *
+ * @return {Array} An array containing the filtered values
+ */
+ filter: function(obj, callback, thisArg) {
+ return obj.filter ? obj.filter.call(obj, callback, thisArg) : filter.call(obj, callback, thisArg);
+ },
+
+ /**
+ * Calls the indexOf function on the passed object with a specified callback. This
+ * uses `Ember.ArrayPolyfill`'s-indexOf method when necessary.
+ *
+ * @method indexOf
+ * @param {Object} obj The object to call indexOn on
+ * @param {Function} callback The callback to execute
+ * @param {Object} index The index to start searching from
+ *
+ */
+ indexOf: function(obj, element, index) {
+ return obj.indexOf ? obj.indexOf.call(obj, element, index) : indexOf.call(obj, element, index);
+ },
+
+ /**
+ * Returns an array of indexes of the first occurrences of the passed elements
+ * on the passed object.
+ *
+ * ```javascript
+ * var array = [1, 2, 3, 4, 5];
+ * Ember.EnumerableUtils.indexesOf(array, [2, 5]); // [1, 4]
+ *
+ * var fubar = "Fubarr";
+ * Ember.EnumerableUtils.indexesOf(fubar, ['b', 'r']); // [2, 4]
+ * ```
+ *
+ * @method indexesOf
+ * @param {Object} obj The object to check for element indexes
+ * @param {Array} elements The elements to search for on *obj*
+ *
+ * @return {Array} An array of indexes.
+ *
+ */
+ indexesOf: function(obj, elements) {
+ return elements === undefined ? [] : utils.map(elements, function(item) {
+ return utils.indexOf(obj, item);
+ });
+ },
+
+ /**
+ * Adds an object to an array. If the array already includes the object this
+ * method has no effect.
+ *
+ * @method addObject
+ * @param {Array} array The array the passed item should be added to
+ * @param {Object} item The item to add to the passed array
+ *
+ * @return 'undefined'
+ */
+ addObject: function(array, item) {
+ var index = utils.indexOf(array, item);
+ if (index === -1) { array.push(item); }
+ },
+
+ /**
+ * Removes an object from an array. If the array does not contain the passed
+ * object this method has no effect.
+ *
+ * @method removeObject
+ * @param {Array} array The array to remove the item from.
+ * @param {Object} item The item to remove from the passed array.
+ *
+ * @return 'undefined'
+ */
+ removeObject: function(array, item) {
+ var index = utils.indexOf(array, item);
+ if (index !== -1) { array.splice(index, 1); }
+ },
+
+ _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;
+ },
+
+ /**
+ * Replaces objects in an array with the passed objects.
+ *
+ * ```javascript
+ * var array = [1,2,3];
+ * Ember.EnumerableUtils.replace(array, 1, 2, [4, 5]); // [1, 4, 5]
+ *
+ * var array = [1,2,3];
+ * Ember.EnumerableUtils.replace(array, 1, 1, [4, 5]); // [1, 4, 5, 3]
+ *
+ * var array = [1,2,3];
+ * Ember.EnumerableUtils.replace(array, 10, 1, [4, 5]); // [1, 2, 3, 4, 5]
+ * ```
+ *
+ * @method replace
+ * @param {Array} array The array the objects should be inserted into.
+ * @param {Number} idx Starting index in the array to replace. If *idx* >=
+ * length, then append to the end of the array.
+ * @param {Number} amt Number of elements that should be removed from the array,
+ * starting at *idx*
+ * @param {Array} objects An array of zero or more objects that should be
+ * inserted into the array at *idx*
+ *
+ * @return {Array} The modified array.
+ */
+ replace: function(array, idx, amt, objects) {
+ if (array.replace) {
+ return array.replace(idx, amt, objects);
+ } else {
+ return utils._replace(array, idx, amt, objects);
+ }
+ },
+
+ /**
+ * Calculates the intersection of two arrays. This method returns a new array
+ * filled with the records that the two passed arrays share with each other.
+ * If there is no intersection, an empty array will be returned.
+ *
+ * ```javascript
+ * var array1 = [1, 2, 3, 4, 5];
+ * var array2 = [1, 3, 5, 6, 7];
+ *
+ * Ember.EnumerableUtils.intersection(array1, array2); // [1, 3, 5]
+ *
+ * var array1 = [1, 2, 3];
+ * var array2 = [4, 5, 6];
+ *
+ * Ember.EnumerableUtils.intersection(array1, array2); // []
+ * ```
+ *
+ * @method intersection
+ * @param {Array} array1 The first array
+ * @param {Array} array2 The second array
+ *
+ * @return {Array} The intersection of the two passed arrays.
+ */
+ intersection: function(array1, array2) {
+ var intersection = [];
+
+ utils.forEach(array1, function(element) {
+ if (utils.indexOf(array2, element) >= 0) {
+ intersection.push(element);
+ }
+ });
+
+ return intersection;
+ }
+};
+
+})();
+
+
+
+(function() {
+/**
+@module ember-metal
+*/
+
+var META_KEY = Ember.META_KEY, get;
+
+var MANDATORY_SETTER = Ember.ENV.MANDATORY_SETTER;
+
+var IS_GLOBAL_PATH = /^([A-Z$]|([0-9][A-Z$])).*[\.\*]/;
+var HAS_THIS = /^this[\.\*]/;
+var FIRST_KEY = /^([^\.\*]+)/;
+
+// ..........................................................
+// GET AND SET
+//
+// If we are on a platform that supports accessors we can use those.
+// Otherwise simulate accessors by looking up the property directly on the
+// object.
+
+/**
+ Gets the value of a property on an object. If the property is computed,
+ the function will be invoked. If the property is not defined but the
+ object implements the `unknownProperty` method then that will be invoked.
+
+ If you plan to run on IE8 and older browsers then you should use this
+ method anytime you want to retrieve a property on an object that you don't
+ know for sure is private. (Properties beginning with an underscore '_'
+ are considered private.)
+
+ On all newer browsers, you only need to use this method to retrieve
+ properties if the property might not be defined on the object and you want
+ to respect the `unknownProperty` handler. Otherwise you can ignore this
+ method.
+
+ Note that if the object itself is `undefined`, this method will throw
+ an error.
+
+ @method get
+ @for Ember
+ @param {Object} obj The object to retrieve from.
+ @param {String} keyName The property key to retrieve
+ @return {Object} the property value or `null`.
+*/
+get = function get(obj, keyName) {
+ // Helpers that operate with 'this' within an #each
+ if (keyName === '') {
+ return obj;
+ }
+
+ if (!keyName && 'string'===typeof obj) {
+ keyName = obj;
+ obj = null;
+ }
+
+ Ember.assert("Cannot call get with "+ keyName +" key.", !!keyName);
+ Ember.assert("Cannot call get with '"+ keyName +"' on an undefined object.", obj !== undefined);
+
+ if (obj === null || keyName.indexOf('.') !== -1) {
+ return getPath(obj, keyName);
+ }
+
+ var meta = obj[META_KEY], desc = meta && meta.descs[keyName], ret;
+ if (desc) {
+ return desc.get(obj, keyName);
+ } else {
+ if (MANDATORY_SETTER && meta && meta.watching[keyName] > 0) {
+ ret = meta.values[keyName];
+ } else {
+ ret = obj[keyName];
+ }
+
+ if (ret === undefined &&
+ 'object' === typeof obj && !(keyName in obj) && 'function' === typeof obj.unknownProperty) {
+ return obj.unknownProperty(keyName);
+ }
+
+ return ret;
+ }
+};
+
+// Currently used only by Ember Data tests
+if (Ember.config.overrideAccessors) {
+ Ember.get = get;
+ Ember.config.overrideAccessors();
+ get = Ember.get;
+}
+
+/**
+ Normalizes a target/path pair to reflect that actual target/path that should
+ be observed, etc. This takes into account passing in global property
+ paths (i.e. a path beginning with a captial letter not defined on the
+ target) and * separators.
+
+ @private
+ @method normalizeTuple
+ @for Ember
+ @param {Object} target The current target. May be `null`.
+ @param {String} path A path on the target or a global property path.
+ @return {Array} a temporary array with the normalized target/path pair.
+*/
+var normalizeTuple = Ember.normalizeTuple = function(target, path) {
+ var hasThis = HAS_THIS.test(path),
+ isGlobal = !hasThis && IS_GLOBAL_PATH.test(path),
+ key;
+
+ if (!target || isGlobal) target = Ember.lookup;
+ if (hasThis) path = path.slice(5);
+
+ if (target === Ember.lookup) {
+ key = path.match(FIRST_KEY)[0];
+ target = get(target, key);
+ path = path.slice(key.length+1);
+ }
+
+ // must return some kind of path to be valid else other things will break.
+ if (!path || path.length===0) throw new Ember.Error('Path cannot be empty');
+
+ return [ target, path ];
+};
+
+var getPath = Ember._getPath = function(root, path) {
+ var hasThis, parts, tuple, idx, len;
+
+ // If there is no root and path is a key name, return that
+ // property from the global object.
+ // E.g. get('Ember') -> Ember
+ if (root === null && path.indexOf('.') === -1) { return get(Ember.lookup, path); }
+
+ // detect complicated paths and normalize them
+ hasThis = HAS_THIS.test(path);
+
+ if (!root || hasThis) {
+ tuple = normalizeTuple(root, path);
+ root = tuple[0];
+ path = tuple[1];
+ tuple.length = 0;
+ }
+
+ parts = path.split(".");
+ len = parts.length;
+ for (idx = 0; root != null && idx < len; idx++) {
+ root = get(root, parts[idx], true);
+ if (root && root.isDestroyed) { return undefined; }
+ }
+ return root;
+};
+
+Ember.getWithDefault = function(root, key, defaultValue) {
+ var value = get(root, key);
+
+ if (value === undefined) { return defaultValue; }
+ return value;
+};
+
+
+Ember.get = get;
+
+})();
+
+
+
+(function() {
+/**
+@module ember-metal
+*/
+
+var o_create = Ember.create,
+ metaFor = Ember.meta,
+ META_KEY = Ember.META_KEY,
+ a_slice = [].slice,
+ /* listener flags */
+ ONCE = 1, SUSPENDED = 2;
+
+/*
+ The event system uses a series of nested hashes to store listeners on an
+ object. When a listener is registered, or when an event arrives, these
+ hashes are consulted to determine which target and action pair to invoke.
+
+ The hashes are stored in the object's meta hash, and look like this:
+
+ // Object's meta hash
+ {
+ listeners: { // variable name: `listenerSet`
+ "foo:changed": [ // variable name: `actions`
+ target, method, flags
+ ]
+ }
+ }
+
+*/
+
+function indexOf(array, target, method) {
+ var index = -1;
+ // hashes are added to the end of the event array
+ // so it makes sense to start searching at the end
+ // of the array and search in reverse
+ for (var i = array.length - 3 ; i >=0; i -= 3) {
+ if (target === array[i] && method === array[i + 1]) {
+ index = i; break;
+ }
+ }
+ return index;
+}
+
+function actionsFor(obj, eventName) {
+ var meta = metaFor(obj, true),
+ actions;
+
+ if (!meta.listeners) { meta.listeners = {}; }
+
+ if (!meta.hasOwnProperty('listeners')) {
+ // setup inherited copy of the listeners object
+ meta.listeners = o_create(meta.listeners);
+ }
+
+ actions = meta.listeners[eventName];
+
+ // if there are actions, but the eventName doesn't exist in our listeners, then copy them from the prototype
+ if (actions && !meta.listeners.hasOwnProperty(eventName)) {
+ actions = meta.listeners[eventName] = meta.listeners[eventName].slice();
+ } else if (!actions) {
+ actions = meta.listeners[eventName] = [];
+ }
+
+ return actions;
+}
+
+function actionsUnion(obj, eventName, otherActions) {
+ var meta = obj[META_KEY],
+ actions = meta && meta.listeners && meta.listeners[eventName];
+
+ if (!actions) { return; }
+ for (var i = actions.length - 3; i >= 0; i -= 3) {
+ var target = actions[i],
+ method = actions[i+1],
+ flags = actions[i+2],
+ actionIndex = indexOf(otherActions, target, method);
+
+ if (actionIndex === -1) {
+ otherActions.push(target, method, flags);
+ }
+ }
+}
+
+function actionsDiff(obj, eventName, otherActions) {
+ var meta = obj[META_KEY],
+ actions = meta && meta.listeners && meta.listeners[eventName],
+ diffActions = [];
+
+ if (!actions) { return; }
+ for (var i = actions.length - 3; i >= 0; i -= 3) {
+ var target = actions[i],
+ method = actions[i+1],
+ flags = actions[i+2],
+ actionIndex = indexOf(otherActions, target, method);
+
+ if (actionIndex !== -1) { continue; }
+
+ otherActions.push(target, method, flags);
+ diffActions.push(target, method, flags);
+ }
+
+ return diffActions;
+}
+
+/**
+ Add an event listener
+
+ @method addListener
+ @for Ember
+ @param obj
+ @param {String} eventName
+ @param {Object|Function} targetOrMethod A target object or a function
+ @param {Function|String} method A function or the name of a function to be called on `target`
+ @param {Boolean} once A flag whether a function should only be called once
+*/
+function addListener(obj, eventName, target, method, once) {
+ Ember.assert("You must pass at least an object and event name to Ember.addListener", !!obj && !!eventName);
+
+ if (!method && 'function' === typeof target) {
+ method = target;
+ target = null;
+ }
+
+ var actions = actionsFor(obj, eventName),
+ actionIndex = indexOf(actions, target, method),
+ flags = 0;
+
+ if (once) flags |= ONCE;
+
+ if (actionIndex !== -1) { return; }
+
+ actions.push(target, method, flags);
+
+ if ('function' === typeof obj.didAddListener) {
+ obj.didAddListener(eventName, target, method);
+ }
+}
+
+/**
+ Remove an event listener
+
+ Arguments should match those passed to `Ember.addListener`.
+
+ @method removeListener
+ @for Ember
+ @param obj
+ @param {String} eventName
+ @param {Object|Function} targetOrMethod A target object or a function
+ @param {Function|String} method A function or the name of a function to be called on `target`
+*/
+function removeListener(obj, eventName, target, method) {
+ Ember.assert("You must pass at least an object and event name to Ember.removeListener", !!obj && !!eventName);
+
+ if (!method && 'function' === typeof target) {
+ method = target;
+ target = null;
+ }
+
+ function _removeListener(target, method) {
+ var actions = actionsFor(obj, eventName),
+ actionIndex = indexOf(actions, target, method);
+
+ // action doesn't exist, give up silently
+ if (actionIndex === -1) { return; }
+
+ actions.splice(actionIndex, 3);
+
+ if ('function' === typeof obj.didRemoveListener) {
+ obj.didRemoveListener(eventName, target, method);
+ }
+ }
+
+ if (method) {
+ _removeListener(target, method);
+ } else {
+ var meta = obj[META_KEY],
+ actions = meta && meta.listeners && meta.listeners[eventName];
+
+ if (!actions) { return; }
+ for (var i = actions.length - 3; i >= 0; i -= 3) {
+ _removeListener(actions[i], actions[i+1]);
+ }
+ }
+}
+
+/**
+ Suspend listener during callback.
+
+ This should only be used by the target of the event listener
+ when it is taking an action that would cause the event, e.g.
+ an object might suspend its property change listener while it is
+ setting that property.
+
+ @method suspendListener
+ @for Ember
+
+ @private
+ @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.
+
+ @method suspendListeners
+ @for Ember
+
+ @private
+ @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;
+};
+
+Ember.addListener = addListener;
+Ember.removeListener = removeListener;
+Ember._suspendListener = suspendListener;
+Ember._suspendListeners = suspendListeners;
+Ember.sendEvent = sendEvent;
+Ember.hasListeners = hasListeners;
+Ember.watchedEvents = watchedEvents;
+Ember.listenersFor = listenersFor;
+Ember.listenersDiff = actionsDiff;
+Ember.listenersUnion = actionsUnion;
+
+})();
+
+
+
+(function() {
+var guidFor = Ember.guidFor,
+ sendEvent = Ember.sendEvent;
+
+/*
+ this.observerSet = {
+ [senderGuid]: { // variable name: `keySet`
+ [keyName]: listIndex
+ }
+ },
+ this.observers = [
+ {
+ sender: obj,
+ keyName: keyName,
+ eventName: eventName,
+ listeners: [
+ [target, method, flags]
+ ]
+ },
+ ...
+ ]
+*/
+var ObserverSet = Ember._ObserverSet = function() {
+ this.clear();
+};
+
+ObserverSet.prototype.add = function(sender, keyName, eventName) {
+ var observerSet = this.observerSet,
+ observers = this.observers,
+ senderGuid = guidFor(sender),
+ keySet = observerSet[senderGuid],
+ index;
+
+ if (!keySet) {
+ observerSet[senderGuid] = keySet = {};
+ }
+ index = keySet[keyName];
+ if (index === undefined) {
+ index = observers.push({
+ sender: sender,
+ keyName: keyName,
+ eventName: eventName,
+ listeners: []
+ }) - 1;
+ keySet[keyName] = index;
+ }
+ return observers[index].listeners;
+};
+
+ObserverSet.prototype.flush = function() {
+ var observers = this.observers, i, len, observer, sender;
+ this.clear();
+ for (i=0, len=observers.length; i < len; ++i) {
+ observer = observers[i];
+ sender = observer.sender;
+ if (sender.isDestroying || sender.isDestroyed) { continue; }
+ sendEvent(sender, observer.eventName, [sender, observer.keyName], observer.listeners);
+ }
+};
+
+ObserverSet.prototype.clear = function() {
+ this.observerSet = {};
+ this.observers = [];
+};
+})();
+
+
+
+(function() {
+var META_KEY = Ember.META_KEY,
+ guidFor = Ember.guidFor,
+ tryFinally = Ember.tryFinally,
+ sendEvent = Ember.sendEvent,
+ listenersUnion = Ember.listenersUnion,
+ listenersDiff = Ember.listenersDiff,
+ ObserverSet = Ember._ObserverSet,
+ beforeObserverSet = new ObserverSet(),
+ observerSet = new ObserverSet(),
+ deferred = 0;
+
+// ..........................................................
+// PROPERTY CHANGES
+//
+
+/**
+ This function is called just before an object property is about to change.
+ It will notify any before observers and prepare caches among other things.
+
+ Normally you will not need to call this method directly but if for some
+ reason you can't directly watch a property you can invoke this method
+ manually along with `Ember.propertyDidChange()` which you should call just
+ after the property value changes.
+
+ @method propertyWillChange
+ @for Ember
+ @param {Object} obj The object with the property that will change
+ @param {String} keyName The property key (or path) that will change.
+ @return {void}
+*/
+function propertyWillChange(obj, keyName) {
+ var m = obj[META_KEY],
+ watching = (m && m.watching[keyName] > 0) || keyName === 'length',
+ proto = m && m.proto,
+ desc = m && m.descs[keyName];
+
+ if (!watching) { return; }
+ if (proto === obj) { return; }
+ if (desc && desc.willChange) { desc.willChange(obj, keyName); }
+ dependentKeysWillChange(obj, keyName, m);
+ chainsWillChange(obj, keyName, m);
+ notifyBeforeObservers(obj, keyName);
+}
+Ember.propertyWillChange = propertyWillChange;
+
+/**
+ This function is called just after an object property has changed.
+ It will notify any observers and clear caches among other things.
+
+ Normally you will not need to call this method directly but if for some
+ reason you can't directly watch a property you can invoke this method
+ manually along with `Ember.propertyWillChange()` which you should call just
+ before the property value changes.
+
+ @method propertyDidChange
+ @for Ember
+ @param {Object} obj The object with the property that will change
+ @param {String} keyName The property key (or path) that will change.
+ @return {void}
+*/
+function propertyDidChange(obj, keyName) {
+ var m = obj[META_KEY],
+ watching = (m && m.watching[keyName] > 0) || keyName === 'length',
+ proto = m && m.proto,
+ desc = m && m.descs[keyName];
+
+ if (proto === obj) { return; }
+
+ // shouldn't this mean that we're watching this key?
+ if (desc && desc.didChange) { desc.didChange(obj, keyName); }
+ if (!watching && keyName !== 'length') { return; }
+
+ dependentKeysDidChange(obj, keyName, m);
+ chainsDidChange(obj, keyName, m, false);
+ notifyObservers(obj, keyName);
+}
+Ember.propertyDidChange = propertyDidChange;
+
+var WILL_SEEN, DID_SEEN;
+
+// called whenever a property is about to change to clear the cache of any dependent keys (and notify those properties of changes, etc...)
+function dependentKeysWillChange(obj, depKey, meta) {
+ if (obj.isDestroying) { return; }
+
+ var seen = WILL_SEEN, top = !seen;
+ if (top) { seen = WILL_SEEN = {}; }
+ iterDeps(propertyWillChange, obj, depKey, seen, meta);
+ if (top) { WILL_SEEN = null; }
+}
+
+// called whenever a property has just changed to update dependent keys
+function dependentKeysDidChange(obj, depKey, meta) {
+ if (obj.isDestroying) { return; }
+
+ var seen = DID_SEEN, top = !seen;
+ if (top) { seen = DID_SEEN = {}; }
+ iterDeps(propertyDidChange, obj, depKey, seen, meta);
+ if (top) { DID_SEEN = null; }
+}
+
+function iterDeps(method, obj, depKey, seen, meta) {
+ var guid = guidFor(obj);
+ if (!seen[guid]) seen[guid] = {};
+ if (seen[guid][depKey]) return;
+ seen[guid][depKey] = true;
+
+ var deps = meta.deps;
+ deps = deps && deps[depKey];
+ if (deps) {
+ for(var key in deps) {
+ var desc = meta.descs[key];
+ if (desc && desc._suspended === obj) continue;
+ method(obj, key);
+ }
+ }
+}
+
+function chainsWillChange(obj, keyName, m) {
+ if (!(m.hasOwnProperty('chainWatchers') &&
+ m.chainWatchers[keyName])) {
+ return;
+ }
+
+ var nodes = m.chainWatchers[keyName],
+ events = [],
+ i, l;
+
+ for(i = 0, l = nodes.length; i < l; i++) {
+ nodes[i].willChange(events);
+ }
+
+ for (i = 0, l = events.length; i < l; i += 2) {
+ propertyWillChange(events[i], events[i+1]);
+ }
+}
+
+function chainsDidChange(obj, keyName, m, suppressEvents) {
+ if (!(m && m.hasOwnProperty('chainWatchers') &&
+ m.chainWatchers[keyName])) {
+ return;
+ }
+
+ var nodes = m.chainWatchers[keyName],
+ events = suppressEvents ? null : [],
+ i, l;
+
+ for(i = 0, l = nodes.length; i < l; i++) {
+ nodes[i].didChange(events);
+ }
+
+ if (suppressEvents) {
+ return;
+ }
+
+ for (i = 0, l = events.length; i < l; i += 2) {
+ propertyDidChange(events[i], events[i+1]);
+ }
+}
+
+Ember.overrideChains = function(obj, keyName, m) {
+ chainsDidChange(obj, keyName, m, true);
+};
+
+/**
+ @method beginPropertyChanges
+ @chainable
+ @private
+*/
+function beginPropertyChanges() {
+ deferred++;
+}
+
+Ember.beginPropertyChanges = beginPropertyChanges;
+
+/**
+ @method endPropertyChanges
+ @private
+*/
+function endPropertyChanges() {
+ deferred--;
+ if (deferred<=0) {
+ beforeObserverSet.clear();
+ observerSet.flush();
+ }
+}
+
+Ember.endPropertyChanges = endPropertyChanges;
+
+/**
+ Make a series of property changes together in an
+ exception-safe way.
+
+ ```javascript
+ Ember.changeProperties(function() {
+ obj1.set('foo', mayBlowUpWhenSet);
+ obj2.set('bar', baz);
+ });
+ ```
+
+ @method changeProperties
+ @param {Function} callback
+ @param [binding]
+*/
+Ember.changeProperties = function(cb, binding) {
+ beginPropertyChanges();
+ tryFinally(cb, endPropertyChanges, binding);
+};
+
+function notifyBeforeObservers(obj, keyName) {
+ if (obj.isDestroying) { return; }
+
+ var eventName = keyName + ':before', listeners, diff;
+ if (deferred) {
+ listeners = beforeObserverSet.add(obj, keyName, eventName);
+ diff = listenersDiff(obj, eventName, listeners);
+ sendEvent(obj, eventName, [obj, keyName], diff);
+ } else {
+ sendEvent(obj, eventName, [obj, keyName]);
+ }
+}
+
+function notifyObservers(obj, keyName) {
+ if (obj.isDestroying) { return; }
+
+ var eventName = keyName + ':change', listeners;
+ if (deferred) {
+ listeners = observerSet.add(obj, keyName, eventName);
+ listenersUnion(obj, eventName, listeners);
+ } else {
+ sendEvent(obj, eventName, [obj, keyName]);
+ }
+}
+
+})();
+
+
+
+(function() {
+// META_KEY
+// _getPath
+// propertyWillChange, propertyDidChange
+
+var META_KEY = Ember.META_KEY,
+ MANDATORY_SETTER = Ember.ENV.MANDATORY_SETTER,
+ IS_GLOBAL = /^([A-Z$]|([0-9][A-Z$]))/,
+ getPath = Ember._getPath;
+
+/**
+ Sets the value of a property on an object, respecting computed properties
+ and notifying observers and other listeners of the change. If the
+ property is not defined but the object implements the `setUnknownProperty`
+ method then that will be invoked as well.
+
+ @method set
+ @for Ember
+ @param {Object} obj The object to modify.
+ @param {String} keyName The property key to set
+ @param {Object} value The value to set
+ @return {Object} the passed value.
+*/
+var set = function set(obj, keyName, value, tolerant) {
+ if (typeof obj === 'string') {
+ Ember.assert("Path '" + obj + "' must be global if no obj is given.", IS_GLOBAL.test(obj));
+ value = keyName;
+ keyName = obj;
+ obj = null;
+ }
+
+ Ember.assert("Cannot call set with "+ keyName +" key.", !!keyName);
+
+ if (!obj || keyName.indexOf('.') !== -1) {
+ return setPath(obj, keyName, value, tolerant);
+ }
+
+ Ember.assert("You need to provide an object and key to `set`.", !!obj && keyName !== undefined);
+ Ember.assert('calling set on destroyed object', !obj.isDestroyed);
+
+ var meta = obj[META_KEY], desc = meta && meta.descs[keyName],
+ isUnknown, currentValue;
+ if (desc) {
+ desc.set(obj, keyName, value);
+ } else {
+ isUnknown = 'object' === typeof obj && !(keyName in obj);
+
+ // setUnknownProperty is called if `obj` is an object,
+ // the property does not already exist, and the
+ // `setUnknownProperty` method exists on the object
+ if (isUnknown && 'function' === typeof obj.setUnknownProperty) {
+ obj.setUnknownProperty(keyName, value);
+ } else if (meta && meta.watching[keyName] > 0) {
+ if (MANDATORY_SETTER) {
+ currentValue = meta.values[keyName];
+ } else {
+ currentValue = obj[keyName];
+ }
+ // only trigger a change if the value has changed
+ if (value !== currentValue) {
+ Ember.propertyWillChange(obj, keyName);
+ if (MANDATORY_SETTER) {
+ if ((currentValue === undefined && !(keyName in obj)) || !obj.propertyIsEnumerable(keyName)) {
+ Ember.defineProperty(obj, keyName, null, value); // setup mandatory setter
+ } else {
+ meta.values[keyName] = value;
+ }
+ } else {
+ obj[keyName] = value;
+ }
+ Ember.propertyDidChange(obj, keyName);
+ }
+ } else {
+ obj[keyName] = value;
+ }
+ }
+ return value;
+};
+
+// Currently used only by Ember Data tests
+if (Ember.config.overrideAccessors) {
+ Ember.set = set;
+ Ember.config.overrideAccessors();
+ set = Ember.set;
+}
+
+function setPath(root, path, value, tolerant) {
+ var keyName;
+
+ // get the last part of the path
+ keyName = path.slice(path.lastIndexOf('.') + 1);
+
+ // get the first part of the part
+ path = (path === keyName) ? keyName : path.slice(0, path.length-(keyName.length+1));
+
+ // unless the path is this, look up the first part to
+ // get the root
+ if (path !== 'this') {
+ root = getPath(root, path);
+ }
+
+ if (!keyName || keyName.length === 0) {
+ throw new Ember.Error('Property set failed: You passed an empty path');
+ }
+
+ if (!root) {
+ if (tolerant) { return; }
+ else { throw new Ember.Error('Property set failed: object in path "'+path+'" could not be found or was destroyed.'); }
+ }
+
+ return set(root, keyName, value);
+}
+
+Ember.set = set;
+
+/**
+ Error-tolerant form of `Ember.set`. Will not blow up if any part of the
+ chain is `undefined`, `null`, or destroyed.
+
+ This is primarily used when syncing bindings, which may try to update after
+ an object has been destroyed.
+
+ @method trySet
+ @for Ember
+ @param {Object} obj The object to modify.
+ @param {String} path The property path to set
+ @param {Object} value The value to set
+*/
+Ember.trySet = function(root, path, value) {
+ return set(root, path, value, true);
+};
+
+})();
+
+
+
+(function() {
+/**
+@module ember-metal
+*/
+
+/*
+ JavaScript (before ES6) does not have a Map implementation. Objects,
+ which are often used as dictionaries, may only have Strings as keys.
+
+ Because Ember has a way to get a unique identifier for every object
+ via `Ember.guidFor`, we can implement a performant Map with arbitrary
+ keys. Because it is commonly used in low-level bookkeeping, Map is
+ implemented as a pure JavaScript object for performance.
+
+ This implementation follows the current iteration of the ES6 proposal for
+ maps (http://wiki.ecmascript.org/doku.php?id=harmony:simple_maps_and_sets)
+ with two exceptions. First, because we need our implementation to be pleasant
+ on older browsers, we do not use the `delete` name (using `remove` instead).
+ Second, as we do not have the luxury of in-VM iteration, we implement a
+ forEach method for iteration.
+
+ Map is mocked out to look like an Ember object, so you can do
+ `Ember.Map.create()` for symmetry with other Ember classes.
+*/
+var set = Ember.set,
+ guidFor = Ember.guidFor,
+ indexOf = Ember.ArrayPolyfills.indexOf;
+
+var copy = function(obj) {
+ var output = {};
+
+ for (var prop in obj) {
+ if (obj.hasOwnProperty(prop)) { output[prop] = obj[prop]; }
+ }
+
+ return output;
+};
+
+var copyMap = function(original, newObject) {
+ var keys = original.keys.copy(),
+ values = copy(original.values);
+
+ newObject.keys = keys;
+ newObject.values = values;
+ newObject.length = original.length;
+
+ return newObject;
+};
+
+/**
+ This class is used internally by Ember and Ember Data.
+ Please do not use it at this time. We plan to clean it up
+ and add many tests soon.
+
+ @class OrderedSet
+ @namespace Ember
+ @constructor
+ @private
+*/
+var OrderedSet = Ember.OrderedSet = function() {
+ this.clear();
+};
+
+/**
+ @method create
+ @static
+ @return {Ember.OrderedSet}
+*/
+OrderedSet.create = function() {
+ return new OrderedSet();
+};
+
+
+OrderedSet.prototype = {
+ /**
+ @method clear
+ */
+ clear: function() {
+ this.presenceSet = {};
+ this.list = [];
+ },
+
+ /**
+ @method add
+ @param obj
+ */
+ add: function(obj) {
+ var guid = guidFor(obj),
+ presenceSet = this.presenceSet,
+ list = this.list;
+
+ if (guid in presenceSet) { return; }
+
+ presenceSet[guid] = true;
+ list.push(obj);
+ },
+
+ /**
+ @method remove
+ @param obj
+ */
+ remove: function(obj) {
+ var guid = guidFor(obj),
+ presenceSet = this.presenceSet,
+ list = this.list;
+
+ delete presenceSet[guid];
+
+ var index = indexOf.call(list, obj);
+ if (index > -1) {
+ list.splice(index, 1);
+ }
+ },
+
+ /**
+ @method isEmpty
+ @return {Boolean}
+ */
+ isEmpty: function() {
+ return this.list.length === 0;
+ },
+
+ /**
+ @method has
+ @param obj
+ @return {Boolean}
+ */
+ has: function(obj) {
+ var guid = guidFor(obj),
+ presenceSet = this.presenceSet;
+
+ return guid in presenceSet;
+ },
+
+ /**
+ @method forEach
+ @param {Function} fn
+ @param self
+ */
+ forEach: function(fn, self) {
+ // allow mutation during iteration
+ var list = this.toArray();
+
+ for (var i = 0, j = list.length; i < j; i++) {
+ fn.call(self, list[i]);
+ }
+ },
+
+ /**
+ @method toArray
+ @return {Array}
+ */
+ toArray: function() {
+ return this.list.slice();
+ },
+
+ /**
+ @method copy
+ @return {Ember.OrderedSet}
+ */
+ copy: function() {
+ var set = new OrderedSet();
+
+ set.presenceSet = copy(this.presenceSet);
+ set.list = this.toArray();
+
+ return set;
+ }
+};
+
+/**
+ A Map stores values indexed by keys. Unlike JavaScript's
+ default Objects, the keys of a Map can be any JavaScript
+ object.
+
+ Internally, a Map has two data structures:
+
+ 1. `keys`: an OrderedSet of all of the existing keys
+ 2. `values`: a JavaScript Object indexed by the `Ember.guidFor(key)`
+
+ When a key/value pair is added for the first time, we
+ add the key to the `keys` OrderedSet, and create or
+ replace an entry in `values`. When an entry is deleted,
+ we delete its entry in `keys` and `values`.
+
+ @class Map
+ @namespace Ember
+ @private
+ @constructor
+*/
+var Map = Ember.Map = function() {
+ this.keys = Ember.OrderedSet.create();
+ this.values = {};
+};
+
+/**
+ @method create
+ @static
+*/
+Map.create = function() {
+ return new Map();
+};
+
+Map.prototype = {
+ /**
+ This property will change as the number of objects in the map changes.
+
+ @property length
+ @type number
+ @default 0
+ */
+ length: 0,
+
+
+ /**
+ Retrieve the value associated with a given key.
+
+ @method get
+ @param {*} key
+ @return {*} the value associated with the key, or `undefined`
+ */
+ get: function(key) {
+ var values = this.values,
+ guid = guidFor(key);
+
+ return values[guid];
+ },
+
+ /**
+ Adds a value to the map. If a value for the given key has already been
+ provided, the new value will replace the old value.
+
+ @method set
+ @param {*} key
+ @param {*} value
+ */
+ set: function(key, value) {
+ var keys = this.keys,
+ values = this.values,
+ guid = guidFor(key);
+
+ keys.add(key);
+ values[guid] = value;
+ set(this, 'length', keys.list.length);
+ },
+
+ /**
+ Removes a value from the map for an associated key.
+
+ @method remove
+ @param {*} key
+ @return {Boolean} true if an item was removed, false otherwise
+ */
+ remove: function(key) {
+ // don't use ES6 "delete" because it will be annoying
+ // to use in browsers that are not ES6 friendly;
+ var keys = this.keys,
+ values = this.values,
+ guid = guidFor(key);
+
+ if (values.hasOwnProperty(guid)) {
+ keys.remove(key);
+ delete values[guid];
+ set(this, 'length', keys.list.length);
+ return true;
+ } else {
+ return false;
+ }
+ },
+
+ /**
+ Check whether a key is present.
+
+ @method has
+ @param {*} key
+ @return {Boolean} true if the item was present, false otherwise
+ */
+ has: function(key) {
+ var values = this.values,
+ guid = guidFor(key);
+
+ return values.hasOwnProperty(guid);
+ },
+
+ /**
+ Iterate over all the keys and values. Calls the function once
+ for each key, passing in the key and value, in that order.
+
+ The keys are guaranteed to be iterated over in insertion order.
+
+ @method forEach
+ @param {Function} callback
+ @param {*} self if passed, the `this` value inside the
+ callback. By default, `this` is the map.
+ */
+ forEach: function(callback, self) {
+ var keys = this.keys,
+ values = this.values;
+
+ keys.forEach(function(key) {
+ var guid = guidFor(key);
+ callback.call(self, key, values[guid]);
+ });
+ },
+
+ /**
+ @method copy
+ @return {Ember.Map}
+ */
+ copy: function() {
+ return copyMap(this, new Map());
+ }
+};
+
+/**
+ @class MapWithDefault
+ @namespace Ember
+ @extends Ember.Map
+ @private
+ @constructor
+ @param [options]
+ @param {*} [options.defaultValue]
+*/
+var MapWithDefault = Ember.MapWithDefault = function(options) {
+ Map.call(this);
+ this.defaultValue = options.defaultValue;
+};
+
+/**
+ @method create
+ @static
+ @param [options]
+ @param {*} [options.defaultValue]
+ @return {Ember.MapWithDefault|Ember.Map} If options are passed, returns
+ `Ember.MapWithDefault` otherwise returns `Ember.Map`
+*/
+MapWithDefault.create = function(options) {
+ if (options) {
+ return new MapWithDefault(options);
+ } else {
+ return new Map();
+ }
+};
+
+MapWithDefault.prototype = Ember.create(Map.prototype);
+
+/**
+ Retrieve the value associated with a given key.
+
+ @method get
+ @param {*} key
+ @return {*} the value associated with the key, or the default value
+*/
+MapWithDefault.prototype.get = function(key) {
+ var hasValue = this.has(key);
+
+ if (hasValue) {
+ return Map.prototype.get.call(this, key);
+ } else {
+ var defaultValue = this.defaultValue(key);
+ this.set(key, defaultValue);
+ return defaultValue;
+ }
+};
+
+/**
+ @method copy
+ @return {Ember.MapWithDefault}
+*/
+MapWithDefault.prototype.copy = function() {
+ return copyMap(this, new MapWithDefault({
+ defaultValue: this.defaultValue
+ }));
+};
+
+})();
+
+
+
+(function() {
+function consoleMethod(name) {
+ var consoleObj, logToConsole;
+ if (Ember.imports.console) {
+ consoleObj = Ember.imports.console;
+ } else if (typeof console !== 'undefined') {
+ consoleObj = console;
+ }
+
+ var method = typeof consoleObj === 'object' ? consoleObj[name] : null;
+
+ if (method) {
+ // Older IE doesn't support apply, but Chrome needs it
+ if (typeof method.apply === 'function') {
+ logToConsole = function() {
+ method.apply(consoleObj, arguments);
+ };
+ logToConsole.displayName = 'console.' + name;
+ return logToConsole;
+ } else {
+ return function() {
+ var message = Array.prototype.join.call(arguments, ', ');
+ method(message);
+ };
+ }
+ }
+}
+
+function assertPolyfill(test, message) {
+ if (!test) {
+ try {
+ // attempt to preserve the stack
+ throw new Ember.Error("assertion failed: " + message);
+ } catch(error) {
+ setTimeout(function() {
+ throw error;
+ }, 0);
+ }
+ }
+}
+
+/**
+ Inside Ember-Metal, simply uses the methods from `imports.console`.
+ Override this to provide more robust logging functionality.
+
+ @class Logger
+ @namespace Ember
+*/
+Ember.Logger = {
+ /**
+ Logs the arguments to the console.
+ You can pass as many arguments as you want and they will be joined together with a space.
+
+ ```javascript
+ var foo = 1;
+ Ember.Logger.log('log value of foo:', foo);
+ // "log value of foo: 1" will be printed to the console
+ ```
+
+ @method log
+ @for Ember.Logger
+ @param {*} arguments
+ */
+ log: consoleMethod('log') || Ember.K,
+
+ /**
+ Prints the arguments to the console with a warning icon.
+ You can pass as many arguments as you want and they will be joined together with a space.
+
+ ```javascript
+ Ember.Logger.warn('Something happened!');
+ // "Something happened!" will be printed to the console with a warning icon.
+ ```
+
+ @method warn
+ @for Ember.Logger
+ @param {*} arguments
+ */
+ warn: consoleMethod('warn') || Ember.K,
+
+ /**
+ Prints the arguments to the console with an error icon, red text and a stack trace.
+ You can pass as many arguments as you want and they will be joined together with a space.
+
+ ```javascript
+ Ember.Logger.error('Danger! Danger!');
+ // "Danger! Danger!" will be printed to the console in red text.
+ ```
+
+ @method error
+ @for Ember.Logger
+ @param {*} arguments
+ */
+ error: consoleMethod('error') || Ember.K,
+
+ /**
+ Logs the arguments to the console.
+ You can pass as many arguments as you want and they will be joined together with a space.
+
+ ```javascript
+ var foo = 1;
+ Ember.Logger.info('log value of foo:', foo);
+ // "log value of foo: 1" will be printed to the console
+ ```
+
+ @method info
+ @for Ember.Logger
+ @param {*} arguments
+ */
+ info: consoleMethod('info') || Ember.K,
+
+ /**
+ Logs the arguments to the console in blue text.
+ You can pass as many arguments as you want and they will be joined together with a space.
+
+ ```javascript
+ var foo = 1;
+ Ember.Logger.debug('log value of foo:', foo);
+ // "log value of foo: 1" will be printed to the console
+ ```
+
+ @method debug
+ @for Ember.Logger
+ @param {*} arguments
+ */
+ debug: consoleMethod('debug') || consoleMethod('info') || Ember.K,
+
+ /**
+ If the value passed into `Ember.Logger.assert` is not truthy it will throw an error with a stack trace.
+
+ ```javascript
+ Ember.Logger.assert(true); // undefined
+ Ember.Logger.assert(true === false); // Throws an Assertion failed error.
+ ```
+
+ @method assert
+ @for Ember.Logger
+ @param {Boolean} bool Value to test
+ */
+ assert: consoleMethod('assert') || assertPolyfill
+};
+
+
+})();
+
+
+
+(function() {
+/**
+@module ember-metal
+*/
+
+var META_KEY = Ember.META_KEY,
+ metaFor = Ember.meta,
+ objectDefineProperty = Ember.platform.defineProperty;
+
+var MANDATORY_SETTER = Ember.ENV.MANDATORY_SETTER;
+
+// ..........................................................
+// DESCRIPTOR
+//
+
+/**
+ Objects of this type can implement an interface to respond to requests to
+ get and set. The default implementation handles simple properties.
+
+ You generally won't need to create or subclass this directly.
+
+ @class Descriptor
+ @namespace Ember
+ @private
+ @constructor
+*/
+Ember.Descriptor = function() {};
+
+// ..........................................................
+// DEFINING PROPERTIES API
+//
+
+var MANDATORY_SETTER_FUNCTION = Ember.MANDATORY_SETTER_FUNCTION = function(value) {
+ Ember.assert("You must use Ember.set() to access this property (of " + this + ")", false);
+};
+
+var DEFAULT_GETTER_FUNCTION = Ember.DEFAULT_GETTER_FUNCTION = function(name) {
+ return function() {
+ var meta = this[META_KEY];
+ return meta && meta.values[name];
+ };
+};
+
+/**
+ NOTE: This is a low-level method used by other parts of the API. You almost
+ never want to call this method directly. Instead you should use
+ `Ember.mixin()` to define new properties.
+
+ Defines a property on an object. This method works much like the ES5
+ `Object.defineProperty()` method except that it can also accept computed
+ properties and other special descriptors.
+
+ Normally this method takes only three parameters. However if you pass an
+ instance of `Ember.Descriptor` as the third param then you can pass an
+ optional value as the fourth parameter. This is often more efficient than
+ creating new descriptor hashes for each property.
+
+ ## Examples
+
+ ```javascript
+ // ES5 compatible mode
+ Ember.defineProperty(contact, 'firstName', {
+ writable: true,
+ configurable: false,
+ enumerable: true,
+ value: 'Charles'
+ });
+
+ // define a simple property
+ Ember.defineProperty(contact, 'lastName', undefined, 'Jolley');
+
+ // define a computed property
+ Ember.defineProperty(contact, 'fullName', Ember.computed(function() {
+ return this.firstName+' '+this.lastName;
+ }).property('firstName', 'lastName'));
+ ```
+
+ @private
+ @method defineProperty
+ @for Ember
+ @param {Object} obj the object to define this property on. This may be a prototype.
+ @param {String} keyName the name of the property
+ @param {Ember.Descriptor} [desc] an instance of `Ember.Descriptor` (typically a
+ computed property) or an ES5 descriptor.
+ You must provide this or `data` but not both.
+ @param {*} [data] something other than a descriptor, that will
+ become the explicit value of this property.
+*/
+Ember.defineProperty = function(obj, keyName, desc, data, meta) {
+ var descs, existingDesc, watching, value;
+
+ if (!meta) meta = metaFor(obj);
+ descs = meta.descs;
+ existingDesc = meta.descs[keyName];
+ watching = meta.watching[keyName] > 0;
+
+ if (existingDesc instanceof Ember.Descriptor) {
+ existingDesc.teardown(obj, keyName);
+ }
+
+ if (desc instanceof Ember.Descriptor) {
+ value = desc;
+
+ descs[keyName] = desc;
+ if (MANDATORY_SETTER && watching) {
+ objectDefineProperty(obj, keyName, {
+ configurable: true,
+ enumerable: true,
+ writable: true,
+ value: undefined // make enumerable
+ });
+ } else {
+ obj[keyName] = undefined; // make enumerable
+ }
+
+ if (Ember.FEATURES.isEnabled('composable-computed-properties')) {
+ if (desc.func && desc._dependentCPs) {
+ addImplicitCPs(obj, desc._dependentCPs, meta);
+ }
+ }
+ } else {
+ descs[keyName] = undefined; // shadow descriptor in proto
+ if (desc == null) {
+ value = data;
+
+ if (MANDATORY_SETTER && watching) {
+ meta.values[keyName] = data;
+ objectDefineProperty(obj, keyName, {
+ configurable: true,
+ enumerable: true,
+ set: MANDATORY_SETTER_FUNCTION,
+ get: DEFAULT_GETTER_FUNCTION(keyName)
+ });
+ } else {
+ obj[keyName] = data;
+ }
+ } else {
+ value = desc;
+
+ // compatibility with ES5
+ objectDefineProperty(obj, keyName, desc);
+ }
+ }
+
+ // if key is being watched, override chains that
+ // were initialized with the prototype
+ if (watching) { Ember.overrideChains(obj, keyName, meta); }
+
+ // The `value` passed to the `didDefineProperty` hook is
+ // either the descriptor or data, whichever was passed.
+ if (obj.didDefineProperty) { obj.didDefineProperty(obj, keyName, value); }
+
+ return this;
+};
+
+if (Ember.FEATURES.isEnabled('composable-computed-properties')) {
+ var addImplicitCPs = function defineImplicitCPs(obj, implicitCPs, meta) {
+ var cp, key, length = implicitCPs.length;
+
+ for (var i=0; i<length; ++i) {
+ cp = implicitCPs[i];
+ key = cp.implicitCPKey;
+
+ Ember.defineProperty(obj, key, cp, undefined, meta);
+
+ if (cp._dependentCPs) {
+ addImplicitCPs(obj, cp._dependentCPs, meta);
+ }
+ }
+ };
+}
+
+})();
+
+
+
+(function() {
+var get = Ember.get;
+
+/**
+ To get multiple properties at once, call `Ember.getProperties`
+ with an object followed by a list of strings or an array:
+
+ ```javascript
+ Ember.getProperties(record, 'firstName', 'lastName', 'zipCode');
+ // { firstName: 'John', lastName: 'Doe', zipCode: '10011' }
+ ```
+
+ is equivalent to:
+
+ ```javascript
+ Ember.getProperties(record, ['firstName', 'lastName', 'zipCode']);
+ // { firstName: 'John', lastName: 'Doe', zipCode: '10011' }
+ ```
+
+ @method getProperties
+ @param obj
+ @param {String...|Array} list of keys to get
+ @return {Hash}
+*/
+Ember.getProperties = function(obj) {
+ var ret = {},
+ propertyNames = arguments,
+ i = 1;
+
+ if (arguments.length === 2 && Ember.typeOf(arguments[1]) === 'array') {
+ i = 0;
+ propertyNames = arguments[1];
+ }
+ for(var len = propertyNames.length; i < len; i++) {
+ ret[propertyNames[i]] = get(obj, propertyNames[i]);
+ }
+ return ret;
+};
+
+})();
+
+
+
+(function() {
+var changeProperties = Ember.changeProperties,
+ set = Ember.set;
+
+/**
+ Set a list of properties on an object. These properties are set inside
+ a single `beginPropertyChanges` and `endPropertyChanges` batch, so
+ observers will be buffered.
+
+ ```javascript
+ anObject.setProperties({
+ firstName: "Stanley",
+ lastName: "Stuart",
+ age: "21"
+ })
+ ```
+
+ @method setProperties
+ @param self
+ @param {Object} hash
+ @return self
+*/
+Ember.setProperties = function(self, hash) {
+ changeProperties(function() {
+ for(var prop in hash) {
+ if (hash.hasOwnProperty(prop)) { set(self, prop, hash[prop]); }
+ }
+ });
+ return self;
+};
+
+})();
+
+
+
+(function() {
+var metaFor = Ember.meta, // utils.js
+ typeOf = Ember.typeOf, // utils.js
+ MANDATORY_SETTER = Ember.ENV.MANDATORY_SETTER,
+ o_defineProperty = Ember.platform.defineProperty;
+
+Ember.watchKey = function(obj, keyName, meta) {
+ // can't watch length on Array - it is special...
+ if (keyName === 'length' && typeOf(obj) === 'array') { return; }
+
+ var m = meta || metaFor(obj), watching = m.watching;
+
+ // activate watching first time
+ if (!watching[keyName]) {
+ watching[keyName] = 1;
+
+ if ('function' === typeof obj.willWatchProperty) {
+ obj.willWatchProperty(keyName);
+ }
+
+ if (MANDATORY_SETTER && keyName in obj) {
+ m.values[keyName] = obj[keyName];
+ o_defineProperty(obj, keyName, {
+ configurable: true,
+ enumerable: obj.propertyIsEnumerable(keyName),
+ set: Ember.MANDATORY_SETTER_FUNCTION,
+ get: Ember.DEFAULT_GETTER_FUNCTION(keyName)
+ });
+ }
+ } else {
+ watching[keyName] = (watching[keyName] || 0) + 1;
+ }
+};
+
+
+Ember.unwatchKey = function(obj, keyName, meta) {
+ var m = meta || metaFor(obj), watching = m.watching;
+
+ if (watching[keyName] === 1) {
+ watching[keyName] = 0;
+
+ if ('function' === typeof obj.didUnwatchProperty) {
+ obj.didUnwatchProperty(keyName);
+ }
+
+ if (MANDATORY_SETTER && keyName in obj) {
+ o_defineProperty(obj, keyName, {
+ configurable: true,
+ enumerable: obj.propertyIsEnumerable(keyName),
+ set: function(val) {
+ // redefine to set as enumerable
+ o_defineProperty(obj, keyName, {
+ configurable: true,
+ writable: true,
+ enumerable: true,
+ value: val
+ });
+ delete m.values[keyName];
+ },
+ get: Ember.DEFAULT_GETTER_FUNCTION(keyName)
+ });
+ }
+ } else if (watching[keyName] > 1) {
+ watching[keyName]--;
+ }
+};
+
+})();
+
+
+
+(function() {
+var metaFor = Ember.meta, // utils.js
+ get = Ember.get, // property_get.js
+ normalizeTuple = Ember.normalizeTuple, // property_get.js
+ forEach = Ember.ArrayPolyfills.forEach, // array.js
+ warn = Ember.warn,
+ watchKey = Ember.watchKey,
+ unwatchKey = Ember.unwatchKey,
+ FIRST_KEY = /^([^\.\*]+)/,
+ META_KEY = Ember.META_KEY;
+
+function firstKey(path) {
+ return path.match(FIRST_KEY)[0];
+}
+
+var pendingQueue = [];
+
+// attempts to add the pendingQueue chains again. If some of them end up
+// back in the queue and reschedule is true, schedules a timeout to try
+// again.
+Ember.flushPendingChains = function() {
+ if (pendingQueue.length === 0) { return; } // nothing to do
+
+ var queue = pendingQueue;
+ pendingQueue = [];
+
+ forEach.call(queue, function(q) { q[0].add(q[1]); });
+
+ warn('Watching an undefined global, Ember expects watched globals to be setup by the time the run loop is flushed, check for typos', pendingQueue.length === 0);
+};
+
+
+function addChainWatcher(obj, keyName, node) {
+ if (!obj || ('object' !== typeof obj)) { return; } // nothing to do
+
+ var m = metaFor(obj), nodes = m.chainWatchers;
+
+ if (!m.hasOwnProperty('chainWatchers')) {
+ nodes = m.chainWatchers = {};
+ }
+
+ if (!nodes[keyName]) { nodes[keyName] = []; }
+ nodes[keyName].push(node);
+ watchKey(obj, keyName, m);
+}
+
+var removeChainWatcher = Ember.removeChainWatcher = function(obj, keyName, node) {
+ if (!obj || 'object' !== typeof obj) { return; } // nothing to do
+
+ var m = obj[META_KEY];
+ if (m && !m.hasOwnProperty('chainWatchers')) { return; } // nothing to do
+
+ var nodes = m && m.chainWatchers;
+
+ if (nodes && nodes[keyName]) {
+ nodes = nodes[keyName];
+ for (var i = 0, l = nodes.length; i < l; i++) {
+ if (nodes[i] === node) { nodes.splice(i, 1); }
+ }
+ }
+ unwatchKey(obj, keyName, m);
+};
+
+// A ChainNode watches a single key on an object. If you provide a starting
+// value for the key then the node won't actually watch it. For a root node
+// pass null for parent and key and object for value.
+var ChainNode = Ember._ChainNode = function(parent, key, value) {
+ this._parent = parent;
+ this._key = key;
+
+ // _watching is true when calling get(this._parent, this._key) will
+ // return the value of this node.
+ //
+ // It is false for the root of a chain (because we have no parent)
+ // and for global paths (because the parent node is the object with
+ // the observer on it)
+ this._watching = value===undefined;
+
+ this._value = value;
+ this._paths = {};
+ if (this._watching) {
+ this._object = parent.value();
+ if (this._object) { addChainWatcher(this._object, this._key, this); }
+ }
+
+ // Special-case: the EachProxy relies on immediate evaluation to
+ // establish its observers.
+ //
+ // TODO: Replace this with an efficient callback that the EachProxy
+ // can implement.
+ if (this._parent && this._parent._key === '@each') {
+ this.value();
+ }
+};
+
+var ChainNodePrototype = ChainNode.prototype;
+
+function lazyGet(obj, key) {
+ if (!obj) return undefined;
+
+ var meta = obj[META_KEY];
+ // check if object meant only to be a prototype
+ if (meta && meta.proto === obj) return undefined;
+
+ if (key === "@each") return get(obj, key);
+
+ // if a CP only return cached value
+ var desc = meta && meta.descs[key];
+ if (desc && desc._cacheable) {
+ if (key in meta.cache) {
+ return meta.cache[key];
+ } else {
+ return undefined;
+ }
+ }
+
+ return get(obj, key);
+}
+
+ChainNodePrototype.value = function() {
+ if (this._value === undefined && this._watching) {
+ var obj = this._parent.value();
+ this._value = lazyGet(obj, this._key);
+ }
+ return this._value;
+};
+
+ChainNodePrototype.destroy = function() {
+ if (this._watching) {
+ var obj = this._object;
+ if (obj) { removeChainWatcher(obj, this._key, this); }
+ this._watching = false; // so future calls do nothing
+ }
+};
+
+// copies a top level object only
+ChainNodePrototype.copy = function(obj) {
+ var ret = new ChainNode(null, null, obj),
+ paths = this._paths, path;
+ for (path in paths) {
+ if (paths[path] <= 0) { continue; } // this check will also catch non-number vals.
+ ret.add(path);
+ }
+ return ret;
+};
+
+// called on the root node of a chain to setup watchers on the specified
+// path.
+ChainNodePrototype.add = function(path) {
+ var obj, tuple, key, src, paths;
+
+ paths = this._paths;
+ paths[path] = (paths[path] || 0) + 1;
+
+ obj = this.value();
+ tuple = normalizeTuple(obj, path);
+
+ // the path was a local path
+ if (tuple[0] && tuple[0] === obj) {
+ path = tuple[1];
+ key = firstKey(path);
+ path = path.slice(key.length+1);
+
+ // global path, but object does not exist yet.
+ // put into a queue and try to connect later.
+ } else if (!tuple[0]) {
+ pendingQueue.push([this, path]);
+ tuple.length = 0;
+ return;
+
+ // global path, and object already exists
+ } else {
+ src = tuple[0];
+ key = path.slice(0, 0-(tuple[1].length+1));
+ path = tuple[1];
+ }
+
+ tuple.length = 0;
+ this.chain(key, path, src);
+};
+
+// called on the root node of a chain to teardown watcher on the specified
+// path
+ChainNodePrototype.remove = function(path) {
+ var obj, tuple, key, src, paths;
+
+ paths = this._paths;
+ if (paths[path] > 0) { paths[path]--; }
+
+ obj = this.value();
+ tuple = normalizeTuple(obj, path);
+ if (tuple[0] === obj) {
+ path = tuple[1];
+ key = firstKey(path);
+ path = path.slice(key.length+1);
+ } else {
+ src = tuple[0];
+ key = path.slice(0, 0-(tuple[1].length+1));
+ path = tuple[1];
+ }
+
+ tuple.length = 0;
+ this.unchain(key, path);
+};
+
+ChainNodePrototype.count = 0;
+
+ChainNodePrototype.chain = function(key, path, src) {
+ var chains = this._chains, node;
+ if (!chains) { chains = this._chains = {}; }
+
+ node = chains[key];
+ if (!node) { node = chains[key] = new ChainNode(this, key, src); }
+ node.count++; // count chains...
+
+ // chain rest of path if there is one
+ if (path && path.length>0) {
+ key = firstKey(path);
+ path = path.slice(key.length+1);
+ node.chain(key, path); // NOTE: no src means it will observe changes...
+ }
+};
+
+ChainNodePrototype.unchain = function(key, path) {
+ var chains = this._chains, node = chains[key];
+
+ // unchain rest of path first...
+ if (path && path.length>1) {
+ key = firstKey(path);
+ path = path.slice(key.length+1);
+ node.unchain(key, path);
+ }
+
+ // delete node if needed.
+ node.count--;
+ if (node.count<=0) {
+ delete chains[node._key];
+ node.destroy();
+ }
+
+};
+
+ChainNodePrototype.willChange = function(events) {
+ var chains = this._chains;
+ if (chains) {
+ for(var key in chains) {
+ if (!chains.hasOwnProperty(key)) { continue; }
+ chains[key].willChange(events);
+ }
+ }
+
+ if (this._parent) { this._parent.chainWillChange(this, this._key, 1, events); }
+};
+
+ChainNodePrototype.chainWillChange = function(chain, path, depth, events) {
+ if (this._key) { path = this._key + '.' + path; }
+
+ if (this._parent) {
+ this._parent.chainWillChange(this, path, depth+1, events);
+ } else {
+ if (depth > 1) {
+ events.push(this.value(), path);
+ }
+ path = 'this.' + path;
+ if (this._paths[path] > 0) {
+ events.push(this.value(), path);
+ }
+ }
+};
+
+ChainNodePrototype.chainDidChange = function(chain, path, depth, events) {
+ if (this._key) { path = this._key + '.' + path; }
+ if (this._parent) {
+ this._parent.chainDidChange(this, path, depth+1, events);
+ } else {
+ if (depth > 1) {
+ events.push(this.value(), path);
+ }
+ path = 'this.' + path;
+ if (this._paths[path] > 0) {
+ events.push(this.value(), path);
+ }
+ }
+};
+
+ChainNodePrototype.didChange = function(events) {
+ // invalidate my own value first.
+ if (this._watching) {
+ var obj = this._parent.value();
+ if (obj !== this._object) {
+ removeChainWatcher(this._object, this._key, this);
+ this._object = obj;
+ addChainWatcher(obj, this._key, this);
+ }
+ this._value = undefined;
+
+ // Special-case: the EachProxy relies on immediate evaluation to
+ // establish its observers.
+ if (this._parent && this._parent._key === '@each')
+ this.value();
+ }
+
+ // then notify chains...
+ var chains = this._chains;
+ if (chains) {
+ for(var key in chains) {
+ if (!chains.hasOwnProperty(key)) { continue; }
+ chains[key].didChange(events);
+ }
+ }
+
+ // if no events are passed in then we only care about the above wiring update
+ if (events === null) { return; }
+
+ // and finally tell parent about my path changing...
+ if (this._parent) { this._parent.chainDidChange(this, this._key, 1, events); }
+};
+
+Ember.finishChains = function(obj) {
+ // We only create meta if we really have to
+ var m = obj[META_KEY], chains = m && m.chains;
+ if (chains) {
+ if (chains.value() !== obj) {
+ metaFor(obj).chains = chains = chains.copy(obj);
+ } else {
+ chains.didChange(null);
+ }
+ }
+};
+
+})();
+
+
+
+(function() {
+/**
+ @module ember-metal
+ */
+
+var forEach = Ember.EnumerableUtils.forEach,
+BRACE_EXPANSION = /^((?:[^\.]*\.)*)\{(.*)\}$/;
+
+/**
+ Expands `pattern`, invoking `callback` for each expansion.
+
+ The only pattern supported is brace-expansion, anything else will be passed
+ once to `callback` directly. Brace expansion can only appear at the end of a
+ pattern, for example as the last item in a chain.
+
+ Example
+ ```js
+ function echo(arg){ console.log(arg); }
+
+ Ember.expandProperties('foo.bar', echo); //=> 'foo.bar'
+ Ember.expandProperties('{foo,bar}', echo); //=> 'foo', 'bar'
+ Ember.expandProperties('foo.{bar,baz}', echo); //=> 'foo.bar', 'foo.baz'
+ Ember.expandProperties('{foo,bar}.baz', echo); //=> '{foo,bar}.baz'
+ ```
+
+ @method
+ @private
+ @param {string} pattern The property pattern to expand.
+ @param {function} callback The callback to invoke. It is invoked once per
+ expansion, and is passed the expansion.
+ */
+Ember.expandProperties = function (pattern, callback) {
+ var match, prefix, list;
+
+ if (match = BRACE_EXPANSION.exec(pattern)) {
+ prefix = match[1];
+ list = match[2];
+
+ forEach(list.split(','), function (suffix) {
+ callback(prefix + suffix);
+ });
+ } else {
+ callback(pattern);
+ }
+};
+
+})();
+
+
+
+(function() {
+var metaFor = Ember.meta, // utils.js
+ typeOf = Ember.typeOf, // utils.js
+ ChainNode = Ember._ChainNode; // chains.js
+
+// get the chains for the current object. If the current object has
+// chains inherited from the proto they will be cloned and reconfigured for
+// the current object.
+function chainsFor(obj, meta) {
+ var m = meta || metaFor(obj), ret = m.chains;
+ if (!ret) {
+ ret = m.chains = new ChainNode(null, null, obj);
+ } else if (ret.value() !== obj) {
+ ret = m.chains = ret.copy(obj);
+ }
+ return ret;
+}
+
+Ember.watchPath = function(obj, keyPath, meta) {
+ // can't watch length on Array - it is special...
+ if (keyPath === 'length' && typeOf(obj) === 'array') { return; }
+
+ var m = meta || metaFor(obj), watching = m.watching;
+
+ if (!watching[keyPath]) { // activate watching first time
+ watching[keyPath] = 1;
+ chainsFor(obj, m).add(keyPath);
+ } else {
+ watching[keyPath] = (watching[keyPath] || 0) + 1;
+ }
+};
+
+Ember.unwatchPath = function(obj, keyPath, meta) {
+ var m = meta || metaFor(obj), watching = m.watching;
+
+ if (watching[keyPath] === 1) {
+ watching[keyPath] = 0;
+ chainsFor(obj, m).remove(keyPath);
+ } else if (watching[keyPath] > 1) {
+ watching[keyPath]--;
+ }
+};
+})();
+
+
+
+(function() {
+/**
+@module ember-metal
+*/
+
+var metaFor = Ember.meta, // utils.js
+ GUID_KEY = Ember.GUID_KEY, // utils.js
+ META_KEY = Ember.META_KEY, // utils.js
+ removeChainWatcher = Ember.removeChainWatcher,
+ watchKey = Ember.watchKey, // watch_key.js
+ unwatchKey = Ember.unwatchKey,
+ watchPath = Ember.watchPath, // watch_path.js
+ unwatchPath = Ember.unwatchPath,
+ typeOf = Ember.typeOf, // utils.js
+ generateGuid = Ember.generateGuid,
+ IS_PATH = /[\.\*]/;
+
+// returns true if the passed path is just a keyName
+function isKeyName(path) {
+ return path==='*' || !IS_PATH.test(path);
+}
+
+/**
+ Starts watching a property on an object. Whenever the property changes,
+ invokes `Ember.propertyWillChange` and `Ember.propertyDidChange`. This is the
+ primitive used by observers and dependent keys; usually you will never call
+ this method directly but instead use higher level methods like
+ `Ember.addObserver()`
+
+ @private
+ @method watch
+ @for Ember
+ @param obj
+ @param {String} keyName
+*/
+Ember.watch = function(obj, _keyPath, m) {
+ // can't watch length on Array - it is special...
+ if (_keyPath === 'length' && typeOf(obj) === 'array') { return; }
+
+ if (isKeyName(_keyPath)) {
+ watchKey(obj, _keyPath, m);
+ } else {
+ watchPath(obj, _keyPath, m);
+ }
+};
+
+Ember.isWatching = function isWatching(obj, key) {
+ var meta = obj[META_KEY];
+ return (meta && meta.watching[key]) > 0;
+};
+
+Ember.watch.flushPending = Ember.flushPendingChains;
+
+Ember.unwatch = function(obj, _keyPath, m) {
+ // can't watch length on Array - it is special...
+ if (_keyPath === 'length' && typeOf(obj) === 'array') { return; }
+
+ if (isKeyName(_keyPath)) {
+ unwatchKey(obj, _keyPath, m);
+ } else {
+ unwatchPath(obj, _keyPath, m);
+ }
+};
+
+/**
+ Call on an object when you first beget it from another object. This will
+ setup any chained watchers on the object instance as needed. This method is
+ safe to call multiple times.
+
+ @private
+ @method rewatch
+ @for Ember
+ @param obj
+*/
+Ember.rewatch = function(obj) {
+ var m = obj[META_KEY], chains = m && m.chains;
+
+ // make sure the object has its own guid.
+ if (GUID_KEY in obj && !obj.hasOwnProperty(GUID_KEY)) {
+ generateGuid(obj);
+ }
+
+ // make sure any chained watchers update.
+ if (chains && chains.value() !== obj) {
+ m.chains = chains.copy(obj);
+ }
+};
+
+var NODE_STACK = [];
+
+/**
+ Tears down the meta on an object so that it can be garbage collected.
+ Multiple calls will have no effect.
+
+ @method destroy
+ @for Ember
+ @param {Object} obj the object to destroy
+ @return {void}
+*/
+Ember.destroy = function (obj) {
+ var meta = obj[META_KEY], node, nodes, key, nodeObject;
+ if (meta) {
+ obj[META_KEY] = null;
+ // remove chainWatchers to remove circular references that would prevent GC
+ node = meta.chains;
+ if (node) {
+ NODE_STACK.push(node);
+ // process tree
+ while (NODE_STACK.length > 0) {
+ node = NODE_STACK.pop();
+ // push children
+ nodes = node._chains;
+ if (nodes) {
+ for (key in nodes) {
+ if (nodes.hasOwnProperty(key)) {
+ NODE_STACK.push(nodes[key]);
+ }
+ }
+ }
+ // remove chainWatcher in node object
+ if (node._watching) {
+ nodeObject = node._object;
+ if (nodeObject) {
+ removeChainWatcher(nodeObject, node._key, node);
+ }
+ }
+ }
+ }
+ }
+};
+
+})();
+
+
+
+(function() {
+/**
+@module ember-metal
+*/
+
+Ember.warn("The CP_DEFAULT_CACHEABLE flag has been removed and computed properties are always cached by default. Use `volatile` if you don't want caching.", Ember.ENV.CP_DEFAULT_CACHEABLE !== false);
+
+
+var get = Ember.get,
+ set = Ember.set,
+ metaFor = Ember.meta,
+ a_slice = [].slice,
+ o_create = Ember.create,
+ META_KEY = Ember.META_KEY,
+ watch = Ember.watch,
+ unwatch = Ember.unwatch;
+
+var expandProperties = Ember.expandProperties;
+
+
+// ..........................................................
+// DEPENDENT KEYS
+//
+
+// data structure:
+// meta.deps = {
+// 'depKey': {
+// 'keyName': count,
+// }
+// }
+
+/*
+ This function returns a map of unique dependencies for a
+ given object and key.
+*/
+function keysForDep(depsMeta, depKey) {
+ var keys = depsMeta[depKey];
+ if (!keys) {
+ // if there are no dependencies yet for a the given key
+ // create a new empty list of dependencies for the key
+ keys = depsMeta[depKey] = {};
+ } else if (!depsMeta.hasOwnProperty(depKey)) {
+ // otherwise if the dependency list is inherited from
+ // a superclass, clone the hash
+ keys = depsMeta[depKey] = o_create(keys);
+ }
+ return keys;
+}
+
+function metaForDeps(meta) {
+ return keysForDep(meta, 'deps');
+}
+
+function addDependentKeys(desc, obj, keyName, meta) {
+ // the descriptor has a list of dependent keys, so
+ // add all of its dependent keys.
+ var depKeys = desc._dependentKeys, depsMeta, idx, len, depKey, keys;
+ if (!depKeys) return;
+
+ depsMeta = metaForDeps(meta);
+
+ for(idx = 0, len = depKeys.length; idx < len; idx++) {
+ depKey = depKeys[idx];
+ // Lookup keys meta for depKey
+ keys = keysForDep(depsMeta, depKey);
+ // Increment the number of times depKey depends on keyName.
+ keys[keyName] = (keys[keyName] || 0) + 1;
+ // Watch the depKey
+ watch(obj, depKey, meta);
+ }
+}
+
+function removeDependentKeys(desc, obj, keyName, meta) {
+ // the descriptor has a list of dependent keys, so
+ // add all of its dependent keys.
+ var depKeys = desc._dependentKeys, depsMeta, idx, len, depKey, keys;
+ if (!depKeys) return;
+
+ depsMeta = metaForDeps(meta);
+
+ for(idx = 0, len = depKeys.length; idx < len; idx++) {
+ depKey = depKeys[idx];
+ // Lookup keys meta for depKey
+ keys = keysForDep(depsMeta, depKey);
+ // Increment the number of times depKey depends on keyName.
+ keys[keyName] = (keys[keyName] || 0) - 1;
+ // Watch the depKey
+ unwatch(obj, depKey, meta);
+ }
+}
+
+// ..........................................................
+// COMPUTED PROPERTY
+//
+
+/**
+ A computed property transforms an objects function into a property.
+
+ By default the function backing the computed property will only be called
+ once and the result will be cached. You can specify various properties
+ that your computed property is dependent on. This will force the cached
+ result to be recomputed if the dependencies are modified.
+
+ In the following example we declare a computed property (by calling
+ `.property()` on the fullName function) and setup the properties
+ dependencies (depending on firstName and lastName). The fullName function
+ will be called once (regardless of how many times it is accessed) as long
+ as it's dependencies have not been changed. Once firstName or lastName are updated
+ any future calls (or anything bound) to fullName will incorporate the new
+ values.
+
+ ```javascript
+ Person = Ember.Object.extend({
+ // these will be supplied by `create`
+ firstName: null,
+ lastName: null,
+
+ fullName: function() {
+ var firstName = this.get('firstName');
+ var lastName = this.get('lastName');
+
+ return firstName + ' ' + lastName;
+ }.property('firstName', 'lastName')
+ });
+
+ var tom = Person.create({
+ firstName: "Tom",
+ lastName: "Dale"
+ });
+
+ tom.get('fullName') // "Tom Dale"
+ ```
+
+ You can also define what Ember should do when setting a computed property.
+ If you try to set a computed property, it will be invoked with the key and
+ value you want to set it to. You can also accept the previous value as the
+ third parameter.
+
+ ```javascript
+
+ Person = Ember.Object.extend({
+ // these will be supplied by `create`
+ firstName: null,
+ lastName: null,
+
+ fullName: function(key, value, oldValue) {
+ // getter
+ if (arguments.length === 1) {
+ var firstName = this.get('firstName');
+ var lastName = this.get('lastName');
+
+ return firstName + ' ' + lastName;
+
+ // setter
+ } else {
+ var name = value.split(" ");
+
+ this.set('firstName', name[0]);
+ this.set('lastName', name[1]);
+
+ return value;
+ }
+ }.property('firstName', 'lastName')
+ });
+
+ var person = Person.create();
+ person.set('fullName', "Peter Wagenet");
+ person.get('firstName') // Peter
+ person.get('lastName') // Wagenet
+ ```
+
+ @class ComputedProperty
+ @namespace Ember
+ @extends Ember.Descriptor
+ @constructor
+*/
+function ComputedProperty(func, opts) {
+ this.func = func;
+ if (Ember.FEATURES.isEnabled('composable-computed-properties')) {
+ setDependentKeys(this, opts && opts.dependentKeys);
+ } else {
+ this._dependentKeys = opts && opts.dependentKeys;
+ }
+
+ this._cacheable = (opts && opts.cacheable !== undefined) ? opts.cacheable : true;
+ this._readOnly = opts && (opts.readOnly !== undefined || !!opts.readOnly);
+}
+
+Ember.ComputedProperty = ComputedProperty;
+
+ComputedProperty.prototype = new Ember.Descriptor();
+
+var ComputedPropertyPrototype = ComputedProperty.prototype;
+ComputedPropertyPrototype._dependentKeys = undefined;
+ComputedPropertyPrototype._suspended = undefined;
+ComputedPropertyPrototype._meta = undefined;
+
+if (Ember.FEATURES.isEnabled('composable-computed-properties')) {
+ ComputedPropertyPrototype._dependentCPs = undefined;
+ ComputedPropertyPrototype.implicitCPKey = undefined;
+
+ ComputedPropertyPrototype.toString = function() {
+ if (this.implicitCPKey) {
+ return this.implicitCPKey;
+ }
+ return Ember.Descriptor.prototype.toString.apply(this, arguments);
+ };
+}
+
+/**
+ Properties are cacheable by default. Computed property will automatically
+ cache the return value of your function until one of the dependent keys changes.
+
+ Call `volatile()` to set it into non-cached mode. When in this mode
+ the computed property will not automatically cache the return value.
+
+ However, if a property is properly observable, there is no reason to disable
+ caching.
+
+ @method cacheable
+ @param {Boolean} aFlag optional set to `false` to disable caching
+ @return {Ember.ComputedProperty} this
+ @chainable
+*/
+ComputedPropertyPrototype.cacheable = function(aFlag) {
+ this._cacheable = aFlag !== false;
+ return this;
+};
+
+/**
+ Call on a computed property to set it into non-cached mode. When in this
+ mode the computed property will not automatically cache the return value.
+
+ ```javascript
+ MyApp.outsideService = Ember.Object.extend({
+ value: function() {
+ return OutsideService.getValue();
+ }.property().volatile()
+ }).create();
+ ```
+
+ @method volatile
+ @return {Ember.ComputedProperty} this
+ @chainable
+*/
+ComputedPropertyPrototype.volatile = function() {
+ return this.cacheable(false);
+};
+
+/**
+ Call on a computed property to set it into read-only mode. When in this
+ mode the computed property will throw an error when set.
+
+ ```javascript
+ MyApp.Person = Ember.Object.extend({
+ guid: function() {
+ return 'guid-guid-guid';
+ }.property().readOnly()
+ });
+
+ MyApp.person = MyApp.Person.create();
+
+ MyApp.person.set('guid', 'new-guid'); // will throw an exception
+ ```
+
+ @method readOnly
+ @return {Ember.ComputedProperty} this
+ @chainable
+*/
+ComputedPropertyPrototype.readOnly = function(readOnly) {
+ this._readOnly = readOnly === undefined || !!readOnly;
+ return this;
+};
+
+/**
+ Sets the dependent keys on this computed property. Pass any number of
+ arguments containing key paths that this computed property depends on.
+
+ ```javascript
+ MyApp.President = Ember.Object.extend({
+ fullName: Ember.computed(function() {
+ return this.get('firstName') + ' ' + this.get('lastName');
+
+ // Tell Ember that this computed property depends on firstName
+ // and lastName
+ }).property('firstName', 'lastName')
+ });
+
+ MyApp.president = MyApp.President.create({
+ firstName: 'Barack',
+ lastName: 'Obama',
+ });
+ MyApp.president.get('fullName'); // Barack Obama
+ ```
+
+ @method property
+ @param {String} path* zero or more property paths
+ @return {Ember.ComputedProperty} this
+ @chainable
+*/
+ComputedPropertyPrototype.property = function() {
+ var args;
+
+ var addArg = function (property) {
+ args.push(property);
+ };
+
+ args = [];
+ for (var i = 0, l = arguments.length; i < l; i++) {
+ expandProperties(arguments[i], addArg);
+ }
+
+ if (Ember.FEATURES.isEnabled('composable-computed-properties')) {
+ setDependentKeys(this, args);
+ } else {
+ this._dependentKeys = args;
+ }
+
+ return this;
+};
+
+/**
+ In some cases, you may want to annotate computed properties with additional
+ metadata about how they function or what values they operate on. For example,
+ computed property functions may close over variables that are then no longer
+ available for introspection.
+
+ You can pass a hash of these values to a computed property like this:
+
+ ```
+ person: function() {
+ var personId = this.get('personId');
+ return App.Person.create({ id: personId });
+ }.property().meta({ type: App.Person })
+ ```
+
+ The hash that you pass to the `meta()` function will be saved on the
+ computed property descriptor under the `_meta` key. Ember runtime
+ exposes a public API for retrieving these values from classes,
+ via the `metaForProperty()` function.
+
+ @method meta
+ @param {Hash} meta
+ @chainable
+*/
+
+ComputedPropertyPrototype.meta = function(meta) {
+ if (arguments.length === 0) {
+ return this._meta || {};
+ } else {
+ this._meta = meta;
+ return this;
+ }
+};
+
+/* impl descriptor API */
+ComputedPropertyPrototype.didChange = function(obj, keyName) {
+ // _suspended is set via a CP.set to ensure we don't clear
+ // the cached value set by the setter
+ if (this._cacheable && this._suspended !== obj) {
+ var meta = metaFor(obj);
+ if (keyName in meta.cache) {
+ delete meta.cache[keyName];
+ removeDependentKeys(this, obj, keyName, meta);
+ }
+ }
+};
+
+function finishChains(chainNodes)
+{
+ for (var i=0, l=chainNodes.length; i<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.
+*/
+ComputedPropertyPrototype.get = function(obj, keyName) {
+ var ret, cache, meta, chainNodes;
+ if (this._cacheable) {
+ meta = metaFor(obj);
+ cache = meta.cache;
+ if (keyName in cache) { return cache[keyName]; }
+ ret = cache[keyName] = this.func.call(obj, keyName);
+ chainNodes = meta.chainWatchers && meta.chainWatchers[keyName];
+ if (chainNodes) { finishChains(chainNodes); }
+ addDependentKeys(this, obj, keyName, meta);
+ } else {
+ ret = this.func.call(obj, keyName);
+ }
+ return ret;
+};
+
+/**
+ 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.
+*/
+ComputedPropertyPrototype.set = function(obj, keyName, value) {
+ var cacheable = this._cacheable,
+ func = this.func,
+ meta = metaFor(obj, cacheable),
+ watched = meta.watching[keyName],
+ oldSuspended = this._suspended,
+ hadCachedValue = false,
+ cache = meta.cache,
+ funcArgLength, cachedValue, ret;
+
+ if (this._readOnly) {
+ throw new Ember.Error('Cannot set read-only property "' + keyName + '" on object: ' + Ember.inspect(obj));
+ }
+
+ this._suspended = obj;
+
+ try {
+
+ if (cacheable && cache.hasOwnProperty(keyName)) {
+ cachedValue = cache[keyName];
+ hadCachedValue = true;
+ }
+
+ // 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);
+
+ // For backwards-compatibility with computed properties
+ // that check for arguments.length === 2 to determine if
+ // they are being get or set, only pass the old cached
+ // value if the computed property opts into a third
+ // argument.
+ if (funcArgLength === 3) {
+ ret = func.call(obj, keyName, value, cachedValue);
+ } else if (funcArgLength === 2) {
+ ret = func.call(obj, keyName, value);
+ } else {
+ Ember.defineProperty(obj, keyName, null, cachedValue);
+ Ember.set(obj, keyName, value);
+ return;
+ }
+
+ if (hadCachedValue && cachedValue === ret) { return; }
+
+ if (watched) { Ember.propertyWillChange(obj, keyName); }
+
+ if (hadCachedValue) {
+ delete cache[keyName];
+ }
+
+ if (cacheable) {
+ if (!hadCachedValue) {
+ addDependentKeys(this, obj, keyName, meta);
+ }
+ cache[keyName] = ret;
+ }
+
+ if (watched) { Ember.propertyDidChange(obj, keyName); }
+ } finally {
+ this._suspended = oldSuspended;
+ }
+ return ret;
+};
+
+/* called before property is overridden */
+ComputedPropertyPrototype.teardown = function(obj, keyName) {
+ var meta = metaFor(obj);
+
+ if (keyName in meta.cache) {
+ removeDependentKeys(this, obj, keyName, meta);
+ }
+
+ if (this._cacheable) { delete meta.cache[keyName]; }
+
+ return null; // no value to restore
+};
+
+
+/**
+ This helper returns a new property descriptor that wraps the passed
+ computed property function. You can use this helper to define properties
+ with mixins or via `Ember.defineProperty()`.
+
+ The function you pass will be used to both get and set property values.
+ The function should accept two parameters, key and value. If value is not
+ undefined you should set the value first. In either case return the
+ current value of the property.
+ @method computed
+ @for Ember
+ @param {Function} func The computed property function.
+ @return {Ember.ComputedProperty} property descriptor instance
+*/
+Ember.computed = function(func) {
+ var args;
+
+ if (arguments.length > 1) {
+ args = a_slice.call(arguments, 0, -1);
+ func = a_slice.call(arguments, -1)[0];
+ }
+
+ if (typeof func !== "function") {
+ throw new Ember.Error("Computed Property declared without a property function");
+ }
+
+ var cp = new ComputedProperty(func);
+
+ if (args) {
+ cp.property.apply(cp, args);
+ }
+
+ return cp;
+};
+
+/**
+ Returns the cached value for a property, if one exists.
+ This can be useful for peeking at the value of a computed
+ property that is generated lazily, without accidentally causing
+ it to be created.
+
+ @method cacheFor
+ @for Ember
+ @param {Object} obj the object whose property you want to check
+ @param {String} key the name of the property whose cached value you want
+ to return
+ @return {Object} the cached value
+*/
+Ember.cacheFor = function cacheFor(obj, key) {
+ var meta = obj[META_KEY],
+ cache = meta && meta.cache;
+
+ if (cache && key in cache) {
+ return cache[key];
+ }
+};
+
+function getProperties(self, propertyNames) {
+ var ret = {};
+ for(var i = 0; i < propertyNames.length; i++) {
+ ret[propertyNames[i]] = get(self, propertyNames[i]);
+ }
+ return ret;
+}
+
+var registerComputed, registerComputedWithProperties;
+
+if (Ember.FEATURES.isEnabled('composable-computed-properties')) {
+ var guidFor = Ember.guidFor,
+ map = Ember.EnumerableUtils.map,
+ filter = Ember.EnumerableUtils.filter,
+ typeOf = Ember.typeOf;
+
+ var implicitKey = function (cp) {
+ return [guidFor(cp)].concat(cp._dependentKeys).join('_').replace(/\./g, '_DOT_');
+ };
+
+ var normalizeDependentKey = function (key) {
+ if (key instanceof Ember.ComputedProperty) {
+ return implicitKey(key);
+ } else {
+ return key;
+ }
+ };
+
+ var normalizeDependentKeys = function (keys) {
+ return map(keys, function (key) {
+ return normalizeDependentKey(key);
+ });
+ };
+
+ var selectDependentCPs = function (keys) {
+ return filter(keys, function (key) {
+ return key instanceof Ember.ComputedProperty;
+ });
+ };
+
+ var setDependentKeys = function(cp, dependentKeys) {
+ if (dependentKeys) {
+ cp._dependentKeys = normalizeDependentKeys(dependentKeys);
+ cp._dependentCPs = selectDependentCPs(dependentKeys);
+ } else {
+ cp._dependentKeys = cp._dependentCPs = [];
+ }
+ cp.implicitCPKey = implicitKey(cp);
+ };
+ // expose `normalizeDependentKey[s]` so user CP macros can easily support
+ // composition
+ Ember.computed.normalizeDependentKey = normalizeDependentKey;
+ Ember.computed.normalizeDependentKeys = normalizeDependentKeys;
+
+ registerComputed = function (name, macro) {
+ Ember.computed[name] = function(dependentKey) {
+ var args = normalizeDependentKeys(a_slice.call(arguments));
+ return Ember.computed(dependentKey, function() {
+ return macro.apply(this, args);
+ });
+ };
+ };
+}
+
+if (Ember.FEATURES.isEnabled('composable-computed-properties')) {
+ registerComputedWithProperties = function(name, macro) {
+ Ember.computed[name] = function() {
+ var args = a_slice.call(arguments);
+ var properties = normalizeDependentKeys(args);
+
+ var computed = Ember.computed(function() {
+ return macro.apply(this, [getProperties(this, properties)]);
+ });
+
+ return computed.property.apply(computed, args);
+ };
+ };
+} else {
+ registerComputed = function (name, macro) {
+ Ember.computed[name] = function(dependentKey) {
+ var args = a_slice.call(arguments);
+ return Ember.computed(dependentKey, function() {
+ return macro.apply(this, args);
+ });
+ };
+ };
+
+ registerComputedWithProperties = function(name, macro) {
+ Ember.computed[name] = function() {
+ var properties = a_slice.call(arguments);
+
+ var computed = Ember.computed(function() {
+ return macro.apply(this, [getProperties(this, properties)]);
+ });
+
+ return computed.property.apply(computed, properties);
+ };
+ };
+}
+
+
+if (Ember.FEATURES.isEnabled('composable-computed-properties')) {
+ Ember.computed.literal = function (value) {
+ return Ember.computed(function () {
+ return value;
+ });
+ };
+}
+
+
+ /**
+ A computed property that returns true if the value of the dependent
+ property is null, an empty string, empty array, or empty function.
+
+ Note: When using `Ember.computed.empty` to watch an array make sure to
+ use the `array.[]` syntax so the computed can subscribe to transitions
+ from empty to non-empty states.
+
+ Example
+
+ ```javascript
+ var ToDoList = Ember.Object.extend({
+ done: Ember.computed.empty('todos.[]') // detect array changes
+ });
+ var todoList = ToDoList.create({todos: ['Unit Test', 'Documentation', 'Release']});
+ todoList.get('done'); // false
+ todoList.get('todos').clear(); // []
+ todoList.get('done'); // true
+ ```
+
+ @method computed.empty
+ @for Ember
+ @param {String} dependentKey
+ @return {Ember.ComputedProperty} computed property which negate
+ the original value for property
+ */
+ registerComputed('empty', function(dependentKey) {
+ return Ember.isEmpty(get(this, dependentKey));
+ });
+
+
+/**
+ A computed property that returns true if the value of the dependent
+ property is NOT null, an empty string, empty array, or empty function.
+
+ Note: When using `Ember.computed.notEmpty` to watch an array make sure to
+ use the `array.[]` syntax so the computed can subscribe to transitions
+ from empty to non-empty states.
+
+ Example
+
+ ```javascript
+ var Hamster = Ember.Object.extend({
+ hasStuff: Ember.computed.notEmpty('backpack.[]')
+ });
+ var hamster = Hamster.create({backpack: ['Food', 'Sleeping Bag', 'Tent']});
+ hamster.get('hasStuff'); // true
+ hamster.get('backpack').clear(); // []
+ hamster.get('hasStuff'); // false
+ ```
+
+ @method computed.notEmpty
+ @for Ember
+ @param {String} dependentKey
+ @return {Ember.ComputedProperty} computed property which returns true if
+ original value for property is not empty.
+*/
+registerComputed('notEmpty', function(dependentKey) {
+ return !Ember.isEmpty(get(this, dependentKey));
+});
+
+/**
+ A computed property that returns true if the value of the dependent
+ property is null or undefined. This avoids errors from JSLint complaining
+ about use of ==, which can be technically confusing.
+
+ Example
+
+ ```javascript
+ var Hamster = Ember.Object.extend({
+ isHungry: Ember.computed.none('food')
+ });
+ var hamster = Hamster.create();
+ hamster.get('isHungry'); // true
+ hamster.set('food', 'Banana');
+ hamster.get('isHungry'); // false
+ hamster.set('food', null);
+ hamster.get('isHungry'); // true
+ ```
+
+ @method computed.none
+ @for Ember
+ @param {String} dependentKey
+ @return {Ember.ComputedProperty} computed property which
+ returns true if original value for property is null or undefined.
+*/
+registerComputed('none', function(dependentKey) {
+ return Ember.isNone(get(this, dependentKey));
+});
+
+/**
+ A computed property that returns the inverse boolean value
+ of the original value for the dependent property.
+
+ Example
+
+ ```javascript
+ var User = Ember.Object.extend({
+ isAnonymous: Ember.computed.not('loggedIn')
+ });
+ var user = User.create({loggedIn: false});
+ user.get('isAnonymous'); // true
+ user.set('loggedIn', true);
+ user.get('isAnonymous'); // false
+ ```
+
+ @method computed.not
+ @for Ember
+ @param {String} dependentKey
+ @return {Ember.ComputedProperty} computed property which returns
+ inverse of the original value for property
+*/
+registerComputed('not', function(dependentKey) {
+ return !get(this, dependentKey);
+});
+
+/**
+ A computed property that converts the provided dependent property
+ into a boolean value.
+
+ ```javascript
+ var Hamster = Ember.Object.extend({
+ hasBananas: Ember.computed.bool('numBananas')
+ });
+ var hamster = Hamster.create();
+ hamster.get('hasBananas'); // false
+ hamster.set('numBananas', 0);
+ hamster.get('hasBananas'); // false
+ hamster.set('numBananas', 1);
+ hamster.get('hasBananas'); // true
+ hamster.set('numBananas', null);
+ hamster.get('hasBananas'); // false
+ ```
+
+ @method computed.bool
+ @for Ember
+ @param {String} dependentKey
+ @return {Ember.ComputedProperty} computed property which converts
+ to boolean the original value for property
+*/
+registerComputed('bool', function(dependentKey) {
+ return !!get(this, dependentKey);
+});
+
+/**
+ A computed property which matches the original value for the
+ dependent property against a given RegExp, returning `true`
+ if they values matches the RegExp and `false` if it does not.
+
+ Example
+
+ ```javascript
+ var User = Ember.Object.extend({
+ hasValidEmail: Ember.computed.match('email', /^.+@.+\..+$/)
+ });
+ var user = User.create({loggedIn: false});
+ user.get('hasValidEmail'); // false
+ user.set('email', '');
+ user.get('hasValidEmail'); // false
+ user.set('email', 'ember_hamster(a)example.com');
+ user.get('hasValidEmail'); // true
+ ```
+
+ @method computed.match
+ @for Ember
+ @param {String} dependentKey
+ @param {RegExp} regexp
+ @return {Ember.ComputedProperty} computed property which match
+ the original value for property against a given RegExp
+*/
+registerComputed('match', function(dependentKey, regexp) {
+ var value = get(this, dependentKey);
+ return typeof value === 'string' ? regexp.test(value) : false;
+});
+
+/**
+ A computed property that returns true if the provided dependent property
+ is equal to the given value.
+
+ Example
+
+ ```javascript
+ var Hamster = Ember.Object.extend({
+ napTime: Ember.computed.equal('state', 'sleepy')
+ });
+ var hamster = Hamster.create();
+ hamster.get('napTime'); // false
+ hamster.set('state', 'sleepy');
+ hamster.get('napTime'); // true
+ hamster.set('state', 'hungry');
+ hamster.get('napTime'); // false
+ ```
+
+ @method computed.equal
+ @for Ember
+ @param {String} dependentKey
+ @param {String|Number|Object} value
+ @return {Ember.ComputedProperty} computed property which returns true if
+ the original value for property is equal to the given value.
+*/
+registerComputed('equal', function(dependentKey, value) {
+ return get(this, dependentKey) === value;
+});
+
+/**
+ A computed property that returns true if the provied dependent property
+ is greater than the provided value.
+
+ Example
+
+ ```javascript
+ var Hamster = Ember.Object.extend({
+ hasTooManyBananas: Ember.computed.gt('numBananas', 10)
+ });
+ var hamster = Hamster.create();
+ hamster.get('hasTooManyBananas'); // false
+ hamster.set('numBananas', 3);
+ hamster.get('hasTooManyBananas'); // false
+ hamster.set('numBananas', 11);
+ hamster.get('hasTooManyBananas'); // true
+ ```
+
+ @method computed.gt
+ @for Ember
+ @param {String} dependentKey
+ @param {Number} value
+ @return {Ember.ComputedProperty} computed property which returns true if
+ the original value for property is greater then given value.
+*/
+registerComputed('gt', function(dependentKey, value) {
+ return get(this, dependentKey) > value;
+});
+
+/**
+ A computed property that returns true if the provided dependent property
+ is greater than or equal to the provided value.
+
+ Example
+
+ ```javascript
+ var Hamster = Ember.Object.extend({
+ hasTooManyBananas: Ember.computed.gte('numBananas', 10)
+ });
+ var hamster = Hamster.create();
+ hamster.get('hasTooManyBananas'); // false
+ hamster.set('numBananas', 3);
+ hamster.get('hasTooManyBananas'); // false
+ hamster.set('numBananas', 10);
+ hamster.get('hasTooManyBananas'); // true
+ ```
+
+ @method computed.gte
+ @for Ember
+ @param {String} dependentKey
+ @param {Number} value
+ @return {Ember.ComputedProperty} computed property which returns true if
+ the original value for property is greater or equal then given value.
+*/
+registerComputed('gte', function(dependentKey, value) {
+ return get(this, dependentKey) >= value;
+});
+
+/**
+ A computed property that returns true if the provided dependent property
+ is less than the provided value.
+
+ Example
+
+ ```javascript
+ var Hamster = Ember.Object.extend({
+ needsMoreBananas: Ember.computed.lt('numBananas', 3)
+ });
+ var hamster = Hamster.create();
+ hamster.get('needsMoreBananas'); // true
+ hamster.set('numBananas', 3);
+ hamster.get('needsMoreBananas'); // false
+ hamster.set('numBananas', 2);
+ hamster.get('needsMoreBananas'); // true
+ ```
+
+ @method computed.lt
+ @for Ember
+ @param {String} dependentKey
+ @param {Number} value
+ @return {Ember.ComputedProperty} computed property which returns true if
+ the original value for property is less then given value.
+*/
+registerComputed('lt', function(dependentKey, value) {
+ return get(this, dependentKey) < value;
+});
+
+/**
+ A computed property that returns true if the provided dependent property
+ is less than or equal to the provided value.
+
+ Example
+
+ ```javascript
+ var Hamster = Ember.Object.extend({
+ needsMoreBananas: Ember.computed.lte('numBananas', 3)
+ });
+ var hamster = Hamster.create();
+ hamster.get('needsMoreBananas'); // true
+ hamster.set('numBananas', 5);
+ hamster.get('needsMoreBananas'); // false
+ hamster.set('numBananas', 3);
+ hamster.get('needsMoreBananas'); // true
+ ```
+
+ @method computed.lte
+ @for Ember
+ @param {String} dependentKey
+ @param {Number} value
+ @return {Ember.ComputedProperty} computed property which returns true if
+ the original value for property is less or equal then given value.
+*/
+registerComputed('lte', function(dependentKey, value) {
+ return get(this, dependentKey) <= value;
+});
+
+/**
+ A computed property that performs a logical `and` on the
+ original values for the provided dependent properties.
+
+ Example
+
+ ```javascript
+ var Hamster = Ember.Object.extend({
+ readyForCamp: Ember.computed.and('hasTent', 'hasBackpack')
+ });
+ var hamster = Hamster.create();
+ hamster.get('readyForCamp'); // false
+ hamster.set('hasTent', true);
+ hamster.get('readyForCamp'); // false
+ hamster.set('hasBackpack', true);
+ hamster.get('readyForCamp'); // true
+ ```
+
+ @method computed.and
+ @for Ember
+ @param {String} dependentKey*
+ @return {Ember.ComputedProperty} computed property which performs
+ a logical `and` on the values of all the original values for properties.
+*/
+registerComputedWithProperties('and', function(properties) {
+ for (var key in properties) {
+ if (properties.hasOwnProperty(key) && !properties[key]) {
+ return false;
+ }
+ }
+ return true;
+});
+
+/**
+ A computed property which performs a logical `or` on the
+ original values for the provided dependent properties.
+
+ Example
+
+ ```javascript
+ var Hamster = Ember.Object.extend({
+ readyForRain: Ember.computed.or('hasJacket', 'hasUmbrella')
+ });
+ var hamster = Hamster.create();
+ hamster.get('readyForRain'); // false
+ hamster.set('hasJacket', true);
+ hamster.get('readyForRain'); // true
+ ```
+
+ @method computed.or
+ @for Ember
+ @param {String} dependentKey*
+ @return {Ember.ComputedProperty} computed property which performs
+ a logical `or` on the values of all the original values for properties.
+*/
+registerComputedWithProperties('or', function(properties) {
+ for (var key in properties) {
+ if (properties.hasOwnProperty(key) && properties[key]) {
+ return true;
+ }
+ }
+ return false;
+});
+
+/**
+ A computed property that returns the first truthy value
+ from a list of dependent properties.
+
+ Example
+
+ ```javascript
+ var Hamster = Ember.Object.extend({
+ hasClothes: Ember.computed.any('hat', 'shirt')
+ });
+ var hamster = Hamster.create();
+ hamster.get('hasClothes'); // null
+ hamster.set('shirt', 'Hawaiian Shirt');
+ hamster.get('hasClothes'); // 'Hawaiian Shirt'
+ ```
+
+ @method computed.any
+ @for Ember
+ @param {String} dependentKey*
+ @return {Ember.ComputedProperty} computed property which returns
+ the first truthy value of given list of properties.
+*/
+registerComputedWithProperties('any', function(properties) {
+ for (var key in properties) {
+ if (properties.hasOwnProperty(key) && properties[key]) {
+ return properties[key];
+ }
+ }
+ return null;
+});
+
+/**
+ A computed property that returns the array of values
+ for the provided dependent properties.
+
+ Example
+
+ ```javascript
+ var Hamster = Ember.Object.extend({
+ clothes: Ember.computed.collect('hat', 'shirt')
+ });
+ var hamster = Hamster.create();
+ hamster.get('clothes'); // [null, null]
+ hamster.set('hat', 'Camp Hat');
+ hamster.set('shirt', 'Camp Shirt');
+ hamster.get('clothes'); // ['Camp Hat', 'Camp Shirt']
+ ```
+
+ @method computed.collect
+ @for Ember
+ @param {String} dependentKey*
+ @return {Ember.ComputedProperty} computed property which maps
+ values of all passed properties in to an array.
+*/
+registerComputedWithProperties('collect', function(properties) {
+ var res = [];
+ for (var key in properties) {
+ if (properties.hasOwnProperty(key)) {
+ if (Ember.isNone(properties[key])) {
+ res.push(null);
+ } else {
+ res.push(properties[key]);
+ }
+ }
+ }
+ return res;
+});
+
+/**
+ Creates a new property that is an alias for another property
+ on an object. Calls to `get` or `set` this property behave as
+ though they were called on the original property.
+
+ ```javascript
+ Person = Ember.Object.extend({
+ name: 'Alex Matchneer',
+ nomen: Ember.computed.alias('name')
+ });
+
+ alex = Person.create();
+ alex.get('nomen'); // 'Alex Matchneer'
+ alex.get('name'); // 'Alex Matchneer'
+
+ alex.set('nomen', '@machty');
+ alex.get('name'); // '@machty'
+ ```
+ @method computed.alias
+ @for Ember
+ @param {String} dependentKey
+ @return {Ember.ComputedProperty} computed property which creates an
+ alias to the original value for property.
+*/
+Ember.computed.alias = function(dependentKey) {
+ return Ember.computed(dependentKey, function(key, value) {
+ if (arguments.length > 1) {
+ set(this, dependentKey, value);
+ return value;
+ } else {
+ return get(this, dependentKey);
+ }
+ });
+};
+
+/**
+ Where `computed.alias` aliases `get` and `set`, and allows for bidirectional
+ data flow, `computed.oneWay` only provides an aliased `get`. The `set` will
+ not mutate the upstream property, rather causes the current property to
+ become the value set. This causes the downstream property to permentantly
+ diverge from the upstream property.
+
+ Example
+
+ ```javascript
+ User = Ember.Object.extend({
+ firstName: null,
+ lastName: null,
+ nickName: Ember.computed.oneWay('firstName')
+ });
+
+ user = User.create({
+ firstName: 'Teddy',
+ lastName: 'Zeenny'
+ });
+
+ user.get('nickName');
+ # 'Teddy'
+
+ user.set('nickName', 'TeddyBear');
+ # 'TeddyBear'
+
+ user.get('firstName');
+ # 'Teddy'
+ ```
+
+ @method computed.oneWay
+ @for Ember
+ @param {String} dependentKey
+ @return {Ember.ComputedProperty} computed property which creates a
+ one way computed property to the original value for property.
+*/
+Ember.computed.oneWay = function(dependentKey) {
+ return Ember.computed(dependentKey, function() {
+ return get(this, dependentKey);
+ });
+};
+
+
+
+/**
+ Where `computed.oneWay` provides oneWay bindings, `computed.readOnly` provides
+ a readOnly one way binding. Very often when using `computed.oneWay` one does
+ not also want changes to propogate back up, as they will replace the value.
+
+ This prevents the reverse flow, and also throws an exception when it occurs.
+
+ Example
+
+ ```javascript
+ User = Ember.Object.extend({
+ firstName: null,
+ lastName: null,
+ nickName: Ember.computed.readOnly('firstName')
+ });
+
+ user = User.create({
+ firstName: 'Teddy',
+ lastName: 'Zeenny'
+ });
+
+ user.get('nickName');
+ # 'Teddy'
+
+ user.set('nickName', 'TeddyBear');
+ # throws Exception
+ # throw new Ember.Error('Cannot Set: nickName on: <User:ember27288>' );`
+
+ user.get('firstName');
+ # 'Teddy'
+ ```
+
+ @method computed.readOnly
+ @for Ember
+ @param {String} dependentKey
+ @return {Ember.ComputedProperty} computed property which creates a
+ one way computed property to the original value for property.
+*/
+Ember.computed.readOnly = function(dependentKey) {
+ return Ember.computed(dependentKey, function() {
+ return get(this, dependentKey);
+ }).readOnly();
+};
+
+/**
+ A computed property that acts like a standard getter and setter,
+ but returns the value at the provided `defaultPath` if the
+ property itself has not been set to a value
+
+ Example
+
+ ```javascript
+ var Hamster = Ember.Object.extend({
+ wishList: Ember.computed.defaultTo('favoriteFood')
+ });
+ var hamster = Hamster.create({favoriteFood: 'Banana'});
+ hamster.get('wishList'); // 'Banana'
+ hamster.set('wishList', 'More Unit Tests');
+ hamster.get('wishList'); // 'More Unit Tests'
+ hamster.get('favoriteFood'); // 'Banana'
+ ```
+
+ @method computed.defaultTo
+ @for Ember
+ @param {String} defaultPath
+ @return {Ember.ComputedProperty} computed property which acts like
+ a standard getter and setter, but defaults to the value from `defaultPath`.
+*/
+Ember.computed.defaultTo = function(defaultPath) {
+ return Ember.computed(function(key, newValue, cachedValue) {
+ if (arguments.length === 1) {
+ return cachedValue != null ? cachedValue : get(this, defaultPath);
+ }
+ return newValue != null ? newValue : get(this, defaultPath);
+ });
+};
+
+
+})();
+
+
+
+(function() {
+// Ember.tryFinally
+/**
+@module ember-metal
+*/
+
+var AFTER_OBSERVERS = ':change',
+ BEFORE_OBSERVERS = ':before';
+
+function changeEvent(keyName) {
+ return keyName+AFTER_OBSERVERS;
+}
+
+function beforeEvent(keyName) {
+ return keyName+BEFORE_OBSERVERS;
+}
+
+/**
+ @method addObserver
+ @for Ember
+ @param obj
+ @param {String} path
+ @param {Object|Function} targetOrMethod
+ @param {Function|String} [method]
+*/
+Ember.addObserver = function(obj, _path, target, method) {
+ Ember.addListener(obj, changeEvent(_path), target, method);
+ Ember.watch(obj, _path);
+
+ return this;
+};
+
+Ember.observersFor = function(obj, path) {
+ return Ember.listenersFor(obj, changeEvent(path));
+};
+
+/**
+ @method removeObserver
+ @for Ember
+ @param obj
+ @param {String} path
+ @param {Object|Function} targetOrMethod
+ @param {Function|String} [method]
+*/
+Ember.removeObserver = function(obj, _path, target, method) {
+ Ember.unwatch(obj, _path);
+ Ember.removeListener(obj, changeEvent(_path), target, method);
+
+ return this;
+};
+
+/**
+ @method addBeforeObserver
+ @for Ember
+ @param obj
+ @param {String} path
+ @param {Object|Function} targetOrMethod
+ @param {Function|String} [method]
+*/
+Ember.addBeforeObserver = function(obj, _path, target, method) {
+ Ember.addListener(obj, beforeEvent(_path), target, method);
+ Ember.watch(obj, _path);
+
+ return this;
+};
+
+// Suspend observer during callback.
+//
+// This should only be used by the target of the observer
+// while it is setting the observed path.
+Ember._suspendBeforeObserver = function(obj, path, target, method, callback) {
+ return Ember._suspendListener(obj, beforeEvent(path), target, method, callback);
+};
+
+Ember._suspendObserver = function(obj, path, target, method, callback) {
+ return Ember._suspendListener(obj, changeEvent(path), target, method, callback);
+};
+
+var map = Ember.ArrayPolyfills.map;
+
+Ember._suspendBeforeObservers = function(obj, paths, target, method, callback) {
+ var events = map.call(paths, beforeEvent);
+ return Ember._suspendListeners(obj, events, target, method, callback);
+};
+
+Ember._suspendObservers = function(obj, paths, target, method, callback) {
+ var events = map.call(paths, changeEvent);
+ return Ember._suspendListeners(obj, events, target, method, callback);
+};
+
+Ember.beforeObserversFor = function(obj, path) {
+ return Ember.listenersFor(obj, beforeEvent(path));
+};
+
+/**
+ @method removeBeforeObserver
+ @for Ember
+ @param obj
+ @param {String} path
+ @param {Object|Function} targetOrMethod
+ @param {Function|String} [method]
+*/
+Ember.removeBeforeObserver = function(obj, _path, target, method) {
+ Ember.unwatch(obj, _path);
+ Ember.removeListener(obj, beforeEvent(_path), target, method);
+
+ return this;
+};
+
+})();
+
+
+
+(function() {
+define("backburner/queue",
+ ["exports"],
+ function(__exports__) {
+ "use strict";
+ function Queue(daq, name, options) {
+ this.daq = daq;
+ this.name = name;
+ this.options = options;
+ this._queue = [];
+ }
+
+ Queue.prototype = {
+ daq: null,
+ name: null,
+ options: null,
+ _queue: null,
+
+ push: function(target, method, args, stack) {
+ var queue = this._queue;
+ queue.push(target, method, args, stack);
+ return {queue: this, target: target, method: method};
+ },
+
+ pushUnique: function(target, method, args, stack) {
+ var queue = this._queue, currentTarget, currentMethod, i, l;
+
+ for (i = 0, l = queue.length; i < l; i += 4) {
+ currentTarget = queue[i];
+ currentMethod = queue[i+1];
+
+ if (currentTarget === target && currentMethod === method) {
+ queue[i+2] = args; // replace args
+ queue[i+3] = stack; // replace stack
+ return {queue: this, target: target, method: method}; // TODO: test this code path
+ }
+ }
+
+ this._queue.push(target, method, args, stack);
+ return {queue: this, target: target, method: method};
+ },
+
+ // TODO: remove me, only being used for Ember.run.sync
+ flush: function() {
+ var queue = this._queue,
+ options = this.options,
+ before = options && options.before,
+ after = options && options.after,
+ target, method, args, stack, i, l = queue.length;
+
+ if (l && before) { before(); }
+ for (i = 0; i < l; i += 4) {
+ target = queue[i];
+ method = queue[i+1];
+ args = queue[i+2];
+ stack = queue[i+3]; // Debugging assistance
+
+ // TODO: error handling
+ if (args && args.length > 0) {
+ method.apply(target, args);
+ } else {
+ method.call(target);
+ }
+ }
+ if (l && after) { after(); }
+
+ // check if new items have been added
+ if (queue.length > l) {
+ this._queue = queue.slice(l);
+ this.flush();
+ } else {
+ this._queue.length = 0;
+ }
+ },
+
+ cancel: function(actionToCancel) {
+ var queue = this._queue, currentTarget, currentMethod, i, l;
+
+ for (i = 0, l = queue.length; i < l; i += 4) {
+ currentTarget = queue[i];
+ currentMethod = queue[i+1];
+
+ if (currentTarget === actionToCancel.target && currentMethod === actionToCancel.method) {
+ queue.splice(i, 4);
+ return true;
+ }
+ }
+
+ // if not found in current queue
+ // could be in the queue that is being flushed
+ queue = this._queueBeingFlushed;
+ if (!queue) {
+ return;
+ }
+ for (i = 0, l = queue.length; i < l; i += 4) {
+ currentTarget = queue[i];
+ currentMethod = queue[i+1];
+
+ if (currentTarget === actionToCancel.target && currentMethod === actionToCancel.method) {
+ // don't mess with array during flush
+ // just nullify the method
+ queue[i+1] = null;
+ return true;
+ }
+ }
+ }
+ };
+
+ __exports__.Queue = Queue;
+ });
+
+define("backburner/deferred_action_queues",
+ ["backburner/queue","exports"],
+ function(__dependency1__, __exports__) {
+ "use strict";
+ var Queue = __dependency1__.Queue;
+
+ function DeferredActionQueues(queueNames, options) {
+ var queues = this.queues = {};
+ this.queueNames = queueNames = queueNames || [];
+
+ var queueName;
+ for (var i = 0, l = queueNames.length; i < l; i++) {
+ queueName = queueNames[i];
+ queues[queueName] = new Queue(this, queueName, options[queueName]);
+ }
+ }
+
+ DeferredActionQueues.prototype = {
+ queueNames: null,
+ queues: null,
+
+ schedule: function(queueName, target, method, args, onceFlag, stack) {
+ var queues = this.queues,
+ queue = queues[queueName];
+
+ if (!queue) { throw new Error("You attempted to schedule an action in a queue (" + queueName + ") that doesn't exist"); }
+
+ if (onceFlag) {
+ return queue.pushUnique(target, method, args, stack);
+ } else {
+ return queue.push(target, method, args, stack);
+ }
+ },
+
+ flush: function() {
+ var queues = this.queues,
+ queueNames = this.queueNames,
+ queueName, queue, queueItems, priorQueueNameIndex,
+ queueNameIndex = 0, numberOfQueues = queueNames.length;
+
+ outerloop:
+ while (queueNameIndex < numberOfQueues) {
+ queueName = queueNames[queueNameIndex];
+ queue = queues[queueName];
+ queueItems = queue._queueBeingFlushed = queue._queue.slice();
+ queue._queue = [];
+
+ var options = queue.options,
+ before = options && options.before,
+ after = options && options.after,
+ target, method, args, stack,
+ queueIndex = 0, numberOfQueueItems = queueItems.length;
+
+ if (numberOfQueueItems && before) { before(); }
+ while (queueIndex < numberOfQueueItems) {
+ target = queueItems[queueIndex];
+ method = queueItems[queueIndex+1];
+ args = queueItems[queueIndex+2];
+ stack = queueItems[queueIndex+3]; // Debugging assistance
+
+ if (typeof method === 'string') { method = target[method]; }
+
+ // method could have been nullified / canceled during flush
+ if (method) {
+ // TODO: error handling
+ if (args && args.length > 0) {
+ method.apply(target, args);
+ } else {
+ method.call(target);
+ }
+ }
+
+ queueIndex += 4;
+ }
+ queue._queueBeingFlushed = null;
+ if (numberOfQueueItems && after) { after(); }
+
+ if ((priorQueueNameIndex = indexOfPriorQueueWithActions(this, queueNameIndex)) !== -1) {
+ queueNameIndex = priorQueueNameIndex;
+ continue outerloop;
+ }
+
+ queueNameIndex++;
+ }
+ }
+ };
+
+ function indexOfPriorQueueWithActions(daq, currentQueueIndex) {
+ var queueName, queue;
+
+ for (var i = 0, l = currentQueueIndex; i <= l; i++) {
+ queueName = daq.queueNames[i];
+ queue = daq.queues[queueName];
+ if (queue._queue.length) { return i; }
+ }
+
+ return -1;
+ }
+
+ __exports__.DeferredActionQueues = DeferredActionQueues;
+ });
+
+define("backburner",
+ ["backburner/deferred_action_queues","exports"],
+ function(__dependency1__, __exports__) {
+ "use strict";
+ var DeferredActionQueues = __dependency1__.DeferredActionQueues;
+
+ var slice = [].slice,
+ pop = [].pop,
+ throttlers = [],
+ debouncees = [],
+ timers = [],
+ autorun, laterTimer, laterTimerExpiresAt,
+ global = this,
+ NUMBER = /\d+/;
+
+ function isCoercableNumber(number) {
+ return typeof number === 'number' || NUMBER.test(number);
+ }
+
+ function Backburner(queueNames, options) {
+ this.queueNames = queueNames;
+ this.options = options || {};
+ if (!this.options.defaultQueue) {
+ this.options.defaultQueue = queueNames[0];
+ }
+ this.instanceStack = [];
+ }
+
+ Backburner.prototype = {
+ queueNames: null,
+ options: null,
+ currentInstance: null,
+ instanceStack: null,
+
+ begin: function() {
+ var onBegin = this.options && this.options.onBegin,
+ previousInstance = this.currentInstance;
+
+ if (previousInstance) {
+ this.instanceStack.push(previousInstance);
+ }
+
+ this.currentInstance = new DeferredActionQueues(this.queueNames, this.options);
+ if (onBegin) {
+ onBegin(this.currentInstance, previousInstance);
+ }
+ },
+
+ end: function() {
+ var onEnd = this.options && this.options.onEnd,
+ currentInstance = this.currentInstance,
+ nextInstance = null;
+
+ try {
+ currentInstance.flush();
+ } finally {
+ this.currentInstance = null;
+
+ if (this.instanceStack.length) {
+ nextInstance = this.instanceStack.pop();
+ this.currentInstance = nextInstance;
+ }
+
+ if (onEnd) {
+ onEnd(currentInstance, nextInstance);
+ }
+ }
+ },
+
+ run: function(target, method /*, args */) {
+ var ret;
+ this.begin();
+
+ if (!method) {
+ method = target;
+ target = null;
+ }
+
+ if (typeof method === 'string') {
+ method = target[method];
+ }
+
+ // Prevent Safari double-finally.
+ var finallyAlreadyCalled = false;
+ try {
+ if (arguments.length > 2) {
+ ret = method.apply(target, slice.call(arguments, 2));
+ } else {
+ ret = method.call(target);
+ }
+ } finally {
+ if (!finallyAlreadyCalled) {
+ finallyAlreadyCalled = true;
+ this.end();
+ }
+ }
+ return ret;
+ },
+
+ defer: function(queueName, target, method /* , args */) {
+ if (!method) {
+ method = target;
+ target = null;
+ }
+
+ if (typeof method === 'string') {
+ method = target[method];
+ }
+
+ var stack = this.DEBUG ? new Error() : undefined,
+ args = arguments.length > 3 ? slice.call(arguments, 3) : undefined;
+ if (!this.currentInstance) { createAutorun(this); }
+ return this.currentInstance.schedule(queueName, target, method, args, false, stack);
+ },
+
+ deferOnce: function(queueName, target, method /* , args */) {
+ if (!method) {
+ method = target;
+ target = null;
+ }
+
+ if (typeof method === 'string') {
+ method = target[method];
+ }
+
+ var stack = this.DEBUG ? new Error() : undefined,
+ args = arguments.length > 3 ? slice.call(arguments, 3) : undefined;
+ if (!this.currentInstance) { createAutorun(this); }
+ return this.currentInstance.schedule(queueName, target, method, args, true, stack);
+ },
+
+ setTimeout: function() {
+ var args = slice.call(arguments);
+ var length = args.length;
+ var method, wait, target;
+ var self = this;
+ var methodOrTarget, methodOrWait, methodOrArgs;
+
+ if (length === 0) {
+ return;
+ } else if (length === 1) {
+ method = args.shift();
+ wait = 0;
+ } else if (length === 2) {
+ methodOrTarget = args[0];
+ methodOrWait = args[1];
+
+ if (typeof methodOrWait === 'function' || typeof methodOrTarget[methodOrWait] === 'function') {
+ target = args.shift();
+ method = args.shift();
+ wait = 0;
+ } else if (isCoercableNumber(methodOrWait)) {
+ method = args.shift();
+ wait = args.shift();
+ } else {
+ method = args.shift();
+ wait = 0;
+ }
+ } else {
+ var last = args[args.length - 1];
+
+ if (isCoercableNumber(last)) {
+ wait = args.pop();
+ }
+
+ methodOrTarget = args[0];
+ methodOrArgs = args[1];
+
+ if (typeof methodOrArgs === 'function' || (typeof methodOrArgs === 'string' &&
+ methodOrTarget !== null &&
+ methodOrArgs in methodOrTarget)) {
+ target = args.shift();
+ method = args.shift();
+ } else {
+ method = args.shift();
+ }
+ }
+
+ var executeAt = (+new Date()) + parseInt(wait, 10);
+
+ if (typeof method === 'string') {
+ method = target[method];
+ }
+
+ function fn() {
+ method.apply(target, args);
+ }
+
+ // find position to insert - TODO: binary search
+ var i, l;
+ for (i = 0, l = timers.length; i < l; i += 2) {
+ if (executeAt < timers[i]) { break; }
+ }
+
+ timers.splice(i, 0, executeAt, fn);
+
+ updateLaterTimer(self, executeAt, wait);
+
+ return fn;
+ },
+
+ throttle: function(target, method /* , args, wait, [immediate] */) {
+ var self = this,
+ args = arguments,
+ immediate = pop.call(args),
+ wait,
+ throttler,
+ index,
+ timer;
+
+ if (typeof immediate === "number" || typeof immediate === "string") {
+ wait = immediate;
+ immediate = true;
+ } else {
+ wait = pop.call(args);
+ }
+
+ wait = parseInt(wait, 10);
+
+ index = findThrottler(target, method);
+ if (index > -1) { return throttlers[index]; } // throttled
+
+ timer = global.setTimeout(function() {
+ if (!immediate) {
+ self.run.apply(self, args);
+ }
+ var index = findThrottler(target, method);
+ if (index > -1) { throttlers.splice(index, 1); }
+ }, wait);
+
+ if (immediate) {
+ self.run.apply(self, args);
+ }
+
+ throttler = [target, method, timer];
+
+ throttlers.push(throttler);
+
+ return throttler;
+ },
+
+ debounce: function(target, method /* , args, wait, [immediate] */) {
+ var self = this,
+ args = arguments,
+ immediate = pop.call(args),
+ wait,
+ index,
+ debouncee,
+ timer;
+
+ if (typeof immediate === "number" || typeof immediate === "string") {
+ wait = immediate;
+ immediate = false;
+ } else {
+ wait = pop.call(args);
+ }
+
+ wait = parseInt(wait, 10);
+ // Remove debouncee
+ index = findDebouncee(target, method);
+
+ if (index > -1) {
+ debouncee = debouncees[index];
+ debouncees.splice(index, 1);
+ clearTimeout(debouncee[2]);
+ }
+
+ timer = global.setTimeout(function() {
+ if (!immediate) {
+ self.run.apply(self, args);
+ }
+ var index = findDebouncee(target, method);
+ if (index > -1) {
+ debouncees.splice(index, 1);
+ }
+ }, wait);
+
+ if (immediate && index === -1) {
+ self.run.apply(self, args);
+ }
+
+ debouncee = [target, method, timer];
+
+ debouncees.push(debouncee);
+
+ return debouncee;
+ },
+
+ cancelTimers: function() {
+ var i, len;
+
+ for (i = 0, len = throttlers.length; i < len; i++) {
+ clearTimeout(throttlers[i][2]);
+ }
+ throttlers = [];
+
+ for (i = 0, len = debouncees.length; i < len; i++) {
+ clearTimeout(debouncees[i][2]);
+ }
+ debouncees = [];
+
+ if (laterTimer) {
+ clearTimeout(laterTimer);
+ laterTimer = null;
+ }
+ timers = [];
+
+ if (autorun) {
+ clearTimeout(autorun);
+ autorun = null;
+ }
+ },
+
+ hasTimers: function() {
+ return !!timers.length || autorun;
+ },
+
+ cancel: function(timer) {
+ var timerType = typeof timer;
+
+ if (timer && timerType === 'object' && timer.queue && timer.method) { // we're cancelling a deferOnce
+ return timer.queue.cancel(timer);
+ } else if (timerType === 'function') { // we're cancelling a setTimeout
+ for (var i = 0, l = timers.length; i < l; i += 2) {
+ if (timers[i + 1] === timer) {
+ timers.splice(i, 2); // remove the two elements
+ return true;
+ }
+ }
+ } else if (Object.prototype.toString.call(timer) === "[object Array]"){ // we're cancelling a throttle or debounce
+ return this._cancelItem(findThrottler, throttlers, timer) ||
+ this._cancelItem(findDebouncee, debouncees, timer);
+ } else {
+ return; // timer was null or not a timer
+ }
+ },
+
+ _cancelItem: function(findMethod, array, timer){
+ var item,
+ index;
+
+ if (timer.length < 3) { return false; }
+
+ index = findMethod(timer[0], timer[1]);
+
+ if(index > -1) {
+
+ item = array[index];
+
+ if(item[2] === timer[2]){
+ array.splice(index, 1);
+ clearTimeout(timer[2]);
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ };
+
+ Backburner.prototype.schedule = Backburner.prototype.defer;
+ Backburner.prototype.scheduleOnce = Backburner.prototype.deferOnce;
+ Backburner.prototype.later = Backburner.prototype.setTimeout;
+
+ function createAutorun(backburner) {
+ backburner.begin();
+ autorun = global.setTimeout(function() {
+ autorun = null;
+ backburner.end();
+ });
+ }
+
+ function updateLaterTimer(self, executeAt, wait) {
+ if (!laterTimer || executeAt < laterTimerExpiresAt) {
+ if (laterTimer) {
+ clearTimeout(laterTimer);
+ }
+ laterTimer = global.setTimeout(function() {
+ laterTimer = null;
+ laterTimerExpiresAt = null;
+ executeTimers(self);
+ }, wait);
+ laterTimerExpiresAt = executeAt;
+ }
+ }
+
+ function executeTimers(self) {
+ var now = +new Date(),
+ time, fns, i, l;
+
+ self.run(function() {
+ // TODO: binary search
+ for (i = 0, l = timers.length; i < l; i += 2) {
+ time = timers[i];
+ if (time > now) { break; }
+ }
+
+ fns = timers.splice(0, i);
+
+ for (i = 1, l = fns.length; i < l; i += 2) {
+ self.schedule(self.options.defaultQueue, null, fns[i]);
+ }
+ });
+
+ if (timers.length) {
+ updateLaterTimer(self, timers[0], timers[0] - now);
+ }
+ }
+
+ function findDebouncee(target, method) {
+ var debouncee,
+ index = -1;
+
+ for (var i = 0, l = debouncees.length; i < l; i++) {
+ debouncee = debouncees[i];
+ if (debouncee[0] === target && debouncee[1] === method) {
+ index = i;
+ break;
+ }
+ }
+
+ return index;
+ }
+
+ function findThrottler(target, method) {
+ var throttler,
+ index = -1;
+
+ for (var i = 0, l = throttlers.length; i < l; i++) {
+ throttler = throttlers[i];
+ if (throttler[0] === target && throttler[1] === method) {
+ index = i;
+ break;
+ }
+ }
+
+ return index;
+ }
+
+ __exports__.Backburner = Backburner;
+ });
+})();
+
+
+
+(function() {
+var onBegin = function(current) {
+ Ember.run.currentRunLoop = current;
+};
+
+var onEnd = function(current, next) {
+ Ember.run.currentRunLoop = next;
+};
+
+var Backburner = requireModule('backburner').Backburner,
+ backburner = new Backburner(['sync', 'actions', 'destroy'], {
+ sync: {
+ before: Ember.beginPropertyChanges,
+ after: Ember.endPropertyChanges
+ },
+ defaultQueue: 'actions',
+ onBegin: onBegin,
+ onEnd: onEnd
+ }),
+ slice = [].slice,
+ concat = [].concat;
+
+// ..........................................................
+// Ember.run - this is ideally the only public API the dev sees
+//
+
+/**
+ Runs the passed target and method inside of a RunLoop, ensuring any
+ deferred actions including bindings and views updates are flushed at the
+ end.
+
+ Normally you should not need to invoke this method yourself. However if
+ you are implementing raw event handlers when interfacing with other
+ libraries or plugins, you should probably wrap all of your code inside this
+ call.
+
+ ```javascript
+ Ember.run(function() {
+ // code to be execute within a RunLoop
+ });
+ ```
+
+ @class run
+ @namespace Ember
+ @static
+ @constructor
+ @param {Object} [target] target of method to call
+ @param {Function|String} method Method to invoke.
+ May be a function or a string. If you pass a string
+ then it will be looked up on the passed target.
+ @param {Object} [args*] Any additional arguments you wish to pass to the method.
+ @return {Object} return value from invoking the passed function.
+*/
+Ember.run = function() {
+ if (Ember.onerror) {
+ return onerror(arguments);
+ } else {
+ return backburner.run.apply(backburner, arguments);
+ }
+};
+
+function onerror(args) {
+ try {
+ return backburner.run.apply(backburner, args);
+ } catch(error) {
+ Ember.onerror(error);
+ }
+}
+/**
+ If no run-loop is present, it creates a new one. If a run loop is
+ present it will queue itself to run on the existing run-loops action
+ queue.
+
+ Please note: This is not for normal usage, and should be used sparingly.
+
+ If invoked when not within a run loop:
+
+ ```javascript
+ Ember.run.join(function() {
+ // creates a new run-loop
+ });
+ ```
+
+ Alternatively, if called within an existing run loop:
+
+ ```javascript
+ Ember.run(function() {
+ // creates a new run-loop
+ Ember.run.join(function() {
+ // joins with the existing run-loop, and queues for invocation on
+ // the existing run-loops action queue.
+ });
+ });
+ ```
+
+ @method join
+ @namespace Ember
+ @param {Object} [target] target of method to call
+ @param {Function|String} method Method to invoke.
+ May be a function or a string. If you pass a string
+ then it will be looked up on the passed target.
+ @param {Object} [args*] Any additional arguments you wish to pass to the method.
+ @return {Object} Return value from invoking the passed function. Please note,
+ when called within an existing loop, no return value is possible.
+*/
+Ember.run.join = function(target, method /* args */) {
+ if (!Ember.run.currentRunLoop) {
+ return Ember.run.apply(Ember.run, arguments);
+ }
+
+ var args = slice.call(arguments);
+ args.unshift('actions');
+ Ember.run.schedule.apply(Ember.run, args);
+};
+
+/**
+ Provides a useful utility for when integrating with non-Ember libraries
+ that provide asynchronous callbacks.
+
+ Ember utilizes a run-loop to batch and coalesce changes. This works by
+ marking the start and end of Ember-related Javascript execution.
+
+ When using events such as a View's click handler, Ember wraps the event
+ handler in a run-loop, but when integrating with non-Ember libraries this
+ can be tedious.
+
+ For example, the following is rather verbose but is the correct way to combine
+ third-party events and Ember code.
+
+ ```javascript
+ var that = this;
+ jQuery(window).on('resize', function(){
+ Ember.run(function(){
+ that.handleResize();
+ });
+ });
+ ```
+
+ To reduce the boilerplate, the following can be used to construct a
+ run-loop-wrapped callback handler.
+
+ ```javascript
+ jQuery(window).on('resize', Ember.run.bind(this, this.handleResize));
+ ```
+
+ @method bind
+ @namespace Ember.run
+ @param {Object} [target] target of method to call
+ @param {Function|String} method Method to invoke.
+ May be a function or a string. If you pass a string
+ then it will be looked up on the passed target.
+ @param {Object} [args*] Any additional arguments you wish to pass to the method.
+ @return {Object} return value from invoking the passed function. Please note,
+ when called within an existing loop, no return value is possible.
+*/
+Ember.run.bind = function(target, method /* args*/) {
+ var args = slice.call(arguments);
+ return function() {
+ return Ember.run.join.apply(Ember.run, args.concat(slice.call(arguments)));
+ };
+};
+
+Ember.run.backburner = backburner;
+
+var run = Ember.run;
+
+Ember.run.currentRunLoop = null;
+
+Ember.run.queues = backburner.queueNames;
+
+/**
+ Begins a new RunLoop. Any deferred actions invoked after the begin will
+ be buffered until you invoke a matching call to `Ember.run.end()`. This is
+ a lower-level way to use a RunLoop instead of using `Ember.run()`.
+
+ ```javascript
+ Ember.run.begin();
+ // code to be execute within a RunLoop
+ Ember.run.end();
+ ```
+
+ @method begin
+ @return {void}
+*/
+Ember.run.begin = function() {
+ backburner.begin();
+};
+
+/**
+ Ends a RunLoop. This must be called sometime after you call
+ `Ember.run.begin()` to flush any deferred actions. This is a lower-level way
+ to use a RunLoop instead of using `Ember.run()`.
+
+ ```javascript
+ Ember.run.begin();
+ // code to be execute within a RunLoop
+ Ember.run.end();
+ ```
+
+ @method end
+ @return {void}
+*/
+Ember.run.end = function() {
+ backburner.end();
+};
+
+/**
+ Array of named queues. This array determines the order in which queues
+ are flushed at the end of the RunLoop. You can define your own queues by
+ simply adding the queue name to this array. Normally you should not need
+ to inspect or modify this property.
+
+ @property queues
+ @type Array
+ @default ['sync', 'actions', 'destroy']
+*/
+
+/**
+ Adds the passed target/method and any optional arguments to the named
+ queue to be executed at the end of the RunLoop. If you have not already
+ started a RunLoop when calling this method one will be started for you
+ automatically.
+
+ At the end of a RunLoop, any methods scheduled in this way will be invoked.
+ Methods will be invoked in an order matching the named queues defined in
+ the `Ember.run.queues` property.
+
+ ```javascript
+ Ember.run.schedule('sync', this, function() {
+ // this will be executed in the first RunLoop queue, when bindings are synced
+ console.log("scheduled on sync queue");
+ });
+
+ Ember.run.schedule('actions', this, function() {
+ // this will be executed in the 'actions' queue, after bindings have synced.
+ console.log("scheduled on actions queue");
+ });
+
+ // Note the functions will be run in order based on the run queues order.
+ // Output would be:
+ // scheduled on sync queue
+ // scheduled on actions queue
+ ```
+
+ @method schedule
+ @param {String} queue The name of the queue to schedule against.
+ Default queues are 'sync' and 'actions'
+ @param {Object} [target] target object to use as the context when invoking a method.
+ @param {String|Function} method The method to invoke. If you pass a string it
+ will be resolved on the target object at the time the scheduled item is
+ invoked allowing you to change the target function.
+ @param {Object} [arguments*] Optional arguments to be passed to the queued method.
+ @return {void}
+*/
+Ember.run.schedule = function(queue, target, method) {
+ checkAutoRun();
+ backburner.schedule.apply(backburner, arguments);
+};
+
+// Used by global test teardown
+Ember.run.hasScheduledTimers = function() {
+ return backburner.hasTimers();
+};
+
+// Used by global test teardown
+Ember.run.cancelTimers = function () {
+ backburner.cancelTimers();
+};
+
+/**
+ Immediately flushes any events scheduled in the 'sync' queue. Bindings
+ use this queue so this method is a useful way to immediately force all
+ bindings in the application to sync.
+
+ You should call this method anytime you need any changed state to propagate
+ throughout the app immediately without repainting the UI (which happens
+ in the later 'render' queue added by the `ember-views` package).
+
+ ```javascript
+ Ember.run.sync();
+ ```
+
+ @method sync
+ @return {void}
+*/
+Ember.run.sync = function() {
+ if (backburner.currentInstance) {
+ backburner.currentInstance.queues.sync.flush();
+ }
+};
+
+/**
+ Invokes the passed target/method and optional arguments after a specified
+ period if time. The last parameter of this method must always be a number
+ of milliseconds.
+
+ You should use this method whenever you need to run some action after a
+ period of time instead of using `setTimeout()`. This method will ensure that
+ items that expire during the same script execution cycle all execute
+ together, which is often more efficient than using a real setTimeout.
+
+ ```javascript
+ Ember.run.later(myContext, function() {
+ // code here will execute within a RunLoop in about 500ms with this == myContext
+ }, 500);
+ ```
+
+ @method later
+ @param {Object} [target] target of method to invoke
+ @param {Function|String} method The method to invoke.
+ If you pass a string it will be resolved on the
+ target at the time the method is invoked.
+ @param {Object} [args*] Optional arguments to pass to the timeout.
+ @param {Number} wait Number of milliseconds to wait.
+ @return {String} a string you can use to cancel the timer in
+ `Ember.run.cancel` later.
+*/
+Ember.run.later = function(target, method) {
+ return backburner.later.apply(backburner, arguments);
+};
+
+/**
+ Schedule a function to run one time during the current RunLoop. This is equivalent
+ to calling `scheduleOnce` with the "actions" queue.
+
+ @method once
+ @param {Object} [target] The target of the method to invoke.
+ @param {Function|String} method The method to invoke.
+ If you pass a string it will be resolved on the
+ target at the time the method is invoked.
+ @param {Object} [args*] Optional arguments to pass to the timeout.
+ @return {Object} Timer information for use in cancelling, see `Ember.run.cancel`.
+*/
+Ember.run.once = function(target, method) {
+ checkAutoRun();
+ var args = slice.call(arguments);
+ args.unshift('actions');
+ return backburner.scheduleOnce.apply(backburner, args);
+};
+
+/**
+ Schedules a function to run one time in a given queue of the current RunLoop.
+ Calling this method with the same queue/target/method combination will have
+ no effect (past the initial call).
+
+ Note that although you can pass optional arguments these will not be
+ considered when looking for duplicates. New arguments will replace previous
+ calls.
+
+ ```javascript
+ Ember.run(function() {
+ var sayHi = function() { console.log('hi'); }
+ Ember.run.scheduleOnce('afterRender', myContext, sayHi);
+ Ember.run.scheduleOnce('afterRender', myContext, sayHi);
+ // sayHi will only be executed once, in the afterRender queue of the RunLoop
+ });
+ ```
+
+ Also note that passing an anonymous function to `Ember.run.scheduleOnce` will
+ not prevent additional calls with an identical anonymous function from
+ scheduling the items multiple times, e.g.:
+
+ ```javascript
+ function scheduleIt() {
+ Ember.run.scheduleOnce('actions', myContext, function() { console.log("Closure"); });
+ }
+ scheduleIt();
+ scheduleIt();
+ // "Closure" will print twice, even though we're using `Ember.run.scheduleOnce`,
+ // because the function we pass to it is anonymous and won't match the
+ // previously scheduled operation.
+ ```
+
+ Available queues, and their order, can be found at `Ember.run.queues`
+
+ @method scheduleOnce
+ @param {String} [queue] The name of the queue to schedule against. Default queues are 'sync' and 'actions'.
+ @param {Object} [target] The target of the method to invoke.
+ @param {Function|String} method The method to invoke.
+ If you pass a string it will be resolved on the
+ target at the time the method is invoked.
+ @param {Object} [args*] Optional arguments to pass to the timeout.
+ @return {Object} Timer information for use in cancelling, see `Ember.run.cancel`.
+*/
+Ember.run.scheduleOnce = function(queue, target, method) {
+ checkAutoRun();
+ return backburner.scheduleOnce.apply(backburner, arguments);
+};
+
+/**
+ Schedules an item to run from within a separate run loop, after
+ control has been returned to the system. This is equivalent to calling
+ `Ember.run.later` with a wait time of 1ms.
+
+ ```javascript
+ Ember.run.next(myContext, function() {
+ // code to be executed in the next run loop,
+ // which will be scheduled after the current one
+ });
+ ```
+
+ Multiple operations scheduled with `Ember.run.next` will coalesce
+ into the same later run loop, along with any other operations
+ scheduled by `Ember.run.later` that expire right around the same
+ time that `Ember.run.next` operations will fire.
+
+ Note that there are often alternatives to using `Ember.run.next`.
+ For instance, if you'd like to schedule an operation to happen
+ after all DOM element operations have completed within the current
+ run loop, you can make use of the `afterRender` run loop queue (added
+ by the `ember-views` package, along with the preceding `render` queue
+ where all the DOM element operations happen). Example:
+
+ ```javascript
+ App.MyCollectionView = Ember.CollectionView.extend({
+ didInsertElement: function() {
+ Ember.run.scheduleOnce('afterRender', this, 'processChildElements');
+ },
+ processChildElements: function() {
+ // ... do something with collectionView's child view
+ // elements after they've finished rendering, which
+ // can't be done within the CollectionView's
+ // `didInsertElement` hook because that gets run
+ // before the child elements have been added to the DOM.
+ }
+ });
+ ```
+
+ One benefit of the above approach compared to using `Ember.run.next` is
+ that you will be able to perform DOM/CSS operations before unprocessed
+ elements are rendered to the screen, which may prevent flickering or
+ other artifacts caused by delaying processing until after rendering.
+
+ The other major benefit to the above approach is that `Ember.run.next`
+ introduces an element of non-determinism, which can make things much
+ harder to test, due to its reliance on `setTimeout`; it's much harder
+ to guarantee the order of scheduled operations when they are scheduled
+ outside of the current run loop, i.e. with `Ember.run.next`.
+
+ @method next
+ @param {Object} [target] target of method to invoke
+ @param {Function|String} method The method to invoke.
+ If you pass a string it will be resolved on the
+ target at the time the method is invoked.
+ @param {Object} [args*] Optional arguments to pass to the timeout.
+ @return {Object} Timer information for use in cancelling, see `Ember.run.cancel`.
+*/
+Ember.run.next = function() {
+ var args = slice.call(arguments);
+ args.push(1);
+ return backburner.later.apply(backburner, args);
+};
+
+/**
+ Cancels a scheduled item. Must be a value returned by `Ember.run.later()`,
+ `Ember.run.once()`, `Ember.run.next()`, `Ember.run.debounce()`, or
+ `Ember.run.throttle()`.
+
+ ```javascript
+ var runNext = Ember.run.next(myContext, function() {
+ // will not be executed
+ });
+ Ember.run.cancel(runNext);
+
+ var runLater = Ember.run.later(myContext, function() {
+ // will not be executed
+ }, 500);
+ Ember.run.cancel(runLater);
+
+ var runOnce = Ember.run.once(myContext, function() {
+ // will not be executed
+ });
+ Ember.run.cancel(runOnce);
+
+ var throttle = Ember.run.throttle(myContext, function() {
+ // will not be executed
+ }, 1, false);
+ Ember.run.cancel(throttle);
+
+ var debounce = Ember.run.debounce(myContext, function() {
+ // will not be executed
+ }, 1);
+ Ember.run.cancel(debounce);
+
+ var debounceImmediate = Ember.run.debounce(myContext, function() {
+ // will be executed since we passed in true (immediate)
+ }, 100, true);
+ // the 100ms delay until this method can be called again will be cancelled
+ Ember.run.cancel(debounceImmediate);
+ ```
+
+ @method cancel
+ @param {Object} timer Timer object to cancel
+ @return {Boolean} true if cancelled or false/undefined if it wasn't found
+*/
+Ember.run.cancel = function(timer) {
+ return backburner.cancel(timer);
+};
+
+/**
+ Delay calling the target method until the debounce period has elapsed
+ with no additional debounce calls. If `debounce` is called again before
+ the specified time has elapsed, the timer is reset and the entire period
+ must pass again before the target method is called.
+
+ This method should be used when an event may be called multiple times
+ but the action should only be called once when the event is done firing.
+ A common example is for scroll events where you only want updates to
+ happen once scrolling has ceased.
+
+ ```javascript
+ var myFunc = function() { console.log(this.name + ' ran.'); };
+ var myContext = {name: 'debounce'};
+
+ Ember.run.debounce(myContext, myFunc, 150);
+
+ // less than 150ms passes
+
+ Ember.run.debounce(myContext, myFunc, 150);
+
+ // 150ms passes
+ // myFunc is invoked with context myContext
+ // console logs 'debounce ran.' one time.
+ ```
+
+ Immediate allows you to run the function immediately, but debounce
+ other calls for this function until the wait time has elapsed. If
+ `debounce` is called again before the specified time has elapsed,
+ the timer is reset and the entire period must pass again before
+ the method can be called again.
+
+ ```javascript
+ var myFunc = function() { console.log(this.name + ' ran.'); };
+ var myContext = {name: 'debounce'};
+
+ Ember.run.debounce(myContext, myFunc, 150, true);
+
+ // console logs 'debounce ran.' one time immediately.
+ // 100ms passes
+
+ Ember.run.debounce(myContext, myFunc, 150, true);
+
+ // 150ms passes and nothing else is logged to the console and
+ // the debouncee is no longer being watched
+
+ Ember.run.debounce(myContext, myFunc, 150, true);
+
+ // console logs 'debounce ran.' one time immediately.
+ // 150ms passes and nothing else is logged tot he console and
+ // the debouncee is no longer being watched
+
+ ```
+
+ @method debounce
+ @param {Object} [target] target of method to invoke
+ @param {Function|String} method The method to invoke.
+ May be a function or a string. If you pass a string
+ then it will be looked up on the passed target.
+ @param {Object} [args*] Optional arguments to pass to the timeout.
+ @param {Number} wait Number of milliseconds to wait.
+ @param {Boolean} immediate Trigger the function on the leading instead
+ of the trailing edge of the wait interval. Defaults to false.
+ @return {Array} Timer information for use in cancelling, see `Ember.run.cancel`.
+*/
+Ember.run.debounce = function() {
+ return backburner.debounce.apply(backburner, arguments);
+};
+
+/**
+ Ensure that the target method is never called more frequently than
+ the specified spacing period.
+
+ ```javascript
+ var myFunc = function() { console.log(this.name + ' ran.'); };
+ var myContext = {name: 'throttle'};
+
+ Ember.run.throttle(myContext, myFunc, 150);
+ // myFunc is invoked with context myContext
+
+ // 50ms passes
+ Ember.run.throttle(myContext, myFunc, 150);
+
+ // 50ms passes
+ Ember.run.throttle(myContext, myFunc, 150);
+
+ // 150ms passes
+ Ember.run.throttle(myContext, myFunc, 150);
+ // myFunc is invoked with context myContext
+ // console logs 'throttle ran.' twice, 250ms apart.
+ ```
+
+ @method throttle
+ @param {Object} [target] target of method to invoke
+ @param {Function|String} method The method to invoke.
+ May be a function or a string. If you pass a string
+ then it will be looked up on the passed target.
+ @param {Object} [args*] Optional arguments to pass to the timeout.
+ @param {Number} spacing Number of milliseconds to space out requests.
+ @return {Array} Timer information for use in cancelling, see `Ember.run.cancel`.
+*/
+Ember.run.throttle = function() {
+ return backburner.throttle.apply(backburner, arguments);
+};
+
+// Make sure it's not an autorun during testing
+function checkAutoRun() {
+ if (!Ember.run.currentRunLoop) {
+ Ember.assert("You have turned on testing mode, which disabled the run-loop's autorun. You will need to wrap any code with asynchronous side-effects in an Ember.run", !Ember.testing);
+ }
+}
+
+})();
+
+
+
+(function() {
+// Ember.Logger
+// get
+// set
+// guidFor, meta
+// addObserver, removeObserver
+// Ember.run.schedule
+/**
+@module ember-metal
+*/
+
+// ..........................................................
+// CONSTANTS
+//
+
+/**
+ Debug parameter you can turn on. This will log all bindings that fire to
+ the console. This should be disabled in production code. Note that you
+ can also enable this from the console or temporarily.
+
+ @property LOG_BINDINGS
+ @for Ember
+ @type Boolean
+ @default false
+*/
+Ember.LOG_BINDINGS = false || !!Ember.ENV.LOG_BINDINGS;
+
+var get = Ember.get,
+ set = Ember.set,
+ guidFor = Ember.guidFor,
+ IS_GLOBAL = /^([A-Z$]|([0-9][A-Z$]))/;
+
+/**
+ Returns true if the provided path is global (e.g., `MyApp.fooController.bar`)
+ instead of local (`foo.bar.baz`).
+
+ @method isGlobalPath
+ @for Ember
+ @private
+ @param {String} path
+ @return Boolean
+*/
+var isGlobalPath = Ember.isGlobalPath = function(path) {
+ return IS_GLOBAL.test(path);
+};
+
+function getWithGlobals(obj, path) {
+ return get(isGlobalPath(path) ? Ember.lookup : obj, path);
+}
+
+// ..........................................................
+// BINDING
+//
+
+var Binding = function(toPath, fromPath) {
+ this._direction = 'fwd';
+ this._from = fromPath;
+ this._to = toPath;
+ this._directionMap = Ember.Map.create();
+};
+
+/**
+@class Binding
+@namespace Ember
+*/
+
+Binding.prototype = {
+ /**
+ This copies the Binding so it can be connected to another object.
+
+ @method copy
+ @return {Ember.Binding} `this`
+ */
+ copy: function () {
+ var copy = new Binding(this._to, this._from);
+ if (this._oneWay) { copy._oneWay = true; }
+ return copy;
+ },
+
+ // ..........................................................
+ // CONFIG
+ //
+
+ /**
+ This will set `from` property path to the specified value. It will not
+ attempt to resolve this property path to an actual object until you
+ connect the binding.
+
+ The binding will search for the property path starting at the root object
+ you pass when you `connect()` the binding. It follows the same rules as
+ `get()` - see that method for more information.
+
+ @method from
+ @param {String} path the property path to connect to
+ @return {Ember.Binding} `this`
+ */
+ from: function(path) {
+ this._from = path;
+ return this;
+ },
+
+ /**
+ This will set the `to` property path to the specified value. It will not
+ attempt to resolve this property path to an actual object until you
+ connect the binding.
+
+ The binding will search for the property path starting at the root object
+ you pass when you `connect()` the binding. It follows the same rules as
+ `get()` - see that method for more information.
+
+ @method to
+ @param {String|Tuple} path A property path or tuple
+ @return {Ember.Binding} `this`
+ */
+ to: function(path) {
+ this._to = path;
+ return this;
+ },
+
+ /**
+ Configures the binding as one way. A one-way binding will relay changes
+ on the `from` side to the `to` side, but not the other way around. This
+ means that if you change the `to` side directly, the `from` side may have
+ a different value.
+
+ @method oneWay
+ @return {Ember.Binding} `this`
+ */
+ oneWay: function() {
+ this._oneWay = true;
+ return this;
+ },
+
+ /**
+ @method toString
+ @return {String} string representation of binding
+ */
+ toString: function() {
+ var oneWay = this._oneWay ? '[oneWay]' : '';
+ return "Ember.Binding<" + guidFor(this) + ">(" + this._from + " -> " + this._to + ")" + oneWay;
+ },
+
+ // ..........................................................
+ // CONNECT AND SYNC
+ //
+
+ /**
+ Attempts to connect this binding instance so that it can receive and relay
+ changes. This method will raise an exception if you have not set the
+ from/to properties yet.
+
+ @method connect
+ @param {Object} obj The root object for this binding.
+ @return {Ember.Binding} `this`
+ */
+ connect: function(obj) {
+ Ember.assert('Must pass a valid object to Ember.Binding.connect()', !!obj);
+
+ var fromPath = this._from, toPath = this._to;
+ Ember.trySet(obj, toPath, getWithGlobals(obj, fromPath));
+
+ // add an observer on the object to be notified when the binding should be updated
+ Ember.addObserver(obj, fromPath, this, this.fromDidChange);
+
+ // if the binding is a two-way binding, also set up an observer on the target
+ if (!this._oneWay) { Ember.addObserver(obj, toPath, this, this.toDidChange); }
+
+ this._readyToSync = true;
+
+ return this;
+ },
+
+ /**
+ Disconnects the binding instance. Changes will no longer be relayed. You
+ will not usually need to call this method.
+
+ @method disconnect
+ @param {Object} obj The root object you passed when connecting the binding.
+ @return {Ember.Binding} `this`
+ */
+ disconnect: function(obj) {
+ Ember.assert('Must pass a valid object to Ember.Binding.disconnect()', !!obj);
+
+ var twoWay = !this._oneWay;
+
+ // remove an observer on the object so we're no longer notified of
+ // changes that should update bindings.
+ Ember.removeObserver(obj, this._from, this, this.fromDidChange);
+
+ // if the binding is two-way, remove the observer from the target as well
+ if (twoWay) { Ember.removeObserver(obj, this._to, this, this.toDidChange); }
+
+ this._readyToSync = false; // disable scheduled syncs...
+ return this;
+ },
+
+ // ..........................................................
+ // PRIVATE
+ //
+
+ /* called when the from side changes */
+ fromDidChange: function(target) {
+ this._scheduleSync(target, 'fwd');
+ },
+
+ /* called when the to side changes */
+ toDidChange: function(target) {
+ this._scheduleSync(target, 'back');
+ },
+
+ _scheduleSync: function(obj, dir) {
+ var directionMap = this._directionMap;
+ var existingDir = directionMap.get(obj);
+
+ // if we haven't scheduled the binding yet, schedule it
+ if (!existingDir) {
+ Ember.run.schedule('sync', this, this._sync, obj);
+ directionMap.set(obj, dir);
+ }
+
+ // If both a 'back' and 'fwd' sync have been scheduled on the same object,
+ // default to a 'fwd' sync so that it remains deterministic.
+ if (existingDir === 'back' && dir === 'fwd') {
+ directionMap.set(obj, 'fwd');
+ }
+ },
+
+ _sync: function(obj) {
+ var log = Ember.LOG_BINDINGS;
+
+ // don't synchronize destroyed objects or disconnected bindings
+ if (obj.isDestroyed || !this._readyToSync) { return; }
+
+ // get the direction of the binding for the object we are
+ // synchronizing from
+ var directionMap = this._directionMap;
+ var direction = directionMap.get(obj);
+
+ var fromPath = this._from, toPath = this._to;
+
+ directionMap.remove(obj);
+
+ // if we're synchronizing from the remote object...
+ if (direction === 'fwd') {
+ var fromValue = getWithGlobals(obj, this._from);
+ if (log) {
+ Ember.Logger.log(' ', this.toString(), '->', fromValue, obj);
+ }
+ if (this._oneWay) {
+ Ember.trySet(obj, toPath, fromValue);
+ } else {
+ Ember._suspendObserver(obj, toPath, this, this.toDidChange, function () {
+ Ember.trySet(obj, toPath, fromValue);
+ });
+ }
+ // if we're synchronizing *to* the remote object
+ } else if (direction === 'back') {
+ var toValue = get(obj, this._to);
+ if (log) {
+ Ember.Logger.log(' ', this.toString(), '<-', toValue, obj);
+ }
+ Ember._suspendObserver(obj, fromPath, this, this.fromDidChange, function () {
+ Ember.trySet(Ember.isGlobalPath(fromPath) ? Ember.lookup : obj, fromPath, toValue);
+ });
+ }
+ }
+
+};
+
+function mixinProperties(to, from) {
+ for (var key in from) {
+ if (from.hasOwnProperty(key)) {
+ to[key] = from[key];
+ }
+ }
+}
+
+mixinProperties(Binding, {
+
+ /*
+ See `Ember.Binding.from`.
+
+ @method from
+ @static
+ */
+ from: function() {
+ var C = this, binding = new C();
+ return binding.from.apply(binding, arguments);
+ },
+
+ /*
+ See `Ember.Binding.to`.
+
+ @method to
+ @static
+ */
+ to: function() {
+ var C = this, binding = new C();
+ return binding.to.apply(binding, arguments);
+ },
+
+ /**
+ Creates a new Binding instance and makes it apply in a single direction.
+ A one-way binding will relay changes on the `from` side object (supplied
+ as the `from` argument) the `to` side, but not the other way around.
+ This means that if you change the "to" side directly, the "from" side may have
+ a different value.
+
+ See `Binding.oneWay`.
+
+ @method oneWay
+ @param {String} from from path.
+ @param {Boolean} [flag] (Optional) passing nothing here will make the
+ binding `oneWay`. You can instead pass `false` to disable `oneWay`, making the
+ binding two way again.
+ @return {Ember.Binding} `this`
+ */
+ oneWay: function(from, flag) {
+ var C = this, binding = new C(null, from);
+ return binding.oneWay(flag);
+ }
+
+});
+
+/**
+ An `Ember.Binding` connects the properties of two objects so that whenever
+ the value of one property changes, the other property will be changed also.
+
+ ## Automatic Creation of Bindings with `/^*Binding/`-named Properties
+
+ You do not usually create Binding objects directly but instead describe
+ bindings in your class or object definition using automatic binding
+ detection.
+
+ Properties ending in a `Binding` suffix will be converted to `Ember.Binding`
+ instances. The value of this property should be a string representing a path
+ to another object or a custom binding instanced created using Binding helpers
+ (see "One Way Bindings"):
+
+ ```
+ valueBinding: "MyApp.someController.title"
+ ```
+
+ This will create a binding from `MyApp.someController.title` to the `value`
+ property of your object instance automatically. Now the two values will be
+ kept in sync.
+
+ ## One Way Bindings
+
+ One especially useful binding customization you can use is the `oneWay()`
+ helper. This helper tells Ember that you are only interested in
+ receiving changes on the object you are binding from. For example, if you
+ are binding to a preference and you want to be notified if the preference
+ has changed, but your object will not be changing the preference itself, you
+ could do:
+
+ ```
+ bigTitlesBinding: Ember.Binding.oneWay("MyApp.preferencesController.bigTitles")
+ ```
+
+ This way if the value of `MyApp.preferencesController.bigTitles` changes the
+ `bigTitles` property of your object will change also. However, if you
+ change the value of your `bigTitles` property, it will not update the
+ `preferencesController`.
+
+ One way bindings are almost twice as fast to setup and twice as fast to
+ execute because the binding only has to worry about changes to one side.
+
+ You should consider using one way bindings anytime you have an object that
+ may be created frequently and you do not intend to change a property; only
+ to monitor it for changes (such as in the example above).
+
+ ## Adding Bindings Manually
+
+ All of the examples above show you how to configure a custom binding, but the
+ result of these customizations will be a binding template, not a fully active
+ Binding instance. The binding will actually become active only when you
+ instantiate the object the binding belongs to. It is useful however, to
+ understand what actually happens when the binding is activated.
+
+ For a binding to function it must have at least a `from` property and a `to`
+ property. The `from` property path points to the object/key that you want to
+ bind from while the `to` path points to the object/key you want to bind to.
+
+ When you define a custom binding, you are usually describing the property
+ you want to bind from (such as `MyApp.someController.value` in the examples
+ above). When your object is created, it will automatically assign the value
+ you want to bind `to` based on the name of your binding key. In the
+ examples above, during init, Ember objects will effectively call
+ something like this on your binding:
+
+ ```javascript
+ binding = Ember.Binding.from(this.valueBinding).to("value");
+ ```
+
+ This creates a new binding instance based on the template you provide, and
+ sets the to path to the `value` property of the new object. Now that the
+ binding is fully configured with a `from` and a `to`, it simply needs to be
+ connected to become active. This is done through the `connect()` method:
+
+ ```javascript
+ binding.connect(this);
+ ```
+
+ Note that when you connect a binding you pass the object you want it to be
+ connected to. This object will be used as the root for both the from and
+ to side of the binding when inspecting relative paths. This allows the
+ binding to be automatically inherited by subclassed objects as well.
+
+ Now that the binding is connected, it will observe both the from and to side
+ and relay changes.
+
+ If you ever needed to do so (you almost never will, but it is useful to
+ understand this anyway), you could manually create an active binding by
+ using the `Ember.bind()` helper method. (This is the same method used by
+ to setup your bindings on objects):
+
+ ```javascript
+ Ember.bind(MyApp.anotherObject, "value", "MyApp.someController.value");
+ ```
+
+ Both of these code fragments have the same effect as doing the most friendly
+ form of binding creation like so:
+
+ ```javascript
+ MyApp.anotherObject = Ember.Object.create({
+ valueBinding: "MyApp.someController.value",
+
+ // OTHER CODE FOR THIS OBJECT...
+ });
+ ```
+
+ Ember's built in binding creation method makes it easy to automatically
+ create bindings for you. You should always use the highest-level APIs
+ available, even if you understand how it works underneath.
+
+ @class Binding
+ @namespace Ember
+ @since Ember 0.9
+*/
+Ember.Binding = Binding;
+
+
+/**
+ Global helper method to create a new binding. Just pass the root object
+ along with a `to` and `from` path to create and connect the binding.
+
+ @method bind
+ @for Ember
+ @param {Object} obj The root object of the transform.
+ @param {String} to The path to the 'to' side of the binding.
+ Must be relative to obj.
+ @param {String} from The path to the 'from' side of the binding.
+ Must be relative to obj or a global path.
+ @return {Ember.Binding} binding instance
+*/
+Ember.bind = function(obj, to, from) {
+ return new Ember.Binding(to, from).connect(obj);
+};
+
+/**
+ @method oneWay
+ @for Ember
+ @param {Object} obj The root object of the transform.
+ @param {String} to The path to the 'to' side of the binding.
+ Must be relative to obj.
+ @param {String} from The path to the 'from' side of the binding.
+ Must be relative to obj or a global path.
+ @return {Ember.Binding} binding instance
+*/
+Ember.oneWay = function(obj, to, from) {
+ return new Ember.Binding(to, from).oneWay().connect(obj);
+};
+
+})();
+
+
+
+(function() {
+/**
+@module ember
+@submodule ember-metal
+*/
+
+var Mixin, REQUIRED, Alias,
+ a_map = Ember.ArrayPolyfills.map,
+ a_indexOf = Ember.ArrayPolyfills.indexOf,
+ a_forEach = Ember.ArrayPolyfills.forEach,
+ a_slice = [].slice,
+ o_create = Ember.create,
+ defineProperty = Ember.defineProperty,
+ guidFor = Ember.guidFor,
+ metaFor = Ember.meta,
+ META_KEY = Ember.META_KEY;
+
+var expandProperties = Ember.expandProperties;
+
+function superFunction(){
+ var ret, func = this.__nextSuper;
+ if (func) {
+ this.__nextSuper = null;
+ ret = func.apply(this, arguments);
+ this.__nextSuper = func;
+ }
+ return ret;
+}
+
+function mixinsMeta(obj) {
+ var m = metaFor(obj, true), ret = m.mixins;
+ if (!ret) {
+ ret = m.mixins = {};
+ } else if (!m.hasOwnProperty('mixins')) {
+ ret = m.mixins = o_create(ret);
+ }
+ return ret;
+}
+
+function initMixin(mixin, args) {
+ if (args && args.length > 0) {
+ mixin.mixins = a_map.call(args, function(x) {
+ if (x instanceof Mixin) { return x; }
+
+ // Note: Manually setup a primitive mixin here. This is the only
+ // way to actually get a primitive mixin. This way normal creation
+ // of mixins will give you combined mixins...
+ var mixin = new Mixin();
+ mixin.properties = x;
+ return mixin;
+ });
+ }
+ return mixin;
+}
+
+function isMethod(obj) {
+ return 'function' === typeof obj &&
+ obj.isMethod !== false &&
+ obj !== Boolean && obj !== Object && obj !== Number && obj !== Array && obj !== Date && obj !== String;
+}
+
+var CONTINUE = {};
+
+function mixinProperties(mixinsMeta, mixin) {
+ var guid;
+
+ if (mixin instanceof Mixin) {
+ guid = guidFor(mixin);
+ if (mixinsMeta[guid]) { return CONTINUE; }
+ mixinsMeta[guid] = mixin;
+ return mixin.properties;
+ } else {
+ return mixin; // apply anonymous mixin properties
+ }
+}
+
+function concatenatedMixinProperties(concatProp, props, values, base) {
+ var concats;
+
+ // reset before adding each new mixin to pickup concats from previous
+ concats = values[concatProp] || base[concatProp];
+ if (props[concatProp]) {
+ concats = concats ? concats.concat(props[concatProp]) : props[concatProp];
+ }
+
+ return concats;
+}
+
+function giveDescriptorSuper(meta, key, property, values, descs) {
+ var superProperty;
+
+ // Computed properties override methods, and do not call super to them
+ if (values[key] === undefined) {
+ // Find the original descriptor in a parent mixin
+ superProperty = descs[key];
+ }
+
+ // If we didn't find the original descriptor in a parent mixin, find
+ // it on the original object.
+ superProperty = superProperty || meta.descs[key];
+
+ if (!superProperty || !(superProperty instanceof Ember.ComputedProperty)) {
+ return property;
+ }
+
+ // Since multiple mixins may inherit from the same parent, we need
+ // to clone the computed property so that other mixins do not receive
+ // the wrapped version.
+ property = o_create(property);
+ property.func = Ember.wrap(property.func, superProperty.func);
+
+ return property;
+}
+
+function giveMethodSuper(obj, key, method, values, descs) {
+ var superMethod;
+
+ // Methods overwrite computed properties, and do not call super to them.
+ if (descs[key] === undefined) {
+ // Find the original method in a parent mixin
+ superMethod = values[key];
+ }
+
+ // If we didn't find the original value in a parent mixin, find it in
+ // the original object
+ superMethod = superMethod || obj[key];
+
+ // Only wrap the new method if the original method was a function
+ if ('function' !== typeof superMethod) {
+ return method;
+ }
+
+ return Ember.wrap(method, superMethod);
+}
+
+function applyConcatenatedProperties(obj, key, value, values) {
+ var baseValue = values[key] || obj[key];
+
+ if (baseValue) {
+ if ('function' === typeof baseValue.concat) {
+ return baseValue.concat(value);
+ } else {
+ return Ember.makeArray(baseValue).concat(value);
+ }
+ } else {
+ return Ember.makeArray(value);
+ }
+}
+
+function applyMergedProperties(obj, key, value, values) {
+ var baseValue = values[key] || obj[key];
+
+ if (!baseValue) { return value; }
+
+ var newBase = Ember.merge({}, baseValue),
+ hasFunction = false;
+
+ for (var prop in value) {
+ if (!value.hasOwnProperty(prop)) { continue; }
+
+ var propValue = value[prop];
+ if (isMethod(propValue)) {
+ // TODO: support for Computed Properties, etc?
+ hasFunction = true;
+ newBase[prop] = giveMethodSuper(obj, prop, propValue, baseValue, {});
+ } else {
+ newBase[prop] = propValue;
+ }
+ }
+
+ if (hasFunction) {
+ newBase._super = superFunction;
+ }
+
+ return newBase;
+}
+
+function addNormalizedProperty(base, key, value, meta, descs, values, concats, mergings) {
+ if (value instanceof Ember.Descriptor) {
+ if (value === REQUIRED && descs[key]) { return CONTINUE; }
+
+ // Wrap descriptor function to implement
+ // __nextSuper() if needed
+ if (value.func) {
+ value = giveDescriptorSuper(meta, key, value, values, descs);
+ }
+
+ descs[key] = value;
+ values[key] = undefined;
+ } else {
+ if ((concats && a_indexOf.call(concats, key) >= 0) ||
+ key === 'concatenatedProperties' ||
+ key === 'mergedProperties') {
+ value = applyConcatenatedProperties(base, key, value, values);
+ } else if ((mergings && a_indexOf.call(mergings, key) >= 0)) {
+ value = applyMergedProperties(base, key, value, values);
+ } else if (isMethod(value)) {
+ value = giveMethodSuper(base, key, value, values, descs);
+ }
+
+ descs[key] = undefined;
+ values[key] = value;
+ }
+}
+
+function mergeMixins(mixins, m, descs, values, base, keys) {
+ var mixin, props, key, concats, mergings, meta;
+
+ function removeKeys(keyName) {
+ delete descs[keyName];
+ delete values[keyName];
+ }
+
+ for(var i=0, l=mixins.length; i<l; i++) {
+ mixin = mixins[i];
+ 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]');
+
+ props = mixinProperties(m, mixin);
+ if (props === CONTINUE) { continue; }
+
+ if (props) {
+ meta = metaFor(base);
+ if (base.willMergeMixin) { base.willMergeMixin(props); }
+ concats = concatenatedMixinProperties('concatenatedProperties', props, values, base);
+ mergings = concatenatedMixinProperties('mergedProperties', props, values, base);
+
+ for (key in props) {
+ if (!props.hasOwnProperty(key)) { continue; }
+ keys.push(key);
+ addNormalizedProperty(base, key, props[key], meta, descs, values, concats, mergings);
+ }
+
+ // manually copy toString() because some JS engines do not enumerate it
+ if (props.hasOwnProperty('toString')) { base.toString = props.toString; }
+ } else if (mixin.mixins) {
+ mergeMixins(mixin.mixins, m, descs, values, base, keys);
+ if (mixin._without) { a_forEach.call(mixin._without, removeKeys); }
+ }
+ }
+}
+
+var IS_BINDING = Ember.IS_BINDING = /^.+Binding$/;
+
+function detectBinding(obj, key, value, m) {
+ if (IS_BINDING.test(key)) {
+ var bindings = m.bindings;
+ if (!bindings) {
+ bindings = m.bindings = {};
+ } else if (!m.hasOwnProperty('bindings')) {
+ bindings = m.bindings = o_create(m.bindings);
+ }
+ bindings[key] = value;
+ }
+}
+
+function connectBindings(obj, m) {
+ // TODO Mixin.apply(instance) should disconnect binding if exists
+ var bindings = m.bindings, key, binding, to;
+ if (bindings) {
+ for (key in bindings) {
+ binding = bindings[key];
+ if (binding) {
+ to = key.slice(0, -7); // strip Binding off end
+ if (binding instanceof Ember.Binding) {
+ binding = binding.copy(); // copy prototypes' instance
+ binding.to(to);
+ } else { // binding is string path
+ binding = new Ember.Binding(to, binding);
+ }
+ binding.connect(obj);
+ obj[key] = binding;
+ }
+ }
+ // mark as applied
+ m.bindings = {};
+ }
+}
+
+function finishPartial(obj, m) {
+ connectBindings(obj, m || metaFor(obj));
+ return obj;
+}
+
+function followAlias(obj, desc, m, descs, values) {
+ var altKey = desc.methodName, value;
+ if (descs[altKey] || values[altKey]) {
+ value = values[altKey];
+ desc = descs[altKey];
+ } else if (m.descs[altKey]) {
+ desc = m.descs[altKey];
+ value = undefined;
+ } else {
+ desc = undefined;
+ value = obj[altKey];
+ }
+
+ return { desc: desc, value: value };
+}
+
+function updateObserversAndListeners(obj, key, observerOrListener, pathsKey, updateMethod) {
+ var paths = observerOrListener[pathsKey];
+
+ if (paths) {
+ for (var i=0, l=paths.length; i<l; i++) {
+ Ember[updateMethod](obj, paths[i], null, key);
+ }
+ }
+}
+
+function replaceObserversAndListeners(obj, key, observerOrListener) {
+ var prev = obj[key];
+
+ 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');
+ }
+
+ 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');
+ }
+}
+
+function applyMixin(obj, mixins, partial) {
+ var descs = {}, values = {}, m = metaFor(obj),
+ key, value, desc, keys = [];
+
+ obj._super = superFunction;
+
+ // Go through all mixins and hashes passed in, and:
+ //
+ // * Handle concatenated properties
+ // * Handle merged properties
+ // * Set up _super wrapping if necessary
+ // * Set up computed property descriptors
+ // * Copying `toString` in broken browsers
+ mergeMixins(mixins, mixinsMeta(obj), descs, values, obj, keys);
+
+ for(var i = 0, l = keys.length; i < l; i++) {
+ key = keys[i];
+ if (key === 'constructor' || !values.hasOwnProperty(key)) { continue; }
+
+ desc = descs[key];
+ value = values[key];
+
+ if (desc === REQUIRED) { continue; }
+
+ while (desc && desc instanceof Alias) {
+ var followed = followAlias(obj, desc, m, descs, values);
+ desc = followed.desc;
+ value = followed.value;
+ }
+
+ if (desc === undefined && value === undefined) { continue; }
+
+ replaceObserversAndListeners(obj, key, value);
+ detectBinding(obj, key, value, m);
+ defineProperty(obj, key, desc, value, m);
+ }
+
+ if (!partial) { // don't apply to prototype
+ finishPartial(obj, m);
+ }
+
+ return obj;
+}
+
+/**
+ @method mixin
+ @for Ember
+ @param obj
+ @param mixins*
+ @return obj
+*/
+Ember.mixin = function(obj) {
+ var args = a_slice.call(arguments, 1);
+ applyMixin(obj, args, false);
+ return obj;
+};
+
+/**
+ The `Ember.Mixin` class allows you to create mixins, whose properties can be
+ added to other classes. For instance,
+
+ ```javascript
+ App.Editable = Ember.Mixin.create({
+ edit: function() {
+ console.log('starting to edit');
+ this.set('isEditing', true);
+ },
+ isEditing: false
+ });
+
+ // Mix mixins into classes by passing them as the first arguments to
+ // .extend.
+ App.CommentView = Ember.View.extend(App.Editable, {
+ template: Ember.Handlebars.compile('{{#if view.isEditing}}...{{else}}...{{/if}}')
+ });
+
+ commentView = App.CommentView.create();
+ commentView.edit(); // outputs 'starting to edit'
+ ```
+
+ Note that Mixins are created with `Ember.Mixin.create`, not
+ `Ember.Mixin.extend`.
+
+ 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 a 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());
+ }
+ });
+ ```
+
+ @class Mixin
+ @namespace Ember
+*/
+Ember.Mixin = function() { return initMixin(this, arguments); };
+
+Mixin = Ember.Mixin;
+
+Mixin.prototype = {
+ properties: null,
+ mixins: null,
+ ownerConstructor: null
+};
+
+Mixin._apply = applyMixin;
+
+Mixin.applyPartial = function(obj) {
+ var args = a_slice.call(arguments, 1);
+ return applyMixin(obj, args, true);
+};
+
+Mixin.finishPartial = finishPartial;
+
+Ember.anyUnprocessedMixins = false;
+
+/**
+ @method create
+ @static
+ @param arguments*
+*/
+Mixin.create = function() {
+ Ember.anyUnprocessedMixins = true;
+ var M = this;
+ return initMixin(new M(), arguments);
+};
+
+var MixinPrototype = Mixin.prototype;
+
+/**
+ @method reopen
+ @param arguments*
+*/
+MixinPrototype.reopen = function() {
+ var mixin, tmp;
+
+ if (this.properties) {
+ mixin = Mixin.create();
+ mixin.properties = this.properties;
+ delete this.properties;
+ this.mixins = [mixin];
+ } else if (!this.mixins) {
+ this.mixins = [];
+ }
+
+ var len = arguments.length, mixins = this.mixins, idx;
+
+ for(idx=0; idx < len; idx++) {
+ mixin = arguments[idx];
+ 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]');
+
+ if (mixin instanceof Mixin) {
+ mixins.push(mixin);
+ } else {
+ tmp = Mixin.create();
+ tmp.properties = mixin;
+ mixins.push(tmp);
+ }
+ }
+
+ return this;
+};
+
+/**
+ @method apply
+ @param obj
+ @return applied object
+*/
+MixinPrototype.apply = function(obj) {
+ return applyMixin(obj, [this], false);
+};
+
+MixinPrototype.applyPartial = function(obj) {
+ return applyMixin(obj, [this], true);
+};
+
+function _detect(curMixin, targetMixin, seen) {
+ var guid = guidFor(curMixin);
+
+ if (seen[guid]) { return false; }
+ seen[guid] = true;
+
+ if (curMixin === targetMixin) { return true; }
+ var mixins = curMixin.mixins, loc = mixins ? mixins.length : 0;
+ while (--loc >= 0) {
+ if (_detect(mixins[loc], targetMixin, seen)) { return true; }
+ }
+ return false;
+}
+
+/**
+ @method detect
+ @param obj
+ @return {Boolean}
+*/
+MixinPrototype.detect = function(obj) {
+ if (!obj) { return false; }
+ if (obj instanceof Mixin) { return _detect(obj, this, {}); }
+ var m = obj[META_KEY],
+ mixins = m && m.mixins;
+ if (mixins) {
+ return !!mixins[guidFor(this)];
+ }
+ return false;
+};
+
+MixinPrototype.without = function() {
+ var ret = new Mixin(this);
+ ret._without = a_slice.call(arguments);
+ return ret;
+};
+
+function _keys(ret, mixin, seen) {
+ if (seen[guidFor(mixin)]) { return; }
+ seen[guidFor(mixin)] = true;
+
+ if (mixin.properties) {
+ var props = mixin.properties;
+ for (var key in props) {
+ if (props.hasOwnProperty(key)) { ret[key] = true; }
+ }
+ } else if (mixin.mixins) {
+ a_forEach.call(mixin.mixins, function(x) { _keys(ret, x, seen); });
+ }
+}
+
+MixinPrototype.keys = function() {
+ var keys = {}, seen = {}, ret = [];
+ _keys(keys, this, seen);
+ for(var key in keys) {
+ if (keys.hasOwnProperty(key)) { ret.push(key); }
+ }
+ return ret;
+};
+
+// returns the mixins currently applied to the specified object
+// TODO: Make Ember.mixin
+Mixin.mixins = function(obj) {
+ var m = obj[META_KEY],
+ mixins = m && m.mixins, ret = [];
+
+ if (!mixins) { return ret; }
+
+ for (var key in mixins) {
+ var mixin = mixins[key];
+
+ // skip primitive mixins since these are always anonymous
+ if (!mixin.properties) { ret.push(mixin); }
+ }
+
+ return ret;
+};
+
+REQUIRED = new Ember.Descriptor();
+REQUIRED.toString = function() { return '(Required Property)'; };
+
+/**
+ Denotes a required property for a mixin
+
+ @method required
+ @for Ember
+*/
+Ember.required = function() {
+ return REQUIRED;
+};
+
+Alias = function(methodName) {
+ this.methodName = methodName;
+};
+Alias.prototype = new Ember.Descriptor();
+
+/**
+ Makes a method available via an additional name.
+
+ ```javascript
+ App.Person = Ember.Object.extend({
+ name: function() {
+ return 'Tomhuda Katzdale';
+ },
+ moniker: Ember.aliasMethod('name')
+ });
+
+ var goodGuy = App.Person.create()
+ ```
+
+ @method aliasMethod
+ @for Ember
+ @param {String} methodName name of the method to alias
+ @return {Ember.Descriptor}
+*/
+Ember.aliasMethod = function(methodName) {
+ return new Alias(methodName);
+};
+
+// ..........................................................
+// OBSERVER HELPER
+//
+
+/**
+ Specify a method that observes property changes.
+
+ ```javascript
+ Ember.Object.extend({
+ valueObserver: Ember.observer('value', function() {
+ // Executes whenever the "value" property changes
+ })
+ });
+ ```
+
+ In the future this method may become asynchronous. If you want to ensure
+ synchronous behavior, use `immediateObserver`.
+
+ Also available as `Function.prototype.observes` if prototype extensions are
+ enabled.
+
+ @method observer
+ @for Ember
+ @param {String} propertyNames*
+ @param {Function} func
+ @return func
+*/
+Ember.observer = function() {
+ var func = a_slice.call(arguments, -1)[0];
+ var paths;
+
+ var addWatchedProperty = function (path) { paths.push(path); };
+ var _paths = a_slice.call(arguments, 0, -1);
+
+ if (typeof func !== "function") {
+ // revert to old, soft-deprecated argument ordering
+
+ func = arguments[0];
+ _paths = a_slice.call(arguments, 1);
+ }
+
+ paths = [];
+
+ for (var i=0; i<_paths.length; ++i) {
+ expandProperties(_paths[i], addWatchedProperty);
+ }
+
+ if (typeof func !== "function") {
+ throw new Ember.Error("Ember.observer called without a function");
+ }
+
+ func.__ember_observes__ = paths;
+ return func;
+};
+
+/**
+ Specify a method that observes property changes.
+
+ ```javascript
+ Ember.Object.extend({
+ valueObserver: Ember.immediateObserver('value', function() {
+ // Executes whenever the "value" property changes
+ })
+ });
+ ```
+
+ In the future, `Ember.observer` may become asynchronous. In this event,
+ `Ember.immediateObserver` will maintain the synchronous behavior.
+
+ Also available as `Function.prototype.observesImmediately` if prototype extensions are
+ enabled.
+
+ @method immediateObserver
+ @for Ember
+ @param {String} propertyNames*
+ @param {Function} func
+ @return func
+*/
+Ember.immediateObserver = function() {
+ for (var i=0, l=arguments.length; i<l; i++) {
+ var arg = arguments[i];
+ Ember.assert("Immediate observers must observe internal properties only, not properties on other objects.", typeof arg !== "string" || arg.indexOf('.') === -1);
+ }
+
+ return Ember.observer.apply(this, arguments);
+};
+
+/**
+ When observers fire, they are called with the arguments `obj`, `keyName`.
+
+ Note, `(a)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.(a)each.name', function(obj, keyName) {
+ // some logic
+ // obj.get(keyName) returns friends array
+ })
+ });
+ ```
+
+ Also available as `Function.prototype.observesBefore` if prototype extensions are
+ enabled.
+
+ @method beforeObserver
+ @for Ember
+ @param {String} propertyNames*
+ @param {Function} func
+ @return func
+*/
+Ember.beforeObserver = function() {
+ var func = a_slice.call(arguments, -1)[0];
+ var paths;
+
+ var addWatchedProperty = function(path) { paths.push(path); };
+
+ var _paths = a_slice.call(arguments, 0, -1);
+
+ if (typeof func !== "function") {
+ // revert to old, soft-deprecated argument ordering
+
+ func = arguments[0];
+ _paths = a_slice.call(arguments, 1);
+ }
+
+ paths = [];
+
+ for (var i=0; i<_paths.length; ++i) {
+ expandProperties(_paths[i], addWatchedProperty);
+ }
+
+ if (typeof func !== "function") {
+ throw new Ember.Error("Ember.beforeObserver called without a function");
+ }
+
+ func.__ember_observesBefore__ = paths;
+ return func;
+};
+
+})();
+
+
+
+(function() {
+// Provides a way to register library versions with ember.
+var forEach = Ember.EnumerableUtils.forEach,
+ indexOf = Ember.EnumerableUtils.indexOf;
+
+Ember.libraries = function() {
+ var libraries = [];
+ var coreLibIndex = 0;
+
+ var getLibrary = function(name) {
+ for (var i = 0; i < libraries.length; i++) {
+ if (libraries[i].name === name) {
+ return libraries[i];
+ }
+ }
+ };
+
+ libraries.register = function(name, version) {
+ if (!getLibrary(name)) {
+ libraries.push({name: name, version: version});
+ }
+ };
+
+ libraries.registerCoreLibrary = function(name, version) {
+ if (!getLibrary(name)) {
+ libraries.splice(coreLibIndex++, 0, {name: name, version: version});
+ }
+ };
+
+ libraries.deRegister = function(name) {
+ var lib = getLibrary(name);
+ if (lib) libraries.splice(indexOf(libraries, lib), 1);
+ };
+
+ libraries.each = function (callback) {
+ forEach(libraries, function(lib) {
+ callback(lib.name, lib.version);
+ });
+ };
+
+ return libraries;
+}();
+
+Ember.libraries.registerCoreLibrary('Ember', Ember.VERSION);
+
+})();
+
+
+
+(function() {
+/**
+Ember Metal
+
+@module ember
+@submodule ember-metal
+*/
+
+})();
+
+(function() {
+/**
+ @class RSVP
+ @module RSVP
+ */
+define("rsvp/all",
+ ["./promise","exports"],
+ function(__dependency1__, __exports__) {
+ "use strict";
+ var Promise = __dependency1__["default"];
+
+ /**
+ This is a convenient alias for `RSVP.Promise.all`.
+
+ @method all
+ @for RSVP
+ @param {Array} array Array of promises.
+ @param {String} label An optional label. This is useful
+ for tooling.
+ @static
+ */
+ __exports__["default"] = function all(array, label) {
+ return Promise.all(array, label);
+ };
+ });
+define("rsvp/all_settled",
+ ["./promise","./utils","exports"],
+ function(__dependency1__, __dependency2__, __exports__) {
+ "use strict";
+ var Promise = __dependency1__["default"];
+ var isArray = __dependency2__.isArray;
+ var isNonThenable = __dependency2__.isNonThenable;
+
+ /**
+ `RSVP.allSettled` is similar to `RSVP.all`, but instead of implementing
+ a fail-fast method, it waits until all the promises have returned and
+ shows you all the results. This is useful if you want to handle multiple
+ promises' failure states together as a set.
+
+ Returns a promise that is fulfilled when all the given promises have been
+ settled. The return promise is fulfilled with an array of the states of
+ the promises passed into the `promises` array argument.
+
+ Each state object will either indicate fulfillment or rejection, and
+ provide the corresponding value or reason. The states will take one of
+ the following formats:
+
+ ```javascript
+ { state: 'fulfilled', value: value }
+ or
+ { state: 'rejected', reason: reason }
+ ```
+
+ Example:
+
+ ```javascript
+ var promise1 = RSVP.Promise.resolve(1);
+ var promise2 = RSVP.Promise.reject(new Error('2'));
+ var promise3 = RSVP.Promise.reject(new Error('3'));
+ var promises = [ promise1, promise2, promise3 ];
+
+ RSVP.allSettled(promises).then(function(array){
+ // array == [
+ // { state: 'fulfilled', value: 1 },
+ // { state: 'rejected', reason: Error },
+ // { state: 'rejected', reason: Error }
+ // ]
+ // Note that for the second item, reason.message will be "2", and for the
+ // third item, reason.message will be "3".
+ }, function(error) {
+ // Not run. (This block would only be called if allSettled had failed,
+ // for instance if passed an incorrect argument type.)
+ });
+ ```
+
+ @method allSettled
+ @for RSVP
+ @param {Array} promises
+ @param {String} label - optional string that describes the promise.
+ Useful for tooling.
+ @return {Promise} promise that is fulfilled with an array of the settled
+ states of the constituent promises.
+ @static
+ */
+
+ __exports__["default"] = function allSettled(entries, label) {
+ return new Promise(function(resolve, reject) {
+ if (!isArray(entries)) {
+ throw new TypeError('You must pass an array to allSettled.');
+ }
+
+ var remaining = entries.length;
+ var entry;
+
+ if (remaining === 0) {
+ resolve([]);
+ return;
+ }
+
+ var results = new Array(remaining);
+
+ function fulfilledResolver(index) {
+ return function(value) {
+ resolveAll(index, fulfilled(value));
+ };
+ }
+
+ function rejectedResolver(index) {
+ return function(reason) {
+ resolveAll(index, rejected(reason));
+ };
+ }
+
+ function resolveAll(index, value) {
+ results[index] = value;
+ if (--remaining === 0) {
+ resolve(results);
+ }
+ }
+
+ for (var index = 0; index < entries.length; index++) {
+ entry = entries[index];
+
+ if (isNonThenable(entry)) {
+ resolveAll(index, fulfilled(entry));
+ } else {
+ Promise.cast(entry).then(fulfilledResolver(index), rejectedResolver(index));
+ }
+ }
+ }, label);
+ };
+
+ function fulfilled(value) {
+ return { state: 'fulfilled', value: value };
+ }
+
+ function rejected(reason) {
+ return { state: 'rejected', reason: reason };
+ }
+ });
+define("rsvp/config",
+ ["./events","exports"],
+ function(__dependency1__, __exports__) {
+ "use strict";
+ var EventTarget = __dependency1__["default"];
+
+ var config = {
+ instrument: false
+ };
+
+ EventTarget.mixin(config);
+
+ function configure(name, value) {
+ if (name === 'onerror') {
+ // handle for legacy users that expect the actual
+ // error to be passed to their function added via
+ // `RSVP.configure('onerror', someFunctionHere);`
+ config.on('error', value);
+ return;
+ }
+
+ if (arguments.length === 2) {
+ config[name] = value;
+ } else {
+ return config[name];
+ }
+ }
+
+ __exports__.config = config;
+ __exports__.configure = configure;
+ });
+define("rsvp/defer",
+ ["./promise","exports"],
+ function(__dependency1__, __exports__) {
+ "use strict";
+ var Promise = __dependency1__["default"];
+
+ /**
+ `RSVP.defer` returns an object similar to jQuery's `$.Deferred`.
+ `RSVP.defer` should be used when porting over code reliant on `$.Deferred`'s
+ interface. New code should use the `RSVP.Promise` constructor instead.
+
+ The object returned from `RSVP.defer` is a plain object with three properties:
+
+ * promise - an `RSVP.Promise`.
+ * reject - a function that causes the `promise` property on this object to
+ become rejected
+ * resolve - a function that causes the `promise` property on this object to
+ become fulfilled.
+
+ Example:
+
+ ```javascript
+ var deferred = RSVP.defer();
+
+ deferred.resolve("Success!");
+
+ defered.promise.then(function(value){
+ // value here is "Success!"
+ });
+ ```
+
+ @method defer
+ @for RSVP
+ @param {String} label optional string for labeling the promise.
+ Useful for tooling.
+ @return {Object}
+ */
+
+ __exports__["default"] = function defer(label) {
+ var deferred = { };
+
+ deferred.promise = new Promise(function(resolve, reject) {
+ deferred.resolve = resolve;
+ deferred.reject = reject;
+ }, label);
+
+ return deferred;
+ };
+ });
+define("rsvp/events",
+ ["exports"],
+ function(__exports__) {
+ "use strict";
+ var indexOf = function(callbacks, callback) {
+ for (var i=0, l=callbacks.length; i<l; i++) {
+ if (callbacks[i] === callback) { return i; }
+ }
+
+ return -1;
+ };
+
+ var callbacksFor = function(object) {
+ var callbacks = object._promiseCallbacks;
+
+ if (!callbacks) {
+ callbacks = object._promiseCallbacks = {};
+ }
+
+ return callbacks;
+ };
+
+ /**
+ @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
+ */
+ mixin: function(object) {
+ object.on = this.on;
+ object.off = this.off;
+ object.trigger = this.trigger;
+ object._promiseCallbacks = undefined;
+ return object;
+ },
+
+ /**
+ Registers a callback to be executed when `eventName` is triggered
+
+ ```javascript
+ object.on('event', function(eventInfo){
+ // handle the event
+ });
+
+ object.trigger('event');
+ ```
+
+ @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] = [];
+ }
+
+ if (indexOf(callbacks, callback) === -1) {
+ callbacks.push(callback);
+ }
+ },
+
+ /**
+ You can use `off` to stop firing a particular callback for an event:
+
+ ```javascript
+ function doStuff() { // do stuff! }
+ object.on('stuff', doStuff);
+
+ object.trigger('stuff'); // doStuff will be called
+
+ // Unregister ONLY the doStuff callback
+ object.off('stuff', doStuff);
+ object.trigger('stuff'); // doStuff will NOT be called
+ ```
+
+ 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;
+ }
+
+ callbacks = allCallbacks[eventName];
+
+ index = indexOf(callbacks, callback);
+
+ if (index !== -1) { callbacks.splice(index, 1); }
+ },
+
+ /**
+ 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
+ */
+ trigger: function(eventName, options) {
+ var allCallbacks = callbacksFor(this),
+ callbacks, callbackTuple, callback, binding;
+
+ if (callbacks = allCallbacks[eventName]) {
+ // Don't cache the callbacks.length since it may grow
+ for (var i=0; i<callbacks.length; i++) {
+ callback = callbacks[i];
+
+ 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);
+ }
+ };
+ }
+
+ function onRejection(reason) {
+ remaining = 0;
+ reject(reason);
+ }
+
+ for (var i = 0; i < keys.length; i++) {
+ property = keys[i];
+ entry = object[property];
+
+ if (isNonThenable(entry)) {
+ results[property] = entry;
+ if (--remaining === 0) {
+ resolve(results);
+ }
+ } else {
+ Promise.cast(entry).then(fulfilledTo(property), onRejection);
+ }
+ }
+ });
+ };
+ });
+define("rsvp/instrument",
+ ["./config","./utils","exports"],
+ function(__dependency1__, __dependency2__, __exports__) {
+ "use strict";
+ var config = __dependency1__.config;
+ var now = __dependency2__.now;
+
+ __exports__["default"] = function instrument(eventName, promise, child) {
+ // instrumentation should not disrupt normal usage.
+ try {
+ config.trigger(eventName, {
+ guid: promise._guidKey + promise._id,
+ eventName: eventName,
+ detail: promise._detail,
+ childGuid: child && promise._guidKey + child._id,
+ label: promise._label,
+ timeStamp: now(),
+ stack: new Error(promise._label).stack
+ });
+ } catch(error) {
+ setTimeout(function(){
+ throw error;
+ }, 0);
+ }
+ };
+ });
+define("rsvp/map",
+ ["./promise","./all","./utils","exports"],
+ function(__dependency1__, __dependency2__, __dependency3__, __exports__) {
+ "use strict";
+ var Promise = __dependency1__["default"];
+ var all = __dependency2__["default"];
+ var isArray = __dependency3__.isArray;
+ var isFunction = __dependency3__.isFunction;
+
+ /**
+ `RSVP.map` is similar to JavaScript's native `map` method, except that it
+ waits for all promises to become fulfilled before running the `mapFn` on
+ each item in given to `promises`. `RSVP.map` returns a promise that will
+ become fulfilled with the result of running `mapFn` on the values the promises
+ become fulfilled with.
+
+ For example:
+
+ ```javascript
+
+ var promise1 = RSVP.resolve(1);
+ var promise2 = RSVP.resolve(2);
+ var promise3 = RSVP.resolve(3);
+ var promises = [ promise1, promise2, promise3 ];
+
+ var mapFn = function(item){
+ return item + 1;
+ };
+
+ RSVP.map(promises, mapFn).then(function(result){
+ // result is [ 2, 3, 4 ]
+ });
+ ```
+
+ If any of the `promises` given to `RSVP.map` are rejected, the first promise
+ that is rejected will be given as an argument to the returned promise's
+ rejection handler. For example:
+
+ ```javascript
+ var promise1 = RSVP.resolve(1);
+ var promise2 = RSVP.reject(new Error("2"));
+ var promise3 = RSVP.reject(new Error("3"));
+ var promises = [ promise1, promise2, promise3 ];
+
+ var mapFn = function(item){
+ return item + 1;
+ };
+
+ RSVP.map(promises, mapFn).then(function(array){
+ // Code here never runs because there are rejected promises!
+ }, function(reason) {
+ // reason.message === "2"
+ });
+ ```
+
+ `RSVP.map` will also wait if a promise is returned from `mapFn`. For example,
+ say you want to get all comments from a set of blog posts, but you need
+ the blog posts first becuase they contain a url to those comments.
+
+ ```javscript
+
+ var mapFn = function(blogPost){
+ // getComments does some ajax and returns an RSVP.Promise that is fulfilled
+ // with some comments data
+ return getComments(blogPost.comments_url);
+ };
+
+ // getBlogPosts does some ajax and returns an RSVP.Promise that is fulfilled
+ // with some blog post data
+ RSVP.map(getBlogPosts(), mapFn).then(function(comments){
+ // comments is the result of asking the server for the comments
+ // of all blog posts returned from getBlogPosts()
+ });
+ ```
+
+ @method map
+ @for RSVP
+ @param {Array} promises
+ @param {Function} mapFn function to be called on each fulfilled promise.
+ @param {String} label optional string for labeling the promise.
+ Useful for tooling.
+ @return {Promise} promise that is fulfilled with the result of calling
+ `mapFn` on each fulfilled promise or value when they become fulfilled.
+ The promise will be rejected if any of the given `promises` become rejected.
+ @static
+ */
+ __exports__["default"] = function map(promises, mapFn, label) {
+ return all(promises, label).then(function(results){
+ if (!isArray(promises)) {
+ throw new TypeError('You must pass an array to map.');
+ }
+
+ if (!isFunction(mapFn)){
+ throw new TypeError("You must pass a function to map's second argument.");
+ }
+
+
+ var resultLen = results.length,
+ mappedResults = [],
+ i;
+
+ for (i = 0; i < resultLen; i++){
+ mappedResults.push(mapFn(results[i]));
+ }
+
+ return all(mappedResults, label);
+ });
+ };
+ });
+define("rsvp/node",
+ ["./promise","exports"],
+ function(__dependency1__, __exports__) {
+ "use strict";
+ var Promise = __dependency1__["default"];
+
+ var slice = Array.prototype.slice;
+
+ function makeNodeCallbackFor(resolve, reject) {
+ return function (error, value) {
+ if (error) {
+ reject(error);
+ } else if (arguments.length > 2) {
+ resolve(slice.call(arguments, 1));
+ } else {
+ resolve(value);
+ }
+ };
+ }
+
+ /**
+ `RSVP.denodeify` takes a "node-style" function and returns a function that
+ will return an `RSVP.Promise`. You can use `denodeify` in Node.js or the
+ browser when you'd prefer to use promises over using callbacks. For example,
+ `denodeify` transforms the following:
+
+ ```javascript
+ var fs = require('fs');
+
+ fs.readFile('myfile.txt', function(err, data){
+ if (err) return handleError(err);
+ handleData(data);
+ });
+ ```
+
+ into:
+
+ ```javascript
+ var fs = require('fs');
+
+ var readFile = RSVP.denodeify(fs.readFile);
+
+ readFile('myfile.txt').then(handleData, handleError);
+ ```
+
+ Using `denodeify` makes it easier to compose asynchronous operations instead
+ of using callbacks. For example, instead of:
+
+ ```javascript
+ var fs = require('fs');
+ var log = require('some-async-logger');
+
+ fs.readFile('myfile.txt', function(err, data){
+ if (err) return handleError(err);
+ fs.writeFile('myfile2.txt', data, function(err){
+ if (err) throw err;
+ log('success', function(err) {
+ if (err) throw err;
+ });
+ });
+ });
+ ```
+
+ You can chain the operations together using `then` from the returned promise:
+
+ ```javascript
+ var fs = require('fs');
+ var denodeify = RSVP.denodeify;
+ var readFile = denodeify(fs.readFile);
+ var writeFile = denodeify(fs.writeFile);
+ var log = denodeify(require('some-async-logger'));
+
+ readFile('myfile.txt').then(function(data){
+ return writeFile('myfile2.txt', data);
+ }).then(function(){
+ return log('SUCCESS');
+ }).then(function(){
+ // success handler
+ }, function(reason){
+ // rejection handler
+ });
+ ```
+
+ @method denodeify
+ @for RSVP
+ @param {Function} nodeFunc a "node-style" function that takes a callback as
+ its last argument. The callback expects an error to be passed as its first
+ argument (if an error occurred, otherwise null), and the value from the
+ operation as its second argument ("function(err, value){ }").
+ @param {Any} binding optional argument for binding the "this" value when
+ calling the `nodeFunc` function.
+ @return {Function} a function that wraps `nodeFunc` to return an
+ `RSVP.Promise`
+ @static
+ */
+ __exports__["default"] = function denodeify(nodeFunc, binding) {
+ return function() {
+ var nodeArgs = slice.call(arguments), resolve, reject;
+ var thisArg = this || binding;
+
+ return new Promise(function(resolve, reject) {
+ Promise.all(nodeArgs).then(function(nodeArgs) {
+ try {
+ nodeArgs.push(makeNodeCallbackFor(resolve, reject));
+ nodeFunc.apply(thisArg, nodeArgs);
+ } catch(e) {
+ reject(e);
+ }
+ });
+ });
+ };
+ };
+ });
+define("rsvp/promise",
+ ["./config","./events","./instrument","./utils","./promise/cast","./promise/all","./promise/race","./promise/resolve","./promise/reject","exports"],
+ function(__dependency1__, __dependency2__, __dependency3__, __dependency4__, __dependency5__, __dependency6__, __dependency7__, __dependency8__, __dependency9__, __exports__) {
+ "use strict";
+ var config = __dependency1__.config;
+ var EventTarget = __dependency2__["default"];
+ var instrument = __dependency3__["default"];
+ var objectOrFunction = __dependency4__.objectOrFunction;
+ var isFunction = __dependency4__.isFunction;
+ var now = __dependency4__.now;
+ var cast = __dependency5__["default"];
+ var all = __dependency6__["default"];
+ var race = __dependency7__["default"];
+ var Resolve = __dependency8__["default"];
+ var Reject = __dependency9__["default"];
+
+ var guidKey = 'rsvp_' + now() + '-';
+ var counter = 0;
+
+ function noop() {}
+
+ __exports__["default"] = Promise;
+
+
+ /**
+ Promise objects represent the eventual result of an asynchronous operation. The
+ primary way of interacting with a promise is through its `then` method, which
+ registers callbacks to receive either a promise’s eventual value or the reason
+ why the promise cannot be fulfilled.
+
+ Terminology
+ -----------
+
+ - `promise` is an object or function with a `then` method whose behavior conforms to this specification.
+ - `thenable` is an object or function that defines a `then` method.
+ - `value` is any legal JavaScript value (including undefined, a thenable, or a promise).
+ - `exception` is a value that is thrown using the throw statement.
+ - `reason` is a value that indicates why a promise was rejected.
+ - `settled` the final resting state of a promise, fulfilled or rejected.
+
+ A promise can be in one of three states: pending, fulfilled, or rejected.
+
+ Promises that are fulfilled have a fulfillment value and are in the fulfilled
+ state. Promises that are rejected have a rejection reason and are in the
+ rejected state. A fulfillment value is never a thenable. Similarly, a
+ rejection reason is never a thenable.
+
+ Promises can also be said to *resolve* a value. If this value is also a
+ promise, then the original promise's settled state will match the value's
+ settled state. So a promise that *resolves* a promise that rejects will
+ itself reject, and a promise that *resolves* a promise that fulfills will
+ itself fulfill.
+
+
+ Basic Usage:
+ ------------
+
+ ```js
+ var promise = new Promise(function(resolve, reject) {
+ // on success
+ resolve(value);
+
+ // on failure
+ reject(reason);
+ });
+
+ promise.then(function(value) {
+ // on fulfillment
+ }, function(reason) {
+ // on rejection
+ });
+ ```
+
+ Advanced Usage:
+ ---------------
+
+ Promises shine when abstracting away asynchronous interactions such as
+ `XMLHttpRequest`s.
+
+ ```js
+ function getJSON(url) {
+ return new Promise(function(resolve, reject){
+ var xhr = new XMLHttpRequest();
+
+ xhr.open('GET', url);
+ xhr.onreadystatechange = handler;
+ xhr.responseType = 'json';
+ xhr.setRequestHeader('Accept', 'application/json');
+ xhr.send();
+
+ function handler() {
+ if (this.readyState === this.DONE) {
+ if (this.status === 200) {
+ resolve(this.response);
+ } else {
+ reject(new Error("getJSON: `" + url + "` failed with status: [" + this.status + "]");
+ }
+ }
+ };
+ });
+ }
+
+ getJSON('/posts.json').then(function(json) {
+ // on fulfillment
+ }, function(reason) {
+ // on rejection
+ });
+ ```
+
+ Unlike callbacks, promises are great composable primitives.
+
+ ```js
+ Promise.all([
+ getJSON('/posts'),
+ getJSON('/comments')
+ ]).then(function(values){
+ values[0] // => postsJSON
+ values[1] // => commentsJSON
+
+ return values;
+ });
+ ```
+
+ @class RSVP.Promise
+ @param {function}
+ @param {String} label optional string for labeling the promise.
+ Useful for tooling.
+ @constructor
+ */
+ function Promise(resolver, label) {
+ if (!isFunction(resolver)) {
+ throw new TypeError('You must pass a resolver function as the first argument to the promise constructor');
+ }
+
+ if (!(this instanceof Promise)) {
+ throw new TypeError("Failed to construct 'Promise': Please use the 'new' operator, this object constructor cannot be called as a function.");
+ }
+
+ this._id = counter++;
+ this._label = label;
+ this._subscribers = [];
+
+ if (config.instrument) {
+ instrument('created', this);
+ }
+
+ if (noop !== resolver) {
+ invokeResolver(resolver, this);
+ }
+ }
+
+ function invokeResolver(resolver, promise) {
+ function resolvePromise(value) {
+ resolve(promise, value);
+ }
+
+ function rejectPromise(reason) {
+ reject(promise, reason);
+ }
+
+ try {
+ resolver(resolvePromise, rejectPromise);
+ } catch(e) {
+ rejectPromise(e);
+ }
+ }
+
+ Promise.cast = cast;
+ Promise.all = all;
+ Promise.race = race;
+ Promise.resolve = Resolve;
+ Promise.reject = Reject;
+
+ var PENDING = void 0;
+ var SEALED = 0;
+ var FULFILLED = 1;
+ var REJECTED = 2;
+
+ function subscribe(parent, child, onFulfillment, onRejection) {
+ var subscribers = parent._subscribers;
+ var length = subscribers.length;
+
+ subscribers[length] = child;
+ subscribers[length + FULFILLED] = onFulfillment;
+ subscribers[length + REJECTED] = onRejection;
+ }
+
+ function publish(promise, settled) {
+ var child, callback, subscribers = promise._subscribers, detail = promise._detail;
+
+ if (config.instrument) {
+ instrument(settled === FULFILLED ? 'fulfilled' : 'rejected', promise);
+ }
+
+ for (var i = 0; i < subscribers.length; i += 3) {
+ child = subscribers[i];
+ callback = subscribers[i + settled];
+
+ invokeCallback(settled, child, callback, detail);
+ }
+
+ promise._subscribers = null;
+ }
+
+ Promise.prototype = {
+ constructor: Promise,
+
+ _id: undefined,
+ _guidKey: guidKey,
+ _label: undefined,
+
+ _state: undefined,
+ _detail: undefined,
+ _subscribers: undefined,
+
+ _onerror: function (reason) {
+ config.trigger('error', reason);
+ },
+
+ /**
+ The primary way of interacting with a promise is through its `then` method,
+ which registers callbacks to receive either a promise's eventual value or the
+ reason why the promise cannot be fulfilled.
+
+ ```js
+ findUser().then(function(user){
+ // user is available
+ }, function(reason){
+ // user is unavailable, and you are given the reason why
+ });
+ ```
+
+ Chaining
+ --------
+
+ The return value of `then` is itself a promise. This second, "downstream"
+ promise is resolved with the return value of the first promise's fulfillment
+ or rejection handler, or rejected if the handler throws an exception.
+
+ ```js
+ findUser().then(function (user) {
+ return user.name;
+ }, function (reason) {
+ return "default name";
+ }).then(function (userName) {
+ // If `findUser` fulfilled, `userName` will be the user's name, otherwise it
+ // will be `"default name"`
+ });
+
+ findUser().then(function (user) {
+ throw new Error("Found user, but still unhappy");
+ }, function (reason) {
+ throw new Error("`findUser` rejected and we're unhappy");
+ }).then(function (value) {
+ // never reached
+ }, function (reason) {
+ // if `findUser` fulfilled, `reason` will be "Found user, but still unhappy".
+ // If `findUser` rejected, `reason` will be "`findUser` rejected and we're unhappy".
+ });
+ ```
+ If the downstream promise does not specify a rejection handler, rejection reasons will be propagated further downstream.
+
+ ```js
+ findUser().then(function (user) {
+ throw new PedagogicalException("Upstream error");
+ }).then(function (value) {
+ // never reached
+ }).then(function (value) {
+ // never reached
+ }, function (reason) {
+ // The `PedgagocialException` is propagated all the way down to here
+ });
+ ```
+
+ Assimilation
+ ------------
+
+ Sometimes the value you want to propagate to a downstream promise can only be
+ retrieved asynchronously. This can be achieved by returning a promise in the
+ fulfillment or rejection handler. The downstream promise will then be pending
+ until the returned promise is settled. This is called *assimilation*.
+
+ ```js
+ findUser().then(function (user) {
+ return findCommentsByAuthor(user);
+ }).then(function (comments) {
+ // The user's comments are now available
+ });
+ ```
+
+ If the assimliated promise rejects, then the downstream promise will also reject.
+
+ ```js
+ findUser().then(function (user) {
+ return findCommentsByAuthor(user);
+ }).then(function (comments) {
+ // If `findCommentsByAuthor` fulfills, we'll have the value here
+ }, function (reason) {
+ // If `findCommentsByAuthor` rejects, we'll have the reason here
+ });
+ ```
+
+ Simple Example
+ --------------
+
+ Synchronous Example
+
+ ```javascript
+ var result;
+
+ try {
+ result = findResult();
+ // success
+ } catch(reason) {
+ // failure
+ }
+ ```
+
+ Errback Example
+
+ ```js
+ findResult(function(result, err){
+ if (err) {
+ // failure
+ } else {
+ // success
+ }
+ });
+ ```
+
+ Promise Example;
+
+ ```javascript
+ findResult().then(function(result){
+ // success
+ }, function(reason){
+ // failure
+ });
+ ```
+
+ Advanced Example
+ --------------
+
+ Synchronous Example
+
+ ```javascript
+ var author, books;
+
+ try {
+ author = findAuthor();
+ books = findBooksByAuthor(author);
+ // success
+ } catch(reason) {
+ // failure
+ }
+ ```
+
+ Errback Example
+
+ ```js
+
+ function foundBooks(books) {
+
+ }
+
+ function failure(reason) {
+
+ }
+
+ findAuthor(function(author, err){
+ if (err) {
+ failure(err);
+ // failure
+ } else {
+ try {
+ findBoooksByAuthor(author, function(books, err) {
+ if (err) {
+ failure(err);
+ } else {
+ try {
+ foundBooks(books);
+ } catch(reason) {
+ failure(reason);
+ }
+ }
+ });
+ } catch(error) {
+ failure(err);
+ }
+ // success
+ }
+ });
+ ```
+
+ Promise Example;
+
+ ```javascript
+ findAuthor().
+ then(findBooksByAuthor).
+ then(function(books){
+ // found books
+ }).catch(function(reason){
+ // something went wrong
+ });
+ ```
+
+ @method then
+ @param {Function} onFulfilled
+ @param {Function} onRejected
+ @param {String} label optional string for labeling the promise.
+ Useful for tooling.
+ @return {Promise}
+ */
+ then: function(onFulfillment, onRejection, label) {
+ var promise = this;
+ this._onerror = null;
+
+ var thenPromise = new this.constructor(noop, label);
+
+ if (this._state) {
+ var callbacks = arguments;
+ config.async(function invokePromiseCallback() {
+ invokeCallback(promise._state, thenPromise, callbacks[promise._state - 1], promise._detail);
+ });
+ } else {
+ subscribe(this, thenPromise, onFulfillment, onRejection);
+ }
+
+ if (config.instrument) {
+ instrument('chained', promise, thenPromise);
+ }
+
+ return thenPromise;
+ },
+
+ /**
+ `catch` is simply sugar for `then(undefined, onRejection)` which makes it the same
+ as the catch block of a try/catch statement.
+
+ ```js
+ function findAuthor(){
+ throw new Error("couldn't find that author");
+ }
+
+ // synchronous
+ try {
+ findAuthor();
+ } catch(reason) {
+ // something went wrong
+ }
+
+ // async with promises
+ findAuthor().catch(function(reason){
+ // something went wrong
+ });
+ ```
+
+ @method catch
+ @param {Function} onRejection
+ @param {String} label optional string for labeling the promise.
+ Useful for tooling.
+ @return {Promise}
+ */
+ 'catch': function(onRejection, label) {
+ return this.then(null, onRejection, label);
+ },
+
+ /**
+ `finally` will be invoked regardless of the promise's fate just as native
+ try/catch/finally behaves
+
+ Synchronous example:
+
+ ```js
+ findAuthor() {
+ if (Math.random() > 0.5) {
+ throw new Error();
+ }
+ return new Author();
+ }
+
+ try {
+ return findAuthor(); // succeed or fail
+ } catch(error) {
+ return findOtherAuther();
+ } finally {
+ // always runs
+ // doesn't affect the return value
+ }
+ ```
+
+ Asynchronous example:
+
+ ```js
+ findAuthor().catch(function(reason){
+ return findOtherAuther();
+ }).finally(function(){
+ // author was either found, or not
+ });
+ ```
+
+ @method finally
+ @param {Function} callback
+ @param {String} label optional string for labeling the promise.
+ Useful for tooling.
+ @return {Promise}
+ */
+ 'finally': function(callback, label) {
+ var constructor = this.constructor;
+
+ return this.then(function(value) {
+ return constructor.cast(callback()).then(function(){
+ return value;
+ });
+ }, function(reason) {
+ return constructor.cast(callback()).then(function(){
+ throw reason;
+ });
+ }, label);
+ }
+ };
+
+ function invokeCallback(settled, promise, callback, detail) {
+ var hasCallback = isFunction(callback),
+ value, error, succeeded, failed;
+
+ if (hasCallback) {
+ try {
+ value = callback(detail);
+ succeeded = true;
+ } catch(e) {
+ failed = true;
+ error = e;
+ }
+ } else {
+ value = detail;
+ succeeded = true;
+ }
+
+ if (handleThenable(promise, value)) {
+ return;
+ } else if (hasCallback && succeeded) {
+ resolve(promise, value);
+ } else if (failed) {
+ reject(promise, error);
+ } else if (settled === FULFILLED) {
+ resolve(promise, value);
+ } else if (settled === REJECTED) {
+ reject(promise, value);
+ }
+ }
+
+ function handleThenable(promise, value) {
+ var then = null,
+ resolved;
+
+ try {
+ if (promise === value) {
+ throw new TypeError("A promises callback cannot return that same promise.");
+ }
+
+ if (objectOrFunction(value)) {
+ then = value.then;
+
+ if (isFunction(then)) {
+ then.call(value, function(val) {
+ if (resolved) { return true; }
+ resolved = true;
+
+ if (value !== val) {
+ resolve(promise, val);
+ } else {
+ fulfill(promise, val);
+ }
+ }, function(val) {
+ if (resolved) { return true; }
+ resolved = true;
+
+ reject(promise, val);
+ }, 'derived from: ' + (promise._label || ' unknown promise'));
+
+ return true;
+ }
+ }
+ } catch (error) {
+ if (resolved) { return true; }
+ reject(promise, error);
+ return true;
+ }
+
+ return false;
+ }
+
+ function resolve(promise, value) {
+ if (promise === value) {
+ fulfill(promise, value);
+ } else if (!handleThenable(promise, value)) {
+ fulfill(promise, value);
+ }
+ }
+
+ function fulfill(promise, value) {
+ if (promise._state !== PENDING) { return; }
+ promise._state = SEALED;
+ promise._detail = value;
+
+ config.async(publishFulfillment, promise);
+ }
+
+ function reject(promise, reason) {
+ if (promise._state !== PENDING) { return; }
+ promise._state = SEALED;
+ promise._detail = reason;
+
+ config.async(publishRejection, promise);
+ }
+
+ function publishFulfillment(promise) {
+ publish(promise, promise._state = FULFILLED);
+ }
+
+ function publishRejection(promise) {
+ if (promise._onerror) {
+ promise._onerror(promise._detail);
+ }
+
+ publish(promise, promise._state = REJECTED);
+ }
+ });
+define("rsvp/promise/all",
+ ["../utils","exports"],
+ function(__dependency1__, __exports__) {
+ "use strict";
+ var isArray = __dependency1__.isArray;
+ var isNonThenable = __dependency1__.isNonThenable;
+
+ /**
+ `RSVP.Promise.all` accepts an array of promises, and returns a new promise which
+ is fulfilled with an array of fulfillment values for the passed promises, or
+ rejected with the reason of the first passed promise to be rejected. It casts all
+ elements of the passed iterable to promises as it runs this algorithm.
+
+ Example:
+
+ ```javascript
+ var promise1 = RSVP.resolve(1);
+ var promise2 = RSVP.resolve(2);
+ var promise3 = RSVP.resolve(3);
+ var promises = [ promise1, promise2, promise3 ];
+
+ RSVP.Promise.all(promises).then(function(array){
+ // The array here would be [ 1, 2, 3 ];
+ });
+ ```
+
+ If any of the `promises` given to `RSVP.all` are rejected, the first promise
+ that is rejected will be given as an argument to the returned promises's
+ rejection handler. For example:
+
+ Example:
+
+ ```javascript
+ var promise1 = RSVP.resolve(1);
+ var promise2 = RSVP.reject(new Error("2"));
+ var promise3 = RSVP.reject(new Error("3"));
+ var promises = [ promise1, promise2, promise3 ];
+
+ RSVP.Promise.all(promises).then(function(array){
+ // Code here never runs because there are rejected promises!
+ }, function(error) {
+ // error.message === "2"
+ });
+ ```
+
+ @method all
+ @for Ember.RSVP.Promise
+ @param {Array} entries array of promises
+ @param {String} label optional string for labeling the promise.
+ Useful for tooling.
+ @return {Promise} promise that is fulfilled when all `promises` have been
+ fulfilled, or rejected if any of them become rejected.
+ @static
+ */
+ __exports__["default"] = function all(entries, label) {
+
+ /*jshint validthis:true */
+ var Constructor = this;
+
+ return new Constructor(function(resolve, reject) {
+ if (!isArray(entries)) {
+ throw new TypeError('You must pass an array to all.');
+ }
+
+ var remaining = entries.length;
+ var results = new Array(remaining);
+ var entry, pending = true;
+
+ if (remaining === 0) {
+ resolve(results);
+ return;
+ }
+
+ function fulfillmentAt(index) {
+ return function(value) {
+ results[index] = value;
+ if (--remaining === 0) {
+ resolve(results);
+ }
+ };
+ }
+
+ function onRejection(reason) {
+ remaining = 0;
+ reject(reason);
+ }
+
+ for (var index = 0; index < entries.length; index++) {
+ entry = entries[index];
+ if (isNonThenable(entry)) {
+ results[index] = entry;
+ if (--remaining === 0) {
+ resolve(results);
+ }
+ } else {
+ Constructor.cast(entry).then(fulfillmentAt(index), onRejection);
+ }
+ }
+ }, label);
+ };
+ });
+define("rsvp/promise/cast",
+ ["exports"],
+ function(__exports__) {
+ "use strict";
+ /**
+ `RSVP.Promise.cast` coerces its argument to a promise, or returns the
+ argument if it is already a promise which shares a constructor with the caster.
+
+ Example:
+
+ ```javascript
+ var promise = RSVP.Promise.resolve(1);
+ var casted = RSVP.Promise.cast(promise);
+
+ console.log(promise === casted); // true
+ ```
+
+ In the case of a promise whose constructor does not match, it is assimilated.
+ The resulting promise will fulfill or reject based on the outcome of the
+ promise being casted.
+
+ Example:
+
+ ```javascript
+ var thennable = $.getJSON('/api/foo');
+ var casted = RSVP.Promise.cast(thennable);
+
+ console.log(thennable === casted); // false
+ console.log(casted instanceof RSVP.Promise) // true
+
+ casted.then(function(data) {
+ // data is the value getJSON fulfills with
+ });
+ ```
+
+ In the case of a non-promise, a promise which will fulfill with that value is
+ returned.
+
+ Example:
+
+ ```javascript
+ var value = 1; // could be a number, boolean, string, undefined...
+ var casted = RSVP.Promise.cast(value);
+
+ console.log(value === casted); // false
+ console.log(casted instanceof RSVP.Promise) // true
+
+ casted.then(function(val) {
+ val === value // => true
+ });
+ ```
+
+ `RSVP.Promise.cast` is similar to `RSVP.Promise.resolve`, but `RSVP.Promise.cast` differs in the
+ following ways:
+
+ * `RSVP.Promise.cast` serves as a memory-efficient way of getting a promise, when you
+ have something that could either be a promise or a value. RSVP.resolve
+ will have the same effect but will create a new promise wrapper if the
+ argument is a promise.
+ * `RSVP.Promise.cast` is a way of casting incoming thenables or promise subclasses to
+ promises of the exact class specified, so that the resulting object's `then` is
+ ensured to have the behavior of the constructor you are calling cast on (i.e., RSVP.Promise).
+
+ @method cast
+ @param {Object} object to be casted
+ @param {String} label optional string for labeling the promise.
+ Useful for tooling.
+ @return {Promise} promise
+ @static
+ */
+
+ __exports__["default"] = function cast(object, label) {
+ /*jshint validthis:true */
+ var Constructor = this;
+
+ if (object && typeof object === 'object' && object.constructor === Constructor) {
+ return object;
+ }
+
+ return new Constructor(function(resolve) {
+ resolve(object);
+ }, label);
+ };
+ });
+define("rsvp/promise/race",
+ ["../utils","exports"],
+ function(__dependency1__, __exports__) {
+ "use strict";
+ /* global toString */
+
+ var isArray = __dependency1__.isArray;
+ var isFunction = __dependency1__.isFunction;
+ var isNonThenable = __dependency1__.isNonThenable;
+
+ /**
+ `RSVP.Promise.race` returns a new promise which is settled in the same way as the
+ first passed promise to settle.
+
+ Example:
+
+ ```javascript
+ var promise1 = new RSVP.Promise(function(resolve, reject){
+ setTimeout(function(){
+ resolve("promise 1");
+ }, 200);
+ });
+
+ var promise2 = new RSVP.Promise(function(resolve, reject){
+ setTimeout(function(){
+ resolve("promise 2");
+ }, 100);
+ });
+
+ RSVP.Promise.race([promise1, promise2]).then(function(result){
+ // result === "promise 2" because it was resolved before promise1
+ // was resolved.
+ });
+ ```
+
+ `RSVP.Promise.race` is deterministic in that only the state of the first
+ settled promise matters. For example, even if other promises given to the
+ `promises` array argument are resolved, but the first settled promise has
+ become rejected before the other promises became fulfilled, the returned
+ promise will become rejected:
+
+ ```javascript
+ var promise1 = new RSVP.Promise(function(resolve, reject){
+ setTimeout(function(){
+ resolve("promise 1");
+ }, 200);
+ });
+
+ var promise2 = new RSVP.Promise(function(resolve, reject){
+ setTimeout(function(){
+ reject(new Error("promise 2"));
+ }, 100);
+ });
+
+ RSVP.Promise.race([promise1, promise2]).then(function(result){
+ // Code here never runs
+ }, function(reason){
+ // reason.message === "promise2" because promise 2 became rejected before
+ // promise 1 became fulfilled
+ });
+ ```
+
+ An example real-world use case is implementing timeouts:
+
+ ```javascript
+ RSVP.Promise.race([ajax('foo.json'), timeout(5000)])
+ ```
+
+ @method race
+ @param {Array} promises array of promises to observe
+ @param {String} label optional string for describing the promise returned.
+ Useful for tooling.
+ @return {Promise} a promise which settles in the same way as the first passed
+ promise to settle.
+ @static
+ */
+ __exports__["default"] = function race(entries, label) {
+ /*jshint validthis:true */
+ var Constructor = this, entry;
+
+ return new Constructor(function(resolve, reject) {
+ if (!isArray(entries)) {
+ throw new TypeError('You must pass an array to race.');
+ }
+
+ var pending = true;
+
+ function onFulfillment(value) { if (pending) { pending = false; resolve(value); } }
+ function onRejection(reason) { if (pending) { pending = false; reject(reason); } }
+
+ for (var i = 0; i < entries.length; i++) {
+ entry = entries[i];
+ if (isNonThenable(entry)) {
+ pending = false;
+ resolve(entry);
+ return;
+ } else {
+ Constructor.cast(entry).then(onFulfillment, onRejection);
+ }
+ }
+ }, label);
+ };
+ });
+define("rsvp/promise/reject",
+ ["exports"],
+ function(__exports__) {
+ "use strict";
+ /**
+ `RSVP.Promise.reject` returns a promise rejected with the passed `reason`.
+ It is shorthand for the following:
+
+ ```javascript
+ var promise = new RSVP.Promise(function(resolve, reject){
+ reject(new Error('WHOOPS'));
+ });
+
+ promise.then(function(value){
+ // Code here doesn't run because the promise is rejected!
+ }, function(reason){
+ // reason.message === 'WHOOPS'
+ });
+ ```
+
+ Instead of writing the above, your code now simply becomes the following:
+
+ ```javascript
+ var promise = RSVP.Promise.reject(new Error('WHOOPS'));
+
+ promise.then(function(value){
+ // Code here doesn't run because the promise is rejected!
+ }, function(reason){
+ // reason.message === 'WHOOPS'
+ });
+ ```
+
+ @method reject
+ @param {Any} reason value that the returned promise will be rejected with.
+ @param {String} label optional string for identifying the returned promise.
+ Useful for tooling.
+ @return {Promise} a promise rejected with the given `reason`.
+ @static
+ */
+ __exports__["default"] = function reject(reason, label) {
+ /*jshint validthis:true */
+ var Constructor = this;
+
+ return new Constructor(function (resolve, reject) {
+ reject(reason);
+ }, label);
+ };
+ });
+define("rsvp/promise/resolve",
+ ["exports"],
+ function(__exports__) {
+ "use strict";
+ /**
+ `RSVP.Promise.resolve` returns a promise that will become resolved with the
+ passed `value`. It is shorthand for the following:
+
+ ```javascript
+ var promise = new RSVP.Promise(function(resolve, reject){
+ resolve(1);
+ });
+
+ promise.then(function(value){
+ // value === 1
+ });
+ ```
+
+ Instead of writing the above, your code now simply becomes the following:
+
+ ```javascript
+ var promise = RSVP.Promise.resolve(1);
+
+ promise.then(function(value){
+ // value === 1
+ });
+ ```
+
+ @method resolve
+ @param {Any} value value that the returned promise will be resolved with
+ @param {String} label optional string for identifying the returned promise.
+ Useful for tooling.
+ @return {Promise} a promise that will become fulfilled with the given
+ `value`
+ @static
+ */
+ __exports__["default"] = function resolve(value, label) {
+ /*jshint validthis:true */
+ var Constructor = this;
+
+ return new Constructor(function(resolve, reject) {
+ resolve(value);
+ }, label);
+ };
+ });
+define("rsvp/race",
+ ["./promise","exports"],
+ function(__dependency1__, __exports__) {
+ "use strict";
+ var Promise = __dependency1__["default"];
+
+ /**
+ This is a convenient alias for `RSVP.Promise.race`.
+
+ @method race
+ @param {Array} array Array of promises.
+ @param {String} label An optional label. This is useful
+ for tooling.
+ @static
+ */
+ __exports__["default"] = function race(array, label) {
+ return Promise.race(array, label);
+ };
+ });
+define("rsvp/reject",
+ ["./promise","exports"],
+ function(__dependency1__, __exports__) {
+ "use strict";
+ var Promise = __dependency1__["default"];
+
+ /**
+ This is a convenient alias for `RSVP.Promise.reject`.
+
+ @method reject
+ @for RSVP
+ @param {Any} reason value that the returned promise will be rejected with.
+ @param {String} label optional string for identifying the returned promise.
+ Useful for tooling.
+ @return {Promise} a promise rejected with the given `reason`.
+ @static
+ */
+ __exports__["default"] = function reject(reason, label) {
+ return Promise.reject(reason, label);
+ };
+ });
+define("rsvp/resolve",
+ ["./promise","exports"],
+ function(__dependency1__, __exports__) {
+ "use strict";
+ var Promise = __dependency1__["default"];
+
+ /**
+ This is a convenient alias for `RSVP.Promise.resolve`.
+
+ @method resolve
+ @for RSVP
+ @param {Any} value value that the returned promise will be resolved with
+ @param {String} label optional string for identifying the returned promise.
+ Useful for tooling.
+ @return {Promise} a promise that will become fulfilled with the given
+ `value`
+ @static
+ */
+ __exports__["default"] = function resolve(value, label) {
+ return Promise.resolve(value, label);
+ };
+ });
+define("rsvp/rethrow",
+ ["exports"],
+ function(__exports__) {
+ "use strict";
+ /**
+ `RSVP.rethrow` will rethrow an error on the next turn of the JavaScript event
+ loop in order to aid debugging.
+
+ Promises A+ specifies that any exceptions that occur with a promise must be
+ caught by the promises implementation and bubbled to the last handler. For
+ this reason, it is recommended that you always specify a second rejection
+ handler function to `then`. However, `RSVP.rethrow` will throw the exception
+ outside of the promise, so it bubbles up to your console if in the browser,
+ or domain/cause uncaught exception in Node. `rethrow` will also throw the
+ error again so the error can be handled by the promise per the spec.
+
+ ```javascript
+ function throws(){
+ throw new Error('Whoops!');
+ }
+
+ var promise = new RSVP.Promise(function(resolve, reject){
+ throws();
+ });
+
+ promise.catch(RSVP.rethrow).then(function(){
+ // Code here doesn't run because the promise became rejected due to an
+ // error!
+ }, function (err){
+ // handle the error here
+ });
+ ```
+
+ The 'Whoops' error will be thrown on the next turn of the event loop
+ and you can watch for it in your console. You can also handle it using a
+ rejection handler given to `.then` or `.catch` on the returned promise.
+
+ @method rethrow
+ @for RSVP
+ @param {Error} reason reason the promise became rejected.
+ @throws Error
+ @static
+ */
+ __exports__["default"] = function rethrow(reason) {
+ setTimeout(function() {
+ throw reason;
+ });
+ throw reason;
+ };
+ });
+define("rsvp/utils",
+ ["exports"],
+ function(__exports__) {
+ "use strict";
+ function objectOrFunction(x) {
+ return typeof x === "function" || (typeof x === "object" && x !== null);
+ }
+
+ __exports__.objectOrFunction = objectOrFunction;function isFunction(x) {
+ return typeof x === "function";
+ }
+
+ __exports__.isFunction = isFunction;function isNonThenable(x) {
+ return !objectOrFunction(x);
+ }
+
+ __exports__.isNonThenable = isNonThenable;function isArray(x) {
+ return Object.prototype.toString.call(x) === "[object Array]";
+ }
+
+ __exports__.isArray = isArray;// Date.now is not available in browsers < IE9
+ // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Ob…
+ var now = Date.now || function() { return new Date().getTime(); };
+ __exports__.now = now;
+ var keysOf = Object.keys || function(object) {
+ var result = [];
+
+ for (var prop in object) {
+ result.push(prop);
+ }
+
+ return result;
+ };
+ __exports__.keysOf = keysOf;
+ });
+define("rsvp",
+ ["./rsvp/promise","./rsvp/events","./rsvp/node","./rsvp/all","./rsvp/all_settled","./rsvp/race","./rsvp/hash","./rsvp/rethrow","./rsvp/defer","./rsvp/config","./rsvp/map","./rsvp/resolve","./rsvp/reject","./rsvp/filter","exports"],
+ function(__dependency1__, __dependency2__, __dependency3__, __dependency4__, __dependency5__, __dependency6__, __dependency7__, __dependency8__, __dependency9__, __dependency10__, __dependency11__, __dependency12__, __dependency13__, __dependency14__, __exports__) {
+ "use strict";
+ var Promise = __dependency1__["default"];
+ var EventTarget = __dependency2__["default"];
+ var denodeify = __dependency3__["default"];
+ var all = __dependency4__["default"];
+ var allSettled = __dependency5__["default"];
+ var race = __dependency6__["default"];
+ var hash = __dependency7__["default"];
+ var rethrow = __dependency8__["default"];
+ var defer = __dependency9__["default"];
+ var config = __dependency10__.config;
+ var configure = __dependency10__.configure;
+ var map = __dependency11__["default"];
+ var resolve = __dependency12__["default"];
+ var reject = __dependency13__["default"];
+ var filter = __dependency14__["default"];
+
+ function async(callback, arg) {
+ config.async(callback, arg);
+ }
+
+ function on() {
+ config.on.apply(config, arguments);
+ }
+
+ function off() {
+ config.off.apply(config, arguments);
+ }
+
+ // Set up instrumentation through `window.__PROMISE_INTRUMENTATION__`
+ if (typeof window !== 'undefined' && typeof window.__PROMISE_INSTRUMENTATION__ === 'object') {
+ var callbacks = window.__PROMISE_INSTRUMENTATION__;
+ configure('instrument', true);
+ for (var eventName in callbacks) {
+ if (callbacks.hasOwnProperty(eventName)) {
+ on(eventName, callbacks[eventName]);
+ }
+ }
+ }
+
+ __exports__.Promise = Promise;
+ __exports__.EventTarget = EventTarget;
+ __exports__.all = all;
+ __exports__.allSettled = allSettled;
+ __exports__.race = race;
+ __exports__.hash = hash;
+ __exports__.rethrow = rethrow;
+ __exports__.defer = defer;
+ __exports__.denodeify = denodeify;
+ __exports__.configure = configure;
+ __exports__.on = on;
+ __exports__.off = off;
+ __exports__.resolve = resolve;
+ __exports__.reject = reject;
+ __exports__.async = async;
+ __exports__.map = map;
+ __exports__.filter = filter;
+ });
+
+})();
+
+(function() {
+define("container/container",
+ ["container/inheriting_dict","exports"],
+ function(__dependency1__, __exports__) {
+ "use strict";
+ var InheritingDict = __dependency1__["default"];
+
+ // A lightweight container that helps to assemble and decouple components.
+ // Public api for the container is still in flux.
+ // The public api, specified on the application namespace should be considered the stable api.
+ function Container(parent) {
+ this.parent = parent;
+ this.children = [];
+
+ this.resolver = parent && parent.resolver || function() {};
+
+ this.registry = new InheritingDict(parent && parent.registry);
+ this.cache = new InheritingDict(parent && parent.cache);
+ this.factoryCache = new InheritingDict(parent && parent.factoryCache);
+ this.resolveCache = new InheritingDict(parent && parent.resolveCache);
+ this.typeInjections = new InheritingDict(parent && parent.typeInjections);
+ this.injections = {};
+
+ this.factoryTypeInjections = new InheritingDict(parent && parent.factoryTypeInjections);
+ this.factoryInjections = {};
+
+ this._options = new InheritingDict(parent && parent._options);
+ this._typeOptions = new InheritingDict(parent && parent._typeOptions);
+ }
+
+ Container.prototype = {
+
+ /**
+ @property parent
+ @type Container
+ @default null
+ */
+ parent: null,
+
+ /**
+ @property children
+ @type Array
+ @default []
+ */
+ children: null,
+
+ /**
+ @property resolver
+ @type function
+ */
+ resolver: null,
+
+ /**
+ @property registry
+ @type InheritingDict
+ */
+ registry: null,
+
+ /**
+ @property cache
+ @type InheritingDict
+ */
+ cache: null,
+
+ /**
+ @property typeInjections
+ @type InheritingDict
+ */
+ typeInjections: null,
+
+ /**
+ @property injections
+ @type Object
+ @default {}
+ */
+ injections: null,
+
+ /**
+ @private
+
+ @property _options
+ @type InheritingDict
+ @default null
+ */
+ _options: null,
+
+ /**
+ @private
+
+ @property _typeOptions
+ @type InheritingDict
+ */
+ _typeOptions: null,
+
+ /**
+ Returns a new child of the current container. These children are configured
+ to correctly inherit from the current container.
+
+ @method child
+ @return {Container}
+ */
+ child: function() {
+ var container = new Container(this);
+ this.children.push(container);
+ return container;
+ },
+
+ /**
+ Sets a key-value pair on the current container. If a parent container,
+ has the same key, once set on a child, the parent and child will diverge
+ as expected.
+
+ @method set
+ @param {Object} object
+ @param {String} key
+ @param {any} value
+ */
+ set: function(object, key, value) {
+ object[key] = value;
+ },
+
+ /**
+ Registers a factory for later injection.
+
+ Example:
+
+ ```javascript
+ var container = new Container();
+
+ container.register('model:user', Person, {singleton: false });
+ container.register('fruit:favorite', Orange);
+ container.register('communication:main', Email, {singleton: false});
+ ```
+
+ @method register
+ @param {String} fullName
+ @param {Function} factory
+ @param {Object} options
+ */
+ register: function(fullName, factory, options) {
+ validateFullName(fullName);
+
+ if (factory === undefined) {
+ throw new TypeError('Attempting to register an unknown factory: `' + fullName + '`');
+ }
+
+ var normalizedName = this.normalize(fullName);
+
+ if (this.cache.has(normalizedName)) {
+ throw new Error('Cannot re-register: `' + fullName +'`, as it has already been looked up.');
+ }
+
+ this.registry.set(normalizedName, factory);
+ this._options.set(normalizedName, options || {});
+ },
+
+ /**
+ Unregister a fullName
+
+ ```javascript
+ var container = new Container();
+ container.register('model:user', User);
+
+ container.lookup('model:user') instanceof User //=> true
+
+ container.unregister('model:user')
+ container.lookup('model:user') === undefined //=> true
+ ```
+
+ @method unregister
+ @param {String} fullName
+ */
+ unregister: function(fullName) {
+ validateFullName(fullName);
+
+ var normalizedName = this.normalize(fullName);
+
+ this.registry.remove(normalizedName);
+ this.cache.remove(normalizedName);
+ this.factoryCache.remove(normalizedName);
+ this.resolveCache.remove(normalizedName);
+ this._options.remove(normalizedName);
+ },
+
+ /**
+ Given a fullName return the corresponding factory.
+
+ By default `resolve` will retrieve the factory from
+ its container's registry.
+
+ ```javascript
+ var container = new Container();
+ container.register('api:twitter', Twitter);
+
+ container.resolve('api:twitter') // => Twitter
+ ```
+
+ Optionally the container can be provided with a custom resolver.
+ If provided, `resolve` will first provide the custom resolver
+ the oppertunity to resolve the fullName, otherwise it will fallback
+ to the registry.
+
+ ```javascript
+ var container = new Container();
+ container.resolver = function(fullName) {
+ // lookup via the module system of choice
+ };
+
+ // the twitter factory is added to the module system
+ container.resolve('api:twitter') // => Twitter
+ ```
+
+ @method resolve
+ @param {String} fullName
+ @return {Function} fullName's factory
+ */
+ resolve: function(fullName) {
+ validateFullName(fullName);
+
+ var normalizedName = this.normalize(fullName);
+ var cached = this.resolveCache.get(normalizedName);
+
+ if (cached) { return cached; }
+
+ var resolved = this.resolver(normalizedName) || this.registry.get(normalizedName);
+
+ this.resolveCache.set(normalizedName, resolved);
+
+ return resolved;
+ },
+
+ /**
+ A hook that can be used to describe how the resolver will
+ attempt to find the factory.
+
+ For example, the default Ember `.describe` returns the full
+ class name (including namespace) where Ember's resolver expects
+ to find the `fullName`.
+
+ @method describe
+ @param {String} fullName
+ @return {string} described fullName
+ */
+ describe: function(fullName) {
+ return fullName;
+ },
+
+ /**
+ A hook to enable custom fullName normalization behaviour
+
+ @method normalize
+ @param {String} fullName
+ @return {string} normalized fullName
+ */
+ normalize: function(fullName) {
+ return fullName;
+ },
+
+ /**
+ @method makeToString
+
+ @param {any} factory
+ @param {string} fullName
+ @return {function} toString function
+ */
+ makeToString: function(factory, fullName) {
+ return factory.toString();
+ },
+
+ /**
+ Given a fullName return a corresponding instance.
+
+ The default behaviour is for lookup to return a singleton instance.
+ The singleton is scoped to the container, allowing multiple containers
+ to all have their own locally scoped singletons.
+
+ ```javascript
+ var container = new Container();
+ container.register('api:twitter', Twitter);
+
+ var twitter = container.lookup('api:twitter');
+
+ twitter instanceof Twitter; // => true
+
+ // by default the container will return singletons
+ var twitter2 = container.lookup('api:twitter');
+ twitter instanceof Twitter; // => true
+
+ twitter === twitter2; //=> true
+ ```
+
+ If singletons are not wanted an optional flag can be provided at lookup.
+
+ ```javascript
+ var container = new Container();
+ container.register('api:twitter', Twitter);
+
+ var twitter = container.lookup('api:twitter', { singleton: false });
+ var twitter2 = container.lookup('api:twitter', { singleton: false });
+
+ twitter === twitter2; //=> false
+ ```
+
+ @method lookup
+ @param {String} fullName
+ @param {Object} options
+ @return {any}
+ */
+ lookup: function(fullName, options) {
+ validateFullName(fullName);
+ return lookup(this, this.normalize(fullName), options);
+ },
+
+ /**
+ Given a fullName return the corresponding factory.
+
+ @method lookupFactory
+ @param {String} fullName
+ @return {any}
+ */
+ lookupFactory: function(fullName) {
+ validateFullName(fullName);
+ return factoryFor(this, this.normalize(fullName));
+ },
+
+ /**
+ Given a fullName check if the container is aware of its factory
+ or singleton instance.
+
+ @method has
+ @param {String} fullName
+ @return {Boolean}
+ */
+ has: function(fullName) {
+ validateFullName(fullName);
+ return has(this, this.normalize(fullName));
+ },
+
+ /**
+ Allow registering options for all factories of a type.
+
+ ```javascript
+ var container = new Container();
+
+ // if all of type `connection` must not be singletons
+ container.optionsForType('connection', { singleton: false });
+
+ container.register('connection:twitter', TwitterConnection);
+ container.register('connection:facebook', FacebookConnection);
+
+ var twitter = container.lookup('connection:twitter');
+ var twitter2 = container.lookup('connection:twitter');
+
+ twitter === twitter2; // => false
+
+ var facebook = container.lookup('connection:facebook');
+ var facebook2 = container.lookup('connection:facebook');
+
+ facebook === facebook2; // => false
+ ```
+
+ @method optionsForType
+ @param {String} type
+ @param {Object} options
+ */
+ optionsForType: function(type, options) {
+ if (this.parent) { illegalChildOperation('optionsForType'); }
+
+ this._typeOptions.set(type, options);
+ },
+
+ /**
+ @method options
+ @param {String} type
+ @param {Object} options
+ */
+ options: function(type, options) {
+ this.optionsForType(type, options);
+ },
+
+ /**
+ Used only via `injection`.
+
+ Provides a specialized form of injection, specifically enabling
+ all objects of one type to be injected with a reference to another
+ object.
+
+ For example, provided each object of type `controller` needed a `router`.
+ one would do the following:
+
+ ```javascript
+ var container = new Container();
+
+ container.register('router:main', Router);
+ container.register('controller:user', UserController);
+ container.register('controller:post', PostController);
+
+ container.typeInjection('controller', 'router', 'router:main');
+
+ var user = container.lookup('controller:user');
+ var post = container.lookup('controller:post');
+
+ user.router instanceof Router; //=> true
+ post.router instanceof Router; //=> true
+
+ // both controllers share the same router
+ user.router === post.router; //=> true
+ ```
+
+ @private
+ @method typeInjection
+ @param {String} type
+ @param {String} property
+ @param {String} fullName
+ */
+ typeInjection: function(type, property, fullName) {
+ validateFullName(fullName);
+ if (this.parent) { illegalChildOperation('typeInjection'); }
+
+ addTypeInjection(this.typeInjections, type, property, fullName);
+ },
+
+ /**
+ Defines injection rules.
+
+ These rules are used to inject dependencies onto objects when they
+ are instantiated.
+
+ Two forms of injections are possible:
+
+ * Injecting one fullName on another fullName
+ * Injecting one fullName on a type
+
+ Example:
+
+ ```javascript
+ var container = new Container();
+
+ container.register('source:main', Source);
+ container.register('model:user', User);
+ container.register('model:post', Post);
+
+ // injecting one fullName on another fullName
+ // eg. each user model gets a post model
+ container.injection('model:user', 'post', 'model:post');
+
+ // injecting one fullName on another type
+ container.injection('model', 'source', 'source:main');
+
+ var user = container.lookup('model:user');
+ var post = container.lookup('model:post');
+
+ user.source instanceof Source; //=> true
+ post.source instanceof Source; //=> true
+
+ user.post instanceof Post; //=> true
+
+ // and both models share the same source
+ user.source === post.source; //=> true
+ ```
+
+ @method injection
+ @param {String} factoryName
+ @param {String} property
+ @param {String} injectionName
+ */
+ injection: function(fullName, property, injectionName) {
+ if (this.parent) { illegalChildOperation('injection'); }
+
+ validateFullName(injectionName);
+ var normalizedInjectionName = this.normalize(injectionName);
+
+ if (fullName.indexOf(':') === -1) {
+ return this.typeInjection(fullName, property, normalizedInjectionName);
+ }
+
+ validateFullName(fullName);
+ var normalizedName = this.normalize(fullName);
+
+ addInjection(this.injections, normalizedName, property, normalizedInjectionName);
+ },
+
+
+ /**
+ Used only via `factoryInjection`.
+
+ Provides a specialized form of injection, specifically enabling
+ all factory of one type to be injected with a reference to another
+ object.
+
+ For example, provided each factory of type `model` needed a `store`.
+ one would do the following:
+
+ ```javascript
+ var container = new Container();
+
+ container.register('store:main', SomeStore);
+
+ container.factoryTypeInjection('model', 'store', 'store:main');
+
+ var store = container.lookup('store:main');
+ var UserFactory = container.lookupFactory('model:user');
+
+ UserFactory.store instanceof SomeStore; //=> true
+ ```
+
+ @private
+ @method factoryTypeInjection
+ @param {String} type
+ @param {String} property
+ @param {String} fullName
+ */
+ factoryTypeInjection: function(type, property, fullName) {
+ if (this.parent) { illegalChildOperation('factoryTypeInjection'); }
+
+ addTypeInjection(this.factoryTypeInjections, type, property, this.normalize(fullName));
+ },
+
+ /**
+ Defines factory injection rules.
+
+ Similar to regular injection rules, but are run against factories, via
+ `Container#lookupFactory`.
+
+ These rules are used to inject objects onto factories when they
+ are looked up.
+
+ Two forms of injections are possible:
+
+ * Injecting one fullName on another fullName
+ * Injecting one fullName on a type
+
+ Example:
+
+ ```javascript
+ var container = new Container();
+
+ container.register('store:main', Store);
+ container.register('store:secondary', OtherStore);
+ container.register('model:user', User);
+ container.register('model:post', Post);
+
+ // injecting one fullName on another type
+ container.factoryInjection('model', 'store', 'store:main');
+
+ // injecting one fullName on another fullName
+ container.factoryInjection('model:post', 'secondaryStore', 'store:secondary');
+
+ var UserFactory = container.lookupFactory('model:user');
+ var PostFactory = container.lookupFactory('model:post');
+ var store = container.lookup('store:main');
+
+ UserFactory.store instanceof Store; //=> true
+ UserFactory.secondaryStore instanceof OtherStore; //=> false
+
+ PostFactory.store instanceof Store; //=> true
+ PostFactory.secondaryStore instanceof OtherStore; //=> true
+
+ // and both models share the same source instance
+ UserFactory.store === PostFactory.store; //=> true
+ ```
+
+ @method factoryInjection
+ @param {String} factoryName
+ @param {String} property
+ @param {String} injectionName
+ */
+ factoryInjection: function(fullName, property, injectionName) {
+ if (this.parent) { illegalChildOperation('injection'); }
+
+ var normalizedName = this.normalize(fullName);
+ var normalizedInjectionName = this.normalize(injectionName);
+
+ validateFullName(injectionName);
+
+ if (fullName.indexOf(':') === -1) {
+ return this.factoryTypeInjection(normalizedName, property, normalizedInjectionName);
+ }
+
+ validateFullName(fullName);
+
+ addInjection(this.factoryInjections, normalizedName, property, normalizedInjectionName);
+ },
+
+ /**
+ A depth first traversal, destroying the container, its descendant containers and all
+ their managed objects.
+
+ @method destroy
+ */
+ destroy: function() {
+ for (var i=0, l=this.children.length; i<l; i++) {
+ this.children[i].destroy();
+ }
+
+ this.children = [];
+
+ eachDestroyable(this, function(item) {
+ item.destroy();
+ });
+
+ this.parent = undefined;
+ this.isDestroyed = true;
+ },
+
+ /**
+ @method reset
+ */
+ reset: function() {
+ for (var i=0, l=this.children.length; i<l; i++) {
+ resetCache(this.children[i]);
+ }
+ resetCache(this);
+ }
+ };
+
+ function has(container, fullName){
+ if (container.cache.has(fullName)) {
+ return true;
+ }
+
+ return !!container.resolve(fullName);
+ }
+
+ function lookup(container, fullName, options) {
+ options = options || {};
+
+ if (container.cache.has(fullName) && options.singleton !== false) {
+ return container.cache.get(fullName);
+ }
+
+ var value = instantiate(container, fullName);
+
+ if (value === undefined) { return; }
+
+ if (isSingleton(container, fullName) && options.singleton !== false) {
+ container.cache.set(fullName, value);
+ }
+
+ return value;
+ }
+
+ function illegalChildOperation(operation) {
+ throw new Error(operation + " is not currently supported on child containers");
+ }
+
+ function isSingleton(container, fullName) {
+ var singleton = option(container, fullName, 'singleton');
+
+ return singleton !== false;
+ }
+
+ function buildInjections(container, injections) {
+ var hash = {};
+
+ if (!injections) { return hash; }
+
+ var injection, injectable;
+
+ for (var i=0, l=injections.length; i<l; i++) {
+ injection = injections[i];
+ injectable = lookup(container, injection.fullName);
+
+ if (injectable !== undefined) {
+ hash[injection.property] = injectable;
+ } else {
+ throw new Error('Attempting to inject an unknown injection: `' + injection.fullName + '`');
+ }
+ }
+
+ return hash;
+ }
+
+ function option(container, fullName, optionName) {
+ var options = container._options.get(fullName);
+
+ if (options && options[optionName] !== undefined) {
+ return options[optionName];
+ }
+
+ var type = fullName.split(":")[0];
+ options = container._typeOptions.get(type);
+
+ if (options) {
+ return options[optionName];
+ }
+ }
+
+ function factoryFor(container, fullName) {
+ var name = 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;
+ }
+ }
+
+ 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;
+ }
+
+ function instantiate(container, fullName) {
+ var factory = factoryFor(container, fullName);
+
+ if (option(container, fullName, 'instantiate') === false) {
+ return factory;
+ }
+
+ if (factory) {
+ 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));
+ }
+ }
+ }
+
+ function eachDestroyable(container, callback) {
+ container.cache.eachLocal(function(key, value) {
+ if (option(container, key, 'instantiate') === false) { return; }
+ callback(value);
+ });
+ }
+
+ function resetCache(container) {
+ container.cache.eachLocal(function(key, value) {
+ if (option(container, key, 'instantiate') === false) { return; }
+ value.destroy();
+ });
+ container.cache.dict = {};
+ }
+
+ function addTypeInjection(rules, type, property, fullName) {
+ var injections = rules.get(type);
+
+ if (!injections) {
+ injections = [];
+ rules.set(type, injections);
+ }
+
+ injections.push({
+ property: property,
+ fullName: fullName
+ });
+ }
+
+ var VALID_FULL_NAME_REGEXP = /^[^:]+.+:[^:]+$/;
+ function validateFullName(fullName) {
+ if (!VALID_FULL_NAME_REGEXP.test(fullName)) {
+ throw new TypeError('Invalid Fullname, expected: `type:name` got: ' + fullName);
+ }
+ }
+
+ function addInjection(rules, factoryName, property, injectionName) {
+ var injections = rules[factoryName] = rules[factoryName] || [];
+ injections.push({ property: property, fullName: injectionName });
+ }
+
+ __exports__["default"] = Container;
+ });
+define("container/inheriting_dict",
+ ["exports"],
+ function(__exports__) {
+ "use strict";
+ // A safe and simple inheriting object.
+ function InheritingDict(parent) {
+ this.parent = parent;
+ this.dict = {};
+ }
+
+ InheritingDict.prototype = {
+
+ /**
+ @property parent
+ @type InheritingDict
+ @default null
+ */
+
+ parent: null,
+
+ /**
+ Object used to store the current nodes data.
+
+ @property dict
+ @type Object
+ @default Object
+ */
+ dict: null,
+
+ /**
+ Retrieve the value given a key, if the value is present at the current
+ level use it, otherwise walk up the parent hierarchy and try again. If
+ no matching key is found, return undefined.
+
+ @method get
+ @param {String} key
+ @return {any}
+ */
+ get: function(key) {
+ var dict = this.dict;
+
+ if (dict.hasOwnProperty(key)) {
+ return dict[key];
+ }
+
+ if (this.parent) {
+ return this.parent.get(key);
+ }
+ },
+
+ /**
+ Set the given value for the given key, at the current level.
+
+ @method set
+ @param {String} key
+ @param {Any} value
+ */
+ set: function(key, value) {
+ this.dict[key] = value;
+ },
+
+ /**
+ Delete the given key
+
+ @method remove
+ @param {String} key
+ */
+ remove: function(key) {
+ delete this.dict[key];
+ },
+
+ /**
+ Check for the existence of given a key, if the key is present at the current
+ level return true, otherwise walk up the parent hierarchy and try again. If
+ no matching key is found, return false.
+
+ @method has
+ @param {String} key
+ @return {Boolean}
+ */
+ has: function(key) {
+ var dict = this.dict;
+
+ if (dict.hasOwnProperty(key)) {
+ return true;
+ }
+
+ if (this.parent) {
+ return this.parent.has(key);
+ }
+
+ return false;
+ },
+
+ /**
+ Iterate and invoke a callback for each local key-value pair.
+
+ @method eachLocal
+ @param {Function} callback
+ @param {Object} binding
+ */
+ eachLocal: function(callback, binding) {
+ var dict = this.dict;
+
+ for (var prop in dict) {
+ if (dict.hasOwnProperty(prop)) {
+ callback.call(binding, prop, dict[prop]);
+ }
+ }
+ }
+ };
+
+ __exports__["default"] = InheritingDict;
+ });
+define("container",
+ ["container/container","exports"],
+ function(__dependency1__, __exports__) {
+ "use strict";
+ /**
+ 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;
+
+ var Container = __dependency1__["default"];
+
+ __exports__["default"] = Container;
+ });
+})();
+
+(function() {
+/*globals ENV */
+/**
+@module ember
+@submodule ember-runtime
+*/
+
+var indexOf = Ember.EnumerableUtils.indexOf;
+
+/**
+ This will compare two javascript values of possibly different types.
+ It will tell you which one is greater than the other by returning:
+
+ - -1 if the first is smaller than the second,
+ - 0 if both are equal,
+ - 1 if the first is greater than the second.
+
+ The order is calculated based on `Ember.ORDER_DEFINITION`, if types are different.
+ In case they have the same type an appropriate comparison for this type is made.
+
+ ```javascript
+ Ember.compare('hello', 'hello'); // 0
+ Ember.compare('abc', 'dfg'); // -1
+ Ember.compare(2, 1); // 1
+ ```
+
+ @method compare
+ @for Ember
+ @param {Object} v First value to compare
+ @param {Object} w Second value to compare
+ @return {Number} -1 if v < w, 0 if v = w and 1 if v > w.
+*/
+Ember.compare = function compare(v, w) {
+ if (v === w) { return 0; }
+
+ var type1 = Ember.typeOf(v);
+ var type2 = Ember.typeOf(w);
+
+ var Comparable = Ember.Comparable;
+ if (Comparable) {
+ if (type1==='instance' && Comparable.detect(v.constructor)) {
+ return v.constructor.compare(v, w);
+ }
+
+ if (type2 === 'instance' && Comparable.detect(w.constructor)) {
+ return 1-w.constructor.compare(w, v);
+ }
+ }
+
+ // If we haven't yet generated a reverse-mapping of Ember.ORDER_DEFINITION,
+ // do so now.
+ var mapping = Ember.ORDER_DEFINITION_MAPPING;
+ if (!mapping) {
+ var order = Ember.ORDER_DEFINITION;
+ mapping = Ember.ORDER_DEFINITION_MAPPING = {};
+ var idx, len;
+ for (idx = 0, len = order.length; idx < len; ++idx) {
+ mapping[order[idx]] = idx;
+ }
+
+ // We no longer need Ember.ORDER_DEFINITION.
+ delete Ember.ORDER_DEFINITION;
+ }
+
+ var type1Index = mapping[type1];
+ var type2Index = mapping[type2];
+
+ if (type1Index < type2Index) { return -1; }
+ if (type1Index > type2Index) { return 1; }
+
+ // types are equal - so we have to check values now
+ switch (type1) {
+ case 'boolean':
+ case 'number':
+ if (v < w) { return -1; }
+ if (v > w) { return 1; }
+ return 0;
+
+ case 'string':
+ var comp = v.localeCompare(w);
+ if (comp < 0) { return -1; }
+ if (comp > 0) { return 1; }
+ return 0;
+
+ case 'array':
+ var vLen = v.length;
+ var wLen = w.length;
+ var l = Math.min(vLen, wLen);
+ var r = 0;
+ var i = 0;
+ while (r === 0 && i < l) {
+ r = compare(v[i],w[i]);
+ i++;
+ }
+ if (r !== 0) { return r; }
+
+ // all elements are equal now
+ // shorter array should be ordered first
+ if (vLen < wLen) { return -1; }
+ if (vLen > wLen) { return 1; }
+ // arrays are equal now
+ return 0;
+
+ case 'instance':
+ if (Ember.Comparable && Ember.Comparable.detect(v)) {
+ return v.compare(v, w);
+ }
+ return 0;
+
+ case 'date':
+ var vNum = v.getTime();
+ var wNum = w.getTime();
+ if (vNum < wNum) { return -1; }
+ if (vNum > wNum) { return 1; }
+ return 0;
+
+ default:
+ return 0;
+ }
+};
+
+function _copy(obj, deep, seen, copies) {
+ var ret, loc, key;
+
+ // primitive data types are immutable, just return them.
+ if ('object' !== typeof obj || obj===null) return obj;
+
+ // avoid cyclical loops
+ if (deep && (loc=indexOf(seen, obj))>=0) return copies[loc];
+
+ Ember.assert('Cannot clone an Ember.Object that does not implement Ember.Copyable', !(obj instanceof Ember.Object) || (Ember.Copyable && Ember.Copyable.detect(obj)));
+
+ // IMPORTANT: this specific test will detect a native array only. Any other
+ // object will need to implement Copyable.
+ if (Ember.typeOf(obj) === 'array') {
+ ret = obj.slice();
+ if (deep) {
+ loc = ret.length;
+ while(--loc>=0) ret[loc] = _copy(ret[loc], deep, seen, copies);
+ }
+ } else if (Ember.Copyable && Ember.Copyable.detect(obj)) {
+ ret = obj.copy(deep, seen, copies);
+ } else if (obj instanceof Date) {
+ ret = new Date(obj.getTime());
+ } else {
+ ret = {};
+ for(key in obj) {
+ if (!obj.hasOwnProperty(key)) continue;
+
+ // Prevents browsers that don't respect non-enumerability from
+ // copying internal Ember properties
+ if (key.substring(0,2) === '__') continue;
+
+ ret[key] = deep ? _copy(obj[key], deep, seen, copies) : obj[key];
+ }
+ }
+
+ if (deep) {
+ seen.push(obj);
+ copies.push(ret);
+ }
+
+ return ret;
+}
+
+/**
+ Creates a clone of the passed object. This function can take just about
+ any type of object and create a clone of it, including primitive values
+ (which are not actually cloned because they are immutable).
+
+ If the passed object implements the `clone()` method, then this function
+ will simply call that method and return the result.
+
+ @method copy
+ @for Ember
+ @param {Object} obj The object to clone
+ @param {Boolean} deep If true, a deep copy of the object is made
+ @return {Object} The cloned object
+*/
+Ember.copy = function(obj, deep) {
+ // fast paths
+ if ('object' !== typeof obj || obj===null) return obj; // can't copy primitives
+ if (Ember.Copyable && Ember.Copyable.detect(obj)) return obj.copy(deep);
+ return _copy(obj, deep, deep ? [] : null, deep ? [] : null);
+};
+
+/**
+ Compares two objects, returning true if they are logically equal. This is
+ a deeper comparison than a simple triple equal. For sets it will compare the
+ internal objects. For any other object that implements `isEqual()` it will
+ respect that method.
+
+ ```javascript
+ Ember.isEqual('hello', 'hello'); // true
+ Ember.isEqual(1, 2); // false
+ Ember.isEqual([4,2], [4,2]); // false
+ ```
+
+ @method isEqual
+ @for Ember
+ @param {Object} a first object to compare
+ @param {Object} b second object to compare
+ @return {Boolean}
+*/
+Ember.isEqual = function(a, b) {
+ if (a && 'function'===typeof a.isEqual) return a.isEqual(b);
+ return a === b;
+};
+
+// Used by Ember.compare
+Ember.ORDER_DEFINITION = Ember.ENV.ORDER_DEFINITION || [
+ 'undefined',
+ 'null',
+ 'boolean',
+ 'number',
+ 'string',
+ 'array',
+ 'object',
+ 'instance',
+ 'function',
+ 'class',
+ 'date'
+];
+
+/**
+ Returns all of the keys defined on an object or hash. This is useful
+ when inspecting objects for debugging. On browsers that support it, this
+ uses the native `Object.keys` implementation.
+
+ @method keys
+ @for Ember
+ @param {Object} obj
+ @return {Array} Array containing keys of obj
+*/
+Ember.keys = Object.keys;
+
+if (!Ember.keys || Ember.create.isSimulated) {
+ var prototypeProperties = [
+ 'constructor',
+ 'hasOwnProperty',
+ 'isPrototypeOf',
+ 'propertyIsEnumerable',
+ 'valueOf',
+ 'toLocaleString',
+ 'toString'
+ ],
+ pushPropertyName = function(obj, array, key) {
+ // Prevents browsers that don't respect non-enumerability from
+ // copying internal Ember properties
+ if (key.substring(0,2) === '__') return;
+ if (key === '_super') return;
+ if (indexOf(array, key) >= 0) return;
+ if (!obj.hasOwnProperty(key)) return;
+
+ array.push(key);
+ };
+
+ Ember.keys = function(obj) {
+ var ret = [], key;
+ for (key in obj) {
+ pushPropertyName(obj, ret, key);
+ }
+
+ // IE8 doesn't enumerate property that named the same as prototype properties.
+ for (var i = 0, l = prototypeProperties.length; i < l; i++) {
+ key = prototypeProperties[i];
+
+ pushPropertyName(obj, ret, key);
+ }
+
+ return ret;
+ };
+}
+
+})();
+
+
+
+(function() {
+/**
+@module ember
+@submodule ember-runtime
+*/
+
+var STRING_DASHERIZE_REGEXP = (/[ _]/g);
+var STRING_DASHERIZE_CACHE = {};
+var STRING_DECAMELIZE_REGEXP = (/([a-z\d])([A-Z])/g);
+var STRING_CAMELIZE_REGEXP = (/(\-|_|\.|\s)+(.)?/g);
+var STRING_UNDERSCORE_REGEXP_1 = (/([a-z\d])([A-Z]+)/g);
+var STRING_UNDERSCORE_REGEXP_2 = (/\-|\s+/g);
+var STRING_PARAMETERIZE_REGEXP_1 = (/[_|\/|\s]+/g);
+var STRING_PARAMETERIZE_REGEXP_2 = (/[^a-z0-9\-]+/gi);
+var STRING_PARAMETERIZE_REGEXP_3 = (/[\-]+/g);
+var STRING_PARAMETERIZE_REGEXP_4 = (/^-+|-+$/g);
+
+/**
+ Defines the hash of localized strings for the current language. Used by
+ the `Ember.String.loc()` helper. To localize, add string values to this
+ hash.
+
+ @property STRINGS
+ @for Ember
+ @type Hash
+*/
+Ember.STRINGS = {};
+
+/**
+ Defines string helper methods including string formatting and localization.
+ Unless `Ember.EXTEND_PROTOTYPES.String` is `false` these methods will also be
+ added to the `String.prototype` as well.
+
+ @class String
+ @namespace Ember
+ @static
+*/
+Ember.String = {
+
+ /**
+ Apply formatting options to the string. This will look for occurrences
+ of "%@" in your string and substitute them with the arguments you pass into
+ this method. If you want to control the specific order of replacement,
+ you can add a number after the key as well to indicate which argument
+ you want to insert.
+
+ Ordered insertions are most useful when building loc strings where values
+ you need to insert may appear in different orders.
+
+ ```javascript
+ "Hello %@ %@".fmt('John', 'Doe'); // "Hello John Doe"
+ "Hello %@2, %@1".fmt('John', 'Doe'); // "Hello Doe, John"
+ ```
+
+ @method fmt
+ @param {String} str The string to format
+ @param {Array} formats An array of parameters to interpolate into string.
+ @return {String} formatted string
+ */
+ fmt: function(str, formats) {
+ // first, replace any ORDERED replacements.
+ var idx = 0; // the current index for non-numerical replacements
+ return str.replace(/%@([0-9]+)?/g, function(s, argIndex) {
+ argIndex = (argIndex) ? parseInt(argIndex, 10) - 1 : idx++;
+ s = formats[argIndex];
+ return (s === null) ? '(null)' : (s === undefined) ? '' : Ember.inspect(s);
+ }) ;
+ },
+
+ /**
+ Formats the passed string, but first looks up the string in the localized
+ strings hash. This is a convenient way to localize text. See
+ `Ember.String.fmt()` for more information on formatting.
+
+ Note that it is traditional but not required to prefix localized string
+ keys with an underscore or other character so you can easily identify
+ localized strings.
+
+ ```javascript
+ Ember.STRINGS = {
+ '_Hello World': 'Bonjour le monde',
+ '_Hello %@ %@': 'Bonjour %@ %@'
+ };
+
+ Ember.String.loc("_Hello World"); // 'Bonjour le monde';
+ Ember.String.loc("_Hello %@ %@", ["John", "Smith"]); // "Bonjour John Smith";
+ ```
+
+ @method loc
+ @param {String} str The string to format
+ @param {Array} formats Optional array of parameters to interpolate into string.
+ @return {String} formatted string
+ */
+ loc: function(str, formats) {
+ str = Ember.STRINGS[str] || str;
+ return Ember.String.fmt(str, formats) ;
+ },
+
+ /**
+ Splits a string into separate units separated by spaces, eliminating any
+ empty strings in the process. This is a convenience method for split that
+ is mostly useful when applied to the `String.prototype`.
+
+ ```javascript
+ Ember.String.w("alpha beta gamma").forEach(function(key) {
+ console.log(key);
+ });
+
+ // > alpha
+ // > beta
+ // > gamma
+ ```
+
+ @method w
+ @param {String} str The string to split
+ @return {String} split string
+ */
+ w: function(str) { return str.split(/\s+/); },
+
+ /**
+ Converts a camelized string into all lower case separated by underscores.
+
+ ```javascript
+ 'innerHTML'.decamelize(); // 'inner_html'
+ 'action_name'.decamelize(); // 'action_name'
+ 'css-class-name'.decamelize(); // 'css-class-name'
+ 'my favorite items'.decamelize(); // 'my favorite items'
+ ```
+
+ @method decamelize
+ @param {String} str The string to decamelize.
+ @return {String} the decamelized string.
+ */
+ decamelize: function(str) {
+ return str.replace(STRING_DECAMELIZE_REGEXP, '$1_$2').toLowerCase();
+ },
+
+ /**
+ Replaces underscores, spaces, or camelCase with dashes.
+
+ ```javascript
+ 'innerHTML'.dasherize(); // 'inner-html'
+ 'action_name'.dasherize(); // 'action-name'
+ 'css-class-name'.dasherize(); // 'css-class-name'
+ 'my favorite items'.dasherize(); // 'my-favorite-items'
+ ```
+
+ @method dasherize
+ @param {String} str The string to dasherize.
+ @return {String} the dasherized string.
+ */
+ dasherize: function(str) {
+ var cache = STRING_DASHERIZE_CACHE,
+ hit = cache.hasOwnProperty(str),
+ ret;
+
+ if (hit) {
+ return cache[str];
+ } else {
+ ret = Ember.String.decamelize(str).replace(STRING_DASHERIZE_REGEXP,'-');
+ cache[str] = ret;
+ }
+
+ return ret;
+ },
+
+ /**
+ Returns the lowerCamelCase form of a string.
+
+ ```javascript
+ 'innerHTML'.camelize(); // 'innerHTML'
+ 'action_name'.camelize(); // 'actionName'
+ 'css-class-name'.camelize(); // 'cssClassName'
+ 'my favorite items'.camelize(); // 'myFavoriteItems'
+ 'My Favorite Items'.camelize(); // 'myFavoriteItems'
+ ```
+
+ @method camelize
+ @param {String} str The string to camelize.
+ @return {String} the camelized string.
+ */
+ camelize: function(str) {
+ return str.replace(STRING_CAMELIZE_REGEXP, function(match, separator, chr) {
+ return chr ? chr.toUpperCase() : '';
+ }).replace(/^([A-Z])/, function(match, separator, chr) {
+ return match.toLowerCase();
+ });
+ },
+
+ /**
+ Returns the UpperCamelCase form of a string.
+
+ ```javascript
+ 'innerHTML'.classify(); // 'InnerHTML'
+ 'action_name'.classify(); // 'ActionName'
+ 'css-class-name'.classify(); // 'CssClassName'
+ 'my favorite items'.classify(); // 'MyFavoriteItems'
+ ```
+
+ @method classify
+ @param {String} str the string to classify
+ @return {String} the classified string
+ */
+ classify: function(str) {
+ var parts = str.split("."),
+ out = [];
+
+ for (var i=0, l=parts.length; i<l; i++) {
+ var camelized = Ember.String.camelize(parts[i]);
+ out.push(camelized.charAt(0).toUpperCase() + camelized.substr(1));
+ }
+
+ return out.join(".");
+ },
+
+ /**
+ More general than decamelize. Returns the lower\_case\_and\_underscored
+ form of a string.
+
+ ```javascript
+ 'innerHTML'.underscore(); // 'inner_html'
+ 'action_name'.underscore(); // 'action_name'
+ 'css-class-name'.underscore(); // 'css_class_name'
+ 'my favorite items'.underscore(); // 'my_favorite_items'
+ ```
+
+ @method underscore
+ @param {String} str The string to underscore.
+ @return {String} the underscored string.
+ */
+ underscore: function(str) {
+ return str.replace(STRING_UNDERSCORE_REGEXP_1, '$1_$2').
+ replace(STRING_UNDERSCORE_REGEXP_2, '_').toLowerCase();
+ },
+
+ /**
+ Returns the Capitalized form of a string
+
+ ```javascript
+ 'innerHTML'.capitalize() // 'InnerHTML'
+ 'action_name'.capitalize() // 'Action_name'
+ 'css-class-name'.capitalize() // 'Css-class-name'
+ 'my favorite items'.capitalize() // 'My favorite items'
+ ```
+
+ @method capitalize
+ @param {String} str The string to capitalize.
+ @return {String} The capitalized string.
+ */
+ capitalize: function(str) {
+ return str.charAt(0).toUpperCase() + str.substr(1);
+ }
+};
+
+})();
+
+
+
+(function() {
+/**
+@module ember
+@submodule ember-runtime
+*/
+
+
+
+var fmt = Ember.String.fmt,
+ w = Ember.String.w,
+ loc = Ember.String.loc,
+ camelize = Ember.String.camelize,
+ decamelize = Ember.String.decamelize,
+ dasherize = Ember.String.dasherize,
+ underscore = Ember.String.underscore,
+ capitalize = Ember.String.capitalize,
+ classify = Ember.String.classify;
+
+if (Ember.EXTEND_PROTOTYPES === true || Ember.EXTEND_PROTOTYPES.String) {
+
+ /**
+ See [Ember.String.fmt](/api/classes/Ember.String.html#method_fmt).
+
+ @method fmt
+ @for String
+ */
+ String.prototype.fmt = function() {
+ return fmt(this, arguments);
+ };
+
+ /**
+ See [Ember.String.w](/api/classes/Ember.String.html#method_w).
+
+ @method w
+ @for String
+ */
+ String.prototype.w = function() {
+ return w(this);
+ };
+
+ /**
+ See [Ember.String.loc](/api/classes/Ember.String.html#method_loc).
+
+ @method loc
+ @for String
+ */
+ String.prototype.loc = function() {
+ return loc(this, arguments);
+ };
+
+ /**
+ See [Ember.String.camelize](/api/classes/Ember.String.html#method_camelize).
+
+ @method camelize
+ @for String
+ */
+ String.prototype.camelize = function() {
+ return camelize(this);
+ };
+
+ /**
+ See [Ember.String.decamelize](/api/classes/Ember.String.html#method_decamelize).
+
+ @method decamelize
+ @for String
+ */
+ String.prototype.decamelize = function() {
+ return decamelize(this);
+ };
+
+ /**
+ See [Ember.String.dasherize](/api/classes/Ember.String.html#method_dasherize).
+
+ @method dasherize
+ @for String
+ */
+ String.prototype.dasherize = function() {
+ return dasherize(this);
+ };
+
+ /**
+ See [Ember.String.underscore](/api/classes/Ember.String.html#method_underscore).
+
+ @method underscore
+ @for String
+ */
+ String.prototype.underscore = function() {
+ return underscore(this);
+ };
+
+ /**
+ See [Ember.String.classify](/api/classes/Ember.String.html#method_classify).
+
+ @method classify
+ @for String
+ */
+ String.prototype.classify = function() {
+ return classify(this);
+ };
+
+ /**
+ See [Ember.String.capitalize](/api/classes/Ember.String.html#method_capitalize).
+
+ @method capitalize
+ @for String
+ */
+ String.prototype.capitalize = function() {
+ return capitalize(this);
+ };
+}
+
+})();
+
+
+
+(function() {
+/**
+@module ember
+@submodule ember-runtime
+*/
+
+var get = Ember.get,
+ set = Ember.set,
+ slice = Array.prototype.slice,
+ getProperties = Ember.getProperties;
+
+/**
+ ## Overview
+
+ 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({
+
+ /**
+ 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.get('firstName') + ' ' + this.get('lastName');
+ }.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} 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,
+ META_KEY = Ember.META_KEY,
+ 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
+};
+
+var nullDescriptor = {
+ configurable: true,
+ writable: true,
+ enumerable: false,
+ value: null
+};
+
+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, nullDescriptor);
+ o_defineProperty(this, '__nextSuper', 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. Define computed properties using extend() or reopen() before calling create().", !(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'));
+ }
+ });
+
+ var steve = App.Person.create({
+ name: "Steve"
+ });
+
+ // alerts 'Name is Steve'.
+ ```
+
+ NOTE: If you do override `init` for a framework class like `Ember.View` or
+ `Ember.ArrayController`, be sure to call `this._super()` in your
+ `init` declaration! If you don't, Ember may not have an opportunity to
+ do important setup work, and you'll see strange behavior in your
+ application.
+
+ @method init
+ */
+ init: function() {},
+
+ /**
+ Defines the properties that will be concatenated from the superclass
+ (instead of overridden).
+
+ By default, when you extend an Ember class a property defined in
+ the subclass overrides a property with the same name that is defined
+ in the superclass. However, there are some cases where it is preferable
+ to build up a property's value by combining the superclass' property
+ value with the subclass' value. An example of this in use within Ember
+ is the `classNames` property of `Ember.View`.
+
+ Here is some sample code showing the difference between a concatenated
+ property and a normal one:
+
+ ```javascript
+ App.BarView = Ember.View.extend({
+ someNonConcatenatedProperty: ['bar'],
+ classNames: ['bar']
+ });
+
+ App.FooBarView = App.BarView.extend({
+ someNonConcatenatedProperty: ['foo'],
+ classNames: ['foo'],
+ });
+
+ var fooBarView = App.FooBarView.create();
+ fooBarView.get('someNonConcatenatedProperty'); // ['foo']
+ fooBarView.get('classNames'); // ['ember-view', 'bar', 'foo']
+ ```
+
+ This behavior extends to object creation as well. Continuing the
+ above example:
+
+ ```javascript
+ var view = App.FooBarView.create({
+ someNonConcatenatedProperty: ['baz'],
+ classNames: ['baz']
+ })
+ view.get('someNonConcatenatedProperty'); // ['baz']
+ view.get('classNames'); // ['ember-view', 'bar', 'foo', 'baz']
+ ```
+ Adding a single property that is not an array will just add it in the array:
+
+ ```javascript
+ var view = App.FooBarView.create({
+ classNames: 'baz'
+ })
+ view.get('classNames'); // ['ember-view', 'bar', 'foo', 'baz']
+ ```
+
+ Using the `concatenatedProperties` property, we can tell to Ember that mix
+ the content of the properties.
+
+ In `Ember.View` the `classNameBindings` and `attributeBindings` properties
+ are also concatenated, in addition to `classNames`.
+
+ This feature is available for you to use throughout the Ember object model,
+ although typical app developers are likely to use it infrequently. Since
+ it changes expectations about behavior of properties, you should properly
+ document its usage in each individual concatenated property (to not
+ mislead your users to think they can override the property in a subclass).
+
+ @property concatenatedProperties
+ @type Array
+ @default null
+ */
+ concatenatedProperties: null,
+
+ /**
+ Destroyed object property flag.
+
+ if this property is `true` the observers and bindings were already
+ removed by the effect of calling the `destroy()` method.
+
+ @property isDestroyed
+ @default false
+ */
+ isDestroyed: false,
+
+ /**
+ Destruction scheduled flag. The `destroy()` method has been called.
+
+ The object stays intact until the end of the run loop at which point
+ the `isDestroyed` flag is set.
+
+ @property isDestroying
+ @default false
+ */
+ isDestroying: false,
+
+ /**
+ Destroys an object by setting the `isDestroyed` flag and removing its
+ metadata, which effectively destroys observers and bindings.
+
+ If you try to set a property on a destroyed object, an exception will be
+ raised.
+
+ Note that destruction is scheduled for the end of the run loop and does not
+ happen immediately. It will set an isDestroying flag immediately.
+
+ @method destroy
+ @return {Ember.Object} receiver
+ */
+ destroy: function() {
+ if (this.isDestroying) { return; }
+ this.isDestroying = true;
+
+ schedule('actions', this, this.willDestroy);
+ schedule('destroy', this, this._scheduledDestroy);
+ return this;
+ },
+
+ /**
+ Override to implement teardown.
+
+ @method willDestroy
+ */
+ willDestroy: Ember.K,
+
+ /**
+ Invoked by the run loop to actually destroy the object. This is
+ scheduled for execution by the `destroy` method.
+
+ @private
+ @method _scheduledDestroy
+ */
+ _scheduledDestroy: function() {
+ if (this.isDestroyed) { return; }
+ destroy(this);
+ this.isDestroyed = true;
+ },
+
+ bind: function(to, from) {
+ if (!(from instanceof Ember.Binding)) { from = Ember.Binding.from(from); }
+ from.to(to).connect(this);
+ return from;
+ },
+
+ /**
+ Returns a string representation which attempts to provide more information
+ than Javascript's `toString` typically does, in a generic way for all Ember
+ objects.
+
+ ```javascript
+ App.Person = Em.Object.extend()
+ person = App.Person.create()
+ person.toString() //=> "<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');
+ }
+ });
+ teacher = App.Teacher.create()
+ teacher.toString(); //=> "<App.Teacher:ember1026:Tom Dale>"
+ ```
+
+ @method toString
+ @return {String} string representation
+ */
+ toString: function toString() {
+ var hasToStringExtension = typeof this.toStringExtension === 'function',
+ extension = hasToStringExtension ? ":" + this.toStringExtension() : '';
+ var ret = '<'+this.constructor.toString()+':'+guidFor(this)+extension+'>';
+ this.toString = makeToString(ret);
+ return ret;
+ }
+});
+
+CoreObject.PrototypeMixin.ownerConstructor = CoreObject;
+
+function makeToString(ret) {
+ return function() { return ret; };
+}
+
+if (Ember.config.overridePrototypeMixin) {
+ Ember.config.overridePrototypeMixin(CoreObject.PrototypeMixin);
+}
+
+CoreObject.__super__ = null;
+
+var ClassMixin = Mixin.create({
+
+ ClassMixin: Ember.required(),
+
+ PrototypeMixin: Ember.required(),
+
+ isClass: true,
+
+ isMethod: false,
+
+ /**
+ Creates a new subclass.
+
+ ```javascript
+ App.Person = Ember.Object.extend({
+ say: function(thing) {
+ alert(thing);
+ }
+ });
+ ```
+
+ This defines a new subclass of Ember.Object: `App.Person`. It contains one method: `say()`.
+
+ You can also create a subclass from any existing class by calling its `extend()` method. For example, you might want to create a subclass of Ember's built-in `Ember.View` class:
+
+ ```javascript
+ App.PersonView = Ember.View.extend({
+ tagName: 'li',
+ classNameBindings: ['isAdministrator']
+ });
+ ```
+
+ When defining a subclass, you can override methods but still access the implementation of your parent class by calling the special `_super()` method:
+
+ ```javascript
+ App.Person = Ember.Object.extend({
+ say: function(thing) {
+ var name = this.get('name');
+ alert(name + ' says: ' + thing);
+ }
+ });
+
+ App.Soldier = App.Person.extend({
+ say: function(thing) {
+ this._super(thing + ", sir!");
+ },
+ march: function(numberOfHours) {
+ alert(this.get('name') + ' marches for ' + numberOfHours + ' hours.')
+ }
+ });
+
+ var yehuda = App.Soldier.create({
+ name: "Yehuda Katz"
+ });
+
+ yehuda.say("Yes"); // alerts "Yehuda Katz says: Yes, sir!"
+ ```
+
+ The `create()` on line #17 creates an *instance* of the `App.Soldier` class. The `extend()` on line #8 creates a *subclass* of `App.Person`. Any instance of the `App.Person` class will *not* have the `march()` method.
+
+ You can also pass `Ember.Mixin` classes to add additional properties to the subclass.
+
+ ```javascript
+ App.Person = Ember.Object.extend({
+ say: function(thing) {
+ alert(this.get('name') + ' says: ' + thing);
+ }
+ });
+
+ App.SingingMixin = Ember.Mixin.create({
+ sing: function(thing){
+ alert(this.get('name') + ' sings: la la la ' + thing);
+ }
+ });
+
+ App.BroadwayStar = App.Person.extend(App.SingingMixin, {
+ dance: function() {
+ alert(this.get('name') + ' dances: tap tap tap tap ');
+ }
+ });
+ ```
+
+ The `App.BroadwayStar` class contains three methods: `say()`, `sing()`, and `dance()`.
+
+ @method extend
+ @static
+
+ @param {Ember.Mixin} [mixins]* One or more Ember.Mixin classes
+ @param {Object} [arguments]* Object containing values to use within the new class
+ */
+ extend: function() {
+ var Class = makeCtor(), proto;
+ Class.ClassMixin = Mixin.create(this.ClassMixin);
+ Class.PrototypeMixin = Mixin.create(this.PrototypeMixin);
+
+ Class.ClassMixin.ownerConstructor = Class;
+ Class.PrototypeMixin.ownerConstructor = Class;
+
+ reopen.apply(Class.PrototypeMixin, arguments);
+
+ Class.superclass = this;
+ Class.__super__ = this.prototype;
+
+ proto = Class.prototype = o_create(this.prototype);
+ proto.constructor = Class;
+ generateGuid(proto);
+ meta(proto).proto = proto; // this will disable observers on prototype
+
+ Class.ClassMixin.apply(Class);
+ return Class;
+ },
+
+ /**
+ Equivalent to doing `extend(arguments).create()`.
+ If possible use the normal `create` method instead.
+
+ @method createWithMixins
+ @static
+ @param [arguments]*
+ */
+ createWithMixins: function() {
+ var C = this;
+ if (arguments.length>0) { this._initMixins(arguments); }
+ return new C();
+ },
+
+ /**
+ Creates an instance of a class. Accepts either no arguments, or an object
+ containing values to initialize the newly instantiated object with.
+
+ ```javascript
+ App.Person = Ember.Object.extend({
+ helloWorld: function() {
+ alert("Hi, my name is " + this.get('name'));
+ }
+ });
+
+ var tom = App.Person.create({
+ name: 'Tom Dale'
+ });
+
+ tom.helloWorld(); // alerts "Hi, my name is Tom Dale".
+ ```
+
+ `create` will call the `init` function if defined during
+ `Ember.AnyObject.extend`
+
+ If no arguments are passed to `create`, it will not set values to the new
+ instance during initialization:
+
+ ```javascript
+ var noName = App.Person.create();
+ noName.helloWorld(); // alerts undefined
+ ```
+
+ NOTE: For performance reasons, you cannot declare methods or computed
+ properties during `create`. You should instead declare methods and computed
+ properties when using `extend` or use the `createWithMixins` shorthand.
+
+ @method create
+ @static
+ @param [arguments]*
+ */
+ create: function() {
+ var C = this;
+ if (arguments.length>0) { this._initProperties(arguments); }
+ return new C();
+ },
+
+ /**
+ Augments a constructor's prototype with additional
+ properties and functions:
+
+ ```javascript
+ MyObject = Ember.Object.extend({
+ name: 'an object'
+ });
+
+ o = MyObject.create();
+ o.get('name'); // 'an object'
+
+ MyObject.reopen({
+ say: function(msg){
+ console.log(msg);
+ }
+ })
+
+ o2 = MyObject.create();
+ o2.say("hello"); // logs "hello"
+
+ o.say("goodbye"); // logs "goodbye"
+ ```
+
+ To add functions and properties to the constructor itself,
+ see `reopenClass`
+
+ @method reopen
+ */
+ reopen: function() {
+ this.willReopen();
+ reopen.apply(this.PrototypeMixin, arguments);
+ return this;
+ },
+
+ /**
+ Augments a constructor's own properties and functions:
+
+ ```javascript
+ MyObject = Ember.Object.extend({
+ name: 'an object'
+ });
+
+ MyObject.reopenClass({
+ canBuild: false
+ });
+
+ MyObject.canBuild; // false
+ o = MyObject.create();
+ ```
+
+ In other words, this creates static properties and functions for the class. These are only available on the class
+ and not on any instance of that class.
+
+ ```javascript
+ App.Person = Ember.Object.extend({
+ name : "",
+ sayHello : function(){
+ alert("Hello. My name is " + this.get('name'));
+ }
+ });
+
+ App.Person.reopenClass({
+ species : "Homo sapiens",
+ createPerson: function(newPersonsName){
+ return App.Person.create({
+ name:newPersonsName
+ });
+ }
+ });
+
+ var tom = App.Person.create({
+ name : "Tom Dale"
+ });
+ var yehuda = App.Person.createPerson("Yehuda Katz");
+
+ tom.sayHello(); // "Hello. My name is Tom Dale"
+ yehuda.sayHello(); // "Hello. My name is Yehuda Katz"
+ alert(App.Person.species); // "Homo sapiens"
+ ```
+
+ Note that `species` and `createPerson` are *not* valid on the `tom` and `yehuda`
+ variables. They are only valid on `App.Person`.
+
+ To add functions and properties to instances of
+ a constructor by extending the constructor's prototype
+ see `reopen`
+
+ @method reopenClass
+ */
+ reopenClass: function() {
+ reopen.apply(this.ClassMixin, arguments);
+ applyMixin(this, arguments, false);
+ return this;
+ },
+
+ detect: function(obj) {
+ if ('function' !== typeof obj) { return false; }
+ while(obj) {
+ if (obj===this) { return true; }
+ obj = obj.superclass;
+ }
+ return false;
+ },
+
+ detectInstance: function(obj) {
+ return obj instanceof this;
+ },
+
+ /**
+ In some cases, you may want to annotate computed properties with additional
+ metadata about how they function or what values they operate on. For
+ example, computed property functions may close over variables that are then
+ no longer available for introspection.
+
+ You can pass a hash of these values to a computed property like this:
+
+ ```javascript
+ person: function() {
+ var personId = this.get('personId');
+ return App.Person.create({ id: personId });
+ }.property().meta({ type: App.Person })
+ ```
+
+ Once you've done this, you can retrieve the values saved to the computed
+ property from your class like this:
+
+ ```javascript
+ MyClass.metaForProperty('person');
+ ```
+
+ This will return the original hash that was passed to `meta()`.
+
+ @method metaForProperty
+ @param key {String} property name
+ */
+ metaForProperty: function(key) {
+ var meta = this.proto()[META_KEY],
+ desc = meta && meta.descs[key];
+
+ Ember.assert("metaForProperty() could not find a computed property with key '"+key+"'.", !!desc && desc instanceof Ember.ComputedProperty);
+ return desc._meta || {};
+ },
+
+ /**
+ Iterate over each computed property for the class, passing its name
+ and any associated metadata (see `metaForProperty`) to the callback.
+
+ @method eachComputedProperty
+ @param {Function} callback
+ @param {Object} binding
+ */
+ eachComputedProperty: function(callback, binding) {
+ var proto = this.proto(),
+ descs = meta(proto).descs,
+ empty = {},
+ property;
+
+ for (var name in descs) {
+ property = descs[name];
+
+ if (property instanceof Ember.ComputedProperty) {
+ callback.call(binding || this, name, property._meta || empty);
+ }
+ }
+ }
+
+});
+
+ClassMixin.ownerConstructor = CoreObject;
+
+if (Ember.config.overrideClassMixin) {
+ Ember.config.overrideClassMixin(ClassMixin);
+}
+
+CoreObject.ClassMixin = ClassMixin;
+ClassMixin.apply(CoreObject);
+
+Ember.CoreObject = CoreObject;
+
+})();
+
+
+
+(function() {
+/**
+@module ember
+@submodule ember-runtime
+*/
+
+/**
+ `Ember.Object` is the main base class for all Ember objects. It is a subclass
+ of `Ember.CoreObject` with the `Ember.Observable` mixin applied. For details,
+ see the documentation for each of these.
+
+ @class Object
+ @namespace Ember
+ @extends Ember.CoreObject
+ @uses Ember.Observable
+*/
+Ember.Object = Ember.CoreObject.extend(Ember.Observable);
+Ember.Object.toString = function() { return "Ember.Object"; };
+
+})();
+
+
+
+(function() {
+/**
+@module ember
+@submodule ember-runtime
+*/
+
+var get = Ember.get, indexOf = Ember.ArrayPolyfills.indexOf;
+
+/**
+ A Namespace is an object usually used to contain other objects or methods
+ such as an application or framework. Create a namespace anytime you want
+ to define one of these new containers.
+
+ # Example Usage
+
+ ```javascript
+ MyFramework = Ember.Namespace.create({
+ VERSION: '1.0.0'
+ });
+ ```
+
+ @class Namespace
+ @namespace Ember
+ @extends Ember.Object
+*/
+var Namespace = Ember.Namespace = Ember.Object.extend({
+ isNamespace: true,
+
+ init: function() {
+ Ember.Namespace.NAMESPACES.push(this);
+ Ember.Namespace.PROCESSED = false;
+ },
+
+ toString: function() {
+ var name = get(this, 'name');
+ if (name) { return name; }
+
+ findNamespaces();
+ return this[Ember.GUID_KEY+'_name'];
+ },
+
+ nameClasses: function() {
+ processNamespace([this.toString()], this, {});
+ },
+
+ destroy: function() {
+ var namespaces = Ember.Namespace.NAMESPACES;
+
+ Ember.lookup[this.toString()] = undefined;
+ delete Ember.Namespace.NAMESPACES_BY_ID[this.toString()];
+ 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
+*/
+
+// ..........................................................
+// HELPERS
+//
+
+var get = Ember.get, set = Ember.set;
+var a_slice = Array.prototype.slice;
+var a_indexOf = Ember.EnumerableUtils.indexOf;
+
+var contexts = [];
+
+function popCtx() {
+ return contexts.length===0 ? {} : contexts.pop();
+}
+
+function pushCtx(ctx) {
+ contexts.push(ctx);
+ return null;
+}
+
+function iter(key, value) {
+ var valueProvided = arguments.length === 2;
+
+ function i(item) {
+ var cur = get(item, key);
+ return valueProvided ? value===cur : !!cur;
+ }
+ return i ;
+}
+
+/**
+ This mixin defines the common interface implemented by enumerable objects
+ in Ember. Most of these methods follow the standard Array iteration
+ API defined up to JavaScript 1.8 (excluding language-specific features that
+ cannot be emulated in older versions of JavaScript).
+
+ This mixin is applied automatically to the Array class on page load, so you
+ can use any of these methods on simple arrays. If Array already implements
+ one of these methods, the mixin will not override them.
+
+ ## Writing Your Own Enumerable
+
+ To make your own custom class enumerable, you need two items:
+
+ 1. You must have a length property. This property should change whenever
+ the number of items in your enumerable object changes. If you use this
+ with an `Ember.Object` subclass, you should be sure to change the length
+ property using `set().`
+
+ 2. You must implement `nextObject().` See documentation.
+
+ Once you have these two methods implemented, apply the `Ember.Enumerable` mixin
+ to your class and you will be able to enumerate the contents of your object
+ like any other collection.
+
+ ## Using Ember Enumeration with Other Libraries
+
+ Many other libraries provide some kind of iterator or enumeration like
+ facility. This is often where the most common API conflicts occur.
+ Ember's API is designed to be as friendly as possible with other
+ libraries by implementing only methods that mostly correspond to the
+ JavaScript 1.8 API.
+
+ @class Enumerable
+ @namespace Ember
+ @since Ember 0.9
+*/
+Ember.Enumerable = Ember.Mixin.create({
+
+ /**
+ Implement this method to make your class enumerable.
+
+ This method will be call repeatedly during enumeration. The index value
+ will always begin with 0 and increment monotonically. You don't have to
+ rely on the index value to determine what object to return, but you should
+ always check the value and start from the beginning when you see the
+ requested index is 0.
+
+ The `previousObject` is the object that was returned from the last call
+ to `nextObject` for the current iteration. This is a useful way to
+ manage iteration if you are tracing a linked list, for example.
+
+ Finally the context parameter will always contain a hash you can use as
+ a "scratchpad" to maintain any other state you need in order to iterate
+ properly. The context object is reused and is not reset between
+ iterations so make sure you setup the context with a fresh state whenever
+ the index parameter is 0.
+
+ Generally iterators will continue to call `nextObject` until the index
+ reaches the your current length-1. If you run out of data before this
+ time for some reason, you should simply return undefined.
+
+ The default implementation of this method simply looks up the index.
+ This works great on any Array-like objects.
+
+ @method nextObject
+ @param {Number} index the current index of the iteration
+ @param {Object} previousObject the value returned by the last call to
+ `nextObject`.
+ @param {Object} context a context object you can use to maintain state.
+ @return {Object} the next object in the iteration or undefined
+ */
+ nextObject: Ember.required(Function),
+
+ /**
+ Helper method returns the first object from a collection. This is usually
+ used by bindings and other parts of the framework to extract a single
+ object if the enumerable contains only one item.
+
+ If you override this method, you should implement it so that it will
+ always return the same value each time it is called. If your enumerable
+ contains only one object, this method should always return that object.
+ If your enumerable is empty, this method should return `undefined`.
+
+ ```javascript
+ var arr = ["a", "b", "c"];
+ arr.get('firstObject'); // "a"
+
+ var arr = [];
+ arr.get('firstObject'); // undefined
+ ```
+
+ @property firstObject
+ @return {Object} the object or undefined
+ */
+ firstObject: Ember.computed(function() {
+ if (get(this, 'length')===0) return undefined ;
+
+ // handle generic enumerables
+ var context = popCtx(), ret;
+ ret = this.nextObject(0, null, context);
+ pushCtx(context);
+ return ret ;
+ }).property('[]'),
+
+ /**
+ Helper method returns the last object from a collection. If your enumerable
+ contains only one object, this method should always return that object.
+ If your enumerable is empty, this method should return `undefined`.
+
+ ```javascript
+ var arr = ["a", "b", "c"];
+ arr.get('lastObject'); // "c"
+
+ var arr = [];
+ arr.get('lastObject'); // undefined
+ ```
+
+ @property lastObject
+ @return {Object} the last object or undefined
+ */
+ lastObject: Ember.computed(function() {
+ var len = get(this, 'length');
+ if (len===0) return undefined ;
+ var context = popCtx(), idx=0, cur, last = null;
+ do {
+ last = cur;
+ cur = this.nextObject(idx++, last, context);
+ } while (cur !== undefined);
+ pushCtx(context);
+ return last;
+ }).property('[]'),
+
+ /**
+ Returns `true` if the passed object can be found in the receiver. The
+ default version will iterate through the enumerable until the object
+ is found. You may want to override this with a more efficient version.
+
+ ```javascript
+ var arr = ["a", "b", "c"];
+ arr.contains("a"); // true
+ arr.contains("z"); // false
+ ```
+
+ @method contains
+ @param {Object} obj The object to search for.
+ @return {Boolean} `true` if object is found in enumerable.
+ */
+ contains: function(obj) {
+ return this.find(function(item) { return item===obj; }) !== undefined;
+ },
+
+ /**
+ Iterates through the enumerable, calling the passed function on each
+ item. This method corresponds to the `forEach()` method defined 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.
+
+ 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.
+
+ @method forEach
+ @param {Function} callback The callback to execute
+ @param {Object} [target] The target object to use
+ @return {Object} receiver
+ */
+ forEach: function(callback, target) {
+ if (typeof callback !== "function") throw new TypeError() ;
+ var len = get(this, 'length'), last = null, context = popCtx();
+
+ if (target === undefined) target = null;
+
+ for(var idx=0;idx<len;idx++) {
+ var next = this.nextObject(idx, last, context) ;
+ callback.call(target, next, idx, this);
+ last = next ;
+ }
+ last = null ;
+ context = pushCtx(context);
+ return this ;
+ },
+
+ /**
+ Alias for `mapBy`
+
+ @method getEach
+ @param {String} key name of the property
+ @return {Array} The mapped array.
+ */
+ getEach: function(key) {
+ return this.mapBy(key);
+ },
+
+ /**
+ Sets the value on the named property for each member. This is more
+ efficient than using other methods defined on this helper. If the object
+ implements Ember.Observable, the value will be changed to `set(),` otherwise
+ it will be set directly. `null` objects are skipped.
+
+ @method setEach
+ @param {String} key The key to set
+ @param {Object} value The object to set
+ @return {Object} receiver
+ */
+ setEach: function(key, value) {
+ return this.forEach(function(item) {
+ set(item, key, value);
+ });
+ },
+
+ /**
+ Maps all of the items in the enumeration to another value, returning
+ a new array. This method corresponds to `map()` defined 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 mapped value.
+
+ 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.
+
+ @method map
+ @param {Function} callback The callback to execute
+ @param {Object} [target] The target object to use
+ @return {Array} The mapped array.
+ */
+ map: function(callback, target) {
+ var ret = Ember.A();
+ this.forEach(function(x, idx, i) {
+ ret[idx] = callback.call(target, x, idx,i);
+ });
+ return ret ;
+ },
+
+ /**
+ Similar to map, this specialized function returns the value of the named
+ property on all items in the enumeration.
+
+ @method mapBy
+ @param {String} key name of the property
+ @return {Array} The mapped array.
+ */
+ mapBy: function(key) {
+ return this.map(function(next) {
+ return get(next, key);
+ });
+ },
+
+ /**
+ 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'),
+
+ /**
+ Returns an array with all of the items in the enumeration that the passed
+ function returns true for. This method corresponds to `filter()` defined 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.
+
+ @method filter
+ @param {Function} callback The callback to execute
+ @param {Object} [target] The target object to use
+ @return {Array} A filtered array.
+ */
+ filter: function(callback, target) {
+ var ret = Ember.A();
+ this.forEach(function(x, idx, i) {
+ if (callback.call(target, x, idx, i)) ret.push(x);
+ });
+ return ret ;
+ },
+
+ /**
+ Returns an array with all of the items in the enumeration where the passed
+ function returns false for. This method is the inverse of filter().
+
+ 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 a falsey value to include the item in the results.
+
+ 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.
+
+ @method reject
+ @param {Function} callback The callback to execute
+ @param {Object} [target] The target object to use
+ @return {Array} A rejected array.
+ */
+ reject: function(callback, target) {
+ return this.filter(function() {
+ return !(callback.apply(target, arguments));
+ });
+ },
+
+ /**
+ 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 filterBy
+ @param {String} key the property to test
+ @param {*} [value] optional value to test against.
+ @return {Array} filtered array
+ */
+ filterBy: function(key, value) {
+ return this.filter(iter.apply(this, arguments));
+ },
+
+ /**
+ 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'),
+
+ /**
+ 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 rejectBy
+ @param {String} key the property to test
+ @param {String} [value] optional value to test against.
+ @return {Array} rejected array
+ */
+ rejectBy: function(key, value) {
+ var exactValue = function(item) { return get(item, key) === value; },
+ hasValue = function(item) { return !!get(item, key); },
+ use = (arguments.length === 2 ? exactValue : hasValue);
+
+ return this.reject(use);
+ },
+
+ /**
+ 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'),
+
+ /**
+ Returns the first item in the array for which the callback returns true.
+ This method works similar to the `filter()` method defined in JavaScript 1.6
+ except that it will stop working on the array once a match is found.
+
+ 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.
+
+ @method find
+ @param {Function} callback The callback to execute
+ @param {Object} [target] The target object to use
+ @return {Object} Found item or `undefined`.
+ */
+ find: function(callback, target) {
+ var len = get(this, 'length') ;
+ if (target === undefined) target = null;
+
+ var last = null, next, found = false, ret ;
+ var context = popCtx();
+ for(var idx=0;idx<len && !found;idx++) {
+ next = this.nextObject(idx, last, context) ;
+ if (found = callback.call(target, next, idx, this)) ret = next ;
+ last = next ;
+ }
+ next = last = null ;
+ context = pushCtx(context);
+ return ret ;
+ },
+
+ /**
+ 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 findBy
+ @param {String} key the property to test
+ @param {String} [value] optional value to test against.
+ @return {Object} found item or `undefined`
+ */
+ findBy: function(key, value) {
+ return this.find(iter.apply(this, arguments));
+ },
+
+ /**
+ 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'),
+
+ /**
+ Returns `true` if the passed function returns true for every item in the
+ enumeration. This corresponds with the `every()` 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` or `false`.
+
+ 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.
+
+ Example Usage:
+
+ ```javascript
+ if (people.every(isEngineer)) { Paychecks.addBigBonus(); }
+ ```
+
+ @method every
+ @param {Function} callback The callback to execute
+ @param {Object} [target] The target object to use
+ @return {Boolean}
+ */
+ every: function(callback, target) {
+ return !this.find(function(x, idx, i) {
+ return !callback.call(target, x, idx, i);
+ });
+ },
+
+ /**
+ @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'),
+
+ /**
+ Returns `true` if the passed property resolves to `true` for all items in
+ the enumerable. This method is often simpler/faster than using a callback.
+
+ @method isEvery
+ @param {String} key the property to test
+ @param {String} [value] optional value to test against.
+ @return {Boolean}
+ */
+ isEvery: function(key, value) {
+ return this.every(iter.apply(this, arguments));
+ },
+
+ /**
+ 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.any(isManager)) { Paychecks.addBiggerBonus(); }
+ ```
+
+ @method any
+ @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
+ */
+ any: function(callback, target) {
+ var len = get(this, 'length'),
+ context = popCtx(),
+ found = false,
+ last = null,
+ next, idx;
+
+ if (target === undefined) { target = null; }
+
+ for (idx = 0; idx < len && !found; idx++) {
+ next = this.nextObject(idx, last, context);
+ found = callback.call(target, next, idx, this);
+ last = next;
+ }
+
+ next = last = null;
+ context = pushCtx(context);
+ return found;
+ },
+
+ /**
+ 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'),
+
+ /**
+ Returns `true` if the passed property resolves to `true` for any item in
+ the enumerable. This method is often simpler/faster than using a callback.
+
+ @method isAny
+ @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
+ */
+ isAny: function(key, value) {
+ return this.any(iter.apply(this, arguments));
+ },
+
+ /**
+ @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'),
+
+ /**
+ This will combine the values of the enumerator into a single value. It
+ is a useful way to collect a summary value from an enumeration. This
+ corresponds to the `reduce()` method defined in JavaScript 1.8.
+
+ The callback method you provide should have the following signature (all
+ parameters are optional):
+
+ ```javascript
+ function(previousValue, item, index, enumerable);
+ ```
+
+ - `previousValue` is the value returned by the last call to the iterator.
+ - `item` is the current item in the iteration.
+ - `index` is the current index in the iteration.
+ - `enumerable` is the enumerable object itself.
+
+ Return the new cumulative value.
+
+ In addition to the callback you can also pass an `initialValue`. An error
+ will be raised if you do not pass an initial value and the enumerator is
+ empty.
+
+ Note that unlike the other methods, this method does not allow you to
+ pass a target object to set as this for the callback. It's part of the
+ spec. Sorry.
+
+ @method reduce
+ @param {Function} callback The callback to execute
+ @param {Object} initialValue Initial value for the reduce
+ @param {String} reducerProperty internal use only.
+ @return {Object} The reduced value.
+ */
+ reduce: function(callback, initialValue, reducerProperty) {
+ if (typeof callback !== "function") { throw new TypeError(); }
+
+ var ret = initialValue;
+
+ this.forEach(function(item, i) {
+ ret = callback(ret, item, i, this, reducerProperty);
+ }, this);
+
+ return ret;
+ },
+
+ /**
+ Invokes the named method on every object in the receiver that
+ implements it. This method corresponds to the implementation in
+ Prototype 1.6.
+
+ @method invoke
+ @param {String} methodName the name of the method
+ @param {Object...} args optional arguments to pass as well.
+ @return {Array} return values from calling invoke.
+ */
+ invoke: function(methodName) {
+ var args, ret = Ember.A();
+ if (arguments.length>1) args = a_slice.call(arguments, 1);
+
+ this.forEach(function(x, idx) {
+ var method = x && x[methodName];
+ if ('function' === typeof method) {
+ ret[idx] = args ? method.apply(x, args) : x[methodName]();
+ }
+ }, this);
+
+ return ret;
+ },
+
+ /**
+ Simply converts the enumerable into a genuine array. The order is not
+ guaranteed. Corresponds to the method implemented by Prototype.
+
+ @method toArray
+ @return {Array} the enumerable as an array.
+ */
+ toArray: function() {
+ var ret = Ember.A();
+ this.forEach(function(o, idx) { ret[idx] = o; });
+ return ret ;
+ },
+
+ /**
+ Returns a copy of the array with all null and undefined elements removed.
+
+ ```javascript
+ var arr = ["a", null, "c", undefined];
+ arr.compact(); // ["a", "c"]
+ ```
+
+ @method compact
+ @return {Array} the array without null and undefined elements.
+ */
+ compact: function() {
+ return this.filter(function(value) { return value != null; });
+ },
+
+ /**
+ Returns a new enumerable that excludes the passed value. The default
+ implementation returns an array regardless of the receiver type unless
+ the receiver does not contain the value.
+
+ ```javascript
+ var arr = ["a", "b", "a", "c"];
+ arr.without("a"); // ["b", "c"]
+ ```
+
+ @method without
+ @param {Object} value
+ @return {Ember.Enumerable}
+ */
+ without: function(value) {
+ if (!this.contains(value)) return this; // nothing to do
+ var ret = Ember.A();
+ this.forEach(function(k) {
+ if (k !== value) ret[ret.length] = k;
+ }) ;
+ return ret ;
+ },
+
+ /**
+ Returns a new enumerable that contains only unique values. The default
+ implementation returns an array regardless of the receiver type.
+
+ ```javascript
+ var arr = ["a", "a", "b", "b"];
+ arr.uniq(); // ["a", "b"]
+ ```
+
+ @method uniq
+ @return {Ember.Enumerable}
+ */
+ uniq: function() {
+ var ret = Ember.A();
+ this.forEach(function(k) {
+ if (a_indexOf(ret, k)<0) ret.push(k);
+ });
+ return ret;
+ },
+
+ /**
+ This property will trigger anytime the enumerable's content changes.
+ You can observe this property to be notified of changes to the enumerables
+ content.
+
+ For plain enumerables, this property is read only. `Ember.Array` overrides
+ this method.
+
+ @property []
+ @type Ember.Array
+ @return this
+ */
+ '[]': Ember.computed(function(key, value) {
+ return this;
+ }),
+
+ // ..........................................................
+ // ENUMERABLE OBSERVERS
+ //
+
+ /**
+ Registers an enumerable observer. Must implement `Ember.EnumerableObserver`
+ mixin.
+
+ @method addEnumerableObserver
+ @param {Object} target
+ @param {Hash} [opts]
+ @return this
+ */
+ addEnumerableObserver: function(target, opts) {
+ var willChange = (opts && opts.willChange) || 'enumerableWillChange',
+ didChange = (opts && opts.didChange) || 'enumerableDidChange';
+
+ var hasObservers = get(this, 'hasEnumerableObservers');
+ if (!hasObservers) Ember.propertyWillChange(this, 'hasEnumerableObservers');
+ Ember.addListener(this, '@enumerable:before', target, willChange);
+ Ember.addListener(this, '@enumerable:change', target, didChange);
+ if (!hasObservers) Ember.propertyDidChange(this, 'hasEnumerableObservers');
+ return this;
+ },
+
+ /**
+ Removes a registered enumerable observer.
+
+ @method removeEnumerableObserver
+ @param {Object} target
+ @param {Hash} [opts]
+ @return this
+ */
+ removeEnumerableObserver: function(target, opts) {
+ var willChange = (opts && opts.willChange) || 'enumerableWillChange',
+ didChange = (opts && opts.didChange) || 'enumerableDidChange';
+
+ var hasObservers = get(this, 'hasEnumerableObservers');
+ if (hasObservers) Ember.propertyWillChange(this, 'hasEnumerableObservers');
+ Ember.removeListener(this, '@enumerable:before', target, willChange);
+ Ember.removeListener(this, '@enumerable:change', target, didChange);
+ if (hasObservers) Ember.propertyDidChange(this, 'hasEnumerableObservers');
+ return this;
+ },
+
+ /**
+ Becomes true whenever the array currently has observers watching changes
+ on the array.
+
+ @property hasEnumerableObservers
+ @type Boolean
+ */
+ hasEnumerableObservers: Ember.computed(function() {
+ return Ember.hasListeners(this, '@enumerable:change') || Ember.hasListeners(this, '@enumerable:before');
+ }),
+
+
+ /**
+ Invoke this method just before the contents of your enumerable will
+ change. You can either omit the parameters completely or pass the objects
+ to be removed or added if available or just a count.
+
+ @method enumerableContentWillChange
+ @param {Ember.Enumerable|Number} removing An enumerable of the objects to
+ be removed or the number of items to be removed.
+ @param {Ember.Enumerable|Number} adding An enumerable of the objects to be
+ added or the number of items to be added.
+ @chainable
+ */
+ enumerableContentWillChange: function(removing, adding) {
+
+ var removeCnt, addCnt, hasDelta;
+
+ if ('number' === typeof removing) removeCnt = removing;
+ else if (removing) removeCnt = get(removing, 'length');
+ else removeCnt = removing = -1;
+
+ if ('number' === typeof adding) addCnt = adding;
+ else if (adding) addCnt = get(adding,'length');
+ else addCnt = adding = -1;
+
+ hasDelta = addCnt<0 || removeCnt<0 || addCnt-removeCnt!==0;
+
+ if (removing === -1) removing = null;
+ if (adding === -1) adding = null;
+
+ Ember.propertyWillChange(this, '[]');
+ if (hasDelta) Ember.propertyWillChange(this, 'length');
+ Ember.sendEvent(this, '@enumerable:before', [this, removing, adding]);
+
+ return this;
+ },
+
+ /**
+ Invoke this method when the contents of your enumerable has changed.
+ This will notify any observers watching for content changes. If your are
+ implementing an ordered enumerable (such as an array), also pass the
+ start and end values where the content changed so that it can be used to
+ notify range observers.
+
+ @method enumerableContentDidChange
+ @param {Ember.Enumerable|Number} removing An enumerable of the objects to
+ be removed or the number of items to be removed.
+ @param {Ember.Enumerable|Number} adding An enumerable of the objects to
+ be added or the number of items to be added.
+ @chainable
+ */
+ enumerableContentDidChange: function(removing, adding) {
+ var removeCnt, addCnt, hasDelta;
+
+ if ('number' === typeof removing) removeCnt = removing;
+ else if (removing) removeCnt = get(removing, 'length');
+ else removeCnt = removing = -1;
+
+ if ('number' === typeof adding) addCnt = adding;
+ else if (adding) addCnt = get(adding, 'length');
+ else addCnt = adding = -1;
+
+ hasDelta = addCnt<0 || removeCnt<0 || addCnt-removeCnt!==0;
+
+ if (removing === -1) removing = null;
+ if (adding === -1) adding = null;
+
+ Ember.sendEvent(this, '@enumerable:change', [this, removing, adding]);
+ if (hasDelta) Ember.propertyDidChange(this, 'length');
+ Ember.propertyDidChange(this, '[]');
+
+ return this ;
+ },
+
+ /**
+ Converts the enumerable into an array and sorts by the keys
+ specified in the argument.
+
+ You may provide multiple arguments to sort by multiple properties.
+
+ @method sortBy
+ @param {String} property name(s) to sort on
+ @return {Array} The sorted array.
+ */
+ sortBy: function() {
+ var sortKeys = arguments;
+ return this.toArray().sort(function(a, b){
+ for(var i = 0; i < sortKeys.length; i++) {
+ var key = sortKeys[i],
+ propA = get(a, key),
+ propB = get(b, key);
+ // return 1 or -1 else continue to the next sortKey
+ var compareValue = Ember.compare(propA, propB);
+ if (compareValue) { return compareValue; }
+ }
+ return 0;
+ });
+ }
+});
+
+})();
+
+
+
+(function() {
+/**
+@module ember
+@submodule ember-runtime
+*/
+
+// ..........................................................
+// HELPERS
+//
+
+var get = Ember.get, set = Ember.set, isNone = Ember.isNone, map = Ember.EnumerableUtils.map, cacheFor = Ember.cacheFor;
+
+// ..........................................................
+// ARRAY
+//
+/**
+ This mixin implements Observer-friendly Array-like behavior. It is not a
+ concrete implementation, but it can be used up by other classes that want
+ to appear like arrays.
+
+ For example, ArrayProxy and ArrayController are both concrete classes that can
+ be instantiated to implement array-like behavior. Both of these classes use
+ the Array Mixin by way of the MutableArray mixin, which allows observable
+ changes to be made to the underlying array.
+
+ Unlike `Ember.Enumerable,` this mixin defines methods specifically for
+ collections that provide index-ordered access to their contents. When you
+ are designing code that needs to accept any kind of Array-like object, you
+ should use these methods instead of Array primitives because these will
+ properly notify observers of changes to the array.
+
+ Although these methods are efficient, they do add a layer of indirection to
+ your application so it is a good idea to use them only when you need the
+ flexibility of using both true JavaScript arrays and "virtual" arrays such
+ as controllers and collections.
+
+ You can use the methods defined in this module to access and modify array
+ contents in a KVO-friendly way. You can also be notified whenever the
+ membership of an array changes by changing the syntax of the property to
+ `.observes('*myProperty.[]')`.
+
+ To support `Ember.Array` in your own class, you must override two
+ primitives to use it: `replace()` and `objectAt()`.
+
+ Note that the Ember.Array mixin also incorporates the `Ember.Enumerable`
+ mixin. All `Ember.Array`-like objects are also enumerable.
+
+ @class Array
+ @namespace Ember
+ @uses Ember.Enumerable
+ @since Ember 0.9.0
+*/
+Ember.Array = Ember.Mixin.create(Ember.Enumerable, {
+
+ /**
+ Your array must support the `length` property. Your replace methods should
+ set this property whenever it changes.
+
+ @property {Number} length
+ */
+ length: Ember.required(),
+
+ /**
+ Returns the object at the given `index`. If the given `index` is negative
+ or is greater or equal than the array length, returns `undefined`.
+
+ This is one of the primitives you must implement to support `Ember.Array`.
+ If your object supports retrieving the value of an array item using `get()`
+ (i.e. `myArray.get(0)`), then you do not need to implement this method
+ yourself.
+
+ ```javascript
+ var arr = ['a', 'b', 'c', 'd'];
+ arr.objectAt(0); // "a"
+ arr.objectAt(3); // "d"
+ arr.objectAt(-1); // undefined
+ arr.objectAt(4); // undefined
+ arr.objectAt(5); // undefined
+ ```
+
+ @method objectAt
+ @param {Number} idx The index of the item to return.
+ @return {*} item at index or undefined
+ */
+ objectAt: function(idx) {
+ if ((idx < 0) || (idx>=get(this, 'length'))) return undefined ;
+ return get(this, idx);
+ },
+
+ /**
+ This returns the objects at the specified indexes, using `objectAt`.
+
+ ```javascript
+ var arr = ['a', 'b', 'c', 'd'];
+ arr.objectsAt([0, 1, 2]); // ["a", "b", "c"]
+ arr.objectsAt([2, 3, 4]); // ["c", "d", undefined]
+ ```
+
+ @method objectsAt
+ @param {Array} indexes An array of indexes of items to return.
+ @return {Array}
+ */
+ objectsAt: function(indexes) {
+ var self = this;
+ return map(indexes, function(idx) { return self.objectAt(idx); });
+ },
+
+ // overrides Ember.Enumerable version
+ nextObject: function(idx) {
+ return this.objectAt(idx);
+ },
+
+ /**
+ This is the handler for the special array content property. If you get
+ this property, it will return this. If you set this property it a new
+ array, it will replace the current content.
+
+ This property overrides the default property defined in `Ember.Enumerable`.
+
+ @property []
+ @return this
+ */
+ '[]': Ember.computed(function(key, value) {
+ if (value !== undefined) this.replace(0, get(this, 'length'), value) ;
+ return this ;
+ }),
+
+ firstObject: Ember.computed(function() {
+ return this.objectAt(0);
+ }),
+
+ lastObject: Ember.computed(function() {
+ return this.objectAt(get(this, 'length')-1);
+ }),
+
+ // optimized version from Enumerable
+ contains: function(obj) {
+ return this.indexOf(obj) >= 0;
+ },
+
+ // Add any extra methods to Ember.Array that are native to the built-in Array.
+ /**
+ Returns a new array that is a slice of the receiver. This implementation
+ uses the observable array methods to retrieve the objects for the new
+ slice.
+
+ ```javascript
+ var arr = ['red', 'green', 'blue'];
+ arr.slice(0); // ['red', 'green', 'blue']
+ arr.slice(0, 2); // ['red', 'green']
+ arr.slice(1, 100); // ['green', 'blue']
+ ```
+
+ @method slice
+ @param {Integer} beginIndex (Optional) index to begin slicing from.
+ @param {Integer} endIndex (Optional) index to end the slice at (but not included).
+ @return {Array} New array with specified slice
+ */
+ slice: function(beginIndex, endIndex) {
+ var ret = Ember.A();
+ var length = get(this, 'length') ;
+ if (isNone(beginIndex)) beginIndex = 0 ;
+ if (isNone(endIndex) || (endIndex > length)) endIndex = length ;
+
+ if (beginIndex < 0) beginIndex = length + beginIndex;
+ if (endIndex < 0) endIndex = length + endIndex;
+
+ while(beginIndex < endIndex) {
+ ret[ret.length] = this.objectAt(beginIndex++) ;
+ }
+ return ret ;
+ },
+
+ /**
+ Returns the index of the given object's first occurrence.
+ If no `startAt` argument is given, the starting location to
+ search is 0. If it's negative, will count backward from
+ the end of the array. Returns -1 if no match is found.
+
+ ```javascript
+ var arr = ["a", "b", "c", "d", "a"];
+ arr.indexOf("a"); // 0
+ arr.indexOf("z"); // -1
+ arr.indexOf("a", 2); // 4
+ arr.indexOf("a", -1); // 4
+ arr.indexOf("b", 3); // -1
+ arr.indexOf("a", 100); // -1
+ ```
+
+ @method indexOf
+ @param {Object} object the item to search for
+ @param {Number} startAt optional starting location to search, default 0
+ @return {Number} index or -1 if not found
+ */
+ indexOf: function(object, startAt) {
+ var idx, len = get(this, 'length');
+
+ if (startAt === undefined) startAt = 0;
+ if (startAt < 0) startAt += len;
+
+ for(idx=startAt;idx<len;idx++) {
+ if (this.objectAt(idx) === object) return idx ;
+ }
+ return -1;
+ },
+
+ /**
+ Returns the index of the given object's last occurrence.
+ If no `startAt` argument is given, the search starts from
+ the last position. If it's negative, will count backward
+ from the end of the array. Returns -1 if no match is found.
+
+ ```javascript
+ var arr = ["a", "b", "c", "d", "a"];
+ arr.lastIndexOf("a"); // 4
+ arr.lastIndexOf("z"); // -1
+ arr.lastIndexOf("a", 2); // 0
+ arr.lastIndexOf("a", -1); // 4
+ arr.lastIndexOf("b", 3); // 1
+ arr.lastIndexOf("a", 100); // 4
+ ```
+
+ @method lastIndexOf
+ @param {Object} object the item to search for
+ @param {Number} startAt optional starting location to search, default 0
+ @return {Number} index or -1 if not found
+ */
+ lastIndexOf: function(object, startAt) {
+ var idx, len = get(this, 'length');
+
+ if (startAt === undefined || startAt >= len) startAt = len-1;
+ if (startAt < 0) startAt += len;
+
+ for(idx=startAt;idx>=0;idx--) {
+ if (this.objectAt(idx) === object) return idx ;
+ }
+ return -1;
+ },
+
+ // ..........................................................
+ // ARRAY OBSERVERS
+ //
+
+ /**
+ Adds an array observer to the receiving array. The array observer object
+ normally must implement two methods:
+
+ * `arrayWillChange(observedObj, start, removeCount, addCount)` - This method will be
+ called just before the array is modified.
+ * `arrayDidChange(observedObj, start, removeCount, addCount)` - This method will be
+ called just after the array is modified.
+
+ Both callbacks will be passed the observed object, starting index of the
+ change as well a a count of the items to be removed and added. You can use
+ these callbacks to optionally inspect the array during the change, clear
+ caches, or do any other bookkeeping necessary.
+
+ In addition to passing a target, you can also include an options hash
+ which you can use to override the method names that will be invoked on the
+ target.
+
+ @method addArrayObserver
+ @param {Object} target The observer object.
+ @param {Hash} opts Optional hash of configuration options including
+ `willChange` and `didChange` option.
+ @return {Ember.Array} receiver
+ */
+ addArrayObserver: function(target, opts) {
+ var willChange = (opts && opts.willChange) || 'arrayWillChange',
+ didChange = (opts && opts.didChange) || 'arrayDidChange';
+
+ var hasObservers = get(this, 'hasArrayObservers');
+ if (!hasObservers) Ember.propertyWillChange(this, 'hasArrayObservers');
+ Ember.addListener(this, '@array:before', target, willChange);
+ Ember.addListener(this, '@array:change', target, didChange);
+ if (!hasObservers) Ember.propertyDidChange(this, 'hasArrayObservers');
+ return this;
+ },
+
+ /**
+ Removes an array observer from the object if the observer is current
+ registered. Calling this method multiple times with the same object will
+ have no effect.
+
+ @method removeArrayObserver
+ @param {Object} target The object observing the array.
+ @param {Hash} opts Optional hash of configuration options including
+ `willChange` and `didChange` option.
+ @return {Ember.Array} receiver
+ */
+ removeArrayObserver: function(target, opts) {
+ var willChange = (opts && opts.willChange) || 'arrayWillChange',
+ didChange = (opts && opts.didChange) || 'arrayDidChange';
+
+ var hasObservers = get(this, 'hasArrayObservers');
+ if (hasObservers) Ember.propertyWillChange(this, 'hasArrayObservers');
+ Ember.removeListener(this, '@array:before', target, willChange);
+ Ember.removeListener(this, '@array:change', target, didChange);
+ if (hasObservers) Ember.propertyDidChange(this, 'hasArrayObservers');
+ return this;
+ },
+
+ /**
+ Becomes true whenever the array currently has observers watching changes
+ on the array.
+
+ @property {Boolean} hasArrayObservers
+ */
+ hasArrayObservers: Ember.computed(function() {
+ return Ember.hasListeners(this, '@array:change') || Ember.hasListeners(this, '@array:before');
+ }),
+
+ /**
+ If you are implementing an object that supports `Ember.Array`, call this
+ method just before the array content changes to notify any observers and
+ invalidate any related properties. Pass the starting index of the change
+ as well as a delta of the amounts to change.
+
+ @method arrayContentWillChange
+ @param {Number} startIdx The starting index in the array that will change.
+ @param {Number} removeAmt The number of items that will be removed. If you
+ pass `null` assumes 0
+ @param {Number} addAmt The number of items that will be added. If you
+ pass `null` assumes 0.
+ @return {Ember.Array} receiver
+ */
+ arrayContentWillChange: function(startIdx, removeAmt, addAmt) {
+
+ // if no args are passed assume everything changes
+ if (startIdx===undefined) {
+ startIdx = 0;
+ removeAmt = addAmt = -1;
+ } else {
+ if (removeAmt === undefined) removeAmt=-1;
+ if (addAmt === undefined) addAmt=-1;
+ }
+
+ // Make sure the @each proxy is set up if anyone is observing @each
+ if (Ember.isWatching(this, '@each')) { get(this, '@each'); }
+
+ Ember.sendEvent(this, '@array:before', [this, startIdx, removeAmt, addAmt]);
+
+ var removing, lim;
+ if (startIdx>=0 && removeAmt>=0 && get(this, 'hasEnumerableObservers')) {
+ removing = [];
+ lim = startIdx+removeAmt;
+ for(var idx=startIdx;idx<lim;idx++) removing.push(this.objectAt(idx));
+ } else {
+ removing = removeAmt;
+ }
+
+ this.enumerableContentWillChange(removing, addAmt);
+
+ return this;
+ },
+
+ /**
+ 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
+ */
+ arrayContentDidChange: function(startIdx, removeAmt, addAmt) {
+
+ // if no args are passed assume everything changes
+ if (startIdx===undefined) {
+ startIdx = 0;
+ removeAmt = addAmt = -1;
+ } else {
+ if (removeAmt === undefined) removeAmt=-1;
+ if (addAmt === undefined) addAmt=-1;
+ }
+
+ var adding, lim;
+ if (startIdx>=0 && addAmt>=0 && get(this, 'hasEnumerableObservers')) {
+ adding = [];
+ lim = startIdx+addAmt;
+ for(var idx=startIdx;idx<lim;idx++) adding.push(this.objectAt(idx));
+ } else {
+ adding = addAmt;
+ }
+
+ this.enumerableContentDidChange(removeAmt, adding);
+ Ember.sendEvent(this, '@array:change', [this, startIdx, removeAmt, addAmt]);
+
+ var length = get(this, 'length'),
+ cachedFirst = cacheFor(this, 'firstObject'),
+ cachedLast = cacheFor(this, 'lastObject');
+ if (this.objectAt(0) !== cachedFirst) {
+ Ember.propertyWillChange(this, 'firstObject');
+ Ember.propertyDidChange(this, 'firstObject');
+ }
+ if (this.objectAt(length-1) !== cachedLast) {
+ Ember.propertyWillChange(this, 'lastObject');
+ Ember.propertyDidChange(this, 'lastObject');
+ }
+
+ return this;
+ },
+
+ // ..........................................................
+ // ENUMERATED PROPERTIES
+ //
+
+ /**
+ Returns a special object that can be used to observe individual properties
+ on the array. Just get an equivalent property on this object and it will
+ return an enumerable that maps automatically to the named key on the
+ member objects.
+
+ If you merely want to watch for any items being added or removed to the array,
+ use the `[]` property instead of `@each`.
+
+ @property @each
+ */
+ '@each': Ember.computed(function() {
+ if (!this.__each) this.__each = new Ember.EachProxy(this);
+ return this.__each;
+ })
+
+}) ;
+
+})();
+
+
+
+(function() {
+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,
+ cacheSet = Ember.cacheFor.set,
+ cacheGet = Ember.cacheFor.get,
+ cacheRemove = Ember.cacheFor.remove,
+ // Here we explicitly don't allow `(a)each.foo`; it would require some special
+ // testing, but there's no particular reason why it should be disallowed.
+ eachPropertyPattern = /^(.*)\.(a)each\.(.*)/,
+ doubleEachPropertyPattern = /(.*\.@each){2,}/,
+ arrayBracketPattern = /\.\[\]$/;
+
+var expandProperties = Ember.expandProperties;
+
+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) {
+ this.dependentKeysByGuid[guidFor(dependentArray)] = dependentKey;
+
+ dependentArray.addArrayObserver(this, {
+ willChange: 'dependentArrayWillChange',
+ didChange: 'dependentArrayDidChange'
+ });
+
+ if (this.cp._itemPropertyKeys[dependentKey]) {
+ this.setupPropertyObservers(dependentKey, this.cp._itemPropertyKeys[dependentKey]);
+ }
+ },
+
+ teardownObservers: function (dependentArray, dependentKey) {
+ var itemPropertyKeys = this.cp._itemPropertyKeys[dependentKey] || [];
+
+ delete this.dependentKeysByGuid[guidFor(dependentArray)];
+
+ this.teardownPropertyObservers(dependentKey, itemPropertyKeys);
+
+ dependentArray.removeArrayObserver(this, {
+ willChange: 'dependentArrayWillChange',
+ didChange: 'dependentArrayDidChange'
+ });
+ },
+
+ suspendArrayObservers: function (callback, binding) {
+ var oldSuspended = this.suspended;
+ this.suspended = true;
+ callback.call(binding);
+ this.suspended = oldSuspended;
+ },
+
+ setupPropertyObservers: function (dependentKey, itemPropertyKeys) {
+ var dependentArray = get(this.instanceMeta.context, dependentKey),
+ length = get(dependentArray, 'length'),
+ observerContexts = new Array(length);
+
+ this.resetTransformations(dependentKey, observerContexts);
+
+ forEach(dependentArray, function (item, index) {
+ var observerContext = this.createPropertyObserverContext(dependentArray, index, this.trackedArraysByGuid[dependentKey]);
+ observerContexts[index] = observerContext;
+
+ forEach(itemPropertyKeys, function (propertyKey) {
+ addBeforeObserver(item, propertyKey, this, observerContext.beforeObserver);
+ addObserver(item, propertyKey, this, observerContext.observer);
+ }, this);
+ }, this);
+ },
+
+ teardownPropertyObservers: function (dependentKey, itemPropertyKeys) {
+ var dependentArrayObserver = this,
+ trackedArray = this.trackedArraysByGuid[dependentKey],
+ beforeObserver,
+ observer,
+ item;
+
+ if (!trackedArray) { return; }
+
+ trackedArray.apply(function (observerContexts, offset, operation) {
+ if (operation === Ember.TrackedArray.DELETE) { return; }
+
+ forEach(observerContexts, function (observerContext) {
+ observerContext.destroyed = true;
+ beforeObserver = observerContext.beforeObserver;
+ observer = observerContext.observer;
+ item = observerContext.item;
+
+ forEach(itemPropertyKeys, function (propertyKey) {
+ removeBeforeObserver(item, propertyKey, dependentArrayObserver, beforeObserver);
+ removeObserver(item, propertyKey, dependentArrayObserver, observer);
+ });
+ });
+ });
+ },
+
+ createPropertyObserverContext: function (dependentArray, index, trackedArray) {
+ var observerContext = new ItemPropertyObserverContext(dependentArray, index, trackedArray);
+
+ this.createPropertyObserver(observerContext);
+
+ return observerContext;
+ },
+
+ createPropertyObserver: function (observerContext) {
+ var dependentArrayObserver = this;
+
+ observerContext.beforeObserver = function (obj, keyName) {
+ return dependentArrayObserver.itemPropertyWillChange(obj, keyName, observerContext.dependentArray, observerContext);
+ };
+ observerContext.observer = function (obj, keyName) {
+ return dependentArrayObserver.itemPropertyDidChange(obj, keyName, observerContext.dependentArray, observerContext);
+ };
+ },
+
+ resetTransformations: function (dependentKey, observerContexts) {
+ this.trackedArraysByGuid[dependentKey] = new Ember.TrackedArray(observerContexts);
+ },
+
+ trackAdd: function (dependentKey, index, newItems) {
+ var trackedArray = this.trackedArraysByGuid[dependentKey];
+ if (trackedArray) {
+ trackedArray.addItems(index, newItems);
+ }
+ },
+
+ trackRemove: function (dependentKey, index, removedCount) {
+ var trackedArray = this.trackedArraysByGuid[dependentKey];
+
+ if (trackedArray) {
+ return trackedArray.removeItems(index, removedCount);
+ }
+
+ return [];
+ },
+
+ updateIndexes: function (trackedArray, array) {
+ var length = get(array, 'length');
+ // OPTIMIZE: we could stop updating once we hit the object whose observer
+ // fired; ie partially apply the transformations
+ trackedArray.apply(function (observerContexts, offset, operation) {
+ // we don't even have observer contexts for removed items, even if we did,
+ // they no longer have any index in the array
+ if (operation === Ember.TrackedArray.DELETE) { return; }
+ if (operation === Ember.TrackedArray.RETAIN && observerContexts.length === length && offset === 0) {
+ // If we update many items we don't want to walk the array each time: we
+ // only need to update the indexes at most once per run loop.
+ return;
+ }
+
+ forEach(observerContexts, function (context, index) {
+ context.index = index + offset;
+ });
+ });
+ },
+
+ dependentArrayWillChange: function (dependentArray, index, removedCount, addedCount) {
+ if (this.suspended) { return; }
+
+ var removedItem = this.callbacks.removedItem,
+ changeMeta,
+ guid = guidFor(dependentArray),
+ dependentKey = this.dependentKeysByGuid[guid],
+ itemPropertyKeys = this.cp._itemPropertyKeys[dependentKey] || [],
+ length = get(dependentArray, 'length'),
+ normalizedIndex = normalizeIndex(index, length, 0),
+ normalizedRemoveCount = normalizeRemoveCount(normalizedIndex, length, removedCount),
+ item,
+ itemIndex,
+ sliceIndex,
+ observerContexts;
+
+ observerContexts = this.trackRemove(dependentKey, normalizedIndex, normalizedRemoveCount);
+
+ function removeObservers(propertyKey) {
+ observerContexts[sliceIndex].destroyed = true;
+ removeBeforeObserver(item, propertyKey, this, observerContexts[sliceIndex].beforeObserver);
+ removeObserver(item, propertyKey, this, observerContexts[sliceIndex].observer);
+ }
+
+ for (sliceIndex = normalizedRemoveCount - 1; sliceIndex >= 0; --sliceIndex) {
+ itemIndex = normalizedIndex + sliceIndex;
+ if (itemIndex >= length) { break; }
+
+ item = dependentArray.objectAt(itemIndex);
+
+ forEach(itemPropertyKeys, removeObservers, this);
+
+ changeMeta = createChangeMeta(dependentArray, item, itemIndex, this.instanceMeta.propertyName, this.cp);
+ this.setValue( removedItem.call(
+ this.instanceMeta.context, this.getValue(), item, changeMeta, this.instanceMeta.sugarMeta));
+ }
+ },
+
+ dependentArrayDidChange: function (dependentArray, index, removedCount, addedCount) {
+ if (this.suspended) { return; }
+
+ var addedItem = this.callbacks.addedItem,
+ guid = guidFor(dependentArray),
+ dependentKey = this.dependentKeysByGuid[guid],
+ observerContexts = new Array(addedCount),
+ itemPropertyKeys = this.cp._itemPropertyKeys[dependentKey],
+ length = get(dependentArray, 'length'),
+ normalizedIndex = normalizeIndex(index, length, addedCount),
+ changeMeta,
+ observerContext;
+
+ forEach(dependentArray.slice(normalizedIndex, normalizedIndex + addedCount), function (item, sliceIndex) {
+ if (itemPropertyKeys) {
+ observerContext =
+ observerContexts[sliceIndex] =
+ this.createPropertyObserverContext(dependentArray, normalizedIndex + sliceIndex, this.trackedArraysByGuid[dependentKey]);
+ forEach(itemPropertyKeys, function (propertyKey) {
+ addBeforeObserver(item, propertyKey, this, observerContext.beforeObserver);
+ addObserver(item, propertyKey, this, observerContext.observer);
+ }, this);
+ }
+
+ changeMeta = createChangeMeta(dependentArray, item, normalizedIndex + sliceIndex, this.instanceMeta.propertyName, this.cp);
+ this.setValue( addedItem.call(
+ this.instanceMeta.context, this.getValue(), item, changeMeta, this.instanceMeta.sugarMeta));
+ }, this);
+
+ this.trackAdd(dependentKey, normalizedIndex, observerContexts);
+ },
+
+ itemPropertyWillChange: function (obj, keyName, array, observerContext) {
+ var guid = guidFor(obj);
+
+ if (!this.changedItems[guid]) {
+ this.changedItems[guid] = {
+ array: array,
+ observerContext: observerContext,
+ obj: obj,
+ previousValues: {}
+ };
+ }
+
+ this.changedItems[guid].previousValues[keyName] = get(obj, keyName);
+ },
+
+ itemPropertyDidChange: function(obj, keyName, array, observerContext) {
+ this.flushChanges();
+ },
+
+ flushChanges: function() {
+ var changedItems = this.changedItems, key, c, changeMeta;
+
+ for (key in changedItems) {
+ c = changedItems[key];
+ if (c.observerContext.destroyed) { continue; }
+
+ this.updateIndexes(c.observerContext.trackedArray, c.observerContext.dependentArray);
+
+ changeMeta = createChangeMeta(c.array, c.obj, c.observerContext.index, this.instanceMeta.propertyName, this.cp, c.previousValues);
+ this.setValue(
+ this.callbacks.removedItem.call(this.instanceMeta.context, this.getValue(), c.obj, changeMeta, this.instanceMeta.sugarMeta));
+ this.setValue(
+ this.callbacks.addedItem.call(this.instanceMeta.context, this.getValue(), c.obj, changeMeta, this.instanceMeta.sugarMeta));
+ }
+ this.changedItems = {};
+ }
+};
+
+function normalizeIndex(index, length, newItemsOffset) {
+ if (index < 0) {
+ return Math.max(0, length + index);
+ } else if (index < length) {
+ return index;
+ } else /* index > length */ {
+ return Math.min(length - newItemsOffset, index);
+ }
+}
+
+function normalizeRemoveCount(index, length, removedCount) {
+ return Math.min(removedCount, length - index);
+}
+
+function createChangeMeta(dependentArray, item, index, propertyName, property, previousValues) {
+ var meta = {
+ arrayChanged: dependentArray,
+ index: index,
+ item: item,
+ propertyName: propertyName,
+ property: property
+ };
+
+ if (previousValues) {
+ // previous values only available for item property changes
+ meta.previousValues = previousValues;
+ }
+
+ return meta;
+}
+
+function addItems (dependentArray, callbacks, cp, propertyName, meta) {
+ forEach(dependentArray, function (item, index) {
+ meta.setValue( callbacks.addedItem.call(
+ this, meta.getValue(), item, createChangeMeta(dependentArray, item, index, propertyName, cp), meta.sugarMeta));
+ }, this);
+}
+
+function reset(cp, propertyName) {
+ var callbacks = cp._callbacks(),
+ meta;
+
+ if (cp._hasInstanceMeta(this, propertyName)) {
+ meta = cp._instanceMeta(this, propertyName);
+ meta.setValue(cp.resetValue(meta.getValue()));
+ } else {
+ meta = cp._instanceMeta(this, propertyName);
+ }
+
+ if (cp.options.initialize) {
+ cp.options.initialize.call(this, meta.getValue(), { property: cp, propertyName: propertyName }, meta.sugarMeta);
+ }
+}
+
+function partiallyRecomputeFor(obj, dependentKey) {
+ if (arrayBracketPattern.test(dependentKey)) {
+ return false;
+ }
+
+ var value = get(obj, dependentKey);
+ return Ember.Array.detect(value);
+}
+
+function ReduceComputedPropertyInstanceMeta(context, propertyName, initialValue) {
+ this.context = context;
+ this.propertyName = propertyName;
+ this.cache = metaFor(context).cache;
+
+ this.dependentArrays = {};
+ this.sugarMeta = {};
+
+ this.initialValue = initialValue;
+}
+
+ReduceComputedPropertyInstanceMeta.prototype = {
+ getValue: function () {
+ if (this.propertyName in this.cache) {
+ return this.cache[this.propertyName];
+ } else {
+ return this.initialValue;
+ }
+ },
+
+ setValue: function(newValue, triggerObservers) {
+ // This lets sugars force a recomputation, handy for very simple
+ // implementations of eg max.
+ if (newValue === this.cache[this.propertyName]) {
+ return;
+ }
+
+ if (triggerObservers) {
+ propertyWillChange(this.context, this.propertyName);
+ }
+
+ if (newValue === undefined) {
+ delete this.cache[this.propertyName];
+ } else {
+ this.cache[this.propertyName] = newValue;
+ }
+
+ if (triggerObservers) {
+ propertyDidChange(this.context, this.propertyName);
+ }
+ }
+};
+
+/**
+ A computed property whose dependent keys are arrays and which is updated with
+ "one at a time" semantics.
+
+ @class ReduceComputedProperty
+ @namespace Ember
+ @extends Ember.ComputedProperty
+ @constructor
+*/
+function ReduceComputedProperty(options) {
+ var cp = this;
+
+ this.options = options;
+
+ this._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) {
+ // TODO: Coalesce recomputation by <this, propertyName, cp>.
+ recompute.call(this, 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) {
+ Ember.assert(
+ "dependent array " + dependentKey + " must be an `Ember.Array`. " +
+ "If you are not extending arrays, you will need to wrap native arrays with `Ember.A`",
+ !(Ember.isArray(get(this, dependentKey)) && !Ember.Array.detect(get(this, 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) {
+ return !!metaFor(context).cacheMeta[propertyName];
+};
+
+ReduceComputedProperty.prototype._instanceMeta = function (context, propertyName) {
+ var cacheMeta = metaFor(context).cacheMeta,
+ meta = cacheMeta[propertyName];
+
+ if (!meta) {
+ meta = cacheMeta[propertyName] = 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(args, function (dependentKey) {
+ if (doubleEachPropertyPattern.test(dependentKey)) {
+ throw new Ember.Error("Nested @each properties not supported: " + dependentKey);
+ } else if (match = eachPropertyPattern.exec(dependentKey)) {
+ dependentArrayKey = match[1];
+
+ var itemPropertyKeyPattern = match[2],
+ addItemPropertyKey = function (itemPropertyKey) {
+ cp.itemPropertyKey(dependentArrayKey, itemPropertyKey);
+ };
+
+ expandProperties(itemPropertyKeyPattern, addItemPropertyKey);
+ propertyArgs.add(dependentArrayKey);
+ } else {
+ propertyArgs.add(dependentKey);
+ }
+ });
+
+ return ComputedProperty.prototype.property.apply(this, propertyArgs.toArray());
+
+};
+
+/**
+ Creates a computed property which operates on dependent arrays and
+ is updated with "one at a time" semantics. When items are added or
+ removed from the dependent array(s) a reduce computed only operates
+ on the change instead of re-evaluating the entire array.
+
+ If there are more than one arguments the first arguments are
+ considered to be dependent property keys. The last argument is
+ required to be an options object. The options object can have the
+ following four properties:
+
+ `initialValue` - A value or function that will be used as the initial
+ value for the computed. If this property is a function the result of calling
+ the function will be used as the initial value. This property is required.
+
+ `initialize` - An optional initialize function. Typically this will be used
+ to set up state on the instanceMeta object.
+
+ `removedItem` - A function that is called each time an element is removed
+ from the array.
+
+ `addedItem` - A function that is called each time an element is added to
+ the array.
+
+
+ The `initialize` function has the following signature:
+
+ ```javascript
+ function (initialValue, changeMeta, instanceMeta)
+ ```
+
+ `initialValue` - The value of the `initialValue` property from the
+ options object.
+
+ `changeMeta` - An object which contains meta information about the
+ computed. It contains the following properties:
+
+ - `property` the computed property
+ - `propertyName` the name of the property on the object
+
+ `instanceMeta` - An object that can be used to store meta
+ information needed for calculating your computed. For example a
+ unique computed might use this to store the number of times a given
+ element is found in the dependent array.
+
+
+ The `removedItem` and `addedItem` functions both have the following signature:
+
+ ```javascript
+ function (accumulatedValue, item, changeMeta, instanceMeta)
+ ```
+
+ `accumulatedValue` - The value returned from the last time
+ `removedItem` or `addedItem` was called or `initialValue`.
+
+ `item` - the element added or removed from the array
+
+ `changeMeta` - An object which contains meta information about the
+ change. It contains the following properties:
+
+ - `property` the computed property
+ - `propertyName` the name of the property on the object
+ - `index` the index of the added or removed item
+ - `item` the added or removed item: this is exactly the same as
+ the second arg
+ - `arrayChanged` the array that triggered the change. Can be
+ useful when depending on multiple arrays.
+
+ For property changes triggered on an item property change (when
+ depKey is something like `someArray.(a)each.someProperty`)
+ `changeMeta` will also contain the following property:
+
+ - `previousValues` an object whose keys are the properties that changed on
+ the item, and whose values are the item's previous values.
+
+ `previousValues` is important Ember coalesces item property changes via
+ Ember.run.once. This means that by the time removedItem gets called, item has
+ the new values, but you may need the previous value (eg for sorting &
+ filtering).
+
+ `instanceMeta` - An object that can be used to store meta
+ information needed for calculating your computed. For example a
+ unique computed might use this to store the number of times a given
+ element is found in the dependent array.
+
+ The `removedItem` and `addedItem` functions should return the accumulated
+ value. It is acceptable to not return anything (ie return undefined)
+ to invalidate the computation. This is generally not a good idea for
+ arrayComputed but it's used in eg max and min.
+
+ Note that observers will be fired if either of these functions return a value
+ that differs from the accumulated value. When returning an object that
+ mutates in response to array changes, for example an array that maps
+ everything from some other array (see `Ember.computed.map`), it is usually
+ important that the *same* array be returned to avoid accidentally triggering observers.
+
+ Example
+
+ ```javascript
+ Ember.computed.max = function (dependentKey) {
+ return Ember.reduceComputed(dependentKey, {
+ initialValue: -Infinity,
+
+ addedItem: function (accumulatedValue, item, changeMeta, instanceMeta) {
+ return Math.max(accumulatedValue, item);
+ },
+
+ removedItem: function (accumulatedValue, item, changeMeta, instanceMeta) {
+ if (item < accumulatedValue) {
+ return accumulatedValue;
+ }
+ }
+ });
+ };
+ ```
+
+ Dependent keys may refer to `@this` to observe changes to the object itself,
+ which must be array-like, rather than a property of the object. This is
+ mostly useful for array proxies, to ensure objects are retrieved via
+ `objectAtContent`. This is how you could sort items by properties defined on an item controller.
+
+ Example
+
+ ```javascript
+ App.PeopleController = Ember.ArrayController.extend({
+ itemController: 'person',
+
+ sortedPeople: Ember.computed.sort('@this.@each.reversedName', function(personA, personB) {
+ // `reversedName` isn't defined on Person, but we have access to it via
+ // the item controller App.PersonController. If we'd used
+ // `content.(a)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.(a)each.someProperty`)
+ `changeMeta` will also contain the following property:
+
+ - `previousValues` an object whose keys are the properties that changed on
+ the item, and whose values are the item's previous values.
+
+ `previousValues` is important Ember coalesces item property changes via
+ Ember.run.once. This means that by the time removedItem gets called, item has
+ the new values, but you may need the previous value (eg for sorting &
+ filtering).
+
+ `instanceMeta` - An object that can be used to store meta
+ information needed for calculating your computed. For example a
+ unique computed might use this to store the number of times a given
+ element is found in the dependent array.
+
+ The `removedItem` and `addedItem` functions should return the accumulated
+ value. It is acceptable to not return anything (ie return undefined)
+ to invalidate the computation. This is generally not a good idea for
+ arrayComputed but it's used in eg max and min.
+
+ Example
+
+ ```javascript
+ Ember.computed.map = function(dependentKey, callback) {
+ var options = {
+ addedItem: function(array, item, changeMeta, instanceMeta) {
+ var mapped = callback(item);
+ array.insertAt(changeMeta.index, mapped);
+ return array;
+ },
+ removedItem: function(array, item, changeMeta, instanceMeta) {
+ array.removeAt(changeMeta.index, 1);
+ return array;
+ }
+ };
+
+ return Ember.arrayComputed(dependentKey, options);
+ };
+ ```
+
+ @method arrayComputed
+ @for Ember
+ @param {String} [dependentKeys*]
+ @param {Object} options
+ @return {Ember.ComputedProperty}
+*/
+Ember.arrayComputed = function (options) {
+ var args;
+
+ if (arguments.length > 1) {
+ args = a_slice.call(arguments, 0, -1);
+ options = a_slice.call(arguments, -1)[0];
+ }
+
+ if (typeof options !== "object") {
+ throw new Ember.Error("Array Computed Property declared without an options hash");
+ }
+
+ var cp = new ArrayComputedProperty(options);
+
+ if (args) {
+ cp.property.apply(cp, args);
+ }
+
+ return cp;
+};
+
+})();
+
+
+
+(function() {
+/**
+@module ember
+@submodule ember-runtime
+*/
+
+var get = Ember.get,
+ set = Ember.set,
+ guidFor = Ember.guidFor,
+ merge = Ember.merge,
+ a_slice = [].slice,
+ forEach = Ember.EnumerableUtils.forEach,
+ map = Ember.EnumerableUtils.map,
+ SearchProxy;
+
+/**
+ A computed property that returns the sum of the value
+ in the dependent array.
+
+ @method computed.sum
+ @for Ember
+ @param {String} dependentKey
+ @return {Ember.ComputedProperty} computes the sum of all values in the dependentKey's array
+*/
+
+Ember.computed.sum = function(dependentKey){
+ return Ember.reduceComputed(dependentKey, {
+ initialValue: 0,
+
+ addedItem: function(accumulatedValue, item, changeMeta, instanceMeta){
+ return accumulatedValue + item;
+ },
+
+ removedItem: function(accumulatedValue, item, changeMeta, instanceMeta){
+ return accumulatedValue - item;
+ }
+ });
+};
+
+/**
+ A computed property that calculates the maximum value in the
+ dependent array. This will return `-Infinity` when the dependent
+ array is empty.
+
+ ```javascript
+ App.Person = Ember.Object.extend({
+ childAges: Ember.computed.mapBy('children', 'age'),
+ maxChildAge: Ember.computed.max('childAges')
+ });
+
+ var lordByron = App.Person.create({children: []});
+ lordByron.get('maxChildAge'); // -Infinity
+ lordByron.get('children').pushObject({
+ name: 'Augusta Ada Byron', age: 7
+ });
+ lordByron.get('maxChildAge'); // 7
+ lordByron.get('children').pushObjects([{
+ name: 'Allegra Byron',
+ age: 5
+ }, {
+ name: 'Elizabeth Medora Leigh',
+ age: 8
+ }]);
+ lordByron.get('maxChildAge'); // 8
+ ```
+
+ @method computed.max
+ @for Ember
+ @param {String} dependentKey
+ @return {Ember.ComputedProperty} computes the largest value in the dependentKey's array
+*/
+Ember.computed.max = function (dependentKey) {
+ return Ember.reduceComputed(dependentKey, {
+ initialValue: -Infinity,
+
+ addedItem: function (accumulatedValue, item, changeMeta, instanceMeta) {
+ return Math.max(accumulatedValue, item);
+ },
+
+ removedItem: function (accumulatedValue, item, changeMeta, instanceMeta) {
+ if (item < accumulatedValue) {
+ return accumulatedValue;
+ }
+ }
+ });
+};
+
+/**
+ A computed property that calculates the minimum value in the
+ dependent array. This will return `Infinity` when the dependent
+ array is empty.
+
+ ```javascript
+ App.Person = Ember.Object.extend({
+ childAges: Ember.computed.mapBy('children', 'age'),
+ minChildAge: Ember.computed.min('childAges')
+ });
+
+ var lordByron = App.Person.create({children: []});
+ lordByron.get('minChildAge'); // Infinity
+ lordByron.get('children').pushObject({
+ name: 'Augusta Ada Byron', age: 7
+ });
+ lordByron.get('minChildAge'); // 7
+ lordByron.get('children').pushObjects([{
+ name: 'Allegra Byron',
+ age: 5
+ }, {
+ name: 'Elizabeth Medora Leigh',
+ age: 8
+ }]);
+ lordByron.get('minChildAge'); // 5
+ ```
+
+ @method computed.min
+ @for Ember
+ @param {String} dependentKey
+ @return {Ember.ComputedProperty} computes the smallest value in the dependentKey's array
+*/
+Ember.computed.min = function (dependentKey) {
+ return Ember.reduceComputed(dependentKey, {
+ initialValue: Infinity,
+
+ addedItem: function (accumulatedValue, item, changeMeta, instanceMeta) {
+ return Math.min(accumulatedValue, item);
+ },
+
+ removedItem: function (accumulatedValue, item, changeMeta, instanceMeta) {
+ if (item > accumulatedValue) {
+ return accumulatedValue;
+ }
+ }
+ });
+};
+
+/**
+ Returns an array mapped via the callback
+
+ The callback method you provide should have the following signature.
+ `item` is the current item in the iteration.
+
+ ```javascript
+ function(item);
+ ```
+
+ Example
+
+ ```javascript
+ App.Hamster = Ember.Object.extend({
+ excitingChores: Ember.computed.map('chores', function(chore) {
+ return chore.toUpperCase() + '!';
+ })
+ });
+
+ var hamster = App.Hamster.create({
+ chores: ['clean', 'write more unit tests']
+ });
+ hamster.get('excitingChores'); // ['CLEAN!', 'WRITE MORE UNIT TESTS!']
+ ```
+
+ @method computed.map
+ @for Ember
+ @param {String} dependentKey
+ @param {Function} callback
+ @return {Ember.ComputedProperty} an array mapped via the callback
+*/
+Ember.computed.map = function(dependentKey, callback) {
+ var options = {
+ addedItem: function(array, item, changeMeta, instanceMeta) {
+ var mapped = callback.call(this, item);
+ array.insertAt(changeMeta.index, mapped);
+ return array;
+ },
+ removedItem: function(array, item, changeMeta, instanceMeta) {
+ array.removeAt(changeMeta.index, 1);
+ return array;
+ }
+ };
+
+ return Ember.arrayComputed(dependentKey, options);
+};
+
+/**
+ Returns an array mapped to the specified key.
+
+ ```javascript
+ App.Person = Ember.Object.extend({
+ childAges: Ember.computed.mapBy('children', 'age')
+ });
+
+ var lordByron = App.Person.create({children: []});
+ lordByron.get('childAges'); // []
+ lordByron.get('children').pushObject({name: 'Augusta Ada Byron', age: 7});
+ lordByron.get('childAges'); // [7]
+ lordByron.get('children').pushObjects([{
+ name: 'Allegra Byron',
+ age: 5
+ }, {
+ name: 'Elizabeth Medora Leigh',
+ age: 8
+ }]);
+ lordByron.get('childAges'); // [7, 5, 8]
+ ```
+
+ @method computed.mapBy
+ @for Ember
+ @param {String} dependentKey
+ @param {String} propertyKey
+ @return {Ember.ComputedProperty} an array mapped to the specified key
+*/
+Ember.computed.mapBy = function(dependentKey, propertyKey) {
+ var callback = function(item) { return get(item, propertyKey); };
+ return Ember.computed.map(dependentKey + '.@each.' + propertyKey, callback);
+};
+
+/**
+ @method computed.mapProperty
+ @for Ember
+ @deprecated Use `Ember.computed.mapBy` instead
+ @param dependentKey
+ @param propertyKey
+*/
+Ember.computed.mapProperty = Ember.computed.mapBy;
+
+/**
+ Filters the array by the callback.
+
+ The callback method you provide should have the following signature.
+ `item` is the current item in the iteration.
+
+ ```javascript
+ function(item);
+ ```
+
+ ```javascript
+ App.Hamster = Ember.Object.extend({
+ remainingChores: Ember.computed.filter('chores', function(chore) {
+ return !chore.done;
+ })
+ });
+
+ var hamster = App.Hamster.create({chores: [
+ {name: 'cook', done: true},
+ {name: 'clean', done: true},
+ {name: 'write more unit tests', done: false}
+ ]});
+ hamster.get('remainingChores'); // [{name: 'write more unit tests', done: false}]
+ ```
+
+ @method computed.filter
+ @for Ember
+ @param {String} dependentKey
+ @param {Function} callback
+ @return {Ember.ComputedProperty} the filtered array
+*/
+Ember.computed.filter = function(dependentKey, callback) {
+ var options = {
+ initialize: function (array, changeMeta, instanceMeta) {
+ instanceMeta.filteredArrayIndexes = new Ember.SubArray();
+ },
+
+ addedItem: function(array, item, changeMeta, instanceMeta) {
+ var match = !!callback.call(this, item),
+ filterIndex = instanceMeta.filteredArrayIndexes.addItem(changeMeta.index, match);
+
+ if (match) {
+ array.insertAt(filterIndex, item);
+ }
+
+ return array;
+ },
+
+ removedItem: function(array, item, changeMeta, instanceMeta) {
+ var filterIndex = instanceMeta.filteredArrayIndexes.removeItem(changeMeta.index);
+
+ if (filterIndex > -1) {
+ array.removeAt(filterIndex);
+ }
+
+ return array;
+ }
+ };
+
+ return Ember.arrayComputed(dependentKey, options);
+};
+
+/**
+ Filters the array by the property and value
+
+ ```javascript
+ App.Hamster = Ember.Object.extend({
+ remainingChores: Ember.computed.filterBy('chores', 'done', false)
+ });
+
+ var hamster = App.Hamster.create({chores: [
+ {name: 'cook', done: true},
+ {name: 'clean', done: true},
+ {name: 'write more unit tests', done: false}
+ ]});
+ hamster.get('remainingChores'); // [{name: 'write more unit tests', done: false}]
+ ```
+
+ @method computed.filterBy
+ @for Ember
+ @param {String} dependentKey
+ @param {String} propertyKey
+ @param {*} value
+ @return {Ember.ComputedProperty} the filtered array
+*/
+Ember.computed.filterBy = function(dependentKey, propertyKey, value) {
+ var callback;
+
+ if (arguments.length === 2) {
+ callback = function(item) {
+ return get(item, propertyKey);
+ };
+ } else {
+ callback = function(item) {
+ return get(item, propertyKey) === value;
+ };
+ }
+
+ return Ember.computed.filter(dependentKey + '.@each.' + propertyKey, callback);
+};
+
+/**
+ @method computed.filterProperty
+ @for Ember
+ @param dependentKey
+ @param propertyKey
+ @param value
+ @deprecated Use `Ember.computed.filterBy` instead
+*/
+Ember.computed.filterProperty = Ember.computed.filterBy;
+
+/**
+ A computed property which returns a new array with all the unique
+ elements from one or more dependent arrays.
+
+ Example
+
+ ```javascript
+ App.Hamster = Ember.Object.extend({
+ uniqueFruits: Ember.computed.uniq('fruits')
+ });
+
+ var hamster = App.Hamster.create({fruits: [
+ 'banana',
+ 'grape',
+ 'kale',
+ 'banana'
+ ]});
+ hamster.get('uniqueFruits'); // ['banana', 'grape', 'kale']
+ ```
+
+ @method computed.uniq
+ @for Ember
+ @param {String} propertyKey*
+ @return {Ember.ComputedProperty} computes a new array with all the
+ unique elements from the dependent array
+*/
+Ember.computed.uniq = function() {
+ var args = a_slice.call(arguments);
+ args.push({
+ initialize: function(array, changeMeta, instanceMeta) {
+ instanceMeta.itemCounts = {};
+ },
+
+ addedItem: function(array, item, changeMeta, instanceMeta) {
+ var guid = guidFor(item);
+
+ if (!instanceMeta.itemCounts[guid]) {
+ instanceMeta.itemCounts[guid] = 1;
+ } else {
+ ++instanceMeta.itemCounts[guid];
+ }
+ array.addObject(item);
+ return array;
+ },
+ removedItem: function(array, item, _, instanceMeta) {
+ var guid = guidFor(item),
+ itemCounts = instanceMeta.itemCounts;
+
+ if (--itemCounts[guid] === 0) {
+ array.removeObject(item);
+ }
+ return array;
+ }
+ });
+ return Ember.arrayComputed.apply(null, args);
+};
+
+/**
+ Alias for [Ember.computed.uniq](/api/#method_computed_uniq).
+
+ @method computed.union
+ @for Ember
+ @param {String} propertyKey*
+ @return {Ember.ComputedProperty} computes a new array with all the
+ unique elements from the dependent array
+*/
+Ember.computed.union = Ember.computed.uniq;
+
+/**
+ A computed property which returns a new array with all the duplicated
+ elements from two or more dependent arrays.
+
+ Example
+
+ ```javascript
+ var obj = Ember.Object.createWithMixins({
+ adaFriends: ['Charles Babbage', 'John Hobhouse', 'William King', 'Mary Somerville'],
+ charlesFriends: ['William King', 'Mary Somerville', 'Ada Lovelace', 'George Peacock'],
+ friendsInCommon: Ember.computed.intersect('adaFriends', 'charlesFriends')
+ });
+
+ obj.get('friendsInCommon'); // ['William King', 'Mary Somerville']
+ ```
+
+ @method computed.intersect
+ @for Ember
+ @param {String} propertyKey*
+ @return {Ember.ComputedProperty} computes a new array with all the
+ duplicated elements from the dependent arrays
+*/
+Ember.computed.intersect = function () {
+ var getDependentKeyGuids = function (changeMeta) {
+ return map(changeMeta.property._dependentKeys, function (dependentKey) {
+ return guidFor(dependentKey);
+ });
+ };
+
+ var args = a_slice.call(arguments);
+ args.push({
+ initialize: function (array, changeMeta, instanceMeta) {
+ instanceMeta.itemCounts = {};
+ },
+
+ addedItem: function(array, item, changeMeta, instanceMeta) {
+ var itemGuid = guidFor(item),
+ dependentGuids = getDependentKeyGuids(changeMeta),
+ dependentGuid = guidFor(changeMeta.arrayChanged),
+ numberOfDependentArrays = changeMeta.property._dependentKeys.length,
+ itemCounts = instanceMeta.itemCounts;
+
+ if (!itemCounts[itemGuid]) { itemCounts[itemGuid] = {}; }
+ if (itemCounts[itemGuid][dependentGuid] === undefined) { itemCounts[itemGuid][dependentGuid] = 0; }
+
+ if (++itemCounts[itemGuid][dependentGuid] === 1 &&
+ numberOfDependentArrays === Ember.keys(itemCounts[itemGuid]).length) {
+
+ array.addObject(item);
+ }
+ return array;
+ },
+ removedItem: function(array, item, changeMeta, instanceMeta) {
+ var itemGuid = guidFor(item),
+ dependentGuids = getDependentKeyGuids(changeMeta),
+ dependentGuid = guidFor(changeMeta.arrayChanged),
+ numberOfDependentArrays = changeMeta.property._dependentKeys.length,
+ numberOfArraysItemAppearsIn,
+ itemCounts = instanceMeta.itemCounts;
+
+ if (itemCounts[itemGuid][dependentGuid] === undefined) { itemCounts[itemGuid][dependentGuid] = 0; }
+ if (--itemCounts[itemGuid][dependentGuid] === 0) {
+ delete itemCounts[itemGuid][dependentGuid];
+ numberOfArraysItemAppearsIn = Ember.keys(itemCounts[itemGuid]).length;
+
+ if (numberOfArraysItemAppearsIn === 0) {
+ delete itemCounts[itemGuid];
+ }
+ array.removeObject(item);
+ }
+ return array;
+ }
+ });
+ return Ember.arrayComputed.apply(null, args);
+};
+
+/**
+ A computed property which returns a new array with all the
+ properties from the first dependent array that are not in the second
+ dependent array.
+
+ Example
+
+ ```javascript
+ App.Hamster = Ember.Object.extend({
+ likes: ['banana', 'grape', 'kale'],
+ wants: Ember.computed.setDiff('likes', 'fruits')
+ });
+
+ var hamster = App.Hamster.create({fruits: [
+ 'grape',
+ 'kale',
+ ]});
+ hamster.get('wants'); // ['banana']
+ ```
+
+ @method computed.setDiff
+ @for Ember
+ @param {String} setAProperty
+ @param {String} setBProperty
+ @return {Ember.ComputedProperty} computes a new array with all the
+ items from the first dependent array that are not in the second
+ dependent array
+*/
+Ember.computed.setDiff = function (setAProperty, setBProperty) {
+ if (arguments.length !== 2) {
+ throw new Ember.Error("setDiff requires exactly two dependent arrays.");
+ }
+ return Ember.arrayComputed(setAProperty, setBProperty, {
+ addedItem: function (array, item, changeMeta, instanceMeta) {
+ var setA = get(this, setAProperty),
+ setB = get(this, setBProperty);
+
+ if (changeMeta.arrayChanged === setA) {
+ if (!setB.contains(item)) {
+ array.addObject(item);
+ }
+ } else {
+ array.removeObject(item);
+ }
+ return array;
+ },
+
+ removedItem: function (array, item, changeMeta, instanceMeta) {
+ var setA = get(this, setAProperty),
+ setB = get(this, setBProperty);
+
+ if (changeMeta.arrayChanged === setB) {
+ if (setA.contains(item)) {
+ array.addObject(item);
+ }
+ } else {
+ array.removeObject(item);
+ }
+ return array;
+ }
+ });
+};
+
+function binarySearch(array, item, low, high) {
+ var mid, midItem, res, guidMid, guidItem;
+
+ if (arguments.length < 4) { high = get(array, 'length'); }
+ if (arguments.length < 3) { low = 0; }
+
+ if (low === high) {
+ return low;
+ }
+
+ mid = low + Math.floor((high - low) / 2);
+ midItem = array.objectAt(mid);
+
+ guidMid = _guidFor(midItem);
+ guidItem = _guidFor(item);
+
+ if (guidMid === guidItem) {
+ return mid;
+ }
+
+ res = this.order(midItem, item);
+ if (res === 0) {
+ res = guidMid < guidItem ? -1 : 1;
+ }
+
+
+ if (res < 0) {
+ return this.binarySearch(array, item, mid+1, high);
+ } else if (res > 0) {
+ return this.binarySearch(array, item, low, mid);
+ }
+
+ return mid;
+
+ function _guidFor(item) {
+ if (SearchProxy.detectInstance(item)) {
+ return guidFor(get(item, 'content'));
+ }
+ return guidFor(item);
+ }
+}
+
+
+SearchProxy = Ember.ObjectProxy.extend();
+
+/**
+ A computed property which returns a new array with all the
+ properties from the first dependent array sorted based on a property
+ or sort function.
+
+ The callback method you provide should have the following signature:
+
+ ```javascript
+ function(itemA, itemB);
+ ```
+
+ - `itemA` the first item to compare.
+ - `itemB` the second item to compare.
+
+ This function should return negative number (e.g. `-1`) when `itemA` should come before
+ `itemB`. It should return positive number (e.g. `1`) when `itemA` should come after
+ `itemB`. If the `itemA` and `itemB` are equal this function should return `0`.
+
+ Therefore, if this function is comparing some numeric values, simple `itemA - itemB` or
+ `itemA.get( 'foo' ) - itemB.get( 'foo' )` can be used instead of series of `if`.
+
+ 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 isProxy = itemB instanceof SearchProxy,
+ sortProperty, result, asc;
+
+ for (var i = 0; i < this.sortProperties.length; ++i) {
+ sortProperty = this.sortProperties[i];
+ result = Ember.compare(get(itemA, sortProperty), isProxy ? itemB[sortProperty] : get(itemB, sortProperty));
+
+ if (result !== 0) {
+ asc = this.sortPropertyAscending[sortProperty];
+ return asc ? result : (-1 * result);
+ }
+ }
+
+ return 0;
+ };
+
+ instanceMeta.binarySearch = binarySearch;
+ };
+ }
+
+ return Ember.arrayComputed(itemsKey, {
+ initialize: initFn,
+
+ addedItem: function (array, item, changeMeta, instanceMeta) {
+ var index = instanceMeta.binarySearch(array, item);
+ array.insertAt(index, item);
+ return array;
+ },
+
+ removedItem: function (array, item, changeMeta, instanceMeta) {
+ var proxyProperties, index, searchItem;
+
+ if (changeMeta.previousValues) {
+ proxyProperties = merge({ content: item }, changeMeta.previousValues);
+
+ searchItem = SearchProxy.create(proxyProperties);
+ } else {
+ searchItem = item;
+ }
+
+ index = instanceMeta.binarySearch(array, searchItem);
+ array.removeAt(index);
+ return array;
+ }
+ });
+};
+
+})();
+
+
+
+(function() {
+Ember.RSVP = requireModule('rsvp');
+
+Ember.RSVP.onerrorDefault = function(error) {
+ if (error instanceof Error) {
+ if (Ember.testing) {
+ if (Ember.Test && Ember.Test.adapter) {
+ Ember.Test.adapter.exception(error);
+ } else {
+ throw error;
+ }
+ } else {
+ Ember.Logger.error(error.stack);
+ Ember.assert(error, false);
+ }
+ }
+};
+
+Ember.RSVP.on('error', Ember.RSVP.onerrorDefault);
+
+})();
+
+
+
+(function() {
+/**
+@module ember
+@submodule ember-runtime
+*/
+
+var a_slice = Array.prototype.slice;
+
+var expandProperties = Ember.expandProperties;
+
+if (Ember.EXTEND_PROTOTYPES === true || Ember.EXTEND_PROTOTYPES.Function) {
+
+ /**
+ The `property` extension of Javascript's Function prototype is available
+ when `Ember.EXTEND_PROTOTYPES` or `Ember.EXTEND_PROTOTYPES.Function` is
+ `true`, which is the default.
+
+ Computed properties allow you to treat a function like a property:
+
+ ```javascript
+ MyApp.President = Ember.Object.extend({
+ firstName: '',
+ lastName: '',
+
+ fullName: function() {
+ return this.get('firstName') + ' ' + this.get('lastName');
+
+ // Call this flag to mark the function as a property
+ }.property()
+ });
+
+ var president = MyApp.President.create({
+ firstName: "Barack",
+ lastName: "Obama"
+ });
+
+ president.get('fullName'); // "Barack Obama"
+ ```
+
+ Treating a function like a property is useful because they can work with
+ bindings, just like any other property.
+
+ Many computed properties have dependencies on other properties. For
+ example, in the above example, the `fullName` property depends on
+ `firstName` and `lastName` to determine its value. You can tell Ember
+ about these dependencies like this:
+
+ ```javascript
+ MyApp.President = Ember.Object.extend({
+ firstName: '',
+ lastName: '',
+
+ fullName: function() {
+ return this.get('firstName') + ' ' + this.get('lastName');
+
+ // Tell Ember.js that this computed property depends on firstName
+ // and lastName
+ }.property('firstName', 'lastName')
+ });
+ ```
+
+ Make sure you list these dependencies so Ember knows when to update
+ bindings that connect to a computed property. Changing a dependency
+ will not immediately trigger an update of the computed property, but
+ will instead clear the cache so that it is updated when the next `get`
+ is called on the property.
+
+ See [Ember.ComputedProperty](/api/classes/Ember.ComputedProperty.html), [Ember.computed](/api/#method_computed).
+
+ @method property
+ @for Function
+ */
+ Function.prototype.property = function() {
+ var ret = Ember.computed(this);
+ // ComputedProperty.prototype.property expands properties; no need for us to
+ // do so here.
+ return ret.property.apply(ret, arguments);
+ };
+
+ /**
+ The `observes` extension of Javascript's Function prototype is available
+ when `Ember.EXTEND_PROTOTYPES` or `Ember.EXTEND_PROTOTYPES.Function` is
+ true, which is the default.
+
+ You can observe property changes simply by adding the `observes`
+ call to the end of your method declarations in classes that you write.
+ For example:
+
+ ```javascript
+ Ember.Object.extend({
+ valueObserver: function() {
+ // Executes whenever the "value" property changes
+ }.observes('value')
+ });
+ ```
+
+ In the future this method may become asynchronous. If you want to ensure
+ synchronous behavior, use `observesImmediately`.
+
+ See `Ember.observer`.
+
+ @method observes
+ @for Function
+ */
+ Function.prototype.observes = function() {
+ var addWatchedProperty = function (obs) { watched.push(obs); };
+ var watched = [];
+
+ for (var i=0; i<arguments.length; ++i) {
+ expandProperties(arguments[i], addWatchedProperty);
+ }
+
+ this.__ember_observes__ = watched;
+
+ 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() {
+ var addWatchedProperty = function (obs) { watched.push(obs); };
+ var watched = [];
+
+ for (var i=0; i<arguments.length; ++i) {
+ expandProperties(arguments[i], addWatchedProperty);
+ }
+
+ this.__ember_observesBefore__ = watched;
+
+ 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
+*/
+
+
+/**
+ Implements some standard methods for comparing objects. Add this mixin to
+ any class you create that can compare its instances.
+
+ You should implement the `compare()` method.
+
+ @class Comparable
+ @namespace Ember
+ @since Ember 0.9
+*/
+Ember.Comparable = Ember.Mixin.create({
+
+ /**
+ Override to return the result of the comparison of the two parameters. The
+ compare method should return:
+
+ - `-1` if `a < b`
+ - `0` if `a == b`
+ - `1` if `a > b`
+
+ Default implementation raises an exception.
+
+ @method compare
+ @param a {Object} the first object to compare
+ @param b {Object} the second object to compare
+ @return {Integer} the result of the comparison
+ */
+ compare: Ember.required(Function)
+
+});
+
+
+})();
+
+
+
+(function() {
+/**
+@module ember
+@submodule ember-runtime
+*/
+
+
+
+var get = Ember.get, set = Ember.set;
+
+/**
+ Implements some standard methods for copying an object. Add this mixin to
+ any object you create that can create a copy of itself. This mixin is
+ added automatically to the built-in array.
+
+ You should generally implement the `copy()` method to return a copy of the
+ receiver.
+
+ Note that `frozenCopy()` will only work if you also implement
+ `Ember.Freezable`.
+
+ @class Copyable
+ @namespace Ember
+ @since Ember 0.9
+*/
+Ember.Copyable = Ember.Mixin.create({
+
+ /**
+ Override to return a copy of the receiver. Default implementation raises
+ an exception.
+
+ @method copy
+ @param {Boolean} deep if `true`, a deep copy of the object should be made
+ @return {Object} copy of receiver
+ */
+ copy: Ember.required(Function),
+
+ /**
+ If the object implements `Ember.Freezable`, then this will return a new
+ copy if the object is not frozen and the receiver if the object is frozen.
+
+ Raises an exception if you try to call this method on a object that does
+ not support freezing.
+
+ You should use this method whenever you want a copy of a freezable object
+ since a freezable object can simply return itself without actually
+ consuming more memory.
+
+ @method frozenCopy
+ @return {Object} copy of receiver or receiver
+ */
+ frozenCopy: function() {
+ if (Ember.Freezable && Ember.Freezable.detect(this)) {
+ return get(this, 'isFrozen') ? this : this.copy().freeze();
+ } else {
+ throw new Ember.Error(Ember.String.fmt("%@ does not support freezing", [this]));
+ }
+ }
+});
+
+})();
+
+
+
+(function() {
+/**
+@module ember
+@submodule ember-runtime
+*/
+
+
+var get = Ember.get, set = Ember.set;
+
+/**
+ The `Ember.Freezable` mixin implements some basic methods for marking an
+ object as frozen. Once an object is frozen it should be read only. No changes
+ may be made the internal state of the object.
+
+ ## Enforcement
+
+ To fully support freezing in your subclass, you must include this mixin and
+ override any method that might alter any property on the object to instead
+ raise an exception. You can check the state of an object by checking the
+ `isFrozen` property.
+
+ Although future versions of JavaScript may support language-level freezing
+ object objects, that is not the case today. Even if an object is freezable,
+ it is still technically possible to modify the object, even though it could
+ break other parts of your application that do not expect a frozen object to
+ change. It is, therefore, very important that you always respect the
+ `isFrozen` property on all freezable objects.
+
+ ## Example Usage
+
+ The example below shows a simple object that implement the `Ember.Freezable`
+ protocol.
+
+ ```javascript
+ Contact = Ember.Object.extend(Ember.Freezable, {
+ firstName: null,
+ lastName: null,
+
+ // swaps the names
+ swapNames: function() {
+ if (this.get('isFrozen')) throw Ember.FROZEN_ERROR;
+ var tmp = this.get('firstName');
+ this.set('firstName', this.get('lastName'));
+ this.set('lastName', tmp);
+ return this;
+ }
+
+ });
+
+ c = Contact.create({ firstName: "John", lastName: "Doe" });
+ c.swapNames(); // returns c
+ c.freeze();
+ c.swapNames(); // EXCEPTION
+ ```
+
+ ## Copying
+
+ Usually the `Ember.Freezable` protocol is implemented in cooperation with the
+ `Ember.Copyable` protocol, which defines a `frozenCopy()` method that will
+ return a frozen object, if the object implements this method as well.
+
+ @class Freezable
+ @namespace Ember
+ @since Ember 0.9
+*/
+Ember.Freezable = Ember.Mixin.create({
+
+ /**
+ Set to `true` when the object is frozen. Use this property to detect
+ whether your object is frozen or not.
+
+ @property isFrozen
+ @type Boolean
+ */
+ isFrozen: false,
+
+ /**
+ Freezes the object. Once this method has been called the object should
+ no longer allow any properties to be edited.
+
+ @method freeze
+ @return {Object} receiver
+ */
+ freeze: function() {
+ if (get(this, 'isFrozen')) return this;
+ set(this, 'isFrozen', true);
+ return this;
+ }
+
+});
+
+Ember.FROZEN_ERROR = "Frozen object cannot be modified.";
+
+})();
+
+
+
+(function() {
+/**
+@module ember
+@submodule ember-runtime
+*/
+
+var forEach = Ember.EnumerableUtils.forEach;
+
+/**
+ This mixin defines the API for modifying generic enumerables. These methods
+ can be applied to an object regardless of whether it is ordered or
+ unordered.
+
+ Note that an Enumerable can change even if it does not implement this mixin.
+ For example, a MappedEnumerable cannot be directly modified but if its
+ underlying enumerable changes, it will change also.
+
+ ## Adding Objects
+
+ To add an object to an enumerable, use the `addObject()` method. This
+ method will only add the object to the enumerable if the object is not
+ already present and is of a type supported by the enumerable.
+
+ ```javascript
+ set.addObject(contact);
+ ```
+
+ ## Removing Objects
+
+ To remove an object from an enumerable, use the `removeObject()` method. This
+ will only remove the object if it is present in the enumerable, otherwise
+ this method has no effect.
+
+ ```javascript
+ set.removeObject(contact);
+ ```
+
+ ## Implementing In Your Own Code
+
+ If you are implementing an object and want to support this API, just include
+ this mixin in your class and implement the required methods. In your unit
+ tests, be sure to apply the Ember.MutableEnumerableTests to your object.
+
+ @class MutableEnumerable
+ @namespace Ember
+ @uses Ember.Enumerable
+*/
+Ember.MutableEnumerable = Ember.Mixin.create(Ember.Enumerable, {
+
+ /**
+ __Required.__ You must implement this method to apply this mixin.
+
+ Attempts to add the passed object to the receiver if the object is not
+ already present in the collection. If the object is present, this method
+ has no effect.
+
+ If the passed object is of a type not supported by the receiver,
+ then this method should raise an exception.
+
+ @method addObject
+ @param {Object} object The object to add to the enumerable.
+ @return {Object} the passed object
+ */
+ addObject: Ember.required(Function),
+
+ /**
+ Adds each object in the passed enumerable to the receiver.
+
+ @method addObjects
+ @param {Ember.Enumerable} objects the objects to add.
+ @return {Object} receiver
+ */
+ addObjects: function(objects) {
+ Ember.beginPropertyChanges(this);
+ forEach(objects, function(obj) { this.addObject(obj); }, this);
+ Ember.endPropertyChanges(this);
+ return this;
+ },
+
+ /**
+ __Required.__ You must implement this method to apply this mixin.
+
+ Attempts to remove the passed object from the receiver collection if the
+ object is present in the collection. If the object is not present,
+ this method has no effect.
+
+ If the passed object is of a type not supported by the receiver,
+ then this method should raise an exception.
+
+ @method removeObject
+ @param {Object} object The object to remove from the enumerable.
+ @return {Object} the passed object
+ */
+ removeObject: Ember.required(Function),
+
+
+ /**
+ Removes each object in the passed enumerable from the receiver.
+
+ @method removeObjects
+ @param {Ember.Enumerable} objects the objects to remove
+ @return {Object} receiver
+ */
+ removeObjects: function(objects) {
+ Ember.beginPropertyChanges(this);
+ forEach(objects, function(obj) { this.removeObject(obj); }, this);
+ Ember.endPropertyChanges(this);
+ return this;
+ }
+
+});
+
+})();
+
+
+
+(function() {
+/**
+@module ember
+@submodule ember-runtime
+*/
+// ..........................................................
+// CONSTANTS
+//
+
+var OUT_OF_RANGE_EXCEPTION = "Index out of range" ;
+var EMPTY = [];
+
+// ..........................................................
+// HELPERS
+//
+
+var get = Ember.get, set = Ember.set;
+
+/**
+ This mixin defines the API for modifying array-like objects. These methods
+ can be applied only to a collection that keeps its items in an ordered set.
+ It builds upon the Array mixin and adds methods to modify the array.
+ Concrete implementations of this class include ArrayProxy and ArrayController.
+
+ It is important to use the methods in this class to modify arrays so that
+ changes are observable. This allows the binding system in Ember to function
+ correctly.
+
+ Note that an Array can change even if it does not implement this mixin.
+ For example, one might implement a SparseArray that cannot be directly
+ modified, but if its underlying enumerable changes, it will change also.
+
+ @class MutableArray
+ @namespace Ember
+ @uses Ember.Array
+ @uses Ember.MutableEnumerable
+*/
+Ember.MutableArray = Ember.Mixin.create(Ember.Array, Ember.MutableEnumerable, {
+
+ /**
+ __Required.__ You must implement this method to apply this mixin.
+
+ This is one of the primitives you must implement to support `Ember.Array`.
+ You should replace amt objects started at idx with the objects in the
+ passed array. You should also call `this.enumerableContentDidChange()`
+
+ @method replace
+ @param {Number} idx Starting index in the array to replace. If
+ idx >= length, then append to the end of the array.
+ @param {Number} amt Number of elements that should be removed from
+ the array, starting at *idx*.
+ @param {Array} objects An array of zero or more objects that should be
+ inserted into the array at *idx*
+ */
+ replace: Ember.required(),
+
+ /**
+ Remove all elements from self. This is useful if you
+ want to reuse an existing array without having to recreate it.
+
+ ```javascript
+ var colors = ["red", "green", "blue"];
+ color.length(); // 3
+ colors.clear(); // []
+ colors.length(); // 0
+ ```
+
+ @method clear
+ @return {Ember.Array} An empty Array.
+ */
+ clear: function () {
+ var len = get(this, 'length');
+ if (len === 0) return this;
+ this.replace(0, len, EMPTY);
+ return this;
+ },
+
+ /**
+ This will use the primitive `replace()` method to insert an object at the
+ specified index.
+
+ ```javascript
+ var colors = ["red", "green", "blue"];
+ colors.insertAt(2, "yellow"); // ["red", "green", "yellow", "blue"]
+ colors.insertAt(5, "orange"); // Error: Index out of range
+ ```
+
+ @method insertAt
+ @param {Number} idx index of insert the object at.
+ @param {Object} object object to insert
+ @return this
+ */
+ insertAt: function(idx, object) {
+ if (idx > get(this, 'length')) throw new Ember.Error(OUT_OF_RANGE_EXCEPTION) ;
+ this.replace(idx, 0, [object]) ;
+ return this ;
+ },
+
+ /**
+ Remove an object at the specified index using the `replace()` primitive
+ method. You can pass either a single index, or a start and a length.
+
+ If you pass a start and length that is beyond the
+ length this method will throw an `OUT_OF_RANGE_EXCEPTION`.
+
+ ```javascript
+ var colors = ["red", "green", "blue", "yellow", "orange"];
+ colors.removeAt(0); // ["green", "blue", "yellow", "orange"]
+ colors.removeAt(2, 2); // ["green", "blue"]
+ colors.removeAt(4, 2); // Error: Index out of range
+ ```
+
+ @method removeAt
+ @param {Number} start index, start of range
+ @param {Number} len length of passing range
+ @return {Object} receiver
+ */
+ removeAt: function(start, len) {
+ if ('number' === typeof start) {
+
+ if ((start < 0) || (start >= get(this, 'length'))) {
+ throw new Ember.Error(OUT_OF_RANGE_EXCEPTION);
+ }
+
+ // fast case
+ if (len === undefined) len = 1;
+ this.replace(start, len, EMPTY);
+ }
+
+ return this ;
+ },
+
+ /**
+ Push the object onto the end of the array. Works just like `push()` but it
+ is KVO-compliant.
+
+ ```javascript
+ var colors = ["red", "green"];
+ colors.pushObject("black"); // ["red", "green", "black"]
+ colors.pushObject(["yellow"]); // ["red", "green", ["yellow"]]
+ ```
+
+ @method pushObject
+ @param {*} obj object to push
+ @return The same obj passed as param
+ */
+ pushObject: function(obj) {
+ this.insertAt(get(this, 'length'), obj) ;
+ return obj;
+ },
+
+ /**
+ Add the objects in the passed numerable to the end of the array. Defers
+ notifying observers of the change until all objects are added.
+
+ ```javascript
+ var colors = ["red"];
+ colors.pushObjects(["yellow", "orange"]); // ["red", "yellow", "orange"]
+ ```
+
+ @method pushObjects
+ @param {Ember.Enumerable} objects the objects to add
+ @return {Ember.Array} receiver
+ */
+ pushObjects: function(objects) {
+ if (!(Ember.Enumerable.detect(objects) || Ember.isArray(objects))) {
+ throw new TypeError("Must pass Ember.Enumerable to Ember.MutableArray#pushObjects");
+ }
+ this.replace(get(this, 'length'), 0, objects);
+ return this;
+ },
+
+ /**
+ Pop object from array or nil if none are left. Works just like `pop()` but
+ it is KVO-compliant.
+
+ ```javascript
+ var colors = ["red", "green", "blue"];
+ colors.popObject(); // "blue"
+ console.log(colors); // ["red", "green"]
+ ```
+
+ @method popObject
+ @return object
+ */
+ popObject: function() {
+ var len = get(this, 'length') ;
+ if (len === 0) return null ;
+
+ var ret = this.objectAt(len-1) ;
+ this.removeAt(len-1, 1) ;
+ return ret ;
+ },
+
+ /**
+ Shift an object from start of array or nil if none are left. Works just
+ like `shift()` but it is KVO-compliant.
+
+ ```javascript
+ var colors = ["red", "green", "blue"];
+ colors.shiftObject(); // "red"
+ console.log(colors); // ["green", "blue"]
+ ```
+
+ @method shiftObject
+ @return object
+ */
+ shiftObject: function() {
+ if (get(this, 'length') === 0) return null ;
+ var ret = this.objectAt(0) ;
+ this.removeAt(0) ;
+ return ret ;
+ },
+
+ /**
+ Unshift an object to start of array. Works just like `unshift()` but it is
+ KVO-compliant.
+
+ ```javascript
+ var colors = ["red"];
+ colors.unshiftObject("yellow"); // ["yellow", "red"]
+ colors.unshiftObject(["black"]); // [["black"], "yellow", "red"]
+ ```
+
+ @method unshiftObject
+ @param {*} obj object to unshift
+ @return The same obj passed as param
+ */
+ unshiftObject: function(obj) {
+ this.insertAt(0, obj) ;
+ return obj ;
+ },
+
+ /**
+ Adds the named objects to the beginning of the array. Defers notifying
+ observers until all objects have been added.
+
+ ```javascript
+ var colors = ["red"];
+ colors.unshiftObjects(["black", "white"]); // ["black", "white", "red"]
+ colors.unshiftObjects("yellow"); // Type Error: 'undefined' is not a function
+ ```
+
+ @method unshiftObjects
+ @param {Ember.Enumerable} objects the objects to add
+ @return {Ember.Array} receiver
+ */
+ unshiftObjects: function(objects) {
+ this.replace(0, 0, objects);
+ return this;
+ },
+
+ /**
+ Reverse objects in the array. Works just like `reverse()` but it is
+ KVO-compliant.
+
+ @method reverseObjects
+ @return {Ember.Array} receiver
+ */
+ reverseObjects: function() {
+ var len = get(this, 'length');
+ if (len === 0) return this;
+ var objects = this.toArray().reverse();
+ this.replace(0, len, objects);
+ return this;
+ },
+
+ /**
+ Replace all the the receiver's content with content of the argument.
+ If argument is an empty array receiver will be cleared.
+
+ ```javascript
+ var colors = ["red", "green", "blue"];
+ colors.setObjects(["black", "white"]); // ["black", "white"]
+ colors.setObjects([]); // []
+ ```
+
+ @method setObjects
+ @param {Ember.Array} objects array whose content will be used for replacing
+ the content of the receiver
+ @return {Ember.Array} receiver with the new content
+ */
+ setObjects: function(objects) {
+ if (objects.length === 0) return this.clear();
+
+ var len = get(this, 'length');
+ this.replace(0, len, objects);
+ return this;
+ },
+
+ // ..........................................................
+ // IMPLEMENT Ember.MutableEnumerable
+ //
+
+ removeObject: function(obj) {
+ var loc = get(this, 'length') || 0;
+ while(--loc >= 0) {
+ var curObject = this.objectAt(loc) ;
+ if (curObject === obj) this.removeAt(loc) ;
+ }
+ return this ;
+ },
+
+ addObject: function(obj) {
+ if (!this.contains(obj)) this.pushObject(obj);
+ return this ;
+ }
+
+});
+
+})();
+
+
+
+(function() {
+/**
+@module ember
+@submodule ember-runtime
+*/
+
+var get = Ember.get, set = Ember.set;
+
+/**
+`Ember.TargetActionSupport` is a mixin that can be included in a class
+to add a `triggerAction` method with semantics similar to the Handlebars
+`{{action}}` helper. In normal Ember usage, the `{{action}}` helper is
+usually the best choice. This mixin is most often useful when you are
+doing more complex event handling in View objects.
+
+See also `Ember.ViewTargetActionSupport`, which has
+view-aware defaults for target and actionContext.
+
+@class TargetActionSupport
+@namespace Ember
+@extends Ember.Mixin
+*/
+Ember.TargetActionSupport = Ember.Mixin.create({
+ target: null,
+ action: null,
+ actionContext: null,
+
+ targetObject: Ember.computed(function() {
+ var target = get(this, 'target');
+
+ if (Ember.typeOf(target) === "string") {
+ var value = get(this, target);
+ if (value === undefined) { value = get(Ember.lookup, target); }
+ return value;
+ } else {
+ return target;
+ }
+ }).property('target'),
+
+ actionContextObject: Ember.computed(function() {
+ var actionContext = get(this, 'actionContext');
+
+ if (Ember.typeOf(actionContext) === "string") {
+ var value = get(this, actionContext);
+ if (value === undefined) { value = get(Ember.lookup, actionContext); }
+ return value;
+ } else {
+ return actionContext;
+ }
+ }).property('actionContext'),
+
+ /**
+ Send an `action` with an `actionContext` to a `target`. The action, actionContext
+ and target will be retrieved from properties of the object. For example:
+
+ ```javascript
+ App.SaveButtonView = Ember.View.extend(Ember.TargetActionSupport, {
+ target: Ember.computed.alias('controller'),
+ action: 'save',
+ actionContext: Ember.computed.alias('context'),
+ click: function() {
+ this.triggerAction(); // Sends the `save` action, along with the current context
+ // to the current controller
+ }
+ });
+ ```
+
+ The `target`, `action`, and `actionContext` can be provided as properties of
+ an optional object argument to `triggerAction` as well.
+
+ ```javascript
+ App.SaveButtonView = Ember.View.extend(Ember.TargetActionSupport, {
+ click: function() {
+ this.triggerAction({
+ action: 'save',
+ target: this.get('controller'),
+ actionContext: this.get('context'),
+ }); // Sends the `save` action, along with the current context
+ // to the current controller
+ }
+ });
+ ```
+
+ The `actionContext` defaults to the object you mixing `TargetActionSupport` into.
+ But `target` and `action` must be specified either as properties or with the argument
+ to `triggerAction`, or a combination:
+
+ ```javascript
+ App.SaveButtonView = Ember.View.extend(Ember.TargetActionSupport, {
+ target: Ember.computed.alias('controller'),
+ click: function() {
+ this.triggerAction({
+ action: 'save'
+ }); // Sends the `save` action, along with a reference to `this`,
+ // to the current controller
+ }
+ });
+ ```
+
+ @method triggerAction
+ @param opts {Hash} (optional, with the optional keys action, target and/or actionContext)
+ @return {Boolean} true if the action was sent successfully and did not return false
+ */
+ triggerAction: function(opts) {
+ opts = opts || {};
+ var action = opts.action || get(this, 'action'),
+ target = opts.target || get(this, 'targetObject'),
+ actionContext = opts.actionContext;
+
+ function args(options, actionName) {
+ var ret = [];
+ if (actionName) { ret.push(actionName); }
+
+ return ret.concat(options);
+ }
+
+ if (typeof actionContext === 'undefined') {
+ actionContext = get(this, 'actionContextObject') || this;
+ }
+
+ if (target && action) {
+ var ret;
+
+ if (target.send) {
+ ret = target.send.apply(target, args(actionContext, action));
+ } else {
+ Ember.assert("The action '" + action + "' did not exist on " + target, typeof target[action] === 'function');
+ ret = target[action].apply(target, args(actionContext));
+ }
+
+ if (ret !== false) ret = true;
+
+ return ret;
+ } else {
+ return false;
+ }
+ }
+});
+
+})();
+
+
+
+(function() {
+/**
+@module ember
+@submodule ember-runtime
+*/
+
+/**
+ This mixin allows for Ember objects to subscribe to and emit events.
+
+ ```javascript
+ App.Person = Ember.Object.extend(Ember.Evented, {
+ greet: function() {
+ // ...
+ this.trigger('greet');
+ }
+ });
+
+ var person = App.Person.create();
+
+ person.on('greet', function() {
+ console.log('Our person has greeted');
+ });
+
+ person.greet();
+
+ // outputs: 'Our person has greeted'
+ ```
+
+ You can also chain multiple event subscriptions:
+
+ ```javascript
+ person.on('greet', function() {
+ console.log('Our person has greeted');
+ }).one('greet', function() {
+ console.log('Offer one-time special');
+ }).off('event', this, forgetThis);
+ ```
+
+ @class Evented
+ @namespace Ember
+ */
+Ember.Evented = Ember.Mixin.create({
+
+ /**
+ Subscribes to a named event with given function.
+
+ ```javascript
+ person.on('didLoad', function() {
+ // fired once the person has loaded
+ });
+ ```
+
+ An optional target can be passed in as the 2nd argument that will
+ be set as the "this" for the callback. This is a good way to give your
+ function access to the object triggering the event. When the target
+ parameter is used the callback becomes the third argument.
+
+ @method on
+ @param {String} name The name of the event
+ @param {Object} [target] The "this" binding for the callback
+ @param {Function} method The callback to execute
+ @return this
+ */
+ on: function(name, target, method) {
+ Ember.addListener(this, name, target, method);
+ return this;
+ },
+
+ /**
+ Subscribes a function to a named event and then cancels the subscription
+ after the first time the event is triggered. It is good to use ``one`` when
+ you only care about the first time an event has taken place.
+
+ This function takes an optional 2nd argument that will become the "this"
+ value for the callback. If this argument is passed then the 3rd argument
+ becomes the function.
+
+ @method one
+ @param {String} name The name of the event
+ @param {Object} [target] The "this" binding for the callback
+ @param {Function} method The callback to execute
+ @return this
+ */
+ one: function(name, target, method) {
+ if (!method) {
+ method = target;
+ target = null;
+ }
+
+ Ember.addListener(this, name, target, method, true);
+ return this;
+ },
+
+ /**
+ Triggers a named event for the object. Any additional arguments
+ will be passed as parameters to the functions that are subscribed to the
+ event.
+
+ ```javascript
+ person.on('didEat', function(food) {
+ console.log('person ate some ' + food);
+ });
+
+ person.trigger('didEat', 'broccoli');
+
+ // outputs: person ate some broccoli
+ ```
+ @method trigger
+ @param {String} name The name of the event
+ @param {Object...} args Optional arguments to pass on
+ */
+ trigger: function(name) {
+ var args = [], i, l;
+ for (i = 1, l = arguments.length; i < l; i++) {
+ args.push(arguments[i]);
+ }
+ Ember.sendEvent(this, name, args);
+ },
+
+ /**
+ Cancels subscription for given name, target, and method.
+
+ @method off
+ @param {String} name The name of the event
+ @param {Object} target The target of the subscription
+ @param {Function} method The function of the subscription
+ @return this
+ */
+ off: function(name, target, method) {
+ Ember.removeListener(this, name, target, method);
+ return this;
+ },
+
+ /**
+ Checks to see if object has any subscriptions for named event.
+
+ @method has
+ @param {String} name The name of the event
+ @return {Boolean} does the object have a subscription for event
+ */
+ has: function(name) {
+ return Ember.hasListeners(this, name);
+ }
+});
+
+})();
+
+
+
+(function() {
+var RSVP = requireModule("rsvp");
+
+if (Ember.FEATURES['ember-runtime-test-friendly-promises']) {
+
+ var asyncStart = function() {
+ if (Ember.Test && Ember.Test.adapter) {
+ Ember.Test.adapter.asyncStart();
+ }
+ };
+
+ var asyncEnd = function() {
+ if (Ember.Test && Ember.Test.adapter) {
+ Ember.Test.adapter.asyncEnd();
+ }
+ };
+
+ RSVP.configure('async', function(callback, promise) {
+ var async = !Ember.run.currentRunLoop;
+
+ if (Ember.testing && async) { asyncStart(); }
+
+ Ember.run.backburner.schedule('actions', function(){
+ if (Ember.testing && async) { asyncEnd(); }
+ callback(promise);
+ });
+ });
+} else {
+ RSVP.configure('async', function(callback, promise) {
+ Ember.run.backburner.schedule('actions', function(){
+ callback(promise);
+ });
+ });
+}
+
+RSVP.Promise.prototype.fail = function(callback, label){
+ Ember.deprecate('RSVP.Promise.fail has been renamed as RSVP.Promise.catch');
+ return this['catch'](callback, label);
+};
+
+/**
+@module ember
+@submodule ember-runtime
+*/
+
+var get = Ember.get;
+
+/**
+ @class Deferred
+ @namespace Ember
+ */
+Ember.DeferredMixin = Ember.Mixin.create({
+ /**
+ Add handlers to be called when the Deferred object is resolved or rejected.
+
+ @method then
+ @param {Function} resolve a callback function to be called when done
+ @param {Function} reject a callback function to be called when failed
+ */
+ then: function(resolve, reject, label) {
+ var deferred, promise, entity;
+
+ entity = this;
+ deferred = get(this, '_deferred');
+ promise = deferred.promise;
+
+ function fulfillmentHandler(fulfillment) {
+ if (fulfillment === promise) {
+ return resolve(entity);
+ } else {
+ return resolve(fulfillment);
+ }
+ }
+
+ return promise.then(resolve && fulfillmentHandler, reject, label);
+ },
+
+ /**
+ Resolve a Deferred object and call any `doneCallbacks` with the given args.
+
+ @method resolve
+ */
+ resolve: function(value) {
+ var deferred, promise;
+
+ deferred = get(this, '_deferred');
+ promise = deferred.promise;
+
+ if (value === this) {
+ deferred.resolve(promise);
+ } else {
+ deferred.resolve(value);
+ }
+ },
+
+ /**
+ Reject a Deferred object and call any `failCallbacks` with the given args.
+
+ @method reject
+ */
+ reject: function(value) {
+ get(this, '_deferred').reject(value);
+ },
+
+ _deferred: Ember.computed(function() {
+ return RSVP.defer('Ember: DeferredMixin - ' + this);
+ })
+});
+
+
+})();
+
+
+
+(function() {
+/**
+@module ember
+@submodule ember-runtime
+*/
+
+var get = Ember.get, typeOf = Ember.typeOf;
+
+/**
+ The `Ember.ActionHandler` mixin implements support for moving an `actions`
+ property to an `_actions` property at extend time, and adding `_actions`
+ to the object's mergedProperties list.
+
+ `Ember.ActionHandler` is available on some familiar classes including
+ `Ember.Route`, `Ember.View`, `Ember.Component`, and controllers such as
+ `Ember.Controller` and `Ember.ObjectController`.
+ (Internally the mixin is used by `Ember.CoreView`, `Ember.ControllerMixin`,
+ and `Ember.Route` and available to the above classes through
+ inheritance.)
+
+ @class ActionHandler
+ @namespace Ember
+*/
+Ember.ActionHandler = Ember.Mixin.create({
+ mergedProperties: ['_actions'],
+
+ /**
+ The collection of functions, keyed by name, available on this
+ `ActionHandler` as action targets.
+
+ These functions will be invoked when a matching `{{action}}` is triggered
+ from within a template and the application's current route is this route.
+
+ Actions can also be invoked from other parts of your application
+ via `ActionHandler#send`.
+
+ The `actions` hash will inherit action handlers from
+ the `actions` hash defined on extended parent classes
+ or mixins rather than just replace the entire hash, e.g.:
+
+ ```js
+ App.CanDisplayBanner = Ember.Mixin.create({
+ actions: {
+ displayBanner: function(msg) {
+ // ...
+ }
+ }
+ });
+
+ App.WelcomeRoute = Ember.Route.extend(App.CanDisplayBanner, {
+ actions: {
+ playMusic: function() {
+ // ...
+ }
+ }
+ });
+
+ // `WelcomeRoute`, when active, will be able to respond
+ // to both actions, since the actions hash is merged rather
+ // then replaced when extending mixins / parent classes.
+ this.send('displayBanner');
+ this.send('playMusic');
+ ```
+
+ Within a Controller, Route, View or Component's action handler,
+ the value of the `this` context is the Controller, Route, View or
+ Component object:
+
+ ```js
+ App.SongRoute = Ember.Route.extend({
+ actions: {
+ myAction: function() {
+ this.controllerFor("song");
+ this.transitionTo("other.route");
+ ...
+ }
+ }
+ });
+ ```
+
+ It is also possible to call `this._super()` from within an
+ action handler if it overrides a handler defined on a parent
+ class or mixin:
+
+ Take for example the following routes:
+
+ ```js
+ App.DebugRoute = Ember.Mixin.create({
+ actions: {
+ debugRouteInformation: function() {
+ console.debug("trololo");
+ }
+ }
+ });
+
+ App.AnnoyingDebugRoute = Ember.Route.extend(App.DebugRoute, {
+ actions: {
+ debugRouteInformation: function() {
+ // also call the debugRouteInformation of mixed in App.DebugRoute
+ this._super();
+
+ // show additional annoyance
+ window.alert(...);
+ }
+ }
+ });
+ ```
+
+ ## Bubbling
+
+ By default, an action will stop bubbling once a handler defined
+ on the `actions` hash handles it. To continue bubbling the action,
+ you must return `true` from the handler:
+
+ ```js
+ App.Router.map(function() {
+ this.resource("album", function() {
+ this.route("song");
+ });
+ });
+
+ App.AlbumRoute = Ember.Route.extend({
+ actions: {
+ startPlaying: function() {
+ }
+ }
+ });
+
+ App.AlbumSongRoute = Ember.Route.extend({
+ actions: {
+ startPlaying: function() {
+ // ...
+
+ if (actionShouldAlsoBeTriggeredOnParentRoute) {
+ return true;
+ }
+ }
+ }
+ });
+ ```
+
+ @property actions
+ @type Hash
+ @default null
+ */
+
+ /**
+ Moves `actions` to `_actions` at extend time. Note that this currently
+ modifies the mixin themselves, which is technically dubious but
+ is practically of little consequence. This may change in the future.
+
+ @private
+ @method willMergeMixin
+ */
+ willMergeMixin: function(props) {
+ var hashName;
+
+ if (!props._actions) {
+ Ember.assert("'actions' should not be a function", typeof(props.actions) !== 'function');
+
+ if (typeOf(props.actions) === 'object') {
+ hashName = 'actions';
+ } else if (typeOf(props.events) === 'object') {
+ Ember.deprecate('Action handlers contained in an `events` object are deprecated in favor of putting them in an `actions` object', false);
+ hashName = 'events';
+ }
+
+ if (hashName) {
+ props._actions = Ember.merge(props._actions || {}, props[hashName]);
+ }
+
+ delete props[hashName];
+ }
+ },
+
+ /**
+ Triggers a named action on the `ActionHandler`. Any parameters
+ supplied after the `actionName` string will be passed as arguments
+ to the action target function.
+
+ If the `ActionHandler` has its `target` property set, actions may
+ bubble to the `target`. Bubbling happens when an `actionName` can
+ not be found in the `ActionHandler`'s `actions` hash or if the
+ action target function returns `true`.
+
+ Example
+
+ ```js
+ App.WelcomeRoute = Ember.Route.extend({
+ actions: {
+ playTheme: function() {
+ this.send('playMusic', 'theme.mp3');
+ },
+ playMusic: function(track) {
+ // ...
+ }
+ }
+ });
+ ```
+
+ @method send
+ @param {String} actionName The action to trigger
+ @param {*} context a context to send with the action
+ */
+ send: function(actionName) {
+ var args = [].slice.call(arguments, 1), target;
+
+ if (this._actions && this._actions[actionName]) {
+ if (this._actions[actionName].apply(this, args) === true) {
+ // handler returned true, so this action will bubble
+ } else {
+ return;
+ }
+ } else if (!Ember.FEATURES.isEnabled('ember-routing-drop-deprecated-action-style') && this.deprecatedSend && this.deprecatedSendHandles && this.deprecatedSendHandles(actionName)) {
+ Ember.warn("The current default is deprecated but will prefer to handle actions directly on the controller instead of a similarly named action in the actions hash. To turn off this deprecated feature set: Ember.FEATURES['ember-routing-drop-deprecated-action-style'] = true");
+ if (this.deprecatedSend.apply(this, [].slice.call(arguments)) === true) {
+ // handler return true, so this action will bubble
+ } else {
+ return;
+ }
+ }
+
+ if (target = get(this, 'target')) {
+ Ember.assert("The `target` for " + this + " (" + target + ") does not have a `send` method", typeof target.send === 'function');
+ target.send.apply(target, arguments);
+ }
+ }
+
+});
+
+})();
+
+
+
+(function() {
+var set = Ember.set, get = Ember.get,
+ not = Ember.computed.not,
+ or = Ember.computed.or;
+
+/**
+ @module ember
+ @submodule ember-runtime
+ */
+
+function tap(proxy, promise) {
+ set(proxy, 'isFulfilled', false);
+ set(proxy, 'isRejected', false);
+
+ return promise.then(function(value) {
+ set(proxy, 'isFulfilled', true);
+ set(proxy, 'content', value);
+ return value;
+ }, function(reason) {
+ set(proxy, 'isRejected', true);
+ set(proxy, 'reason', reason);
+ throw reason;
+ }, "Ember: PromiseProxy");
+}
+
+/**
+ A low level mixin making ObjectProxy, ObjectController or ArrayController's promise aware.
+
+ ```javascript
+ var ObjectPromiseController = Ember.ObjectController.extend(Ember.PromiseProxyMixin);
+
+ var controller = ObjectPromiseController.create({
+ promise: $.getJSON('/some/remote/data.json')
+ });
+
+ controller.then(function(json){
+ // the json
+ }, function(reason) {
+ // the reason why you have no json
+ });
+ ```
+
+ the controller has bindable attributes which
+ track the promises life cycle
+
+ ```javascript
+ controller.get('isPending') //=> true
+ controller.get('isSettled') //=> false
+ controller.get('isRejected') //=> false
+ controller.get('isFulfilled') //=> false
+ ```
+
+ When the the $.getJSON completes, and the promise is fulfilled
+ with json, the life cycle attributes will update accordingly.
+
+ ```javascript
+ controller.get('isPending') //=> false
+ controller.get('isSettled') //=> true
+ controller.get('isRejected') //=> false
+ controller.get('isFulfilled') //=> true
+ ```
+
+ As the controller is an ObjectController, and the json now its content,
+ all the json properties will be available directly from the controller.
+
+ ```javascript
+ // Assuming the following json:
+ {
+ firstName: 'Stefan',
+ lastName: 'Penner'
+ }
+
+ // both properties will accessible on the controller
+ controller.get('firstName') //=> 'Stefan'
+ controller.get('lastName') //=> 'Penner'
+ ```
+
+ If the controller is backing a template, the attributes are
+ bindable from within that template
+
+ ```handlebars
+ {{#if isPending}}
+ loading...
+ {{else}}
+ firstName: {{firstName}}
+ lastName: {{lastName}}
+ {{/if}}
+ ```
+ @class Ember.PromiseProxyMixin
+*/
+Ember.PromiseProxyMixin = Ember.Mixin.create({
+ /**
+ If the proxied promise is rejected this will contain the reason
+ provided.
+
+ @property reason
+ @default null
+ */
+ reason: null,
+
+ /**
+ Once the proxied promise has settled this will become `false`.
+
+ @property isPending
+ @default true
+ */
+ isPending: not('isSettled').readOnly(),
+
+ /**
+ Once the proxied promise has settled this will become `true`.
+
+ @property isSettled
+ @default false
+ */
+ isSettled: or('isRejected', 'isFulfilled').readOnly(),
+
+ /**
+ Will become `true` if the proxied promise is rejected.
+
+ @property isRejected
+ @default false
+ */
+ isRejected: false,
+
+ /**
+ Will become `true` if the proxied promise is fulfilled.
+
+ @property isFullfilled
+ @default false
+ */
+ isFulfilled: false,
+
+ /**
+ The promise whose fulfillment value is being proxied by this object.
+
+ This property must be specified upon creation, and should not be
+ changed once created.
+
+ Example:
+
+ ```javascript
+ Ember.ObjectController.extend(Ember.PromiseProxyMixin).create({
+ promise: <thenable>
+ });
+ ```
+
+ @property promise
+ */
+ promise: Ember.computed(function(key, promise) {
+ if (arguments.length === 2) {
+ return tap(this, promise);
+ } else {
+ throw new Ember.Error("PromiseProxy's promise must be set");
+ }
+ }),
+
+ /**
+ An alias to the proxied promise's `then`.
+
+ See RSVP.Promise.then.
+
+ @method then
+ @param {Function} callback
+ @return {RSVP.Promise}
+ */
+ then: promiseAlias('then'),
+
+ /**
+ An alias to the proxied promise's `catch`.
+
+ See RSVP.Promise.catch.
+
+ @method catch
+ @param {Function} callback
+ @return {RSVP.Promise}
+ */
+ 'catch': promiseAlias('catch'),
+
+ /**
+ An alias to the proxied promise's `finally`.
+
+ See RSVP.Promise.finally.
+
+ @method finally
+ @param {Function} callback
+ @return {RSVP.Promise}
+ */
+ 'finally': promiseAlias('finally')
+
+});
+
+function promiseAlias(name) {
+ return function () {
+ var promise = get(this, 'promise');
+ return promise[name].apply(promise, arguments);
+ };
+}
+
+})();
+
+
+
+(function() {
+
+})();
+
+
+
+(function() {
+var get = Ember.get,
+ forEach = Ember.EnumerableUtils.forEach,
+ RETAIN = 'r',
+ INSERT = 'i',
+ DELETE = 'd';
+
+/**
+ An `Ember.TrackedArray` tracks array operations. It's useful when you want to
+ lazily compute the indexes of items in an array after they've been shifted by
+ subsequent operations.
+
+ @class TrackedArray
+ @namespace Ember
+ @param {array} [items=[]] The array to be tracked. This is used just to get
+ the initial items for the starting state of retain:n.
+*/
+Ember.TrackedArray = function (items) {
+ if (arguments.length < 1) { items = []; }
+
+ var length = get(items, 'length');
+
+ if (length) {
+ this._operations = [new ArrayOperation(RETAIN, length, items)];
+ } else {
+ this._operations = [];
+ }
+};
+
+Ember.TrackedArray.RETAIN = RETAIN;
+Ember.TrackedArray.INSERT = INSERT;
+Ember.TrackedArray.DELETE = DELETE;
+
+Ember.TrackedArray.prototype = {
+
+ /**
+ Track that `newItems` were added to the tracked array at `index`.
+
+ @method addItems
+ @param index
+ @param newItems
+ */
+ addItems: function (index, newItems) {
+ var count = get(newItems, 'length');
+ if (count < 1) { return; }
+
+ var match = this._findArrayOperation(index),
+ arrayOperation = match.operation,
+ arrayOperationIndex = match.index,
+ arrayOperationRangeStart = match.rangeStart,
+ composeIndex,
+ splitIndex,
+ splitItems,
+ splitArrayOperation,
+ newArrayOperation;
+
+ newArrayOperation = new ArrayOperation(INSERT, count, newItems);
+
+ if (arrayOperation) {
+ if (!match.split) {
+ // insert left of arrayOperation
+ this._operations.splice(arrayOperationIndex, 0, newArrayOperation);
+ composeIndex = arrayOperationIndex;
+ } else {
+ this._split(arrayOperationIndex, index - arrayOperationRangeStart, newArrayOperation);
+ composeIndex = arrayOperationIndex + 1;
+ }
+ } else {
+ // insert at end
+ this._operations.push(newArrayOperation);
+ composeIndex = arrayOperationIndex;
+ }
+
+ this._composeInsert(composeIndex);
+ },
+
+ /**
+ Track that `count` items were removed at `index`.
+
+ @method removeItems
+ @param index
+ @param count
+ */
+ removeItems: function (index, count) {
+ if (count < 1) { return; }
+
+ var match = this._findArrayOperation(index),
+ arrayOperation = match.operation,
+ arrayOperationIndex = match.index,
+ arrayOperationRangeStart = match.rangeStart,
+ newArrayOperation,
+ composeIndex;
+
+ newArrayOperation = new ArrayOperation(DELETE, count);
+ if (!match.split) {
+ // insert left of arrayOperation
+ this._operations.splice(arrayOperationIndex, 0, newArrayOperation);
+ composeIndex = arrayOperationIndex;
+ } else {
+ this._split(arrayOperationIndex, index - arrayOperationRangeStart, newArrayOperation);
+ composeIndex = arrayOperationIndex + 1;
+ }
+
+ return this._composeDelete(composeIndex);
+ },
+
+ /**
+ Apply all operations, reducing them to retain:n, for `n`, the number of
+ items in the array.
+
+ `callback` will be called for each operation and will be passed the following arguments:
+
+ * {array} items The items for the given operation
+ * {number} offset The computed offset of the items, ie the index in the
+ array of the first item for this operation.
+ * {string} operation The type of the operation. One of
+ `Ember.TrackedArray.{RETAIN, DELETE, INSERT}`
+
+ @method apply
+ @param {function} callback
+ */
+ apply: function (callback) {
+ var items = [],
+ offset = 0;
+
+ forEach(this._operations, function (arrayOperation) {
+ callback(arrayOperation.items, offset, arrayOperation.type);
+
+ if (arrayOperation.type !== DELETE) {
+ offset += arrayOperation.count;
+ items = items.concat(arrayOperation.items);
+ }
+ });
+
+ this._operations = [new ArrayOperation(RETAIN, items.length, items)];
+ },
+
+ /**
+ Return an `ArrayOperationMatch` for the operation that contains the item at `index`.
+
+ @method _findArrayOperation
+
+ @param {number} index the index of the item whose operation information
+ should be returned.
+ @private
+ */
+ _findArrayOperation: function (index) {
+ var arrayOperationIndex,
+ len,
+ split = false,
+ arrayOperation,
+ arrayOperationRangeStart,
+ arrayOperationRangeEnd;
+
+ // OPTIMIZE: we could search these faster if we kept a balanced tree.
+ // find leftmost arrayOperation to the right of `index`
+ for (arrayOperationIndex = arrayOperationRangeStart = 0, len = this._operations.length; arrayOperationIndex < len; ++arrayOperationIndex) {
+ arrayOperation = this._operations[arrayOperationIndex];
+
+ if (arrayOperation.type === DELETE) { continue; }
+
+ arrayOperationRangeEnd = arrayOperationRangeStart + arrayOperation.count - 1;
+
+ if (index === arrayOperationRangeStart) {
+ break;
+ } else if (index > arrayOperationRangeStart && index <= arrayOperationRangeEnd) {
+ split = true;
+ break;
+ } else {
+ arrayOperationRangeStart = arrayOperationRangeEnd + 1;
+ }
+ }
+
+ return new ArrayOperationMatch(arrayOperation, arrayOperationIndex, split, arrayOperationRangeStart);
+ },
+
+ _split: function (arrayOperationIndex, splitIndex, newArrayOperation) {
+ var arrayOperation = this._operations[arrayOperationIndex],
+ splitItems = arrayOperation.items.slice(splitIndex),
+ splitArrayOperation = new ArrayOperation(arrayOperation.type, splitItems.length, splitItems);
+
+ // truncate LHS
+ arrayOperation.count = splitIndex;
+ arrayOperation.items = arrayOperation.items.slice(0, splitIndex);
+
+ this._operations.splice(arrayOperationIndex + 1, 0, newArrayOperation, splitArrayOperation);
+ },
+
+ // see SubArray for a better implementation.
+ _composeInsert: function (index) {
+ var newArrayOperation = this._operations[index],
+ leftArrayOperation = this._operations[index-1], // may be undefined
+ rightArrayOperation = this._operations[index+1], // may be undefined
+ leftOp = leftArrayOperation && leftArrayOperation.type,
+ rightOp = rightArrayOperation && rightArrayOperation.type;
+
+ if (leftOp === INSERT) {
+ // merge left
+ leftArrayOperation.count += newArrayOperation.count;
+ leftArrayOperation.items = leftArrayOperation.items.concat(newArrayOperation.items);
+
+ if (rightOp === INSERT) {
+ // also merge right (we have split an insert with an insert)
+ leftArrayOperation.count += rightArrayOperation.count;
+ leftArrayOperation.items = leftArrayOperation.items.concat(rightArrayOperation.items);
+ this._operations.splice(index, 2);
+ } else {
+ // only merge left
+ this._operations.splice(index, 1);
+ }
+ } else if (rightOp === INSERT) {
+ // merge right
+ newArrayOperation.count += rightArrayOperation.count;
+ newArrayOperation.items = newArrayOperation.items.concat(rightArrayOperation.items);
+ this._operations.splice(index + 1, 1);
+ }
+ },
+
+ _composeDelete: function (index) {
+ var arrayOperation = this._operations[index],
+ deletesToGo = arrayOperation.count,
+ leftArrayOperation = this._operations[index-1], // may be undefined
+ leftOp = leftArrayOperation && leftArrayOperation.type,
+ nextArrayOperation,
+ nextOp,
+ nextCount,
+ removeNewAndNextOp = false,
+ removedItems = [];
+
+ if (leftOp === DELETE) {
+ arrayOperation = leftArrayOperation;
+ index -= 1;
+ }
+
+ for (var i = index + 1; deletesToGo > 0; ++i) {
+ nextArrayOperation = this._operations[i];
+ nextOp = nextArrayOperation.type;
+ nextCount = nextArrayOperation.count;
+
+ if (nextOp === DELETE) {
+ arrayOperation.count += nextCount;
+ continue;
+ }
+
+ if (nextCount > deletesToGo) {
+ // d:2 {r,i}:5 we reduce the retain or insert, but it stays
+ removedItems = removedItems.concat(nextArrayOperation.items.splice(0, deletesToGo));
+ nextArrayOperation.count -= deletesToGo;
+
+ // In the case where we truncate the last arrayOperation, we don't need to
+ // remove it; also the deletesToGo reduction is not the entirety of
+ // nextCount
+ i -= 1;
+ nextCount = deletesToGo;
+
+ deletesToGo = 0;
+ } else {
+ if (nextCount === deletesToGo) {
+ // Handle edge case of d:2 i:2 in which case both operations go away
+ // during composition.
+ removeNewAndNextOp = true;
+ }
+ removedItems = removedItems.concat(nextArrayOperation.items);
+ deletesToGo -= nextCount;
+ }
+
+ if (nextOp === INSERT) {
+ // d:2 i:3 will result in delete going away
+ arrayOperation.count -= nextCount;
+ }
+ }
+
+ if (arrayOperation.count > 0) {
+ // compose our new delete with possibly several operations to the right of
+ // disparate types
+ this._operations.splice(index+1, i-1-index);
+ } else {
+ // The delete operation can go away; it has merely reduced some other
+ // operation, as in d:3 i:4; it may also have eliminated that operation,
+ // as in d:3 i:3.
+ this._operations.splice(index, removeNewAndNextOp ? 2 : 1);
+ }
+
+ return removedItems;
+ },
+
+ toString: function () {
+ var str = "";
+ forEach(this._operations, function (operation) {
+ str += " " + operation.type + ":" + operation.count;
+ });
+ return str.substring(1);
+ }
+};
+
+/**
+ Internal data structure to represent an array operation.
+
+ @method ArrayOperation
+ @private
+ @param {string} type The type of the operation. One of
+ `Ember.TrackedArray.{RETAIN, INSERT, DELETE}`
+ @param {number} count The number of items in this operation.
+ @param {array} items The items of the operation, if included. RETAIN and
+ INSERT include their items, DELETE does not.
+*/
+function ArrayOperation (operation, count, items) {
+ this.type = operation; // RETAIN | INSERT | DELETE
+ this.count = count;
+ this.items = items;
+}
+
+/**
+ Internal data structure used to include information when looking up operations
+ by item index.
+
+ @method ArrayOperationMatch
+ @private
+ @param {ArrayOperation} operation
+ @param {number} index The index of `operation` in the array of operations.
+ @param {boolean} split Whether or not the item index searched for would
+ require a split for a new operation type.
+ @param {number} rangeStart The index of the first item in the operation,
+ with respect to the tracked array. The index of the last item can be computed
+ from `rangeStart` and `operation.count`.
+*/
+function ArrayOperationMatch(operation, index, split, rangeStart) {
+ this.operation = operation;
+ this.index = index;
+ this.split = split;
+ this.rangeStart = rangeStart;
+}
+
+})();
+
+
+
+(function() {
+var get = Ember.get,
+ forEach = Ember.EnumerableUtils.forEach,
+ RETAIN = 'r',
+ FILTER = 'f';
+
+function Operation (type, count) {
+ this.type = type;
+ this.count = count;
+}
+
+/**
+ An `Ember.SubArray` tracks an array in a way similar to, but more specialized
+ than, `Ember.TrackedArray`. It is useful for keeping track of the indexes of
+ items within a filtered array.
+
+ @class SubArray
+ @namespace Ember
+*/
+Ember.SubArray = function (length) {
+ if (arguments.length < 1) { length = 0; }
+
+ if (length > 0) {
+ this._operations = [new Operation(RETAIN, length)];
+ } else {
+ this._operations = [];
+ }
+};
+
+Ember.SubArray.prototype = {
+ /**
+ Track that an item was added to the tracked array.
+
+ @method addItem
+
+ @param {number} index The index of the item in the tracked array.
+ @param {boolean} match `true` iff the item is included in the subarray.
+
+ @return {number} The index of the item in the subarray.
+ */
+ addItem: function(index, match) {
+ var returnValue = -1,
+ itemType = match ? RETAIN : FILTER,
+ self = this;
+
+ this._findOperation(index, function(operation, operationIndex, rangeStart, rangeEnd, seenInSubArray) {
+ var newOperation, splitOperation;
+
+ if (itemType === operation.type) {
+ ++operation.count;
+ } else if (index === rangeStart) {
+ // insert to the left of `operation`
+ self._operations.splice(operationIndex, 0, new Operation(itemType, 1));
+ } else {
+ newOperation = new Operation(itemType, 1);
+ splitOperation = new Operation(operation.type, rangeEnd - index + 1);
+ operation.count = index - rangeStart;
+
+ self._operations.splice(operationIndex + 1, 0, newOperation, splitOperation);
+ }
+
+ if (match) {
+ if (operation.type === RETAIN) {
+ returnValue = seenInSubArray + (index - rangeStart);
+ } else {
+ returnValue = seenInSubArray;
+ }
+ }
+
+ self._composeAt(operationIndex);
+ }, function(seenInSubArray) {
+ self._operations.push(new Operation(itemType, 1));
+
+ if (match) {
+ returnValue = seenInSubArray;
+ }
+
+ self._composeAt(self._operations.length-1);
+ });
+
+ return returnValue;
+ },
+
+ /**
+ Track that an item was removed from the tracked array.
+
+ @method removeItem
+
+ @param {number} index The index of the item in the tracked array.
+
+ @return {number} The index of the item in the subarray, or `-1` if the item
+ was not in the subarray.
+ */
+ removeItem: function(index) {
+ var returnValue = -1,
+ self = this;
+
+ this._findOperation(index, function (operation, operationIndex, rangeStart, rangeEnd, seenInSubArray) {
+ if (operation.type === RETAIN) {
+ returnValue = seenInSubArray + (index - rangeStart);
+ }
+
+ if (operation.count > 1) {
+ --operation.count;
+ } else {
+ self._operations.splice(operationIndex, 1);
+ self._composeAt(operationIndex);
+ }
+ }, function() {
+ throw new Ember.Error("Can't remove an item that has never been added.");
+ });
+
+ return returnValue;
+ },
+
+
+ _findOperation: function (index, foundCallback, notFoundCallback) {
+ var operationIndex,
+ len,
+ operation,
+ rangeStart,
+ rangeEnd,
+ seenInSubArray = 0;
+
+ // OPTIMIZE: change to balanced tree
+ // find leftmost operation to the right of `index`
+ for (operationIndex = rangeStart = 0, len = this._operations.length; operationIndex < len; rangeStart = rangeEnd + 1, ++operationIndex) {
+ operation = this._operations[operationIndex];
+ rangeEnd = rangeStart + operation.count - 1;
+
+ if (index >= rangeStart && index <= rangeEnd) {
+ foundCallback(operation, operationIndex, rangeStart, rangeEnd, seenInSubArray);
+ return;
+ } else if (operation.type === RETAIN) {
+ seenInSubArray += operation.count;
+ }
+ }
+
+ notFoundCallback(seenInSubArray);
+ },
+
+ _composeAt: function(index) {
+ var op = this._operations[index],
+ otherOp;
+
+ if (!op) {
+ // Composing out of bounds is a no-op, as when removing the last operation
+ // in the list.
+ return;
+ }
+
+ if (index > 0) {
+ otherOp = this._operations[index-1];
+ if (otherOp.type === op.type) {
+ op.count += otherOp.count;
+ this._operations.splice(index-1, 1);
+ --index;
+ }
+ }
+
+ if (index < this._operations.length-1) {
+ otherOp = this._operations[index+1];
+ if (otherOp.type === op.type) {
+ op.count += otherOp.count;
+ this._operations.splice(index+1, 1);
+ }
+ }
+ },
+
+ toString: function () {
+ var str = "";
+ forEach(this._operations, function (operation) {
+ str += " " + operation.type + ":" + operation.count;
+ });
+ return str.substring(1);
+ }
+};
+
+})();
+
+
+
+(function() {
+Ember.Container = requireModule('container')['default'];
+Ember.Container.set = Ember.set;
+
+})();
+
+
+
+(function() {
+Ember.Application = Ember.Namespace.extend();
+
+})();
+
+
+
+(function() {
+/**
+@module ember
+@submodule ember-runtime
+*/
+
+var OUT_OF_RANGE_EXCEPTION = "Index out of range";
+var EMPTY = [];
+
+var get = Ember.get, set = Ember.set;
+
+/**
+ An ArrayProxy wraps any other object that implements `Ember.Array` and/or
+ `Ember.MutableArray,` forwarding all requests. This makes it very useful for
+ a number of binding use cases or other cases where being able to swap
+ out the underlying array is useful.
+
+ A simple example of usage:
+
+ ```javascript
+ var pets = ['dog', 'cat', 'fish'];
+ var ap = Ember.ArrayProxy.create({ content: Ember.A(pets) });
+
+ ap.get('firstObject'); // 'dog'
+ ap.set('content', ['amoeba', 'paramecium']);
+ ap.get('firstObject'); // 'amoeba'
+ ```
+
+ This class can also be useful as a layer to transform the contents of
+ an array, as they are accessed. This can be done by overriding
+ `objectAtContent`:
+
+ ```javascript
+ var pets = ['dog', 'cat', 'fish'];
+ var ap = Ember.ArrayProxy.create({
+ content: Ember.A(pets),
+ objectAtContent: function(idx) {
+ return this.get('content').objectAt(idx).toUpperCase();
+ }
+ });
+
+ ap.get('firstObject'); // . 'DOG'
+ ```
+
+ @class ArrayProxy
+ @namespace Ember
+ @extends Ember.Object
+ @uses Ember.MutableArray
+*/
+Ember.ArrayProxy = Ember.Object.extend(Ember.MutableArray, {
+
+ /**
+ The content array. Must be an object that implements `Ember.Array` and/or
+ `Ember.MutableArray.`
+
+ @property content
+ @type Ember.Array
+ */
+ content: null,
+
+ /**
+ The array that the proxy pretends to be. In the default `ArrayProxy`
+ implementation, this and `content` are the same. Subclasses of `ArrayProxy`
+ can override this property to provide things like sorting and filtering.
+
+ @property arrangedContent
+ */
+ arrangedContent: Ember.computed.alias('content'),
+
+ /**
+ Should actually retrieve the object at the specified index from the
+ content. You can override this method in subclasses to transform the
+ content item to something new.
+
+ This method will only be called if content is non-`null`.
+
+ @method objectAtContent
+ @param {Number} idx The index to retrieve.
+ @return {Object} the value or undefined if none found
+ */
+ objectAtContent: function(idx) {
+ return get(this, 'arrangedContent').objectAt(idx);
+ },
+
+ /**
+ Should actually replace the specified objects on the content array.
+ You can override this method in subclasses to transform the content item
+ into something new.
+
+ This method will only be called if content is non-`null`.
+
+ @method replaceContent
+ @param {Number} idx The starting index
+ @param {Number} amt The number of items to remove from the content.
+ @param {Array} objects Optional array of objects to insert or null if no
+ objects.
+ @return {void}
+ */
+ replaceContent: function(idx, amt, objects) {
+ get(this, 'content').replace(idx, amt, objects);
+ },
+
+ /**
+ Invoked when the content property is about to change. Notifies observers that the
+ entire array content will change.
+
+ @private
+ @method _contentWillChange
+ */
+ _contentWillChange: Ember.beforeObserver('content', function() {
+ this._teardownContent();
+ }),
+
+ _teardownContent: function() {
+ var content = get(this, 'content');
+
+ if (content) {
+ content.removeArrayObserver(this, {
+ willChange: 'contentArrayWillChange',
+ didChange: 'contentArrayDidChange'
+ });
+ }
+ },
+
+ contentArrayWillChange: Ember.K,
+ contentArrayDidChange: Ember.K,
+
+ /**
+ Invoked when the content property changes. Notifies observers that the
+ entire array content has changed.
+
+ @private
+ @method _contentDidChange
+ */
+ _contentDidChange: Ember.observer('content', function() {
+ var content = get(this, 'content');
+
+ Ember.assert("Can't set ArrayProxy's content to itself", content !== this);
+
+ this._setupContent();
+ }),
+
+ _setupContent: function() {
+ var content = get(this, 'content');
+
+ if (content) {
+ Ember.assert(Ember.String.fmt('ArrayProxy expects an Array or ' +
+ 'Ember.ArrayProxy, but you passed %@', [typeof content]),
+ Ember.isArray(content) || content.isDestroyed);
+
+ content.addArrayObserver(this, {
+ willChange: 'contentArrayWillChange',
+ didChange: 'contentArrayDidChange'
+ });
+ }
+ },
+
+ _arrangedContentWillChange: Ember.beforeObserver('arrangedContent', function() {
+ var arrangedContent = get(this, 'arrangedContent'),
+ len = arrangedContent ? get(arrangedContent, 'length') : 0;
+
+ this.arrangedContentArrayWillChange(this, 0, len, undefined);
+ this.arrangedContentWillChange(this);
+
+ this._teardownArrangedContent(arrangedContent);
+ }),
+
+ _arrangedContentDidChange: Ember.observer('arrangedContent', function() {
+ var arrangedContent = get(this, 'arrangedContent'),
+ len = arrangedContent ? get(arrangedContent, 'length') : 0;
+
+ Ember.assert("Can't set ArrayProxy's content to itself", arrangedContent !== this);
+
+ this._setupArrangedContent();
+
+ this.arrangedContentDidChange(this);
+ this.arrangedContentArrayDidChange(this, 0, undefined, len);
+ }),
+
+ _setupArrangedContent: function() {
+ var arrangedContent = get(this, 'arrangedContent');
+
+ if (arrangedContent) {
+ Ember.assert(Ember.String.fmt('ArrayProxy expects an Array or ' +
+ 'Ember.ArrayProxy, but you passed %@', [typeof arrangedContent]),
+ Ember.isArray(arrangedContent) || arrangedContent.isDestroyed);
+
+ arrangedContent.addArrayObserver(this, {
+ willChange: 'arrangedContentArrayWillChange',
+ didChange: 'arrangedContentArrayDidChange'
+ });
+ }
+ },
+
+ _teardownArrangedContent: function() {
+ var arrangedContent = get(this, 'arrangedContent');
+
+ if (arrangedContent) {
+ arrangedContent.removeArrayObserver(this, {
+ willChange: 'arrangedContentArrayWillChange',
+ didChange: 'arrangedContentArrayDidChange'
+ });
+ }
+ },
+
+ arrangedContentWillChange: Ember.K,
+ arrangedContentDidChange: Ember.K,
+
+ objectAt: function(idx) {
+ return get(this, 'content') && this.objectAtContent(idx);
+ },
+
+ length: Ember.computed(function() {
+ var arrangedContent = get(this, 'arrangedContent');
+ return arrangedContent ? get(arrangedContent, 'length') : 0;
+ // No dependencies since Enumerable notifies length of change
+ }),
+
+ _replace: function(idx, amt, objects) {
+ var content = get(this, 'content');
+ Ember.assert('The content property of '+ this.constructor + ' should be set before modifying it', content);
+ if (content) this.replaceContent(idx, amt, objects);
+ return this;
+ },
+
+ replace: function() {
+ if (get(this, 'arrangedContent') === get(this, 'content')) {
+ this._replace.apply(this, arguments);
+ } else {
+ throw new Ember.Error("Using replace on an arranged ArrayProxy is not allowed.");
+ }
+ },
+
+ _insertAt: function(idx, object) {
+ if (idx > get(this, 'content.length')) throw new Ember.Error(OUT_OF_RANGE_EXCEPTION);
+ this._replace(idx, 0, [object]);
+ return this;
+ },
+
+ insertAt: function(idx, object) {
+ if (get(this, 'arrangedContent') === get(this, 'content')) {
+ return this._insertAt(idx, object);
+ } else {
+ throw new Ember.Error("Using insertAt on an arranged ArrayProxy is not allowed.");
+ }
+ },
+
+ removeAt: function(start, len) {
+ if ('number' === typeof start) {
+ var content = get(this, 'content'),
+ arrangedContent = get(this, 'arrangedContent'),
+ indices = [], i;
+
+ if ((start < 0) || (start >= get(this, 'length'))) {
+ throw new Ember.Error(OUT_OF_RANGE_EXCEPTION);
+ }
+
+ if (len === undefined) len = 1;
+
+ // Get a list of indices in original content to remove
+ for (i=start; i<start+len; i++) {
+ // Use arrangedContent here so we avoid confusion with objects transformed by objectAtContent
+ indices.push(content.indexOf(arrangedContent.objectAt(i)));
+ }
+
+ // Replace in reverse order since indices will change
+ indices.sort(function(a,b) { return b - a; });
+
+ Ember.beginPropertyChanges();
+ for (i=0; i<indices.length; i++) {
+ this._replace(indices[i], 1, EMPTY);
+ }
+ Ember.endPropertyChanges();
+ }
+
+ return this ;
+ },
+
+ pushObject: function(obj) {
+ this._insertAt(get(this, 'content.length'), obj) ;
+ return obj ;
+ },
+
+ 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;
+ },
+
+ setObjects: function(objects) {
+ if (objects.length === 0) return this.clear();
+
+ var len = get(this, 'length');
+ this._replace(0, len, objects);
+ return this;
+ },
+
+ unshiftObject: function(obj) {
+ this._insertAt(0, obj) ;
+ return obj ;
+ },
+
+ unshiftObjects: function(objects) {
+ this._replace(0, 0, objects);
+ return this;
+ },
+
+ slice: function() {
+ var arr = this.toArray();
+ return arr.slice.apply(arr, arguments);
+ },
+
+ arrangedContentArrayWillChange: function(item, idx, removedCnt, addedCnt) {
+ this.arrayContentWillChange(idx, removedCnt, addedCnt);
+ },
+
+ arrangedContentArrayDidChange: function(item, idx, removedCnt, addedCnt) {
+ this.arrayContentDidChange(idx, removedCnt, addedCnt);
+ },
+
+ init: function() {
+ this._super();
+ this._setupContent();
+ this._setupArrangedContent();
+ },
+
+ willDestroy: function() {
+ this._teardownArrangedContent();
+ this._teardownContent();
+ }
+});
+
+})();
+
+
+
+(function() {
+/**
+@module ember
+@submodule ember-runtime
+*/
+
+
+var set = Ember.set, get = Ember.get, guidFor = Ember.guidFor;
+var forEach = Ember.EnumerableUtils.forEach,
+ indexOf = Ember.ArrayPolyfills.indexOf;
+
+var EachArray = Ember.Object.extend(Ember.Array, {
+
+ init: function(content, keyName, owner) {
+ this._super();
+ this._keyName = keyName;
+ this._owner = owner;
+ this._content = content;
+ },
+
+ objectAt: function(idx) {
+ var item = this._content.objectAt(idx);
+ return item && get(item, this._keyName);
+ },
+
+ length: Ember.computed(function() {
+ var content = this._content;
+ return content ? get(content, 'length') : 0;
+ })
+
+});
+
+var IS_OBSERVER = /^.+:(before|change)$/;
+
+function addObserverForContentKey(content, keyName, proxy, idx, loc) {
+ var objects = proxy._objects, guid;
+ if (!objects) objects = proxy._objects = {};
+
+ while(--loc>=idx) {
+ var item = content.objectAt(loc);
+ if (item) {
+ Ember.assert('When using @each to observe the array ' + content + ', the array must return an object', Ember.typeOf(item) === 'instance' || Ember.typeOf(item) === 'object');
+ Ember.addBeforeObserver(item, keyName, proxy, 'contentKeyWillChange');
+ Ember.addObserver(item, keyName, proxy, 'contentKeyDidChange');
+
+ // keep track of the index each item was found at so we can map
+ // it back when the obj changes.
+ guid = guidFor(item);
+ if (!objects[guid]) objects[guid] = [];
+ objects[guid].push(loc);
+ }
+ }
+}
+
+function removeObserverForContentKey(content, keyName, proxy, idx, loc) {
+ var objects = proxy._objects;
+ if (!objects) objects = proxy._objects = {};
+ var indicies, guid;
+
+ while(--loc>=idx) {
+ var item = content.objectAt(loc);
+ if (item) {
+ Ember.removeBeforeObserver(item, keyName, proxy, 'contentKeyWillChange');
+ Ember.removeObserver(item, keyName, proxy, 'contentKeyDidChange');
+
+ guid = guidFor(item);
+ indicies = objects[guid];
+ indicies[indexOf.call(indicies, loc)] = null;
+ }
+ }
+}
+
+/**
+ This is the object instance returned when you get the `@each` property on an
+ array. It uses the unknownProperty handler to automatically create
+ EachArray instances for property names.
+
+ @private
+ @class EachProxy
+ @namespace Ember
+ @extends Ember.Object
+*/
+Ember.EachProxy = Ember.Object.extend({
+
+ init: function(content) {
+ this._super();
+ this._content = content;
+ content.addArrayObserver(this);
+
+ // in case someone is already observing some keys make sure they are
+ // added
+ forEach(Ember.watchedEvents(this), function(eventName) {
+ this.didAddListener(eventName);
+ }, this);
+ },
+
+ /**
+ You can directly access mapped properties by simply requesting them.
+ The `unknownProperty` handler will generate an EachArray of each item.
+
+ @method unknownProperty
+ @param keyName {String}
+ @param value {*}
+ */
+ unknownProperty: function(keyName, value) {
+ var ret;
+ ret = new EachArray(this._content, keyName, this);
+ Ember.defineProperty(this, keyName, null, ret);
+ this.beginObservingContentKey(keyName);
+ return ret;
+ },
+
+ // ..........................................................
+ // ARRAY CHANGES
+ // Invokes whenever the content array itself changes.
+
+ arrayWillChange: function(content, idx, removedCnt, addedCnt) {
+ var keys = this._keys, key, lim;
+
+ lim = removedCnt>0 ? idx+removedCnt : -1;
+ Ember.beginPropertyChanges(this);
+
+ for(key in keys) {
+ if (!keys.hasOwnProperty(key)) { continue; }
+
+ if (lim>0) { removeObserverForContentKey(content, key, this, idx, lim); }
+
+ Ember.propertyWillChange(this, key);
+ }
+
+ Ember.propertyWillChange(this._content, '@each');
+ Ember.endPropertyChanges(this);
+ },
+
+ arrayDidChange: function(content, idx, removedCnt, addedCnt) {
+ var keys = this._keys, lim;
+
+ lim = addedCnt>0 ? idx+addedCnt : -1;
+ Ember.changeProperties(function() {
+ for(var key in keys) {
+ if (!keys.hasOwnProperty(key)) { continue; }
+
+ if (lim>0) { addObserverForContentKey(content, key, this, idx, lim); }
+
+ Ember.propertyDidChange(this, key);
+ }
+
+ Ember.propertyDidChange(this._content, '@each');
+ }, this);
+ },
+
+ // ..........................................................
+ // LISTEN FOR NEW OBSERVERS AND OTHER EVENT LISTENERS
+ // Start monitoring keys based on who is listening...
+
+ didAddListener: function(eventName) {
+ if (IS_OBSERVER.test(eventName)) {
+ this.beginObservingContentKey(eventName.slice(0, -7));
+ }
+ },
+
+ didRemoveListener: function(eventName) {
+ if (IS_OBSERVER.test(eventName)) {
+ this.stopObservingContentKey(eventName.slice(0, -7));
+ }
+ },
+
+ // ..........................................................
+ // CONTENT KEY OBSERVING
+ // Actual watch keys on the source content.
+
+ beginObservingContentKey: function(keyName) {
+ var keys = this._keys;
+ if (!keys) keys = this._keys = {};
+ if (!keys[keyName]) {
+ keys[keyName] = 1;
+ var content = this._content,
+ len = get(content, 'length');
+ addObserverForContentKey(content, keyName, this, 0, len);
+ } else {
+ keys[keyName]++;
+ }
+ },
+
+ stopObservingContentKey: function(keyName) {
+ var keys = this._keys;
+ if (keys && (keys[keyName]>0) && (--keys[keyName]<=0)) {
+ var content = this._content,
+ len = get(content, 'length');
+ removeObserverForContentKey(content, keyName, this, 0, len);
+ }
+ },
+
+ contentKeyWillChange: function(obj, keyName) {
+ Ember.propertyWillChange(this, keyName);
+ },
+
+ contentKeyDidChange: function(obj, keyName) {
+ Ember.propertyDidChange(this, keyName);
+ }
+
+});
+
+
+
+})();
+
+
+
+(function() {
+/**
+@module ember
+@submodule ember-runtime
+*/
+
+
+var get = Ember.get, set = Ember.set, replace = Ember.EnumerableUtils._replace;
+
+// Add Ember.Array to Array.prototype. Remove methods with native
+// implementations and supply some more optimized versions of generic methods
+// because they are so common.
+var NativeArray = Ember.Mixin.create(Ember.MutableArray, Ember.Observable, Ember.Copyable, {
+
+ // because length is a built-in property we need to know to just get the
+ // original property.
+ get: function(key) {
+ if (key==='length') return this.length;
+ else if ('number' === typeof key) return this[key];
+ else return this._super(key);
+ },
+
+ objectAt: function(idx) {
+ return this[idx];
+ },
+
+ // primitive for array support.
+ replace: function(idx, amt, objects) {
+
+ if (this.isFrozen) throw Ember.FROZEN_ERROR;
+
+ // if we replaced exactly the same number of items, then pass only the
+ // replaced range. Otherwise, pass the full remaining array length
+ // since everything has shifted
+ var len = objects ? get(objects, 'length') : 0;
+ this.arrayContentWillChange(idx, amt, len);
+
+ if (len === 0) {
+ this.splice(idx, amt);
+ } else {
+ replace(this, idx, amt, objects);
+ }
+
+ this.arrayContentDidChange(idx, amt, len);
+ return this;
+ },
+
+ // If you ask for an unknown property, then try to collect the value
+ // from member items.
+ unknownProperty: function(key, value) {
+ var ret;// = this.reducedProperty(key, value) ;
+ if ((value !== undefined) && ret === undefined) {
+ ret = this[key] = value;
+ }
+ return ret ;
+ },
+
+ // If browser did not implement indexOf natively, then override with
+ // specialized version
+ indexOf: function(object, startAt) {
+ var idx, len = this.length;
+
+ if (startAt === undefined) startAt = 0;
+ else startAt = (startAt < 0) ? Math.ceil(startAt) : Math.floor(startAt);
+ if (startAt < 0) startAt += len;
+
+ for(idx=startAt;idx<len;idx++) {
+ if (this[idx] === object) return idx ;
+ }
+ return -1;
+ },
+
+ lastIndexOf: function(object, startAt) {
+ var idx, len = this.length;
+
+ if (startAt === undefined) startAt = len-1;
+ else startAt = (startAt < 0) ? Math.ceil(startAt) : Math.floor(startAt);
+ if (startAt < 0) startAt += len;
+
+ for(idx=startAt;idx>=0;idx--) {
+ if (this[idx] === object) return idx ;
+ }
+ return -1;
+ },
+
+ copy: function(deep) {
+ if (deep) {
+ return this.map(function(item) { return Ember.copy(item, true); });
+ }
+
+ return this.slice();
+ }
+});
+
+// Remove any methods implemented natively so we don't override them
+var ignore = ['length'];
+Ember.EnumerableUtils.forEach(NativeArray.keys(), function(methodName) {
+ if (Array.prototype[methodName]) ignore.push(methodName);
+});
+
+if (ignore.length>0) {
+ NativeArray = NativeArray.without.apply(NativeArray, ignore);
+}
+
+/**
+ The NativeArray mixin contains the properties needed to to make the native
+ Array support Ember.MutableArray and all of its dependent APIs. Unless you
+ have `Ember.EXTEND_PROTOTYPES` or `Ember.EXTEND_PROTOTYPES.Array` set to
+ false, this will be applied automatically. Otherwise you can apply the mixin
+ at anytime by calling `Ember.NativeArray.activate`.
+
+ @class NativeArray
+ @namespace Ember
+ @uses Ember.MutableArray
+ @uses Ember.Observable
+ @uses Ember.Copyable
+*/
+Ember.NativeArray = NativeArray;
+
+/**
+ Creates an `Ember.NativeArray` from an Array like object.
+ Does not modify the original object. Ember.A is not needed if
+ `Ember.EXTEND_PROTOTYPES` is `true` (the default value). However,
+ it is recommended that you use Ember.A when creating addons for
+ ember or when you can not guarantee that `Ember.EXTEND_PROTOTYPES`
+ will be `true`.
+
+ Example
+
+ ```js
+ var Pagination = Ember.CollectionView.extend({
+ tagName: 'ul',
+ classNames: ['pagination'],
+ init: function() {
+ this._super();
+ if (!this.get('content')) {
+ this.set('content', Ember.A([]));
+ }
+ }
+ });
+ ```
+
+ @method A
+ @for Ember
+ @return {Ember.NativeArray}
+*/
+Ember.A = function(arr) {
+ if (arr === undefined) { arr = []; }
+ return Ember.Array.detect(arr) ? arr : Ember.NativeArray.apply(arr);
+};
+
+/**
+ Activates the mixin on the Array.prototype if not already applied. Calling
+ this method more than once is safe. This will be called when ember is loaded
+ unless you have `Ember.EXTEND_PROTOTYPES` or `Ember.EXTEND_PROTOTYPES.Array`
+ set to `false`.
+
+ Example
+
+ ```js
+ if (Ember.EXTEND_PROTOTYPES === true || Ember.EXTEND_PROTOTYPES.Array) {
+ Ember.NativeArray.activate();
+ }
+ ```
+
+ @method activate
+ @for Ember.NativeArray
+ @static
+ @return {void}
+*/
+Ember.NativeArray.activate = function() {
+ NativeArray.apply(Array.prototype);
+
+ Ember.A = function(arr) { return arr || []; };
+};
+
+if (Ember.EXTEND_PROTOTYPES === true || Ember.EXTEND_PROTOTYPES.Array) {
+ Ember.NativeArray.activate();
+}
+
+
+})();
+
+
+
+(function() {
+/**
+@module ember
+@submodule ember-runtime
+*/
+
+var get = Ember.get, set = Ember.set, guidFor = Ember.guidFor, isNone = Ember.isNone, fmt = Ember.String.fmt;
+
+/**
+ An unordered collection of objects.
+
+ A Set works a bit like an array except that its items are not ordered. You
+ can create a set to efficiently test for membership for an object. You can
+ also iterate through a set just like an array, even accessing objects by
+ index, however there is no guarantee as to their order.
+
+ All Sets are observable via the Enumerable Observer API - which works
+ on any enumerable object including both Sets and Arrays.
+
+ ## Creating a Set
+
+ You can create a set like you would most objects using
+ `new Ember.Set()`. Most new sets you create will be empty, but you can
+ also initialize the set with some content by passing an array or other
+ enumerable of objects to the constructor.
+
+ Finally, you can pass in an existing set and the set will be copied. You
+ can also create a copy of a set by calling `Ember.Set#copy()`.
+
+ ```javascript
+ // creates a new empty set
+ var foundNames = new Ember.Set();
+
+ // creates a set with four names in it.
+ var names = new Ember.Set(["Charles", "Tom", "Juan", "Alex"]); // :P
+
+ // creates a copy of the names set.
+ var namesCopy = new Ember.Set(names);
+
+ // same as above.
+ var anotherNamesCopy = names.copy();
+ ```
+
+ ## Adding/Removing Objects
+
+ You generally add or remove objects from a set using `add()` or
+ `remove()`. You can add any type of object including primitives such as
+ numbers, strings, and booleans.
+
+ Unlike arrays, objects can only exist one time in a set. If you call `add()`
+ on a set with the same object multiple times, the object will only be added
+ once. Likewise, calling `remove()` with the same object multiple times will
+ remove the object the first time and have no effect on future calls until
+ you add the object to the set again.
+
+ NOTE: You cannot add/remove `null` or `undefined` to a set. Any attempt to do
+ so will be ignored.
+
+ In addition to add/remove you can also call `push()`/`pop()`. Push behaves
+ just like `add()` but `pop()`, unlike `remove()` will pick an arbitrary
+ object, remove it and return it. This is a good way to use a set as a job
+ queue when you don't care which order the jobs are executed in.
+
+ ## Testing for an Object
+
+ To test for an object's presence in a set you simply call
+ `Ember.Set#contains()`.
+
+ ## Observing changes
+
+ When using `Ember.Set`, you can observe the `"[]"` property to be
+ alerted whenever the content changes. You can also add an enumerable
+ observer to the set to be notified of specific objects that are added and
+ removed from the set. See [Ember.Enumerable](/api/classes/Ember.Enumerable.html)
+ for more information on enumerables.
+
+ This is often unhelpful. If you are filtering sets of objects, for instance,
+ it is very inefficient to re-filter all of the items each time the set
+ changes. It would be better if you could just adjust the filtered set based
+ on what was changed on the original set. The same issue applies to merging
+ sets, as well.
+
+ ## Other Methods
+
+ `Ember.Set` primary implements other mixin APIs. For a complete reference
+ on the methods you will use with `Ember.Set`, please consult these mixins.
+ The most useful ones will be `Ember.Enumerable` and
+ `Ember.MutableEnumerable` which implement most of the common iterator
+ methods you are used to on Array.
+
+ Note that you can also use the `Ember.Copyable` and `Ember.Freezable`
+ APIs on `Ember.Set` as well. Once a set is frozen it can no longer be
+ modified. The benefit of this is that when you call `frozenCopy()` on it,
+ Ember will avoid making copies of the set. This allows you to write
+ code that can know with certainty when the underlying set data will or
+ will not be modified.
+
+ @class Set
+ @namespace Ember
+ @extends Ember.CoreObject
+ @uses Ember.MutableEnumerable
+ @uses Ember.Copyable
+ @uses Ember.Freezable
+ @since Ember 0.9
+*/
+Ember.Set = Ember.CoreObject.extend(Ember.MutableEnumerable, Ember.Copyable, Ember.Freezable,
+ {
+
+ // ..........................................................
+ // IMPLEMENT ENUMERABLE APIS
+ //
+
+ /**
+ This property will change as the number of objects in the set changes.
+
+ @property length
+ @type number
+ @default 0
+ */
+ length: 0,
+
+ /**
+ Clears the set. This is useful if you want to reuse an existing set
+ without having to recreate it.
+
+ ```javascript
+ var colors = new Ember.Set(["red", "green", "blue"]);
+ colors.length; // 3
+ colors.clear();
+ colors.length; // 0
+ ```
+
+ @method clear
+ @return {Ember.Set} An empty Set
+ */
+ clear: function() {
+ if (this.isFrozen) { throw new Ember.Error(Ember.FROZEN_ERROR); }
+
+ var len = get(this, 'length');
+ if (len === 0) { return this; }
+
+ var guid;
+
+ this.enumerableContentWillChange(len, 0);
+ Ember.propertyWillChange(this, 'firstObject');
+ Ember.propertyWillChange(this, 'lastObject');
+
+ for (var i=0; i < len; i++) {
+ guid = guidFor(this[i]);
+ delete this[guid];
+ delete this[i];
+ }
+
+ set(this, 'length', 0);
+
+ Ember.propertyDidChange(this, 'firstObject');
+ Ember.propertyDidChange(this, 'lastObject');
+ this.enumerableContentDidChange(len, 0);
+
+ return this;
+ },
+
+ /**
+ Returns true if the passed object is also an enumerable that contains the
+ same objects as the receiver.
+
+ ```javascript
+ var colors = ["red", "green", "blue"],
+ same_colors = new Ember.Set(colors);
+
+ same_colors.isEqual(colors); // true
+ same_colors.isEqual(["purple", "brown"]); // false
+ ```
+
+ @method isEqual
+ @param {Ember.Set} obj the other object.
+ @return {Boolean}
+ */
+ isEqual: function(obj) {
+ // fail fast
+ if (!Ember.Enumerable.detect(obj)) return false;
+
+ var loc = get(this, 'length');
+ if (get(obj, 'length') !== loc) return false;
+
+ while(--loc >= 0) {
+ if (!obj.contains(this[loc])) return false;
+ }
+
+ return true;
+ },
+
+ /**
+ Adds an object to the set. Only non-`null` objects can be added to a set
+ and those can only be added once. If the object is already in the set or
+ the passed value is null this method will have no effect.
+
+ This is an alias for `Ember.MutableEnumerable.addObject()`.
+
+ ```javascript
+ var colors = new Ember.Set();
+ colors.add("blue"); // ["blue"]
+ colors.add("blue"); // ["blue"]
+ colors.add("red"); // ["blue", "red"]
+ colors.add(null); // ["blue", "red"]
+ colors.add(undefined); // ["blue", "red"]
+ ```
+
+ @method add
+ @param {Object} obj The object to add.
+ @return {Ember.Set} The set itself.
+ */
+ add: Ember.aliasMethod('addObject'),
+
+ /**
+ Removes the object from the set if it is found. If you pass a `null` value
+ or an object that is already not in the set, this method will have no
+ effect. This is an alias for `Ember.MutableEnumerable.removeObject()`.
+
+ ```javascript
+ var colors = new Ember.Set(["red", "green", "blue"]);
+ colors.remove("red"); // ["blue", "green"]
+ colors.remove("purple"); // ["blue", "green"]
+ colors.remove(null); // ["blue", "green"]
+ ```
+
+ @method remove
+ @param {Object} obj The object to remove
+ @return {Ember.Set} The set itself.
+ */
+ remove: Ember.aliasMethod('removeObject'),
+
+ /**
+ Removes the last element from the set and returns it, or `null` if it's empty.
+
+ ```javascript
+ var colors = new Ember.Set(["green", "blue"]);
+ colors.pop(); // "blue"
+ colors.pop(); // "green"
+ colors.pop(); // null
+ ```
+
+ @method pop
+ @return {Object} The removed object from the set or null.
+ */
+ pop: function() {
+ if (get(this, 'isFrozen')) throw new Ember.Error(Ember.FROZEN_ERROR);
+ var obj = this.length > 0 ? this[this.length-1] : null;
+ this.remove(obj);
+ return obj;
+ },
+
+ /**
+ Inserts the given object on to the end of the set. It returns
+ the set itself.
+
+ This is an alias for `Ember.MutableEnumerable.addObject()`.
+
+ ```javascript
+ var colors = new Ember.Set();
+ colors.push("red"); // ["red"]
+ colors.push("green"); // ["red", "green"]
+ colors.push("blue"); // ["red", "green", "blue"]
+ ```
+
+ @method push
+ @return {Ember.Set} The set itself.
+ */
+ push: Ember.aliasMethod('addObject'),
+
+ /**
+ Removes the last element from the set and returns it, or `null` if it's empty.
+
+ This is an alias for `Ember.Set.pop()`.
+
+ ```javascript
+ var colors = new Ember.Set(["green", "blue"]);
+ colors.shift(); // "blue"
+ colors.shift(); // "green"
+ colors.shift(); // null
+ ```
+
+ @method shift
+ @return {Object} The removed object from the set or null.
+ */
+ shift: Ember.aliasMethod('pop'),
+
+ /**
+ Inserts the given object on to the end of the set. It returns
+ the set itself.
+
+ This is an alias of `Ember.Set.push()`
+
+ ```javascript
+ var colors = new Ember.Set();
+ colors.unshift("red"); // ["red"]
+ colors.unshift("green"); // ["red", "green"]
+ colors.unshift("blue"); // ["red", "green", "blue"]
+ ```
+
+ @method unshift
+ @return {Ember.Set} The set itself.
+ */
+ unshift: Ember.aliasMethod('push'),
+
+ /**
+ Adds each object in the passed enumerable to the set.
+
+ This is an alias of `Ember.MutableEnumerable.addObjects()`
+
+ ```javascript
+ var colors = new Ember.Set();
+ colors.addEach(["red", "green", "blue"]); // ["red", "green", "blue"]
+ ```
+
+ @method addEach
+ @param {Ember.Enumerable} objects the objects to add.
+ @return {Ember.Set} The set itself.
+ */
+ addEach: Ember.aliasMethod('addObjects'),
+
+ /**
+ Removes each object in the passed enumerable to the set.
+
+ This is an alias of `Ember.MutableEnumerable.removeObjects()`
+
+ ```javascript
+ var colors = new Ember.Set(["red", "green", "blue"]);
+ colors.removeEach(["red", "blue"]); // ["green"]
+ ```
+
+ @method removeEach
+ @param {Ember.Enumerable} objects the objects to remove.
+ @return {Ember.Set} The set itself.
+ */
+ removeEach: Ember.aliasMethod('removeObjects'),
+
+ // ..........................................................
+ // PRIVATE ENUMERABLE SUPPORT
+ //
+
+ init: function(items) {
+ this._super();
+ if (items) this.addObjects(items);
+ },
+
+ // implement Ember.Enumerable
+ nextObject: function(idx) {
+ return this[idx];
+ },
+
+ // more optimized version
+ firstObject: Ember.computed(function() {
+ return this.length > 0 ? this[0] : undefined;
+ }),
+
+ // more optimized version
+ lastObject: Ember.computed(function() {
+ return this.length > 0 ? this[this.length-1] : undefined;
+ }),
+
+ // implements Ember.MutableEnumerable
+ addObject: function(obj) {
+ if (get(this, 'isFrozen')) throw new Ember.Error(Ember.FROZEN_ERROR);
+ if (isNone(obj)) return this; // nothing to do
+
+ var guid = guidFor(obj),
+ idx = this[guid],
+ len = get(this, 'length'),
+ added ;
+
+ if (idx>=0 && idx<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() {
+var DeferredMixin = Ember.DeferredMixin, // mixins/deferred
+ get = Ember.get;
+
+var Deferred = Ember.Object.extend(DeferredMixin);
+
+Deferred.reopenClass({
+ promise: function(callback, binding) {
+ var deferred = Deferred.create();
+ callback.call(binding, deferred);
+ return deferred;
+ }
+});
+
+Ember.Deferred = Deferred;
+
+})();
+
+
+
+(function() {
+/*globals CustomEvent */
+
+var forEach = Ember.ArrayPolyfills.forEach;
+
+/**
+ @module ember
+ @submodule ember-runtime
+*/
+
+var loadHooks = Ember.ENV.EMBER_LOAD_HOOKS || {};
+var loaded = {};
+
+/**
+ Detects when a specific package of Ember (e.g. 'Ember.Handlebars')
+ has fully loaded and is available for extension.
+
+ The provided `callback` will be called with the `name` passed
+ resolved from a string into the object:
+
+ ``` javascript
+ Ember.onLoad('Ember.Handlebars' function(hbars){
+ hbars.registerHelper(...);
+ });
+ ```
+
+ @method onLoad
+ @for Ember
+ @param name {String} name of hook
+ @param callback {Function} callback to be called
+*/
+Ember.onLoad = function(name, callback) {
+ var object;
+
+ loadHooks[name] = loadHooks[name] || Ember.A();
+ loadHooks[name].pushObject(callback);
+
+ if (object = loaded[name]) {
+ callback(object);
+ }
+};
+
+/**
+ Called when an Ember.js package (e.g Ember.Handlebars) has finished
+ loading. Triggers any callbacks registered for this event.
+
+ @method runLoadHooks
+ @for Ember
+ @param name {String} name of hook
+ @param object {Object} object to pass to callbacks
+*/
+Ember.runLoadHooks = function(name, object) {
+ loaded[name] = object;
+
+ if (typeof window === 'object' && typeof window.dispatchEvent === 'function' && typeof CustomEvent === "function") {
+ var event = new CustomEvent(name, {detail: object, name: name});
+ window.dispatchEvent(event);
+ }
+
+ if (loadHooks[name]) {
+ forEach.call(loadHooks[name], function(callback) {
+ callback(object);
+ });
+ }
+};
+
+})();
+
+
+
+(function() {
+
+})();
+
+
+
+(function() {
+var get = Ember.get;
+
+/**
+@module ember
+@submodule ember-runtime
+*/
+
+/**
+ `Ember.ControllerMixin` provides a standard interface for all classes that
+ compose Ember's controller layer: `Ember.Controller`,
+ `Ember.ArrayController`, and `Ember.ObjectController`.
+
+ @class ControllerMixin
+ @namespace Ember
+ @uses Ember.ActionHandler
+*/
+Ember.ControllerMixin = Ember.Mixin.create(Ember.ActionHandler, {
+ /* ducktype as a controller */
+ isController: true,
+
+ /**
+ The object to which actions from the view should be sent.
+
+ For example, when a Handlebars template uses the `{{action}}` helper,
+ it will attempt to send the action to the view's controller's `target`.
+
+ By default, a controller's `target` is set to the router after it is
+ instantiated by `Ember.Application#initialize`.
+
+ @property target
+ @default null
+ */
+ target: null,
+
+ container: null,
+
+ parentController: null,
+
+ store: null,
+
+ model: Ember.computed.alias('content'),
+
+ deprecatedSendHandles: function(actionName) {
+ return !!this[actionName];
+ },
+
+ deprecatedSend: function(actionName) {
+ var args = [].slice.call(arguments, 1);
+ Ember.assert('' + this + " has the action " + actionName + " but it is not a function", typeof this[actionName] === 'function');
+ Ember.deprecate('Action handlers implemented directly on controllers are deprecated in favor of action handlers on an `actions` object ( action: `' + actionName + '` on ' + this + ')', false);
+ this[actionName].apply(this, args);
+ return;
+ }
+});
+
+/**
+ @class Controller
+ @namespace Ember
+ @extends Ember.Object
+ @uses Ember.ControllerMixin
+*/
+Ember.Controller = Ember.Object.extend(Ember.ControllerMixin);
+
+})();
+
+
+
+(function() {
+/**
+@module ember
+@submodule ember-runtime
+*/
+
+var get = Ember.get, set = Ember.set, forEach = Ember.EnumerableUtils.forEach;
+
+/**
+ `Ember.SortableMixin` provides a standard interface for array proxies
+ to specify a sort order and maintain this sorting when objects are added,
+ removed, or updated without changing the implicit order of their underlying
+ content array:
+
+ ```javascript
+ songs = [
+ {trackNumber: 4, title: 'Ob-La-Di, Ob-La-Da'},
+ {trackNumber: 2, title: 'Back in the U.S.S.R.'},
+ {trackNumber: 3, title: 'Glass Onion'},
+ ];
+
+ songsController = Ember.ArrayController.create({
+ content: songs,
+ sortProperties: ['trackNumber'],
+ sortAscending: true
+ });
+
+ songsController.get('firstObject'); // {trackNumber: 2, title: 'Back in the U.S.S.R.'}
+
+ songsController.addObject({trackNumber: 1, title: 'Dear Prudence'});
+ songsController.get('firstObject'); // {trackNumber: 1, title: 'Dear Prudence'}
+ ```
+
+ If you add or remove the properties to sort by or change the sort direction the content
+ sort order will be automatically updated.
+
+ ```javascript
+ songsController.set('sortProperties', ['title']);
+ songsController.get('firstObject'); // {trackNumber: 2, title: 'Back in the U.S.S.R.'}
+
+ songsController.toggleProperty('sortAscending');
+ songsController.get('firstObject'); // {trackNumber: 4, title: 'Ob-La-Di, Ob-La-Da'}
+ ```
+
+ SortableMixin works by sorting the arrangedContent array, which is the array that
+ arrayProxy displays. Due to the fact that the underlying 'content' array is not changed, that
+ array will not display the sorted list:
+
+ ```javascript
+ songsController.get('content').get('firstObject'); // Returns the unsorted original content
+ songsController.get('firstObject'); // Returns the sorted content.
+ ```
+
+ Although the sorted content can also be accessed through the arrangedContent property,
+ it is preferable to use the proxied class and not the arrangedContent array directly.
+
+ @class SortableMixin
+ @namespace Ember
+ @uses Ember.MutableEnumerable
+*/
+Ember.SortableMixin = Ember.Mixin.create(Ember.MutableEnumerable, {
+
+ /**
+ Specifies which properties dictate the arrangedContent's sort order.
+
+ When specifying multiple properties the sorting will use properties
+ from the `sortProperties` array prioritized from first to last.
+
+ @property {Array} sortProperties
+ */
+ sortProperties: null,
+
+ /**
+ Specifies the arrangedContent's sort direction
+
+ @property {Boolean} sortAscending
+ */
+ sortAscending: true,
+
+ /**
+ The function used to compare two values. You can override this if you
+ want to do custom comparisons. Functions must be of the type expected by
+ Array#sort, i.e.
+ return 0 if the two parameters are equal,
+ return a negative value if the first parameter is smaller than the second or
+ return a positive value otherwise:
+
+ ```javascript
+ function(x,y) { // These are assumed to be integers
+ if (x === y)
+ return 0;
+ return x < y ? -1 : 1;
+ }
+ ```
+
+ @property sortFunction
+ @type {Function}
+ @default Ember.compare
+ */
+ sortFunction: Ember.compare,
+
+ orderBy: function(item1, item2) {
+ var result = 0,
+ sortProperties = get(this, 'sortProperties'),
+ sortAscending = get(this, 'sortAscending'),
+ sortFunction = get(this, 'sortFunction');
+
+ Ember.assert("you need to define `sortProperties`", !!sortProperties);
+
+ forEach(sortProperties, function(propertyName) {
+ if (result === 0) {
+ result = sortFunction(get(item1, propertyName), get(item2, propertyName));
+ if ((result !== 0) && !sortAscending) {
+ result = (-1) * result;
+ }
+ }
+ });
+
+ return result;
+ },
+
+ destroy: function() {
+ var content = get(this, 'content'),
+ sortProperties = get(this, 'sortProperties');
+
+ if (content && sortProperties) {
+ forEach(content, function(item) {
+ forEach(sortProperties, function(sortProperty) {
+ Ember.removeObserver(item, sortProperty, this, 'contentItemSortPropertyDidChange');
+ }, this);
+ }, this);
+ }
+
+ return this._super();
+ },
+
+ isSorted: Ember.computed.bool('sortProperties'),
+
+ /**
+ Overrides the default arrangedContent from arrayProxy in order to sort by sortFunction.
+ Also sets up observers for each sortProperty on each item in the content Array.
+
+ @property arrangedContent
+ */
+
+ arrangedContent: Ember.computed('content', 'sortProperties.@each', function(key, value) {
+ var content = get(this, 'content'),
+ isSorted = get(this, 'isSorted'),
+ sortProperties = get(this, 'sortProperties'),
+ self = this;
+
+ if (content && isSorted) {
+ content = content.slice();
+ content.sort(function(item1, item2) {
+ return self.orderBy(item1, item2);
+ });
+ forEach(content, function(item) {
+ forEach(sortProperties, function(sortProperty) {
+ Ember.addObserver(item, sortProperty, this, 'contentItemSortPropertyDidChange');
+ }, this);
+ }, this);
+ return Ember.A(content);
+ }
+
+ return content;
+ }),
+
+ _contentWillChange: Ember.beforeObserver('content', function() {
+ var content = get(this, 'content'),
+ sortProperties = get(this, 'sortProperties');
+
+ if (content && sortProperties) {
+ forEach(content, function(item) {
+ forEach(sortProperties, function(sortProperty) {
+ Ember.removeObserver(item, sortProperty, this, 'contentItemSortPropertyDidChange');
+ }, this);
+ }, this);
+ }
+
+ this._super();
+ }),
+
+ sortAscendingWillChange: Ember.beforeObserver('sortAscending', function() {
+ this._lastSortAscending = get(this, 'sortAscending');
+ }),
+
+ sortAscendingDidChange: Ember.observer('sortAscending', function() {
+ if (get(this, 'sortAscending') !== this._lastSortAscending) {
+ var arrangedContent = get(this, 'arrangedContent');
+ arrangedContent.reverseObjects();
+ }
+ }),
+
+ contentArrayWillChange: function(array, idx, removedCount, addedCount) {
+ var isSorted = get(this, 'isSorted');
+
+ if (isSorted) {
+ var arrangedContent = get(this, 'arrangedContent');
+ var removedObjects = array.slice(idx, idx+removedCount);
+ var sortProperties = get(this, 'sortProperties');
+
+ forEach(removedObjects, function(item) {
+ arrangedContent.removeObject(item);
+
+ forEach(sortProperties, function(sortProperty) {
+ Ember.removeObserver(item, sortProperty, this, 'contentItemSortPropertyDidChange');
+ }, this);
+ }, this);
+ }
+
+ return this._super(array, idx, removedCount, addedCount);
+ },
+
+ contentArrayDidChange: function(array, idx, removedCount, addedCount) {
+ var isSorted = get(this, 'isSorted'),
+ sortProperties = get(this, 'sortProperties');
+
+ if (isSorted) {
+ var addedObjects = array.slice(idx, idx+addedCount);
+
+ forEach(addedObjects, function(item) {
+ this.insertItemSorted(item);
+
+ forEach(sortProperties, function(sortProperty) {
+ Ember.addObserver(item, sortProperty, this, 'contentItemSortPropertyDidChange');
+ }, this);
+ }, this);
+ }
+
+ return this._super(array, idx, removedCount, addedCount);
+ },
+
+ insertItemSorted: function(item) {
+ var arrangedContent = get(this, 'arrangedContent');
+ var length = get(arrangedContent, 'length');
+
+ var idx = this._binarySearch(item, 0, length);
+ arrangedContent.insertAt(idx, item);
+ },
+
+ contentItemSortPropertyDidChange: function(item) {
+ var arrangedContent = get(this, 'arrangedContent'),
+ oldIndex = arrangedContent.indexOf(item),
+ leftItem = arrangedContent.objectAt(oldIndex - 1),
+ rightItem = arrangedContent.objectAt(oldIndex + 1),
+ leftResult = leftItem && this.orderBy(item, leftItem),
+ rightResult = rightItem && this.orderBy(item, rightItem);
+
+ if (leftResult < 0 || rightResult > 0) {
+ arrangedContent.removeObject(item);
+ this.insertItemSorted(item);
+ }
+ },
+
+ _binarySearch: function(item, low, high) {
+ var mid, midItem, res, arrangedContent;
+
+ if (low === high) {
+ return low;
+ }
+
+ arrangedContent = get(this, 'arrangedContent');
+
+ mid = low + Math.floor((high - low) / 2);
+ midItem = arrangedContent.objectAt(mid);
+
+ res = this.orderBy(midItem, item);
+
+ if (res < 0) {
+ return this._binarySearch(item, mid+1, high);
+ } else if (res > 0) {
+ return this._binarySearch(item, low, mid);
+ }
+
+ return mid;
+ }
+});
+
+})();
+
+
+
+(function() {
+/**
+@module ember
+@submodule ember-runtime
+*/
+
+var get = Ember.get, set = Ember.set, forEach = Ember.EnumerableUtils.forEach,
+ replace = Ember.EnumerableUtils.replace;
+
+/**
+ `Ember.ArrayController` provides a way for you to publish a collection of
+ objects so that you can easily bind to the collection from a Handlebars
+ `#each` helper, an `Ember.CollectionView`, or other controllers.
+
+ The advantage of using an `ArrayController` is that you only have to set up
+ your view bindings once; to change what's displayed, simply swap out the
+ `content` property on the controller.
+
+ For example, imagine you wanted to display a list of items fetched via an XHR
+ request. Create an `Ember.ArrayController` and set its `content` property:
+
+ ```javascript
+ MyApp.listController = Ember.ArrayController.create();
+
+ $.get('people.json', function(data) {
+ MyApp.listController.set('content', data);
+ });
+ ```
+
+ Then, create a view that binds to your new controller:
+
+ ```handlebars
+ {{#each MyApp.listController}}
+ {{firstName}} {{lastName}}
+ {{/each}}
+ ```
+
+ Although you are binding to the controller, the behavior of this controller
+ is to pass through any methods or properties to the underlying array. This
+ capability comes from `Ember.ArrayProxy`, which this class inherits from.
+
+ Sometimes you want to display computed properties within the body of an
+ `#each` helper that depend on the underlying items in `content`, but are not
+ present on those items. To do this, set `itemController` to the name of a
+ controller (probably an `ObjectController`) that will wrap each individual item.
+
+ For example:
+
+ ```handlebars
+ {{#each post in controller}}
+ <li>{{title}} ({{titleLength}} characters)</li>
+ {{/each}}
+ ```
+
+ ```javascript
+ App.PostsController = Ember.ArrayController.extend({
+ itemController: 'post'
+ });
+
+ App.PostController = Ember.ObjectController.extend({
+ // the `title` property will be proxied to the underlying post.
+
+ titleLength: function() {
+ return this.get('title').length;
+ }.property('title')
+ });
+ ```
+
+ In some cases it is helpful to return a different `itemController` depending
+ on the particular item. Subclasses can do this by overriding
+ `lookupItemController`.
+
+ For example:
+
+ ```javascript
+ App.MyArrayController = Ember.ArrayController.extend({
+ lookupItemController: function( object ) {
+ if (object.get('isSpecial')) {
+ return "special"; // use App.SpecialController
+ } else {
+ return "regular"; // use App.RegularController
+ }
+ }
+ });
+ ```
+
+ The itemController instances will have a `parentController` property set to
+ the `ArrayController` instance.
+
+ @class ArrayController
+ @namespace Ember
+ @extends Ember.ArrayProxy
+ @uses Ember.SortableMixin
+ @uses Ember.ControllerMixin
+*/
+
+Ember.ArrayController = Ember.ArrayProxy.extend(Ember.ControllerMixin,
+ Ember.SortableMixin, {
+
+ /**
+ The controller used to wrap items, if any.
+
+ @property itemController
+ @type String
+ @default null
+ */
+ itemController: null,
+
+ /**
+ Return the name of the controller to wrap items, or `null` if items should
+ be returned directly. The default implementation simply returns the
+ `itemController` property, but subclasses can override this method to return
+ different controllers for different objects.
+
+ For example:
+
+ ```javascript
+ App.MyArrayController = Ember.ArrayController.extend({
+ lookupItemController: function( object ) {
+ if (object.get('isSpecial')) {
+ return "special"; // use App.SpecialController
+ } else {
+ return "regular"; // use App.RegularController
+ }
+ }
+ });
+ ```
+
+ @method lookupItemController
+ @param {Object} object
+ @return {String}
+ */
+ lookupItemController: function(object) {
+ return get(this, 'itemController');
+ },
+
+ objectAtContent: function(idx) {
+ var length = get(this, 'length'),
+ arrangedContent = get(this,'arrangedContent'),
+ object = arrangedContent && arrangedContent.objectAt(idx);
+
+ if (idx >= 0 && idx < length) {
+ var controllerClass = this.lookupItemController(object);
+ if (controllerClass) {
+ return this.controllerAt(idx, object, controllerClass);
+ }
+ }
+
+ // When `controllerClass` is falsy, we have not opted in to using item
+ // controllers, so return the object directly.
+
+ // When the index is out of range, we want to return the "out of range"
+ // value, whatever that might be. Rather than make assumptions
+ // (e.g. guessing `null` or `undefined`) we defer this to `arrangedContent`.
+ return object;
+ },
+
+ arrangedContentDidChange: function() {
+ this._super();
+ this._resetSubControllers();
+ },
+
+ arrayContentDidChange: function(idx, removedCnt, addedCnt) {
+ var subControllers = get(this, '_subControllers'),
+ subControllersToRemove = subControllers.slice(idx, idx+removedCnt);
+
+ forEach(subControllersToRemove, function(subController) {
+ if (subController) { subController.destroy(); }
+ });
+
+ replace(subControllers, idx, removedCnt, new Array(addedCnt));
+
+ // The shadow array of subcontrollers must be updated before we trigger
+ // observers, otherwise observers will get the wrong subcontainer when
+ // calling `objectAt`
+ this._super(idx, removedCnt, addedCnt);
+ },
+
+ init: function() {
+ this._super();
+
+ this.set('_subControllers', Ember.A());
+ },
+
+ content: Ember.computed(function () {
+ return Ember.A();
+ }),
+
+ /**
+ * Flag to mark as being "virtual". Used to keep this instance
+ * from participating in the parentController hierarchy.
+ *
+ * @private
+ * @type Boolean
+ */
+ _isVirtual: false,
+
+ controllerAt: function(idx, object, controllerClass) {
+ var container = get(this, 'container'),
+ subControllers = get(this, '_subControllers'),
+ subController = subControllers[idx],
+ fullName;
+
+ if (subController) { return subController; }
+
+ fullName = "controller:" + controllerClass;
+
+ if (!container.has(fullName)) {
+ throw new Ember.Error('Could not resolve itemController: "' + controllerClass + '"');
+ }
+ var parentController;
+ if (this._isVirtual) {
+ parentController = get(this, 'parentController');
+ }
+ parentController = parentController || this;
+ subController = container.lookupFactory(fullName).create({
+ target: this,
+ parentController: parentController,
+ content: object
+ });
+
+ subControllers[idx] = subController;
+
+ return subController;
+ },
+
+ _subControllers: null,
+
+ _resetSubControllers: function() {
+ var subControllers = get(this, '_subControllers');
+ if (subControllers) {
+ forEach(subControllers, function(subController) {
+ if (subController) { subController.destroy(); }
+ });
+ }
+
+ this.set('_subControllers', Ember.A());
+ }
+});
+
+})();
+
+
+
+(function() {
+/**
+@module ember
+@submodule ember-runtime
+*/
+
+/**
+ `Ember.ObjectController` is part of Ember's Controller layer. It is intended
+ to wrap a single object, proxying unhandled attempts to `get` and `set` to the underlying
+ content object, and to forward unhandled action attempts to its `target`.
+
+ `Ember.ObjectController` derives this functionality from its superclass
+ `Ember.ObjectProxy` and the `Ember.ControllerMixin` mixin.
+
+ @class ObjectController
+ @namespace Ember
+ @extends Ember.ObjectProxy
+ @uses Ember.ControllerMixin
+**/
+Ember.ObjectController = Ember.ObjectProxy.extend(Ember.ControllerMixin);
+
+})();
+
+
+
+(function() {
+
+})();
+
+
+
+(function() {
+/**
+Ember Runtime
+
+@module ember
+@submodule ember-runtime
+@requires ember-metal
+*/
+
+})();
+
+(function() {
+/**
+@module ember
+@submodule ember-views
+*/
+
+var jQuery = (Ember.imports && Ember.imports.jQuery) || (this && this.jQuery);
+if (!jQuery && typeof require === 'function') {
+ jQuery = require('jquery');
+}
+
+Ember.assert("Ember Views require jQuery between 1.7 and 2.1", jQuery && (jQuery().jquery.match(/^((1\.(7|8|9|10|11))|(2\.(0|1)))(\.\d+)?(pre|rc\d?)?/) || Ember.ENV.FORCE_JQUERY));
+
+/**
+ Alias for jQuery
+
+ @method $
+ @for Ember
+*/
+Ember.$ = jQuery;
+
+})();
+
+
+
+(function() {
+/**
+@module ember
+@submodule ember-views
+*/
+if (Ember.$) {
+ // http://www.whatwg.org/specs/web-apps/current-work/multipage/dnd.html#dndeve…
+ var dragEvents = Ember.String.w('dragstart drag dragenter dragleave dragover drop dragend');
+
+ // Copies the `dataTransfer` property from a browser event object onto the
+ // jQuery event object for the specified events
+ Ember.EnumerableUtils.forEach(dragEvents, function(eventName) {
+ Ember.$.event.fixHooks[eventName] = { props: ['dataTransfer'] };
+ });
+}
+
+})();
+
+
+
+(function() {
+/**
+@module ember
+@submodule ember-views
+*/
+
+/* BEGIN METAMORPH HELPERS */
+
+// Internet Explorer prior to 9 does not allow setting innerHTML if the first element
+// is a "zero-scope" element. This problem can be worked around by making
+// the first node an invisible text node. We, like Modernizr, use ­
+
+var needsShy = typeof document !== 'undefined' && (function() {
+ var testEl = document.createElement('div');
+ testEl.innerHTML = "<div></div>";
+ testEl.firstChild.innerHTML = "<script></script>";
+ return testEl.firstChild.innerHTML === '';
+})();
+
+// IE 8 (and likely earlier) likes to move whitespace preceeding
+// a script tag to appear after it. This means that we can
+// accidentally remove whitespace when updating a morph.
+var movesWhitespace = typeof document !== 'undefined' && (function() {
+ var testEl = document.createElement('div');
+ testEl.innerHTML = "Test: <script type='text/x-placeholder'></script>Value";
+ return testEl.childNodes[0].nodeValue === 'Test:' &&
+ testEl.childNodes[2].nodeValue === ' Value';
+})();
+
+// Use this to find children by ID instead of using jQuery
+var findChildById = function(element, id) {
+ if (element.getAttribute('id') === id) { return element; }
+
+ var len = element.childNodes.length, idx, node, found;
+ for (idx=0; idx<len; idx++) {
+ node = element.childNodes[idx];
+ found = node.nodeType === 1 && findChildById(node, id);
+ if (found) { return found; }
+ }
+};
+
+var setInnerHTMLWithoutFix = function(element, html) {
+ if (needsShy) {
+ html = '­' + html;
+ }
+
+ var matches = [];
+ if (movesWhitespace) {
+ // Right now we only check for script tags with ids with the
+ // goal of targeting morphs.
+ html = html.replace(/(\s+)(<script id='([^']+)')/g, function(match, spaces, tag, id) {
+ matches.push([id, spaces]);
+ return tag;
+ });
+ }
+
+ element.innerHTML = html;
+
+ // If we have to do any whitespace adjustments do them now
+ if (matches.length > 0) {
+ var len = matches.length, idx;
+ for (idx=0; idx<len; idx++) {
+ var script = findChildById(element, matches[idx][0]),
+ node = document.createTextNode(matches[idx][1]);
+ script.parentNode.insertBefore(node, script);
+ }
+ }
+
+ if (needsShy) {
+ var shyElement = element.firstChild;
+ while (shyElement.nodeType === 1 && !shyElement.nodeName) {
+ shyElement = shyElement.firstChild;
+ }
+ if (shyElement.nodeType === 3 && shyElement.nodeValue.charAt(0) === "\u00AD") {
+ shyElement.nodeValue = shyElement.nodeValue.slice(1);
+ }
+ }
+};
+
+/* END METAMORPH HELPERS */
+
+
+var innerHTMLTags = {};
+var canSetInnerHTML = function(tagName) {
+ if (innerHTMLTags[tagName] !== undefined) {
+ return innerHTMLTags[tagName];
+ }
+
+ var canSet = true;
+
+ // IE 8 and earlier don't allow us to do innerHTML on select
+ if (tagName.toLowerCase() === 'select') {
+ var el = document.createElement('select');
+ setInnerHTMLWithoutFix(el, '<option value="test">Test</option>');
+ canSet = el.options.length === 1;
+ }
+
+ innerHTMLTags[tagName] = canSet;
+
+ return canSet;
+};
+
+var setInnerHTML = function(element, html) {
+ var tagName = element.tagName;
+
+ if (canSetInnerHTML(tagName)) {
+ setInnerHTMLWithoutFix(element, html);
+ } else {
+ // Firefox versions < 11 do not have support for element.outerHTML.
+ var outerHTML = element.outerHTML || new XMLSerializer().serializeToString(element);
+ Ember.assert("Can't set innerHTML on "+element.tagName+" in this browser", outerHTML);
+
+ var startTag = outerHTML.match(new RegExp("<"+tagName+"([^>]*)>", 'i'))[0],
+ endTag = '</'+tagName+'>';
+
+ var wrapper = document.createElement('div');
+ setInnerHTMLWithoutFix(wrapper, startTag + html + endTag);
+ element = wrapper.firstChild;
+ while (element.tagName !== tagName) {
+ element = element.nextSibling;
+ }
+ }
+
+ return element;
+};
+
+function isSimpleClick(event) {
+ var modifier = event.shiftKey || event.metaKey || event.altKey || event.ctrlKey,
+ secondaryClick = event.which > 1; // IE9 may return undefined
+
+ return !modifier && !secondaryClick;
+}
+
+Ember.ViewUtils = {
+ setInnerHTML: setInnerHTML,
+ isSimpleClick: isSimpleClick
+};
+
+})();
+
+
+
+(function() {
+/**
+@module ember
+@submodule ember-views
+*/
+
+var get = Ember.get, set = Ember.set;
+
+var ClassSet = function() {
+ this.seen = {};
+ this.list = [];
+};
+
+ClassSet.prototype = {
+ add: function(string) {
+ if (string in this.seen) { return; }
+ this.seen[string] = true;
+
+ this.list.push(string);
+ },
+
+ toDOM: function() {
+ return this.list.join(" ");
+ }
+};
+
+var BAD_TAG_NAME_TEST_REGEXP = /[^a-zA-Z0-9\-]/;
+var BAD_TAG_NAME_REPLACE_REGEXP = /[^a-zA-Z0-9\-]/g;
+
+function stripTagName(tagName) {
+ if (!tagName) {
+ return tagName;
+ }
+
+ if (!BAD_TAG_NAME_TEST_REGEXP.test(tagName)) {
+ return tagName;
+ }
+
+ return tagName.replace(BAD_TAG_NAME_REPLACE_REGEXP, '');
+}
+
+var BAD_CHARS_REGEXP = /&(?!\w+;)|[<>"'`]/g;
+var POSSIBLE_CHARS_REGEXP = /[&<>"'`]/;
+
+function escapeAttribute(value) {
+ // Stolen shamelessly from Handlebars
+
+ var escape = {
+ "<": "<",
+ ">": ">",
+ '"': """,
+ "'": "'",
+ "`": "`"
+ };
+
+ var escapeChar = function(chr) {
+ return escape[chr] || "&";
+ };
+
+ var string = value.toString();
+
+ if(!POSSIBLE_CHARS_REGEXP.test(string)) { return string; }
+ return string.replace(BAD_CHARS_REGEXP, escapeChar);
+}
+
+// IE 6/7 have bugs around setting names on inputs during creation.
+// From http://msdn.microsoft.com/en-us/library/ie/ms536389(v=vs.85).aspx:
+// "To include the NAME attribute at run time on objects created with the createElement method, use the eTag."
+var canSetNameOnInputs = (function() {
+ var div = document.createElement('div'),
+ el = document.createElement('input');
+
+ el.setAttribute('name', 'foo');
+ div.appendChild(el);
+
+ return !!div.innerHTML.match('foo');
+})();
+
+/**
+ `Ember.RenderBuffer` gathers information regarding the a view and generates the
+ final representation. `Ember.RenderBuffer` will generate HTML which can be pushed
+ to the DOM.
+
+ ```javascript
+ var buffer = Ember.RenderBuffer('div');
+ ```
+
+ @class RenderBuffer
+ @namespace Ember
+ @constructor
+ @param {String} tagName tag name (such as 'div' or 'p') used for the buffer
+*/
+Ember.RenderBuffer = function(tagName) {
+ return new Ember._RenderBuffer(tagName);
+};
+
+Ember._RenderBuffer = function(tagName) {
+ this.tagNames = [tagName || null];
+ this.buffer = "";
+};
+
+Ember._RenderBuffer.prototype = {
+
+ // The root view's element
+ _element: null,
+
+ _hasElement: true,
+
+ /**
+ An internal set used to de-dupe class names when `addClass()` is
+ used. After each call to `addClass()`, the `classes` property
+ will be updated.
+
+ @private
+ @property elementClasses
+ @type Array
+ @default []
+ */
+ elementClasses: null,
+
+ /**
+ Array of class names which will be applied in the class attribute.
+
+ You can use `setClasses()` to set this property directly. If you
+ use `addClass()`, it will be maintained for you.
+
+ @property classes
+ @type Array
+ @default []
+ */
+ classes: null,
+
+ /**
+ The id in of the element, to be applied in the id attribute.
+
+ You should not set this property yourself, rather, you should use
+ the `id()` method of `Ember.RenderBuffer`.
+
+ @property elementId
+ @type String
+ @default null
+ */
+ elementId: null,
+
+ /**
+ A hash keyed on the name of the attribute and whose value will be
+ applied to that attribute. For example, if you wanted to apply a
+ `data-view="Foo.bar"` property to an element, you would set the
+ elementAttributes hash to `{'data-view':'Foo.bar'}`.
+
+ You should not maintain this hash yourself, rather, you should use
+ the `attr()` method of `Ember.RenderBuffer`.
+
+ @property elementAttributes
+ @type Hash
+ @default {}
+ */
+ elementAttributes: null,
+
+ /**
+ A hash keyed on the name of the properties and whose value will be
+ applied to that property. For example, if you wanted to apply a
+ `checked=true` property to an element, you would set the
+ elementProperties hash to `{'checked':true}`.
+
+ You should not maintain this hash yourself, rather, you should use
+ the `prop()` method of `Ember.RenderBuffer`.
+
+ @property elementProperties
+ @type Hash
+ @default {}
+ */
+ elementProperties: null,
+
+ /**
+ The tagname of the element an instance of `Ember.RenderBuffer` represents.
+
+ Usually, this gets set as the first parameter to `Ember.RenderBuffer`. For
+ example, if you wanted to create a `p` tag, then you would call
+
+ ```javascript
+ Ember.RenderBuffer('p')
+ ```
+
+ @property elementTag
+ @type String
+ @default null
+ */
+ elementTag: null,
+
+ /**
+ A hash keyed on the name of the style attribute and whose value will
+ be applied to that attribute. For example, if you wanted to apply a
+ `background-color:black;` style to an element, you would set the
+ elementStyle hash to `{'background-color':'black'}`.
+
+ You should not maintain this hash yourself, rather, you should use
+ the `style()` method of `Ember.RenderBuffer`.
+
+ @property elementStyle
+ @type Hash
+ @default {}
+ */
+ elementStyle: null,
+
+ /**
+ Nested `RenderBuffers` will set this to their parent `RenderBuffer`
+ instance.
+
+ @property parentBuffer
+ @type Ember._RenderBuffer
+ */
+ parentBuffer: null,
+
+ /**
+ Adds a string of HTML to the `RenderBuffer`.
+
+ @method push
+ @param {String} string HTML to push into the buffer
+ @chainable
+ */
+ push: function(string) {
+ this.buffer += string;
+ return this;
+ },
+
+ /**
+ Adds a class to the buffer, which will be rendered to the class attribute.
+
+ @method addClass
+ @param {String} className Class name to add to the buffer
+ @chainable
+ */
+ addClass: function(className) {
+ // lazily create elementClasses
+ this.elementClasses = (this.elementClasses || new ClassSet());
+ this.elementClasses.add(className);
+ this.classes = this.elementClasses.list;
+
+ return this;
+ },
+
+ setClasses: function(classNames) {
+ this.elementClasses = null;
+ var len = classNames.length, i;
+ for (i = 0; i < len; i++) {
+ this.addClass(classNames[i]);
+ }
+ },
+
+ /**
+ Sets the elementID to be used for the element.
+
+ @method id
+ @param {String} id
+ @chainable
+ */
+ id: function(id) {
+ this.elementId = id;
+ return this;
+ },
+
+ // duck type attribute functionality like jQuery so a render buffer
+ // can be used like a jQuery object in attribute binding scenarios.
+
+ /**
+ Adds an attribute which will be rendered to the element.
+
+ @method attr
+ @param {String} name The name of the attribute
+ @param {String} value The value to add to the attribute
+ @chainable
+ @return {Ember.RenderBuffer|String} this or the current attribute value
+ */
+ attr: function(name, value) {
+ var attributes = this.elementAttributes = (this.elementAttributes || {});
+
+ if (arguments.length === 1) {
+ return attributes[name];
+ } else {
+ attributes[name] = value;
+ }
+
+ return this;
+ },
+
+ /**
+ Remove an attribute from the list of attributes to render.
+
+ @method removeAttr
+ @param {String} name The name of the attribute
+ @chainable
+ */
+ removeAttr: function(name) {
+ var attributes = this.elementAttributes;
+ if (attributes) { delete attributes[name]; }
+
+ return this;
+ },
+
+ /**
+ Adds a property which will be rendered to the element.
+
+ @method prop
+ @param {String} name The name of the property
+ @param {String} value The value to add to the property
+ @chainable
+ @return {Ember.RenderBuffer|String} this or the current property value
+ */
+ prop: function(name, value) {
+ var properties = this.elementProperties = (this.elementProperties || {});
+
+ if (arguments.length === 1) {
+ return properties[name];
+ } else {
+ properties[name] = value;
+ }
+
+ return this;
+ },
+
+ /**
+ Remove an property from the list of properties to render.
+
+ @method removeProp
+ @param {String} name The name of the property
+ @chainable
+ */
+ removeProp: function(name) {
+ var properties = this.elementProperties;
+ if (properties) { delete properties[name]; }
+
+ return this;
+ },
+
+ /**
+ Adds a style to the style attribute which will be rendered to the element.
+
+ @method style
+ @param {String} name Name of the style
+ @param {String} value
+ @chainable
+ */
+ style: function(name, value) {
+ this.elementStyle = (this.elementStyle || {});
+
+ this.elementStyle[name] = value;
+ return this;
+ },
+
+ begin: function(tagName) {
+ this.tagNames.push(tagName || null);
+ return this;
+ },
+
+ pushOpeningTag: function() {
+ var tagName = this.currentTagName();
+ if (!tagName) { return; }
+
+ if (this._hasElement && !this._element && this.buffer.length === 0) {
+ this._element = this.generateElement();
+ return;
+ }
+
+ var buffer = this.buffer,
+ id = this.elementId,
+ classes = this.classes,
+ attrs = this.elementAttributes,
+ props = this.elementProperties,
+ style = this.elementStyle,
+ attr, prop;
+
+ buffer += '<' + stripTagName(tagName);
+
+ if (id) {
+ buffer += ' id="' + escapeAttribute(id) + '"';
+ this.elementId = null;
+ }
+ if (classes) {
+ buffer += ' class="' + escapeAttribute(classes.join(' ')) + '"';
+ this.classes = null;
+ this.elementClasses = null;
+ }
+
+ if (style) {
+ buffer += ' style="';
+
+ for (prop in style) {
+ if (style.hasOwnProperty(prop)) {
+ buffer += prop + ':' + escapeAttribute(style[prop]) + ';';
+ }
+ }
+
+ buffer += '"';
+
+ this.elementStyle = null;
+ }
+
+ if (attrs) {
+ for (attr in attrs) {
+ if (attrs.hasOwnProperty(attr)) {
+ buffer += ' ' + attr + '="' + escapeAttribute(attrs[attr]) + '"';
+ }
+ }
+
+ this.elementAttributes = null;
+ }
+
+ if (props) {
+ for (prop in props) {
+ if (props.hasOwnProperty(prop)) {
+ var value = props[prop];
+ if (value || typeof(value) === 'number') {
+ if (value === true) {
+ buffer += ' ' + prop + '="' + prop + '"';
+ } else {
+ buffer += ' ' + prop + '="' + escapeAttribute(props[prop]) + '"';
+ }
+ }
+ }
+ }
+
+ this.elementProperties = null;
+ }
+
+ buffer += '>';
+ this.buffer = buffer;
+ },
+
+ pushClosingTag: function() {
+ var tagName = this.tagNames.pop();
+ if (tagName) { this.buffer += '</' + stripTagName(tagName) + '>'; }
+ },
+
+ currentTagName: function() {
+ return this.tagNames[this.tagNames.length-1];
+ },
+
+ generateElement: function() {
+ var tagName = this.tagNames.pop(), // pop since we don't need to close
+ id = this.elementId,
+ classes = this.classes,
+ attrs = this.elementAttributes,
+ props = this.elementProperties,
+ style = this.elementStyle,
+ styleBuffer = '', attr, prop, tagString;
+
+ if (attrs && attrs.name && !canSetNameOnInputs) {
+ // IE allows passing a tag to createElement. See note on `canSetNameOnInputs` above as well.
+ tagString = '<'+stripTagName(tagName)+' name="'+escapeAttribute(attrs.name)+'">';
+ } else {
+ tagString = tagName;
+ }
+
+ var element = document.createElement(tagString),
+ $element = Ember.$(element);
+
+ if (id) {
+ $element.attr('id', id);
+ this.elementId = null;
+ }
+ if (classes) {
+ $element.attr('class', classes.join(' '));
+ this.classes = null;
+ this.elementClasses = null;
+ }
+
+ if (style) {
+ for (prop in style) {
+ if (style.hasOwnProperty(prop)) {
+ styleBuffer += (prop + ':' + style[prop] + ';');
+ }
+ }
+
+ $element.attr('style', styleBuffer);
+
+ this.elementStyle = null;
+ }
+
+ if (attrs) {
+ for (attr in attrs) {
+ if (attrs.hasOwnProperty(attr)) {
+ $element.attr(attr, attrs[attr]);
+ }
+ }
+
+ this.elementAttributes = null;
+ }
+
+ if (props) {
+ for (prop in props) {
+ if (props.hasOwnProperty(prop)) {
+ $element.prop(prop, props[prop]);
+ }
+ }
+
+ this.elementProperties = null;
+ }
+
+ return element;
+ },
+
+ /**
+ @method element
+ @return {DOMElement} The element corresponding to the generated HTML
+ of this buffer
+ */
+ element: function() {
+ var html = this.innerString();
+
+ if (html) {
+ this._element = Ember.ViewUtils.setInnerHTML(this._element, html);
+ }
+
+ return this._element;
+ },
+
+ /**
+ Generates the HTML content for this buffer.
+
+ @method string
+ @return {String} The generated HTML
+ */
+ string: function() {
+ if (this._hasElement && this._element) {
+ // Firefox versions < 11 do not have support for element.outerHTML.
+ var thisElement = this.element(), outerHTML = thisElement.outerHTML;
+ if (typeof outerHTML === 'undefined') {
+ return Ember.$('<div/>').append(thisElement).html();
+ }
+ return outerHTML;
+ } else {
+ return this.innerString();
+ }
+ },
+
+ innerString: function() {
+ return this.buffer;
+ }
+};
+
+})();
+
+
+
+(function() {
+/**
+@module ember
+@submodule ember-views
+*/
+
+var get = Ember.get, set = Ember.set, fmt = Ember.String.fmt;
+
+/**
+ `Ember.EventDispatcher` handles delegating browser events to their
+ corresponding `Ember.Views.` For example, when you click on a view,
+ `Ember.EventDispatcher` ensures that that view's `mouseDown` method gets
+ called.
+
+ @class EventDispatcher
+ @namespace Ember
+ @private
+ @extends Ember.Object
+*/
+Ember.EventDispatcher = Ember.Object.extend({
+
+ /**
+ The set of events names (and associated handler function names) to be setup
+ and dispatched by the `EventDispatcher`. Custom events can added to this list at setup
+ time, generally via the `Ember.Application.customEvents` hash. Only override this
+ default set to prevent the EventDispatcher from listening on some events all together.
+
+ This set will be modified by `setup` to also include any events added at that time.
+
+ @property events
+ @type Object
+ */
+ events: {
+ touchstart : 'touchStart',
+ touchmove : 'touchMove',
+ touchend : 'touchEnd',
+ touchcancel : 'touchCancel',
+ keydown : 'keyDown',
+ keyup : 'keyUp',
+ keypress : 'keyPress',
+ mousedown : 'mouseDown',
+ mouseup : 'mouseUp',
+ contextmenu : 'contextMenu',
+ click : 'click',
+ dblclick : 'doubleClick',
+ mousemove : 'mouseMove',
+ focusin : 'focusIn',
+ focusout : 'focusOut',
+ mouseenter : 'mouseEnter',
+ mouseleave : 'mouseLeave',
+ submit : 'submit',
+ input : 'input',
+ change : 'change',
+ dragstart : 'dragStart',
+ drag : 'drag',
+ dragenter : 'dragEnter',
+ dragleave : 'dragLeave',
+ dragover : 'dragOver',
+ drop : 'drop',
+ dragend : 'dragEnd'
+ },
+
+ /**
+ The root DOM element to which event listeners should be attached. Event
+ listeners will be attached to the document unless this is overridden.
+
+ Can be specified as a DOMElement or a selector string.
+
+ The default body is a string since this may be evaluated before document.body
+ exists in the DOM.
+
+ @private
+ @property rootElement
+ @type DOMElement
+ @default 'body'
+ */
+ rootElement: 'body',
+
+ /**
+ Sets up event listeners for standard browser events.
+
+ This will be called after the browser sends a `DOMContentReady` event. By
+ default, it will set up all of the listeners on the document body. If you
+ would like to register the listeners on a different element, set the event
+ dispatcher's `root` property.
+
+ @private
+ @method setup
+ @param addedEvents {Hash}
+ */
+ setup: function(addedEvents, rootElement) {
+ var event, events = get(this, 'events');
+
+ Ember.$.extend(events, addedEvents || {});
+
+
+ if (!Ember.isNone(rootElement)) {
+ set(this, 'rootElement', rootElement);
+ }
+
+ rootElement = Ember.$(get(this, 'rootElement'));
+
+ Ember.assert(fmt('You cannot use the same root element (%@) multiple times in an Ember.Application', [rootElement.selector || rootElement[0].tagName]), !rootElement.is('.ember-application'));
+ Ember.assert('You cannot make a new Ember.Application using a root element that is a descendent of an existing Ember.Application', !rootElement.closest('.ember-application').length);
+ Ember.assert('You cannot make a new Ember.Application using a root element that is an ancestor of an existing Ember.Application', !rootElement.find('.ember-application').length);
+
+ rootElement.addClass('ember-application');
+
+ Ember.assert('Unable to add "ember-application" class to rootElement. Make sure you set rootElement to the body or an element in the body.', rootElement.is('.ember-application'));
+
+ for (event in events) {
+ if (events.hasOwnProperty(event)) {
+ this.setupHandler(rootElement, event, events[event]);
+ }
+ }
+ },
+
+ /**
+ Registers an event listener on the document. If the given event is
+ triggered, the provided event handler will be triggered on the target view.
+
+ If the target view does not implement the event handler, or if the handler
+ returns `false`, the parent view will be called. The event will continue to
+ bubble to each successive parent view until it reaches the top.
+
+ For example, to have the `mouseDown` method called on the target view when
+ a `mousedown` event is received from the browser, do the following:
+
+ ```javascript
+ setupHandler('mousedown', 'mouseDown');
+ ```
+
+ @private
+ @method setupHandler
+ @param {Element} rootElement
+ @param {String} event the browser-originated event to listen to
+ @param {String} eventName the name of the method to call on the view
+ */
+ setupHandler: function(rootElement, event, eventName) {
+ var self = this;
+
+ rootElement.on(event + '.ember', '.ember-view', function(evt, triggeringManager) {
+ var view = Ember.View.views[this.id],
+ result = true, manager = null;
+
+ manager = self._findNearestEventManager(view, eventName);
+
+ if (manager && manager !== triggeringManager) {
+ result = self._dispatchEvent(manager, evt, eventName, view);
+ } else if (view) {
+ result = self._bubbleEvent(view, evt, eventName);
+ } else {
+ evt.stopPropagation();
+ }
+
+ return result;
+ });
+
+ rootElement.on(event + '.ember', '[data-ember-action]', function(evt) {
+ var actionId = Ember.$(evt.currentTarget).attr('data-ember-action'),
+ action = Ember.Handlebars.ActionHelper.registeredActions[actionId];
+
+ // We have to check for action here since in some cases, jQuery will trigger
+ // an event on `removeChild` (i.e. focusout) after we've already torn down the
+ // action handlers for the view.
+ if (action && action.eventName === eventName) {
+ return action.handler(evt);
+ }
+ });
+ },
+
+ _findNearestEventManager: function(view, eventName) {
+ var manager = null;
+
+ while (view) {
+ manager = get(view, 'eventManager');
+ if (manager && manager[eventName]) { break; }
+
+ view = get(view, 'parentView');
+ }
+
+ return manager;
+ },
+
+ _dispatchEvent: function(object, evt, eventName, view) {
+ var result = true;
+
+ var handler = object[eventName];
+ if (Ember.typeOf(handler) === 'function') {
+ result = Ember.run(object, handler, evt, view);
+ // Do not preventDefault in eventManagers.
+ evt.stopPropagation();
+ }
+ else {
+ result = this._bubbleEvent(view, evt, eventName);
+ }
+
+ return result;
+ },
+
+ _bubbleEvent: function(view, evt, eventName) {
+ return Ember.run(view, view.handleEvent, eventName, evt);
+ },
+
+ destroy: function() {
+ var rootElement = get(this, 'rootElement');
+ Ember.$(rootElement).off('.ember', '**').removeClass('ember-application');
+ return this._super();
+ }
+});
+
+})();
+
+
+
+(function() {
+/**
+@module ember
+@submodule ember-views
+*/
+
+// Add a new named queue for rendering views that happens
+// after bindings have synced, and a queue for scheduling actions
+// that that should occur after view rendering.
+var queues = Ember.run.queues,
+ indexOf = Ember.ArrayPolyfills.indexOf;
+queues.splice(indexOf.call(queues, 'actions')+1, 0, 'render', 'afterRender');
+
+})();
+
+
+
+(function() {
+
+})();
+
+
+
+(function() {
+var states = {};
+
+/**
+@module ember
+@submodule ember-views
+*/
+
+var get = Ember.get, set = Ember.set,
+ guidFor = Ember.guidFor,
+ a_forEach = Ember.EnumerableUtils.forEach,
+ a_addObject = Ember.EnumerableUtils.addObject,
+ meta = Ember.meta,
+ defineProperty = Ember.defineProperty;
+
+function nullViewsBuffer(view) {
+ view.buffer = null;
+}
+
+var childViewsProperty = Ember.computed(function() {
+ var childViews = this._childViews, ret = Ember.A(), view = this;
+
+ a_forEach(childViews, function(view) {
+ var currentChildViews;
+ if (view.isVirtual) {
+ if (currentChildViews = get(view, 'childViews')) {
+ ret.pushObjects(currentChildViews);
+ }
+ } else {
+ ret.push(view);
+ }
+ });
+
+ ret.replace = function (idx, removedCount, addedViews) {
+ if (view instanceof Ember.ContainerView) {
+ Ember.deprecate("Manipulating an Ember.ContainerView through its childViews property is deprecated. Please use the ContainerView instance itself as an Ember.MutableArray.");
+ return view.replace(idx, removedCount, addedViews);
+ }
+ throw new Ember.Error("childViews is immutable");
+ };
+
+ return ret;
+});
+
+Ember.warn("The VIEW_PRESERVES_CONTEXT flag has been removed and the functionality can no longer be disabled.", Ember.ENV.VIEW_PRESERVES_CONTEXT !== false);
+
+/**
+ Global hash of shared templates. This will automatically be populated
+ by the build tools so that you can store your Handlebars templates in
+ separate files that get loaded into JavaScript at buildtime.
+
+ @property TEMPLATES
+ @for Ember
+ @type Hash
+*/
+Ember.TEMPLATES = {};
+
+/**
+ `Ember.CoreView` is an abstract class that exists to give view-like behavior
+ to both Ember's main view class `Ember.View` and other classes like
+ `Ember._SimpleMetamorphView` that don't need the fully functionaltiy of
+ `Ember.View`.
+
+ Unless you have specific needs for `CoreView`, you will use `Ember.View`
+ in your applications.
+
+ @class CoreView
+ @namespace Ember
+ @extends Ember.Object
+ @uses Ember.Evented
+ @uses Ember.ActionHandler
+*/
+
+Ember.CoreView = Ember.Object.extend(Ember.Evented, Ember.ActionHandler, {
+ isView: true,
+
+ states: states,
+
+ init: function() {
+ this._super();
+ this.transitionTo('preRender');
+ this._isVisible = get(this, 'isVisible');
+ },
+
+ /**
+ If the view is currently inserted into the DOM of a parent view, this
+ property will point to the parent of the view.
+
+ @property parentView
+ @type Ember.View
+ @default null
+ */
+ parentView: Ember.computed('_parentView', function() {
+ var parent = this._parentView;
+
+ if (parent && parent.isVirtual) {
+ return get(parent, 'parentView');
+ } else {
+ return parent;
+ }
+ }),
+
+ state: null,
+
+ _parentView: null,
+
+ // return the current view, not including virtual views
+ concreteView: Ember.computed('parentView', function() {
+ if (!this.isVirtual) { return this; }
+ else { return get(this, 'parentView'); }
+ }),
+
+ instrumentName: 'core_view',
+
+ instrumentDetails: function(hash) {
+ hash.object = this.toString();
+ },
+
+ /**
+ Invoked by the view system when this view needs to produce an HTML
+ representation. This method will create a new render buffer, if needed,
+ then apply any default attributes, such as class names and visibility.
+ Finally, the `render()` method is invoked, which is responsible for
+ doing the bulk of the rendering.
+
+ You should not need to override this method; instead, implement the
+ `template` property, or if you need more control, override the `render`
+ method.
+
+ @method renderToBuffer
+ @param {Ember.RenderBuffer} buffer the render buffer. If no buffer is
+ passed, a default buffer, using the current view's `tagName`, will
+ be used.
+ @private
+ */
+ renderToBuffer: function(parentBuffer, bufferOperation) {
+ var name = 'render.' + this.instrumentName,
+ details = {};
+
+ this.instrumentDetails(details);
+
+ return Ember.instrument(name, details, function instrumentRenderToBuffer() {
+ return this._renderToBuffer(parentBuffer, bufferOperation);
+ }, this);
+ },
+
+ _renderToBuffer: function(parentBuffer, bufferOperation) {
+ // If this is the top-most view, start a new buffer. Otherwise,
+ // create a new buffer relative to the original using the
+ // provided buffer operation (for example, `insertAfter` will
+ // insert a new buffer after the "parent buffer").
+ var tagName = this.tagName;
+
+ if (tagName === null || tagName === undefined) {
+ tagName = 'div';
+ }
+
+ var buffer = this.buffer = parentBuffer && parentBuffer.begin(tagName) || Ember.RenderBuffer(tagName);
+ this.transitionTo('inBuffer', false);
+
+ this.beforeRender(buffer);
+ this.render(buffer);
+ this.afterRender(buffer);
+
+ return buffer;
+ },
+
+ /**
+ Override the default event firing from `Ember.Evented` to
+ also call methods with the given name.
+
+ @method trigger
+ @param name {String}
+ @private
+ */
+ trigger: function(name) {
+ this._super.apply(this, arguments);
+ var method = this[name];
+ if (method) {
+ var args = [], i, l;
+ for (i = 1, l = arguments.length; i < l; i++) {
+ args.push(arguments[i]);
+ }
+ return method.apply(this, args);
+ }
+ },
+
+ deprecatedSendHandles: function(actionName) {
+ return !!this[actionName];
+ },
+
+ deprecatedSend: function(actionName) {
+ var args = [].slice.call(arguments, 1);
+ Ember.assert('' + this + " has the action " + actionName + " but it is not a function", typeof this[actionName] === 'function');
+ Ember.deprecate('Action handlers implemented directly on views are deprecated in favor of action handlers on an `actions` object ( action: `' + actionName + '` on ' + this + ')', false);
+ this[actionName].apply(this, args);
+ return;
+ },
+
+ has: function(name) {
+ return Ember.typeOf(this[name]) === 'function' || this._super(name);
+ },
+
+ destroy: function() {
+ var parent = this._parentView;
+
+ if (!this._super()) { return; }
+
+ // destroy the element -- this will avoid each child view destroying
+ // the element over and over again...
+ if (!this.removedFromDOM) { this.destroyElement(); }
+
+ // remove from parent if found. Don't call removeFromParent,
+ // as removeFromParent will try to remove the element from
+ // the DOM again.
+ if (parent) { parent.removeChild(this); }
+
+ this.transitionTo('destroying', false);
+
+ return this;
+ },
+
+ clearRenderedChildren: Ember.K,
+ triggerRecursively: Ember.K,
+ invokeRecursively: Ember.K,
+ transitionTo: Ember.K,
+ destroyElement: Ember.K
+});
+
+var ViewCollection = Ember._ViewCollection = function(initialViews) {
+ var views = this.views = initialViews || [];
+ this.length = views.length;
+};
+
+ViewCollection.prototype = {
+ length: 0,
+
+ trigger: function(eventName) {
+ var views = this.views, view;
+ for (var i = 0, l = views.length; i < l; i++) {
+ view = views[i];
+ if (view.trigger) { view.trigger(eventName); }
+ }
+ },
+
+ triggerRecursively: function(eventName) {
+ var views = this.views;
+ for (var i = 0, l = views.length; i < l; i++) {
+ views[i].triggerRecursively(eventName);
+ }
+ },
+
+ invokeRecursively: function(fn) {
+ var views = this.views, view;
+
+ for (var i = 0, l = views.length; i < l; i++) {
+ view = views[i];
+ fn(view);
+ }
+ },
+
+ transitionTo: function(state, children) {
+ var views = this.views;
+ for (var i = 0, l = views.length; i < l; i++) {
+ views[i].transitionTo(state, children);
+ }
+ },
+
+ push: function() {
+ this.length += arguments.length;
+ var views = this.views;
+ return views.push.apply(views, arguments);
+ },
+
+ objectAt: function(idx) {
+ return this.views[idx];
+ },
+
+ forEach: function(callback) {
+ var views = this.views;
+ return a_forEach(views, callback);
+ },
+
+ clear: function() {
+ this.length = 0;
+ this.views.length = 0;
+ }
+};
+
+var EMPTY_ARRAY = [];
+
+/**
+ `Ember.View` is the class in Ember responsible for encapsulating templates of
+ HTML content, combining templates with data to render as sections of a page's
+ DOM, and registering and responding to user-initiated events.
+
+ ## HTML Tag
+
+ The default HTML tag name used for a view's DOM representation is `div`. This
+ can be customized by setting the `tagName` property. The following view
+ class:
+
+ ```javascript
+ ParagraphView = Ember.View.extend({
+ tagName: 'em'
+ });
+ ```
+
+ Would result in instances with the following HTML:
+
+ ```html
+ <em id="ember1" class="ember-view"></em>
+ ```
+
+ ## HTML `class` Attribute
+
+ The HTML `class` attribute of a view's tag can be set by providing a
+ `classNames` property that is set to an array of strings:
+
+ ```javascript
+ MyView = Ember.View.extend({
+ classNames: ['my-class', 'my-other-class']
+ });
+ ```
+
+ Will result in view instances with an HTML representation of:
+
+ ```html
+ <div id="ember1" class="ember-view my-class my-other-class"></div>
+ ```
+
+ `class` attribute values can also be set by providing a `classNameBindings`
+ property set to an array of properties names for the view. The return value
+ of these properties will be added as part of the value for the view's `class`
+ attribute. These properties can be computed properties:
+
+ ```javascript
+ MyView = Ember.View.extend({
+ classNameBindings: ['propertyA', 'propertyB'],
+ propertyA: 'from-a',
+ propertyB: function() {
+ if (someLogic) { return 'from-b'; }
+ }.property()
+ });
+ ```
+
+ Will result in view instances with an HTML representation of:
+
+ ```html
+ <div id="ember1" class="ember-view from-a from-b"></div>
+ ```
+
+ If the value of a class name binding returns a boolean the property name
+ itself will be used as the class name if the property is true. The class name
+ will not be added if the value is `false` or `undefined`.
+
+ ```javascript
+ MyView = Ember.View.extend({
+ classNameBindings: ['hovered'],
+ hovered: true
+ });
+ ```
+
+ Will result in view instances with an HTML representation of:
+
+ ```html
+ <div id="ember1" class="ember-view hovered"></div>
+ ```
+
+ When using boolean class name bindings you can supply a string value other
+ than the property name for use as the `class` HTML attribute by appending the
+ preferred value after a ":" character when defining the binding:
+
+ ```javascript
+ MyView = Ember.View.extend({
+ classNameBindings: ['awesome:so-very-cool'],
+ awesome: true
+ });
+ ```
+
+ Will result in view instances with an HTML representation of:
+
+ ```html
+ <div id="ember1" class="ember-view so-very-cool"></div>
+ ```
+
+ Boolean value class name bindings whose property names are in a
+ camelCase-style format will be converted to a dasherized format:
+
+ ```javascript
+ MyView = Ember.View.extend({
+ classNameBindings: ['isUrgent'],
+ isUrgent: true
+ });
+ ```
+
+ Will result in view instances with an HTML representation of:
+
+ ```html
+ <div id="ember1" class="ember-view is-urgent"></div>
+ ```
+
+ Class name bindings can also refer to object values that are found by
+ traversing a path relative to the view itself:
+
+ ```javascript
+ MyView = Ember.View.extend({
+ classNameBindings: ['messages.empty']
+ messages: Ember.Object.create({
+ empty: true
+ })
+ });
+ ```
+
+ Will result in view instances with an HTML representation of:
+
+ ```html
+ <div id="ember1" class="ember-view empty"></div>
+ ```
+
+ If you want to add a class name for a property which evaluates to true and
+ and a different class name if it evaluates to false, you can pass a binding
+ like this:
+
+ ```javascript
+ // Applies 'enabled' class when isEnabled is true and 'disabled' when isEnabled is false
+ Ember.View.extend({
+ classNameBindings: ['isEnabled:enabled:disabled']
+ isEnabled: true
+ });
+ ```
+
+ Will result in view instances with an HTML representation of:
+
+ ```html
+ <div id="ember1" class="ember-view enabled"></div>
+ ```
+
+ When isEnabled is `false`, the resulting HTML reprensentation looks like
+ this:
+
+ ```html
+ <div id="ember1" class="ember-view disabled"></div>
+ ```
+
+ This syntax offers the convenience to add a class if a property is `false`:
+
+ ```javascript
+ // Applies no class when isEnabled is true and class 'disabled' when isEnabled is false
+ Ember.View.extend({
+ classNameBindings: ['isEnabled::disabled']
+ isEnabled: true
+ });
+ ```
+
+ Will result in view instances with an HTML representation of:
+
+ ```html
+ <div id="ember1" class="ember-view"></div>
+ ```
+
+ When the `isEnabled` property on the view is set to `false`, it will result
+ in view instances with an HTML representation of:
+
+ ```html
+ <div id="ember1" class="ember-view disabled"></div>
+ ```
+
+ Updates to the the value of a class name binding will result in automatic
+ update of the HTML `class` attribute in the view's rendered HTML
+ representation. If the value becomes `false` or `undefined` the class name
+ will be removed.
+
+ Both `classNames` and `classNameBindings` are concatenated properties. See
+ [Ember.Object](/api/classes/Ember.Object.html) documentation for more
+ information about concatenated properties.
+
+ ## HTML Attributes
+
+ The HTML attribute section of a view's tag can be set by providing an
+ `attributeBindings` property set to an array of property names on the view.
+ The return value of these properties will be used as the value of the view's
+ HTML associated attribute:
+
+ ```javascript
+ AnchorView = Ember.View.extend({
+ tagName: 'a',
+ attributeBindings: ['href'],
+ href: 'http://google.com'
+ });
+ ```
+
+ Will result in view instances with an HTML representation of:
+
+ ```html
+ <a id="ember1" class="ember-view" href="http://google.com"></a>
+ ```
+
+ If the return value of an `attributeBindings` monitored property is a boolean
+ the property will follow HTML's pattern of repeating the attribute's name as
+ its value:
+
+ ```javascript
+ MyTextInput = Ember.View.extend({
+ tagName: 'input',
+ attributeBindings: ['disabled'],
+ disabled: true
+ });
+ ```
+
+ Will result in view instances with an HTML representation of:
+
+ ```html
+ <input id="ember1" class="ember-view" disabled="disabled" />
+ ```
+
+ `attributeBindings` can refer to computed properties:
+
+ ```javascript
+ MyTextInput = Ember.View.extend({
+ tagName: 'input',
+ attributeBindings: ['disabled'],
+ disabled: function() {
+ if (someLogic) {
+ return true;
+ } else {
+ return false;
+ }
+ }.property()
+ });
+ ```
+
+ Updates to the the property of an attribute binding will result in automatic
+ update of the HTML attribute in the view's rendered HTML representation.
+
+ `attributeBindings` is a concatenated property. See [Ember.Object](/api/classes/Ember.Object.html)
+ documentation for more information about concatenated properties.
+
+ ## Templates
+
+ The HTML contents of a view's rendered representation are determined by its
+ template. Templates can be any function that accepts an optional context
+ parameter and returns a string of HTML that will be inserted within the
+ view's tag. Most typically in Ember this function will be a compiled
+ `Ember.Handlebars` template.
+
+ ```javascript
+ AView = Ember.View.extend({
+ template: Ember.Handlebars.compile('I am the template')
+ });
+ ```
+
+ Will result in view instances with an HTML representation of:
+
+ ```html
+ <div id="ember1" class="ember-view">I am the template</div>
+ ```
+
+ Within an Ember application is more common to define a Handlebars templates as
+ part of a page:
+
+ ```html
+ <script type='text/x-handlebars' data-template-name='some-template'>
+ Hello
+ </script>
+ ```
+
+ And associate it by name using a view's `templateName` property:
+
+ ```javascript
+ AView = Ember.View.extend({
+ templateName: 'some-template'
+ });
+ ```
+
+ If you have nested resources, your Handlebars template will look like this:
+
+ ```html
+ <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'
+ });
+ ```
+
+ Using a value for `templateName` that does not have a Handlebars template
+ with a matching `data-template-name` attribute will throw an error.
+
+ For views classes that may have a template later defined (e.g. as the block
+ portion of a `{{view}}` Handlebars helper call in another template or in
+ a subclass), you can provide a `defaultTemplate` property set to compiled
+ template function. If a template is not later provided for the view instance
+ the `defaultTemplate` value will be used:
+
+ ```javascript
+ AView = Ember.View.extend({
+ defaultTemplate: Ember.Handlebars.compile('I was the default'),
+ template: null,
+ templateName: null
+ });
+ ```
+
+ Will result in instances with an HTML representation of:
+
+ ```html
+ <div id="ember1" class="ember-view">I was the default</div>
+ ```
+
+ If a `template` or `templateName` is provided it will take precedence over
+ `defaultTemplate`:
+
+ ```javascript
+ AView = Ember.View.extend({
+ defaultTemplate: Ember.Handlebars.compile('I was the default')
+ });
+
+ aView = AView.create({
+ template: Ember.Handlebars.compile('I was the template, not default')
+ });
+ ```
+
+ Will result in the following HTML representation when rendered:
+
+ ```html
+ <div id="ember1" class="ember-view">I was the template, not default</div>
+ ```
+
+ ## View Context
+
+ The default context of the compiled template is the view's controller:
+
+ ```javascript
+ AView = Ember.View.extend({
+ template: Ember.Handlebars.compile('Hello {{excitedGreeting}}')
+ });
+
+ aController = Ember.Object.create({
+ firstName: 'Barry',
+ excitedGreeting: function() {
+ return this.get("content.firstName") + "!!!"
+ }.property()
+ });
+
+ aView = AView.create({
+ controller: aController,
+ });
+ ```
+
+ Will result in an HTML representation of:
+
+ ```html
+ <div id="ember1" class="ember-view">Hello Barry!!!</div>
+ ```
+
+ A context can also be explicitly supplied through the view's `context`
+ property. If the view has neither `context` nor `controller` properties, the
+ `parentView`'s context will be used.
+
+ ## Layouts
+
+ Views can have a secondary template that wraps their main template. Like
+ primary templates, layouts can be any function that accepts an optional
+ context parameter and returns a string of HTML that will be inserted inside
+ view's tag. Views whose HTML element is self closing (e.g. `<input />`)
+ cannot have a layout and this property will be ignored.
+
+ Most typically in Ember a layout will be a compiled `Ember.Handlebars`
+ template.
+
+ A view's layout can be set directly with the `layout` property or reference
+ an existing Handlebars template by name with the `layoutName` property.
+
+ A template used as a layout must contain a single use of the Handlebars
+ `{{yield}}` helper. The HTML contents of a view's rendered `template` will be
+ inserted at this location:
+
+ ```javascript
+ AViewWithLayout = Ember.View.extend({
+ layout: Ember.Handlebars.compile("<div class='my-decorative-class'>{{yield}}</div>")
+ template: Ember.Handlebars.compile("I got wrapped"),
+ });
+ ```
+
+ Will result in view instances with an HTML representation of:
+
+ ```html
+ <div id="ember1" class="ember-view">
+ <div class="my-decorative-class">
+ I got wrapped
+ </div>
+ </div>
+ ```
+
+ See [Ember.Handlebars.helpers.yield](/api/classes/Ember.Handlebars.helpers.html#method_yield)
+ for more information.
+
+ ## Responding to Browser Events
+
+ Views can respond to user-initiated events in one of three ways: method
+ implementation, through an event manager, and through `{{action}}` helper use
+ in their template or layout.
+
+ ### Method Implementation
+
+ Views can respond to user-initiated events by implementing a method that
+ matches the event name. A `jQuery.Event` object will be passed as the
+ argument to this method.
+
+ ```javascript
+ AView = Ember.View.extend({
+ click: function(event) {
+ // will be called when when an instance's
+ // rendered element is clicked
+ }
+ });
+ ```
+
+ ### Event Managers
+
+ Views can define an object as their `eventManager` property. This object can
+ then implement methods that match the desired event names. Matching events
+ that occur on the view's rendered HTML or the rendered HTML of any of its DOM
+ descendants will trigger this method. A `jQuery.Event` object will be passed
+ as the first argument to the method and an `Ember.View` object as the
+ second. The `Ember.View` will be the view whose rendered HTML was interacted
+ with. This may be the view with the `eventManager` property or one of its
+ descendent views.
+
+ ```javascript
+ AView = Ember.View.extend({
+ eventManager: Ember.Object.create({
+ doubleClick: function(event, view) {
+ // will be called when when an instance's
+ // rendered element or any rendering
+ // of this views's descendent
+ // elements is clicked
+ }
+ })
+ });
+ ```
+
+ An event defined for an event manager takes precedence over events of the
+ same name handled through methods on the view.
+
+ ```javascript
+ AView = Ember.View.extend({
+ mouseEnter: function(event) {
+ // will never trigger.
+ },
+ eventManager: Ember.Object.create({
+ mouseEnter: function(event, view) {
+ // takes precedence over AView#mouseEnter
+ }
+ })
+ });
+ ```
+
+ Similarly a view's event manager will take precedence for events of any views
+ rendered as a descendent. A method name that matches an event name will not
+ be called if the view instance was rendered inside the HTML representation of
+ a view that has an `eventManager` property defined that handles events of the
+ name. Events not handled by the event manager will still trigger method calls
+ on the descendent.
+
+ ```javascript
+ OuterView = Ember.View.extend({
+ template: Ember.Handlebars.compile("outer {{#view InnerView}}inner{{/view}} outer"),
+ eventManager: Ember.Object.create({
+ mouseEnter: function(event, view) {
+ // view might be instance of either
+ // OuterView or InnerView depending on
+ // where on the page the user interaction occured
+ }
+ })
+ });
+
+ InnerView = Ember.View.extend({
+ click: function(event) {
+ // will be called if rendered inside
+ // an OuterView because OuterView's
+ // eventManager doesn't handle click events
+ },
+ mouseEnter: function(event) {
+ // will never be called if rendered inside
+ // an OuterView.
+ }
+ });
+ ```
+
+ ### Handlebars `{{action}}` Helper
+
+ See [Handlebars.helpers.action](/api/classes/Ember.Handlebars.helpers.html#method_action).
+
+ ### Event Names
+
+ All of the event handling approaches described above respond to the same set
+ of events. The names of the built-in events are listed below. (The hash of
+ built-in events exists in `Ember.EventDispatcher`.) Additional, custom events
+ can be registered by using `Ember.Application.customEvents`.
+
+ Touch events:
+
+ * `touchStart`
+ * `touchMove`
+ * `touchEnd`
+ * `touchCancel`
+
+ Keyboard events
+
+ * `keyDown`
+ * `keyUp`
+ * `keyPress`
+
+ Mouse events
+
+ * `mouseDown`
+ * `mouseUp`
+ * `contextMenu`
+ * `click`
+ * `doubleClick`
+ * `mouseMove`
+ * `focusIn`
+ * `focusOut`
+ * `mouseEnter`
+ * `mouseLeave`
+
+ Form events:
+
+ * `submit`
+ * `change`
+ * `focusIn`
+ * `focusOut`
+ * `input`
+
+ HTML5 drag and drop events:
+
+ * `dragStart`
+ * `drag`
+ * `dragEnter`
+ * `dragLeave`
+ * `drop`
+ * `dragEnd`
+
+ ## Handlebars `{{view}}` Helper
+
+ Other `Ember.View` instances can be included as part of a view's template by
+ using the `{{view}}` Handlebars helper. See [Ember.Handlebars.helpers.view](/api/classes/Ember.Handlebars.helpers.html#method_view)
+ for additional information.
+
+ @class View
+ @namespace Ember
+ @extends Ember.CoreView
+*/
+Ember.View = Ember.CoreView.extend({
+
+ concatenatedProperties: ['classNames', 'classNameBindings', 'attributeBindings'],
+
+ /**
+ @property isView
+ @type Boolean
+ @default true
+ @static
+ */
+ isView: true,
+
+ // ..........................................................
+ // TEMPLATE SUPPORT
+ //
+
+ /**
+ The name of the template to lookup if no template is provided.
+
+ By default `Ember.View` will lookup a template with this name in
+ `Ember.TEMPLATES` (a shared global object).
+
+ @property templateName
+ @type String
+ @default null
+ */
+ templateName: null,
+
+ /**
+ The name of the layout to lookup if no layout is provided.
+
+ By default `Ember.View` will lookup a template with this name in
+ `Ember.TEMPLATES` (a shared global object).
+
+ @property layoutName
+ @type String
+ @default null
+ */
+ layoutName: null,
+
+ /**
+ The template used to render the view. This should be a function that
+ accepts an optional context parameter and returns a string of HTML that
+ will be inserted into the DOM relative to its parent view.
+
+ In general, you should set the `templateName` property instead of setting
+ the template yourself.
+
+ @property template
+ @type Function
+ */
+ template: Ember.computed('templateName', function(key, value) {
+ if (value !== undefined) { return value; }
+
+ var templateName = get(this, 'templateName'),
+ template = this.templateForName(templateName, 'template');
+
+ Ember.assert("You specified the templateName " + templateName + " for " + this + ", but it did not exist.", !templateName || template);
+
+ return template || get(this, 'defaultTemplate');
+ }),
+
+ /**
+ The controller managing this view. If this property is set, it will be
+ made available for use by the template.
+
+ @property controller
+ @type Object
+ */
+ controller: Ember.computed('_parentView', function(key) {
+ var parentView = get(this, '_parentView');
+ return parentView ? get(parentView, 'controller') : null;
+ }),
+
+ /**
+ A view may contain a layout. A layout is a regular template but
+ supersedes the `template` property during rendering. It is the
+ responsibility of the layout template to retrieve the `template`
+ property from the view (or alternatively, call `Handlebars.helpers.yield`,
+ `{{yield}}`) to render it in the correct location.
+
+ This is useful for a view that has a shared wrapper, but which delegates
+ the rendering of the contents of the wrapper to the `template` property
+ on a subclass.
+
+ @property layout
+ @type Function
+ */
+ layout: Ember.computed(function(key) {
+ var layoutName = get(this, 'layoutName'),
+ layout = this.templateForName(layoutName, 'layout');
+
+ Ember.assert("You specified the layoutName " + layoutName + " for " + this + ", but it did not exist.", !layoutName || layout);
+
+ return layout || get(this, 'defaultLayout');
+ }).property('layoutName'),
+
+ _yield: function(context, options) {
+ var template = get(this, 'template');
+ if (template) { template(context, options); }
+ },
+
+ templateForName: function(name, type) {
+ if (!name) { return; }
+ Ember.assert("templateNames are not allowed to contain periods: "+name, name.indexOf('.') === -1);
+
+ // the defaultContainer is deprecated
+ var container = this.container || (Ember.Container && Ember.Container.defaultContainer);
+ return container && container.lookup('template:' + name);
+ },
+
+ /**
+ The object from which templates should access properties.
+
+ This object will be passed to the template function each time the render
+ method is called, but it is up to the individual function to decide what
+ to do with it.
+
+ By default, this will be the view's controller.
+
+ @property context
+ @type Object
+ */
+ context: Ember.computed(function(key, value) {
+ if (arguments.length === 2) {
+ set(this, '_context', value);
+ return value;
+ } else {
+ return get(this, '_context');
+ }
+ }).volatile(),
+
+ /**
+ Private copy of the view's template context. This can be set directly
+ by Handlebars without triggering the observer that causes the view
+ to be re-rendered.
+
+ The context of a view is looked up as follows:
+
+ 1. Supplied context (usually by Handlebars)
+ 2. Specified controller
+ 3. `parentView`'s context (for a child of a ContainerView)
+
+ The code in Handlebars that overrides the `_context` property first
+ checks to see whether the view has a specified controller. This is
+ something of a hack and should be revisited.
+
+ @property _context
+ @private
+ */
+ _context: Ember.computed(function(key) {
+ var parentView, controller;
+
+ if (controller = get(this, 'controller')) {
+ return controller;
+ }
+
+ parentView = this._parentView;
+ if (parentView) {
+ return get(parentView, '_context');
+ }
+
+ return null;
+ }),
+
+ /**
+ If a value that affects template rendering changes, the view should be
+ re-rendered to reflect the new value.
+
+ @method _contextDidChange
+ @private
+ */
+ _contextDidChange: Ember.observer('context', function() {
+ this.rerender();
+ }),
+
+ /**
+ If `false`, the view will appear hidden in DOM.
+
+ @property isVisible
+ @type Boolean
+ @default null
+ */
+ isVisible: true,
+
+ /**
+ Array of child views. You should never edit this array directly.
+ Instead, use `appendChild` and `removeFromParent`.
+
+ @property childViews
+ @type Array
+ @default []
+ @private
+ */
+ childViews: childViewsProperty,
+
+ _childViews: EMPTY_ARRAY,
+
+ // When it's a virtual view, we need to notify the parent that their
+ // childViews will change.
+ _childViewsWillChange: Ember.beforeObserver('childViews', function() {
+ if (this.isVirtual) {
+ var parentView = get(this, 'parentView');
+ if (parentView) { Ember.propertyWillChange(parentView, 'childViews'); }
+ }
+ }),
+
+ // When it's a virtual view, we need to notify the parent that their
+ // childViews did change.
+ _childViewsDidChange: Ember.observer('childViews', function() {
+ if (this.isVirtual) {
+ var parentView = get(this, 'parentView');
+ if (parentView) { Ember.propertyDidChange(parentView, 'childViews'); }
+ }
+ }),
+
+ /**
+ Return the nearest ancestor that is an instance of the provided
+ class.
+
+ @method nearestInstanceOf
+ @param {Class} klass Subclass of Ember.View (or Ember.View itself)
+ @return Ember.View
+ @deprecated
+ */
+ nearestInstanceOf: function(klass) {
+ Ember.deprecate("nearestInstanceOf is deprecated and will be removed from future releases. Use nearestOfType.");
+ var view = get(this, 'parentView');
+
+ while (view) {
+ if (view instanceof klass) { return view; }
+ view = get(view, 'parentView');
+ }
+ },
+
+ /**
+ Return the nearest ancestor that is an instance of the provided
+ class or mixin.
+
+ @method nearestOfType
+ @param {Class,Mixin} klass Subclass of Ember.View (or Ember.View itself),
+ or an instance of Ember.Mixin.
+ @return Ember.View
+ */
+ nearestOfType: function(klass) {
+ var view = get(this, 'parentView'),
+ isOfType = klass instanceof Ember.Mixin ?
+ function(view) { return klass.detect(view); } :
+ function(view) { return klass.detect(view.constructor); };
+
+ while (view) {
+ if (isOfType(view)) { return view; }
+ view = get(view, 'parentView');
+ }
+ },
+
+ /**
+ Return the nearest ancestor that has a given property.
+
+ @function nearestWithProperty
+ @param {String} property A property name
+ @return Ember.View
+ */
+ nearestWithProperty: function(property) {
+ var view = get(this, 'parentView');
+
+ while (view) {
+ if (property in view) { return view; }
+ view = get(view, 'parentView');
+ }
+ },
+
+ /**
+ Return the nearest ancestor whose parent is an instance of
+ `klass`.
+
+ @method nearestChildOf
+ @param {Class} klass Subclass of Ember.View (or Ember.View itself)
+ @return Ember.View
+ */
+ nearestChildOf: function(klass) {
+ var view = get(this, 'parentView');
+
+ while (view) {
+ if (get(view, 'parentView') instanceof klass) { return view; }
+ view = get(view, 'parentView');
+ }
+ },
+
+ /**
+ When the parent view changes, recursively invalidate `controller`
+
+ @method _parentViewDidChange
+ @private
+ */
+ _parentViewDidChange: Ember.observer('_parentView', function() {
+ if (this.isDestroying) { return; }
+
+ this.trigger('parentViewDidChange');
+
+ if (get(this, 'parentView.controller') && !get(this, 'controller')) {
+ this.notifyPropertyChange('controller');
+ }
+ }),
+
+ _controllerDidChange: Ember.observer('controller', function() {
+ if (this.isDestroying) { return; }
+
+ this.rerender();
+
+ this.forEachChildView(function(view) {
+ view.propertyDidChange('controller');
+ });
+ }),
+
+ cloneKeywords: function() {
+ var templateData = get(this, 'templateData');
+
+ var keywords = templateData ? Ember.copy(templateData.keywords) : {};
+ set(keywords, 'view', get(this, 'concreteView'));
+ set(keywords, '_view', this);
+ set(keywords, 'controller', get(this, 'controller'));
+
+ return keywords;
+ },
+
+ /**
+ Called on your view when it should push strings of HTML into a
+ `Ember.RenderBuffer`. Most users will want to override the `template`
+ or `templateName` properties instead of this method.
+
+ By default, `Ember.View` will look for a function in the `template`
+ property and invoke it with the value of `context`. The value of
+ `context` will be the view's controller unless you override it.
+
+ @method render
+ @param {Ember.RenderBuffer} buffer The render buffer
+ */
+ render: function(buffer) {
+ // If this view has a layout, it is the responsibility of the
+ // the layout to render the view's template. Otherwise, render the template
+ // directly.
+ var template = get(this, 'layout') || get(this, 'template');
+
+ if (template) {
+ var context = get(this, 'context');
+ var keywords = this.cloneKeywords();
+ var output;
+
+ var data = {
+ view: this,
+ buffer: buffer,
+ isRenderData: true,
+ keywords: keywords,
+ insideGroup: get(this, 'templateData.insideGroup')
+ };
+
+ // Invoke the template with the provided template context, which
+ // is the view's controller by default. A hash of data is also passed that provides
+ // the template with access to the view and render buffer.
+
+ Ember.assert('template must be a function. Did you mean to call Ember.Handlebars.compile("...") or specify templateName instead?', typeof template === 'function');
+ // The template should write directly to the render buffer instead
+ // of returning a string.
+ output = template(context, { data: data });
+
+ // If the template returned a string instead of writing to the buffer,
+ // push the string onto the buffer.
+ if (output !== undefined) { buffer.push(output); }
+ }
+ },
+
+ /**
+ Renders the view again. This will work regardless of whether the
+ view is already in the DOM or not. If the view is in the DOM, the
+ rendering process will be deferred to give bindings a chance
+ to synchronize.
+
+ If children were added during the rendering process using `appendChild`,
+ `rerender` will remove them, because they will be added again
+ if needed by the next `render`.
+
+ In general, if the display of your view changes, you should modify
+ the DOM element directly instead of manually calling `rerender`, which can
+ be slow.
+
+ @method rerender
+ */
+ rerender: function() {
+ return this.currentState.rerender(this);
+ },
+
+ clearRenderedChildren: function() {
+ var lengthBefore = this.lengthBeforeRender,
+ lengthAfter = this.lengthAfterRender;
+
+ // If there were child views created during the last call to render(),
+ // remove them under the assumption that they will be re-created when
+ // we re-render.
+
+ // VIEW-TODO: Unit test this path.
+ var childViews = this._childViews;
+ for (var i=lengthAfter-1; i>=lengthBefore; i--) {
+ if (childViews[i]) { childViews[i].destroy(); }
+ }
+ },
+
+ /**
+ Iterates over the view's `classNameBindings` array, inserts the value
+ of the specified property into the `classNames` array, then creates an
+ observer to update the view's element if the bound property ever changes
+ in the future.
+
+ @method _applyClassNameBindings
+ @private
+ */
+ _applyClassNameBindings: function(classBindings) {
+ var classNames = this.classNames,
+ elem, newClass, dasherizedClass;
+
+ // Loop through all of the configured bindings. These will be either
+ // property names ('isUrgent') or property paths relative to the view
+ // ('content.isUrgent')
+ a_forEach(classBindings, function(binding) {
+
+ Ember.assert("classNameBindings must not have spaces in them. Multiple class name bindings can be provided as elements of an array, e.g. ['foo', ':bar']", binding.indexOf(' ') === -1);
+
+ // Variable in which the old class value is saved. The observer function
+ // closes over this variable, so it knows which string to remove when
+ // the property changes.
+ var oldClass;
+ // Extract just the property name from bindings like 'foo:bar'
+ var parsedPath = Ember.View._parsePropertyPath(binding);
+
+ // Set up an observer on the context. If the property changes, toggle the
+ // class name.
+ var observer = function() {
+ // Get the current value of the property
+ newClass = this._classStringForProperty(binding);
+ elem = this.$();
+
+ // If we had previously added a class to the element, remove it.
+ if (oldClass) {
+ elem.removeClass(oldClass);
+ // Also remove from classNames so that if the view gets rerendered,
+ // the class doesn't get added back to the DOM.
+ classNames.removeObject(oldClass);
+ }
+
+ // If necessary, add a new class. Make sure we keep track of it so
+ // it can be removed in the future.
+ if (newClass) {
+ elem.addClass(newClass);
+ oldClass = newClass;
+ } else {
+ oldClass = null;
+ }
+ };
+
+ // Get the class name for the property at its current value
+ dasherizedClass = this._classStringForProperty(binding);
+
+ if (dasherizedClass) {
+ // Ensure that it gets into the classNames array
+ // so it is displayed when we render.
+ a_addObject(classNames, dasherizedClass);
+
+ // Save a reference to the class name so we can remove it
+ // if the observer fires. Remember that this variable has
+ // been closed over by the observer.
+ oldClass = dasherizedClass;
+ }
+
+ this.registerObserver(this, parsedPath.path, observer);
+ // Remove className so when the view is rerendered,
+ // the className is added based on binding reevaluation
+ this.one('willClearRender', function() {
+ if (oldClass) {
+ classNames.removeObject(oldClass);
+ oldClass = null;
+ }
+ });
+
+ }, this);
+ },
+
+ _unspecifiedAttributeBindings: null,
+
+ /**
+ Iterates through the view's attribute bindings, sets up observers for each,
+ then applies the current value of the attributes to the passed render buffer.
+
+ @method _applyAttributeBindings
+ @param {Ember.RenderBuffer} buffer
+ @private
+ */
+ _applyAttributeBindings: function(buffer, attributeBindings) {
+ var attributeValue,
+ unspecifiedAttributeBindings = this._unspecifiedAttributeBindings = this._unspecifiedAttributeBindings || {};
+
+ a_forEach(attributeBindings, function(binding) {
+ var split = binding.split(':'),
+ property = split[0],
+ attributeName = split[1] || property;
+
+ if (property in this) {
+ this._setupAttributeBindingObservation(property, attributeName);
+
+ // Determine the current value and add it to the render buffer
+ // if necessary.
+ attributeValue = get(this, property);
+ Ember.View.applyAttributeBindings(buffer, attributeName, attributeValue);
+ } else {
+ unspecifiedAttributeBindings[property] = attributeName;
+ }
+ }, this);
+
+ // Lazily setup setUnknownProperty after attributeBindings are initially applied
+ this.setUnknownProperty = this._setUnknownProperty;
+ },
+
+ _setupAttributeBindingObservation: function(property, attributeName) {
+ var attributeValue, elem;
+
+ // Create an observer to add/remove/change the attribute if the
+ // JavaScript property changes.
+ var observer = function() {
+ elem = this.$();
+
+ attributeValue = get(this, property);
+
+ Ember.View.applyAttributeBindings(elem, attributeName, attributeValue);
+ };
+
+ this.registerObserver(this, property, observer);
+ },
+
+ /**
+ We're using setUnknownProperty as a hook to setup attributeBinding observers for
+ properties that aren't defined on a view at initialization time.
+
+ Note: setUnknownProperty will only be called once for each property.
+
+ @method setUnknownProperty
+ @param key
+ @param value
+ @private
+ */
+ setUnknownProperty: null, // Gets defined after initialization by _applyAttributeBindings
+
+ _setUnknownProperty: function(key, value) {
+ var attributeName = this._unspecifiedAttributeBindings && this._unspecifiedAttributeBindings[key];
+ if (attributeName) {
+ this._setupAttributeBindingObservation(key, attributeName);
+ }
+
+ defineProperty(this, key);
+ return set(this, key, value);
+ },
+
+ /**
+ Given a property name, returns a dasherized version of that
+ property name if the property evaluates to a non-falsy value.
+
+ For example, if the view has property `isUrgent` that evaluates to true,
+ passing `isUrgent` to this method will return `"is-urgent"`.
+
+ @method _classStringForProperty
+ @param property
+ @private
+ */
+ _classStringForProperty: function(property) {
+ var parsedPath = Ember.View._parsePropertyPath(property);
+ var path = parsedPath.path;
+
+ var val = get(this, path);
+ if (val === undefined && Ember.isGlobalPath(path)) {
+ val = get(Ember.lookup, path);
+ }
+
+ return Ember.View._classStringForValue(path, val, parsedPath.className, parsedPath.falsyClassName);
+ },
+
+ // ..........................................................
+ // ELEMENT SUPPORT
+ //
+
+ /**
+ Returns the current DOM element for the view.
+
+ @property element
+ @type DOMElement
+ */
+ element: Ember.computed('_parentView', function(key, value) {
+ if (value !== undefined) {
+ return this.currentState.setElement(this, value);
+ } else {
+ return this.currentState.getElement(this);
+ }
+ }),
+
+ /**
+ Returns a jQuery object for this view's element. If you pass in a selector
+ string, this method will return a jQuery object, using the current element
+ as its buffer.
+
+ For example, calling `view.$('li')` will return a jQuery object containing
+ all of the `li` elements inside the DOM element of this view.
+
+ @method $
+ @param {String} [selector] a jQuery-compatible selector string
+ @return {jQuery} the jQuery object for the DOM node
+ */
+ $: function(sel) {
+ return this.currentState.$(this, sel);
+ },
+
+ mutateChildViews: function(callback) {
+ var childViews = this._childViews,
+ idx = childViews.length,
+ view;
+
+ while(--idx >= 0) {
+ view = childViews[idx];
+ callback(this, view, idx);
+ }
+
+ return this;
+ },
+
+ forEachChildView: function(callback) {
+ var childViews = this._childViews;
+
+ if (!childViews) { return this; }
+
+ var len = childViews.length,
+ view, idx;
+
+ for (idx = 0; idx < len; idx++) {
+ view = childViews[idx];
+ callback(view);
+ }
+
+ return this;
+ },
+
+ /**
+ Appends the view's element to the specified parent element.
+
+ If the view does not have an HTML representation yet, `createElement()`
+ will be called automatically.
+
+ Note that this method just schedules the view to be appended; the DOM
+ element will not be appended to the given element until all bindings have
+ finished synchronizing.
+
+ This is not typically a function that you will need to call directly when
+ building your application. You might consider using `Ember.ContainerView`
+ instead. If you do need to use `appendTo`, be sure that the target element
+ you are providing is associated with an `Ember.Application` and does not
+ have an ancestor element that is associated with an Ember view.
+
+ @method appendTo
+ @param {String|DOMElement|jQuery} A selector, element, HTML string, or jQuery object
+ @return {Ember.View} receiver
+ */
+ appendTo: function(target) {
+ // Schedule the DOM element to be created and appended to the given
+ // element after bindings have synchronized.
+ this._insertElementLater(function() {
+ Ember.assert("You tried to append to (" + target + ") but that isn't in the DOM", Ember.$(target).length > 0);
+ Ember.assert("You cannot append to an existing Ember.View. Consider using Ember.ContainerView instead.", !Ember.$(target).is('.ember-view') && !Ember.$(target).parents().is('.ember-view'));
+ this.$().appendTo(target);
+ });
+
+ return this;
+ },
+
+ /**
+ Replaces the content of the specified parent element with this view's
+ element. If the view does not have an HTML representation yet,
+ `createElement()` will be called automatically.
+
+ Note that this method just schedules the view to be appended; the DOM
+ element will not be appended to the given element until all bindings have
+ finished synchronizing
+
+ @method replaceIn
+ @param {String|DOMElement|jQuery} target A selector, element, HTML string, or jQuery object
+ @return {Ember.View} received
+ */
+ replaceIn: function(target) {
+ Ember.assert("You tried to replace in (" + target + ") but that isn't in the DOM", Ember.$(target).length > 0);
+ Ember.assert("You cannot replace an existing Ember.View. Consider using Ember.ContainerView instead.", !Ember.$(target).is('.ember-view') && !Ember.$(target).parents().is('.ember-view'));
+
+ this._insertElementLater(function() {
+ Ember.$(target).empty();
+ this.$().appendTo(target);
+ });
+
+ return this;
+ },
+
+ /**
+ Schedules a DOM operation to occur during the next render phase. This
+ ensures that all bindings have finished synchronizing before the view is
+ rendered.
+
+ To use, pass a function that performs a DOM operation.
+
+ Before your function is called, this view and all child views will receive
+ the `willInsertElement` event. After your function is invoked, this view
+ and all of its child views will receive the `didInsertElement` event.
+
+ ```javascript
+ view._insertElementLater(function() {
+ this.createElement();
+ this.$().appendTo('body');
+ });
+ ```
+
+ @method _insertElementLater
+ @param {Function} fn the function that inserts the element into the DOM
+ @private
+ */
+ _insertElementLater: function(fn) {
+ this._scheduledInsert = Ember.run.scheduleOnce('render', this, '_insertElement', fn);
+ },
+
+ _insertElement: function (fn) {
+ this._scheduledInsert = null;
+ this.currentState.insertElement(this, fn);
+ },
+
+ /**
+ Appends the view's element to the document body. If the view does
+ not have an HTML representation yet, `createElement()` will be called
+ automatically.
+
+ If your application uses the `rootElement` property, you must append
+ the view within that element. Rendering views outside of the `rootElement`
+ is not supported.
+
+ Note that this method just schedules the view to be appended; the DOM
+ element will not be appended to the document body until all bindings have
+ finished synchronizing.
+
+ @method append
+ @return {Ember.View} receiver
+ */
+ append: function() {
+ return this.appendTo(document.body);
+ },
+
+ /**
+ Removes the view's element from the element to which it is attached.
+
+ @method remove
+ @return {Ember.View} receiver
+ */
+ remove: function() {
+ // What we should really do here is wait until the end of the run loop
+ // to determine if the element has been re-appended to a different
+ // element.
+ // In the interim, we will just re-render if that happens. It is more
+ // important than elements get garbage collected.
+ if (!this.removedFromDOM) { this.destroyElement(); }
+ this.invokeRecursively(function(view) {
+ if (view.clearRenderedChildren) { view.clearRenderedChildren(); }
+ });
+ },
+
+ elementId: null,
+
+ /**
+ Attempts to discover the element in the parent element. The default
+ implementation looks for an element with an ID of `elementId` (or the
+ view's guid if `elementId` is null). You can override this method to
+ provide your own form of lookup. For example, if you want to discover your
+ element using a CSS class name instead of an ID.
+
+ @method findElementInParentElement
+ @param {DOMElement} parentElement The parent's DOM element
+ @return {DOMElement} The discovered element
+ */
+ findElementInParentElement: function(parentElem) {
+ var id = "#" + this.elementId;
+ return Ember.$(id)[0] || Ember.$(id, parentElem)[0];
+ },
+
+ /**
+ Creates a DOM representation of the view and all of its
+ child views by recursively calling the `render()` method.
+
+ After the element has been created, `didInsertElement` will
+ be called on this view and all of its child views.
+
+ @method createElement
+ @return {Ember.View} receiver
+ */
+ createElement: function() {
+ if (get(this, 'element')) { return this; }
+
+ var buffer = this.renderToBuffer();
+ set(this, 'element', buffer.element());
+
+ return this;
+ },
+
+ /**
+ Called when a view is going to insert an element into the DOM.
+
+ @event willInsertElement
+ */
+ willInsertElement: Ember.K,
+
+ /**
+ Called when the element of the view has been inserted into the DOM
+ or after the view was re-rendered. Override this function to do any
+ set up that requires an element in the document body.
+
+ @event didInsertElement
+ */
+ didInsertElement: Ember.K,
+
+ /**
+ Called when the view is about to rerender, but before anything has
+ been torn down. This is a good opportunity to tear down any manual
+ observers you have installed based on the DOM state
+
+ @event willClearRender
+ */
+ willClearRender: Ember.K,
+
+ /**
+ Run this callback on the current view (unless includeSelf is false) and recursively on child views.
+
+ @method invokeRecursively
+ @param fn {Function}
+ @param includeSelf {Boolean} Includes itself if true.
+ @private
+ */
+ invokeRecursively: function(fn, includeSelf) {
+ var childViews = (includeSelf === false) ? this._childViews : [this];
+ var currentViews, view, currentChildViews;
+
+ while (childViews.length) {
+ currentViews = childViews.slice();
+ childViews = [];
+
+ for (var i=0, l=currentViews.length; i<l; i++) {
+ view = currentViews[i];
+ currentChildViews = view._childViews ? view._childViews.slice(0) : null;
+ fn(view);
+ if (currentChildViews) {
+ childViews.push.apply(childViews, currentChildViews);
+ }
+ }
+ }
+ },
+
+ triggerRecursively: function(eventName) {
+ var childViews = [this], currentViews, view, currentChildViews;
+
+ while (childViews.length) {
+ currentViews = childViews.slice();
+ childViews = [];
+
+ for (var i=0, l=currentViews.length; i<l; i++) {
+ view = currentViews[i];
+ currentChildViews = view._childViews ? view._childViews.slice(0) : null;
+ if (view.trigger) { view.trigger(eventName); }
+ if (currentChildViews) {
+ childViews.push.apply(childViews, currentChildViews);
+ }
+
+ }
+ }
+ },
+
+ 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;
+ },
+
+ /**
+ Destroys any existing element along with the element for any child views
+ as well. If the view does not currently have a element, then this method
+ will do nothing.
+
+ If you implement `willDestroyElement()` on your view, then this method will
+ be invoked on your view before your element is destroyed to give you a
+ chance to clean up any event handlers, etc.
+
+ If you write a `willDestroyElement()` handler, you can assume that your
+ `didInsertElement()` handler was called earlier for the same element.
+
+ You should not call or override this method yourself, but you may
+ want to implement the above callbacks.
+
+ @method destroyElement
+ @return {Ember.View} receiver
+ */
+ destroyElement: function() {
+ return this.currentState.destroyElement(this);
+ },
+
+ /**
+ Called when the element of the view is going to be destroyed. Override
+ this function to do any teardown that requires an element, like removing
+ event listeners.
+
+ @event willDestroyElement
+ */
+ willDestroyElement: Ember.K,
+
+ /**
+ Triggers the `willDestroyElement` event (which invokes the
+ `willDestroyElement()` method if it exists) on this view and all child
+ views.
+
+ Before triggering `willDestroyElement`, it first triggers the
+ `willClearRender` event recursively.
+
+ @method _notifyWillDestroyElement
+ @private
+ */
+ _notifyWillDestroyElement: function() {
+ var viewCollection = this.viewHierarchyCollection();
+ viewCollection.trigger('willClearRender');
+ viewCollection.trigger('willDestroyElement');
+ return viewCollection;
+ },
+
+ /**
+ If this view's element changes, we need to invalidate the caches of our
+ child views so that we do not retain references to DOM elements that are
+ no longer needed.
+
+ @method _elementDidChange
+ @private
+ */
+ _elementDidChange: Ember.observer('element', function() {
+ this.forEachChildView(function(view) {
+ delete meta(view).cache.element;
+ });
+ }),
+
+ /**
+ Called when the parentView property has changed.
+
+ @event parentViewDidChange
+ */
+ parentViewDidChange: Ember.K,
+
+ instrumentName: 'view',
+
+ instrumentDetails: function(hash) {
+ hash.template = get(this, 'templateName');
+ this._super(hash);
+ },
+
+ _renderToBuffer: function(parentBuffer, bufferOperation) {
+ this.lengthBeforeRender = this._childViews.length;
+ var buffer = this._super(parentBuffer, bufferOperation);
+ this.lengthAfterRender = this._childViews.length;
+
+ return buffer;
+ },
+
+ renderToBufferIfNeeded: function (buffer) {
+ return this.currentState.renderToBufferIfNeeded(this, buffer);
+ },
+
+ beforeRender: function(buffer) {
+ this.applyAttributesToBuffer(buffer);
+ buffer.pushOpeningTag();
+ },
+
+ afterRender: function(buffer) {
+ buffer.pushClosingTag();
+ },
+
+ applyAttributesToBuffer: function(buffer) {
+ // Creates observers for all registered class name and attribute bindings,
+ // then adds them to the element.
+ var classNameBindings = get(this, 'classNameBindings');
+ if (classNameBindings.length) {
+ this._applyClassNameBindings(classNameBindings);
+ }
+
+ // Pass the render buffer so the method can apply attributes directly.
+ // This isn't needed for class name bindings because they use the
+ // existing classNames infrastructure.
+ var attributeBindings = get(this, 'attributeBindings');
+ if (attributeBindings.length) {
+ this._applyAttributeBindings(buffer, attributeBindings);
+ }
+
+ buffer.setClasses(this.classNames);
+ buffer.id(this.elementId);
+
+ var role = get(this, 'ariaRole');
+ if (role) {
+ buffer.attr('role', role);
+ }
+
+ if (get(this, 'isVisible') === false) {
+ buffer.style('display', 'none');
+ }
+ },
+
+ // ..........................................................
+ // STANDARD RENDER PROPERTIES
+ //
+
+ /**
+ Tag name for the view's outer element. The tag name is only used when an
+ element is first created. If you change the `tagName` for an element, you
+ must destroy and recreate the view element.
+
+ By default, the render buffer will use a `<div>` tag for views.
+
+ @property tagName
+ @type String
+ @default null
+ */
+
+ // We leave this null by default so we can tell the difference between
+ // the default case and a user-specified tag.
+ tagName: null,
+
+ /**
+ The WAI-ARIA role of the control represented by this view. For example, a
+ button may have a role of type 'button', or a pane may have a role of
+ type 'alertdialog'. This property is used by assistive software to help
+ visually challenged users navigate rich web applications.
+
+ The full list of valid WAI-ARIA roles is available at:
+ [http://www.w3.org/TR/wai-aria/roles#roles_categorization](http://www.w3.org…
+
+ @property ariaRole
+ @type String
+ @default null
+ */
+ ariaRole: null,
+
+ /**
+ Standard CSS class names to apply to the view's outer element. This
+ property automatically inherits any class names defined by the view's
+ superclasses as well.
+
+ @property classNames
+ @type Array
+ @default ['ember-view']
+ */
+ classNames: ['ember-view'],
+
+ /**
+ A list of properties of the view to apply as class names. If the property
+ is a string value, the value of that string will be applied as a class
+ name.
+
+ ```javascript
+ // Applies the 'high' class to the view element
+ Ember.View.extend({
+ classNameBindings: ['priority']
+ priority: 'high'
+ });
+ ```
+
+ If the value of the property is a Boolean, the name of that property is
+ added as a dasherized class name.
+
+ ```javascript
+ // Applies the 'is-urgent' class to the view element
+ Ember.View.extend({
+ classNameBindings: ['isUrgent']
+ isUrgent: true
+ });
+ ```
+
+ If you would prefer to use a custom value instead of the dasherized
+ property name, you can pass a binding like this:
+
+ ```javascript
+ // Applies the 'urgent' class to the view element
+ Ember.View.extend({
+ classNameBindings: ['isUrgent:urgent']
+ isUrgent: true
+ });
+ ```
+
+ This list of properties is inherited from the view's superclasses as well.
+
+ @property classNameBindings
+ @type Array
+ @default []
+ */
+ classNameBindings: EMPTY_ARRAY,
+
+ /**
+ A list of properties of the view to apply as attributes. If the property is
+ a string value, the value of that string will be applied as the attribute.
+
+ ```javascript
+ // Applies the type attribute to the element
+ // with the value "button", like <div type="button">
+ Ember.View.extend({
+ attributeBindings: ['type'],
+ type: 'button'
+ });
+ ```
+
+ If the value of the property is a Boolean, the name of that property is
+ added as an attribute.
+
+ ```javascript
+ // Renders something like <div enabled="enabled">
+ Ember.View.extend({
+ attributeBindings: ['enabled'],
+ enabled: true
+ });
+ ```
+
+ @property attributeBindings
+ */
+ attributeBindings: EMPTY_ARRAY,
+
+ // .......................................................
+ // CORE DISPLAY METHODS
+ //
+
+ /**
+ Setup a view, but do not finish waking it up.
+
+ * configure `childViews`
+ * register the view with the global views hash, which is used for event
+ dispatch
+
+ @method init
+ @private
+ */
+ init: function() {
+ this.elementId = this.elementId || guidFor(this);
+
+ this._super();
+
+ // setup child views. be sure to clone the child views array first
+ this._childViews = this._childViews.slice();
+
+ Ember.assert("Only arrays are allowed for 'classNameBindings'", Ember.typeOf(this.classNameBindings) === 'array');
+ this.classNameBindings = Ember.A(this.classNameBindings.slice());
+
+ Ember.assert("Only arrays are allowed for 'classNames'", Ember.typeOf(this.classNames) === 'array');
+ this.classNames = Ember.A(this.classNames.slice());
+ },
+
+ appendChild: function(view, options) {
+ return this.currentState.appendChild(this, view, options);
+ },
+
+ /**
+ Removes the child view from the parent view.
+
+ @method removeChild
+ @param {Ember.View} view
+ @return {Ember.View} receiver
+ */
+ removeChild: function(view) {
+ // If we're destroying, the entire subtree will be
+ // freed, and the DOM will be handled separately,
+ // so no need to mess with childViews.
+ if (this.isDestroying) { return; }
+
+ // update parent node
+ set(view, '_parentView', null);
+
+ // remove view from childViews array.
+ var childViews = this._childViews;
+
+ Ember.EnumerableUtils.removeObject(childViews, view);
+
+ this.propertyDidChange('childViews'); // HUH?! what happened to will change?
+
+ return this;
+ },
+
+ /**
+ Removes all children from the `parentView`.
+
+ @method removeAllChildren
+ @return {Ember.View} receiver
+ */
+ removeAllChildren: function() {
+ return this.mutateChildViews(function(parentView, view) {
+ parentView.removeChild(view);
+ });
+ },
+
+ destroyAllChildren: function() {
+ return this.mutateChildViews(function(parentView, view) {
+ view.destroy();
+ });
+ },
+
+ /**
+ Removes the view from its `parentView`, if one is found. Otherwise
+ does nothing.
+
+ @method removeFromParent
+ @return {Ember.View} receiver
+ */
+ removeFromParent: function() {
+ var parent = this._parentView;
+
+ // Remove DOM element from parent
+ this.remove();
+
+ if (parent) { parent.removeChild(this); }
+ return this;
+ },
+
+ /**
+ You must call `destroy` on a view to destroy the view (and all of its
+ child views). This will remove the view from any parent node, then make
+ sure that the DOM element managed by the view can be released by the
+ memory manager.
+
+ @method destroy
+ */
+ destroy: function() {
+ var childViews = this._childViews,
+ // get parentView before calling super because it'll be destroyed
+ nonVirtualParentView = get(this, 'parentView'),
+ viewName = this.viewName,
+ childLen, i;
+
+ if (!this._super()) { return; }
+
+ childLen = childViews.length;
+ for (i=childLen-1; i>=0; i--) {
+ childViews[i].removedFromDOM = true;
+ }
+
+ // remove from non-virtual parent view if viewName was specified
+ if (viewName && nonVirtualParentView) {
+ nonVirtualParentView.set(viewName, null);
+ }
+
+ childLen = childViews.length;
+ for (i=childLen-1; i>=0; i--) {
+ childViews[i].destroy();
+ }
+
+ return this;
+ },
+
+ /**
+ Instantiates a view to be added to the childViews array during view
+ initialization. You generally will not call this method directly unless
+ you are overriding `createChildViews()`. Note that this method will
+ automatically configure the correct settings on the new view instance to
+ act as a child of the parent.
+
+ @method createChildView
+ @param {Class|String} viewClass
+ @param {Hash} [attrs] Attributes to add
+ @return {Ember.View} new instance
+ */
+ createChildView: function(view, attrs) {
+ if (!view) {
+ throw new TypeError("createChildViews first argument must exist");
+ }
+
+ if (view.isView && view._parentView === this && view.container === this.container) {
+ return view;
+ }
+
+ attrs = attrs || {};
+ attrs._parentView = this;
+
+ if (Ember.CoreView.detect(view)) {
+ attrs.templateData = attrs.templateData || get(this, 'templateData');
+
+ attrs.container = this.container;
+ view = view.create(attrs);
+
+ // don't set the property on a virtual view, as they are invisible to
+ // consumers of the view API
+ if (view.viewName) {
+ set(get(this, 'concreteView'), view.viewName, view);
+ }
+ } else if ('string' === typeof view) {
+ var fullName = 'view:' + view;
+ var View = this.container.lookupFactory(fullName);
+
+ Ember.assert("Could not find view: '" + fullName + "'", !!View);
+
+ attrs.templateData = get(this, 'templateData');
+ view = View.create(attrs);
+ } else {
+ Ember.assert('You must pass instance or subclass of View', view.isView);
+ attrs.container = this.container;
+
+ if (!get(view, 'templateData')) {
+ attrs.templateData = get(this, 'templateData');
+ }
+
+ Ember.setProperties(view, attrs);
+
+ }
+
+ return view;
+ },
+
+ becameVisible: Ember.K,
+ becameHidden: Ember.K,
+
+ /**
+ When the view's `isVisible` property changes, toggle the visibility
+ element of the actual DOM element.
+
+ @method _isVisibleDidChange
+ @private
+ */
+ _isVisibleDidChange: Ember.observer('isVisible', function() {
+ if (this._isVisible === get(this, 'isVisible')) { return ; }
+ Ember.run.scheduleOnce('render', this, this._toggleVisibility);
+ }),
+
+ _toggleVisibility: function() {
+ var $el = this.$();
+ if (!$el) { return; }
+
+ var isVisible = get(this, 'isVisible');
+
+ if (this._isVisible === isVisible) { return ; }
+
+ $el.toggle(isVisible);
+
+ this._isVisible = isVisible;
+
+ if (this._isAncestorHidden()) { return; }
+
+ if (isVisible) {
+ this._notifyBecameVisible();
+ } else {
+ this._notifyBecameHidden();
+ }
+ },
+
+ _notifyBecameVisible: function() {
+ this.trigger('becameVisible');
+
+ this.forEachChildView(function(view) {
+ var isVisible = get(view, 'isVisible');
+
+ if (isVisible || isVisible === null) {
+ view._notifyBecameVisible();
+ }
+ });
+ },
+
+ _notifyBecameHidden: function() {
+ this.trigger('becameHidden');
+ this.forEachChildView(function(view) {
+ var isVisible = get(view, 'isVisible');
+
+ if (isVisible || isVisible === null) {
+ view._notifyBecameHidden();
+ }
+ });
+ },
+
+ _isAncestorHidden: function() {
+ var parent = get(this, 'parentView');
+
+ while (parent) {
+ if (get(parent, 'isVisible') === false) { return true; }
+
+ parent = get(parent, 'parentView');
+ }
+
+ return false;
+ },
+
+ clearBuffer: function() {
+ this.invokeRecursively(nullViewsBuffer);
+ },
+
+ transitionTo: function(state, children) {
+ var priorState = this.currentState,
+ currentState = this.currentState = this.states[state];
+ this.state = state;
+
+ if (priorState && priorState.exit) { priorState.exit(this); }
+ if (currentState.enter) { currentState.enter(this); }
+ if (state === 'inDOM') { delete Ember.meta(this).cache.element; }
+
+ if (children !== false) {
+ this.forEachChildView(function(view) {
+ view.transitionTo(state);
+ });
+ }
+ },
+
+ // .......................................................
+ // EVENT HANDLING
+ //
+
+ /**
+ Handle events from `Ember.EventDispatcher`
+
+ @method handleEvent
+ @param eventName {String}
+ @param evt {Event}
+ @private
+ */
+ handleEvent: function(eventName, evt) {
+ return this.currentState.handleEvent(this, eventName, evt);
+ },
+
+ registerObserver: function(root, path, target, observer) {
+ if (!observer && 'function' === typeof target) {
+ observer = target;
+ target = null;
+ }
+
+ if (!root || typeof root !== 'object') {
+ return;
+ }
+
+ var view = this,
+ stateCheckedObserver = function() {
+ view.currentState.invokeObserver(this, observer);
+ },
+ scheduledObserver = function() {
+ Ember.run.scheduleOnce('render', this, stateCheckedObserver);
+ };
+
+ Ember.addObserver(root, path, target, scheduledObserver);
+
+ this.one('willClearRender', function() {
+ Ember.removeObserver(root, path, target, scheduledObserver);
+ });
+ }
+
+});
+
+/*
+ Describe how the specified actions should behave in the various
+ states that a view can exist in. Possible states:
+
+ * preRender: when a view is first instantiated, and after its
+ element was destroyed, it is in the preRender state
+ * inBuffer: once a view has been rendered, but before it has
+ been inserted into the DOM, it is in the inBuffer state
+ * hasElement: the DOM representation of the view is created,
+ and is ready to be inserted
+ * inDOM: once a view has been inserted into the DOM it is in
+ the inDOM state. A view spends the vast majority of its
+ existence in this state.
+ * destroyed: once a view has been destroyed (using the destroy
+ method), it is in this state. No further actions can be invoked
+ on a destroyed view.
+*/
+
+ // in the destroyed state, everything is illegal
+
+ // before rendering has begun, all legal manipulations are noops.
+
+ // inside the buffer, legal manipulations are done on the buffer
+
+ // once the view has been inserted into the DOM, legal manipulations
+ // are done on the DOM element.
+
+function notifyMutationListeners() {
+ Ember.run.once(Ember.View, 'notifyMutationListeners');
+}
+
+var DOMManager = {
+ prepend: function(view, html) {
+ view.$().prepend(html);
+ notifyMutationListeners();
+ },
+
+ after: function(view, html) {
+ view.$().after(html);
+ notifyMutationListeners();
+ },
+
+ html: function(view, html) {
+ view.$().html(html);
+ notifyMutationListeners();
+ },
+
+ replace: function(view) {
+ var element = get(view, 'element');
+
+ set(view, 'element', null);
+
+ view._insertElementLater(function() {
+ Ember.$(element).replaceWith(get(view, 'element'));
+ notifyMutationListeners();
+ });
+ },
+
+ remove: function(view) {
+ view.$().remove();
+ notifyMutationListeners();
+ },
+
+ empty: function(view) {
+ view.$().empty();
+ notifyMutationListeners();
+ }
+};
+
+Ember.View.reopen({
+ domManager: DOMManager
+});
+
+Ember.View.reopenClass({
+
+ /**
+ Parse a path and return an object which holds the parsed properties.
+
+ For example a path like "content.isEnabled:enabled:disabled" will return the
+ following object:
+
+ ```javascript
+ {
+ path: "content.isEnabled",
+ className: "enabled",
+ falsyClassName: "disabled",
+ classNames: ":enabled:disabled"
+ }
+ ```
+
+ @method _parsePropertyPath
+ @static
+ @private
+ */
+ _parsePropertyPath: function(path) {
+ var split = path.split(':'),
+ propertyPath = split[0],
+ classNames = "",
+ className,
+ falsyClassName;
+
+ // check if the property is defined as prop:class or prop:trueClass:falseClass
+ if (split.length > 1) {
+ className = split[1];
+ if (split.length === 3) { falsyClassName = split[2]; }
+
+ classNames = ':' + className;
+ if (falsyClassName) { classNames += ":" + falsyClassName; }
+ }
+
+ return {
+ path: propertyPath,
+ classNames: classNames,
+ className: (className === '') ? undefined : className,
+ falsyClassName: falsyClassName
+ };
+ },
+
+ /**
+ Get the class name for a given value, based on the path, optional
+ `className` and optional `falsyClassName`.
+
+ - if a `className` or `falsyClassName` has been specified:
+ - if the value is truthy and `className` has been specified,
+ `className` is returned
+ - if the value is falsy and `falsyClassName` has been specified,
+ `falsyClassName` is returned
+ - otherwise `null` is returned
+ - if the value is `true`, the dasherized last part of the supplied path
+ is returned
+ - if the value is not `false`, `undefined` or `null`, the `value`
+ is returned
+ - if none of the above rules apply, `null` is returned
+
+ @method _classStringForValue
+ @param path
+ @param val
+ @param className
+ @param falsyClassName
+ @static
+ @private
+ */
+ _classStringForValue: function(path, val, className, falsyClassName) {
+ // When using the colon syntax, evaluate the truthiness or falsiness
+ // of the value to determine which className to return
+ if (className || falsyClassName) {
+ if (className && !!val) {
+ return className;
+
+ } else if (falsyClassName && !val) {
+ return falsyClassName;
+
+ } else {
+ return null;
+ }
+
+ // If value is a Boolean and true, return the dasherized property
+ // name.
+ } else if (val === true) {
+ // Normalize property path to be suitable for use
+ // as a class name. For exaple, content.foo.barBaz
+ // becomes bar-baz.
+ var parts = path.split('.');
+ return Ember.String.dasherize(parts[parts.length-1]);
+
+ // If the value is not false, undefined, or null, return the current
+ // value of the property.
+ } else if (val !== false && val != null) {
+ return val;
+
+ // Nothing to display. Return null so that the old class is removed
+ // but no new class is added.
+ } else {
+ return null;
+ }
+ }
+});
+
+var mutation = Ember.Object.extend(Ember.Evented).create();
+
+Ember.View.addMutationListener = function(callback) {
+ mutation.on('change', callback);
+};
+
+Ember.View.removeMutationListener = function(callback) {
+ mutation.off('change', callback);
+};
+
+Ember.View.notifyMutationListeners = function() {
+ mutation.trigger('change');
+};
+
+/**
+ Global views hash
+
+ @property views
+ @static
+ @type Hash
+*/
+Ember.View.views = {};
+
+// If someone overrides the child views computed property when
+// defining their class, we want to be able to process the user's
+// supplied childViews and then restore the original computed property
+// at view initialization time. This happens in Ember.ContainerView's init
+// method.
+Ember.View.childViewsProperty = childViewsProperty;
+
+Ember.View.applyAttributeBindings = function(elem, name, value) {
+ var type = Ember.typeOf(value);
+
+ // if this changes, also change the logic in ember-handlebars/lib/helpers/binding.js
+ if (name !== 'value' && (type === 'string' || (type === 'number' && !isNaN(value)))) {
+ if (value !== elem.attr(name)) {
+ elem.attr(name, value);
+ }
+ } else if (name === 'value' || type === 'boolean') {
+ if (Ember.isNone(value) || value === false) {
+ // `null`, `undefined` or `false` should remove attribute
+ elem.removeAttr(name);
+ elem.prop(name, '');
+ } else if (value !== elem.prop(name)) {
+ // value should always be properties
+ elem.prop(name, value);
+ }
+ } else if (!value) {
+ elem.removeAttr(name);
+ }
+};
+
+Ember.View.states = states;
+
+})();
+
+
+
+(function() {
+/**
+@module ember
+@submodule ember-views
+*/
+
+var get = Ember.get, set = Ember.set;
+
+Ember.View.states._default = {
+ // appendChild is only legal while rendering the buffer.
+ appendChild: function() {
+ throw "You can't use appendChild outside of the rendering process";
+ },
+
+ $: function() {
+ return undefined;
+ },
+
+ getElement: function() {
+ return null;
+ },
+
+ // Handle events from `Ember.EventDispatcher`
+ handleEvent: function() {
+ return true; // continue event propagation
+ },
+
+ destroyElement: function(view) {
+ set(view, 'element', null);
+ if (view._scheduledInsert) {
+ Ember.run.cancel(view._scheduledInsert);
+ view._scheduledInsert = null;
+ }
+ return view;
+ },
+
+ renderToBufferIfNeeded: function () {
+ return false;
+ },
+
+ rerender: Ember.K,
+ invokeObserver: Ember.K
+};
+
+})();
+
+
+
+(function() {
+/**
+@module ember
+@submodule ember-views
+*/
+
+var preRender = Ember.View.states.preRender = Ember.create(Ember.View.states._default);
+
+Ember.merge(preRender, {
+ // a view leaves the preRender state once its element has been
+ // created (createElement).
+ insertElement: function(view, fn) {
+ view.createElement();
+ var viewCollection = view.viewHierarchyCollection();
+
+ viewCollection.trigger('willInsertElement');
+
+ fn.call(view);
+
+ // We transition to `inDOM` if the element exists in the DOM
+ var element = view.get('element');
+ if (document.body.contains(element)) {
+ viewCollection.transitionTo('inDOM', false);
+ viewCollection.trigger('didInsertElement');
+ }
+ },
+
+ renderToBufferIfNeeded: function(view, buffer) {
+ view.renderToBuffer(buffer);
+ return true;
+ },
+
+ empty: Ember.K,
+
+ setElement: function(view, value) {
+ if (value !== null) {
+ view.transitionTo('hasElement');
+ }
+ return value;
+ }
+});
+
+})();
+
+
+
+(function() {
+/**
+@module ember
+@submodule ember-views
+*/
+
+var get = Ember.get, set = Ember.set;
+
+var inBuffer = Ember.View.states.inBuffer = Ember.create(Ember.View.states._default);
+
+Ember.merge(inBuffer, {
+ $: function(view, sel) {
+ // if we don't have an element yet, someone calling this.$() is
+ // trying to update an element that isn't in the DOM. Instead,
+ // rerender the view to allow the render method to reflect the
+ // changes.
+ view.rerender();
+ return Ember.$();
+ },
+
+ // when a view is rendered in a buffer, rerendering it simply
+ // replaces the existing buffer with a new one
+ rerender: function(view) {
+ throw new Ember.Error("Something you did caused a view to re-render after it rendered but before it was inserted into the DOM.");
+ },
+
+ // when a view is rendered in a buffer, appending a child
+ // view will render that view and append the resulting
+ // buffer into its buffer.
+ appendChild: function(view, childView, options) {
+ var buffer = view.buffer, _childViews = view._childViews;
+
+ childView = view.createChildView(childView, options);
+ if (!_childViews.length) { _childViews = view._childViews = _childViews.slice(); }
+ _childViews.push(childView);
+
+ childView.renderToBuffer(buffer);
+
+ view.propertyDidChange('childViews');
+
+ return childView;
+ },
+
+ // when a view is rendered in a buffer, destroying the
+ // element will simply destroy the buffer and put the
+ // state back into the preRender state.
+ destroyElement: function(view) {
+ view.clearBuffer();
+ var viewCollection = view._notifyWillDestroyElement();
+ viewCollection.transitionTo('preRender', false);
+
+ return view;
+ },
+
+ empty: function() {
+ Ember.assert("Emptying a view in the inBuffer state is not allowed and " +
+ "should not happen under normal circumstances. Most likely " +
+ "there is a bug in your application. This may be due to " +
+ "excessive property change notifications.");
+ },
+
+ renderToBufferIfNeeded: function (view, buffer) {
+ return false;
+ },
+
+ // It should be impossible for a rendered view to be scheduled for
+ // insertion.
+ insertElement: function() {
+ throw "You can't insert an element that has already been rendered";
+ },
+
+ setElement: function(view, value) {
+ if (value === null) {
+ view.transitionTo('preRender');
+ } else {
+ view.clearBuffer();
+ view.transitionTo('hasElement');
+ }
+
+ return value;
+ },
+
+ invokeObserver: function(target, observer) {
+ observer.call(target);
+ }
+});
+
+
+})();
+
+
+
+(function() {
+/**
+@module ember
+@submodule ember-views
+*/
+
+var get = Ember.get, set = Ember.set;
+
+var hasElement = Ember.View.states.hasElement = Ember.create(Ember.View.states._default);
+
+Ember.merge(hasElement, {
+ $: function(view, sel) {
+ var elem = get(view, 'element');
+ return sel ? Ember.$(sel, elem) : Ember.$(elem);
+ },
+
+ getElement: function(view) {
+ var parent = get(view, 'parentView');
+ if (parent) { parent = get(parent, 'element'); }
+ if (parent) { return view.findElementInParentElement(parent); }
+ return Ember.$("#" + get(view, 'elementId'))[0];
+ },
+
+ setElement: function(view, value) {
+ if (value === null) {
+ view.transitionTo('preRender');
+ } else {
+ throw "You cannot set an element to a non-null value when the element is already in the DOM.";
+ }
+
+ return value;
+ },
+
+ // once the view has been inserted into the DOM, rerendering is
+ // deferred to allow bindings to synchronize.
+ rerender: function(view) {
+ view.triggerRecursively('willClearRender');
+
+ view.clearRenderedChildren();
+
+ view.domManager.replace(view);
+ return view;
+ },
+
+ // once the view is already in the DOM, destroying it removes it
+ // from the DOM, nukes its element, and puts it back into the
+ // preRender state if inDOM.
+
+ destroyElement: function(view) {
+ view._notifyWillDestroyElement();
+ view.domManager.remove(view);
+ set(view, 'element', null);
+ if (view._scheduledInsert) {
+ Ember.run.cancel(view._scheduledInsert);
+ view._scheduledInsert = null;
+ }
+ return view;
+ },
+
+ empty: function(view) {
+ var _childViews = view._childViews, len, idx;
+ if (_childViews) {
+ len = _childViews.length;
+ for (idx = 0; idx < len; idx++) {
+ _childViews[idx]._notifyWillDestroyElement();
+ }
+ }
+ view.domManager.empty(view);
+ },
+
+ // Handle events from `Ember.EventDispatcher`
+ handleEvent: function(view, eventName, evt) {
+ if (view.has(eventName)) {
+ // Handler should be able to re-dispatch events, so we don't
+ // preventDefault or stopPropagation.
+ return view.trigger(eventName, evt);
+ } else {
+ return true; // continue event propagation
+ }
+ },
+
+ invokeObserver: function(target, observer) {
+ observer.call(target);
+ }
+});
+})();
+
+
+
+(function() {
+/**
+@module ember
+@submodule ember-views
+*/
+
+var hasElement = Ember.View.states.hasElement;
+var inDOM = Ember.View.states.inDOM = Ember.create(hasElement);
+
+Ember.merge(inDOM, {
+ enter: function(view) {
+ // Register the view for event handling. This hash is used by
+ // Ember.EventDispatcher to dispatch incoming events.
+ if (!view.isVirtual) {
+ Ember.assert("Attempted to register a view with an id already in use: "+view.elementId, !Ember.View.views[view.elementId]);
+ Ember.View.views[view.elementId] = view;
+ }
+
+ view.addBeforeObserver('elementId', function() {
+ throw new Ember.Error("Changing a view's elementId after creation is not allowed");
+ });
+ },
+
+ exit: function(view) {
+ if (!this.isVirtual) delete Ember.View.views[view.elementId];
+ },
+
+ insertElement: function(view, fn) {
+ throw "You can't insert an element into the DOM that has already been inserted";
+ }
+});
+
+})();
+
+
+
+(function() {
+/**
+@module ember
+@submodule ember-views
+*/
+
+var destroyingError = "You can't call %@ on a view being destroyed", fmt = Ember.String.fmt;
+
+var destroying = Ember.View.states.destroying = Ember.create(Ember.View.states._default);
+
+Ember.merge(destroying, {
+ appendChild: function() {
+ throw fmt(destroyingError, ['appendChild']);
+ },
+ rerender: function() {
+ throw fmt(destroyingError, ['rerender']);
+ },
+ destroyElement: function() {
+ throw fmt(destroyingError, ['destroyElement']);
+ },
+ empty: function() {
+ throw fmt(destroyingError, ['empty']);
+ },
+
+ setElement: function() {
+ throw fmt(destroyingError, ["set('element', ...)"]);
+ },
+
+ renderToBufferIfNeeded: function() {
+ return false;
+ },
+
+ // Since element insertion is scheduled, don't do anything if
+ // the view has been destroyed between scheduling and execution
+ insertElement: Ember.K
+});
+
+
+})();
+
+
+
+(function() {
+Ember.View.cloneStates = function(from) {
+ var into = {};
+
+ into._default = {};
+ into.preRender = Ember.create(into._default);
+ into.destroying = Ember.create(into._default);
+ into.inBuffer = Ember.create(into._default);
+ into.hasElement = Ember.create(into._default);
+ into.inDOM = Ember.create(into.hasElement);
+
+ for (var stateName in from) {
+ if (!from.hasOwnProperty(stateName)) { continue; }
+ Ember.merge(into[stateName], from[stateName]);
+ }
+
+ return into;
+};
+
+})();
+
+
+
+(function() {
+var states = Ember.View.cloneStates(Ember.View.states);
+
+/**
+@module ember
+@submodule ember-views
+*/
+
+var get = Ember.get, set = Ember.set;
+var forEach = Ember.EnumerableUtils.forEach;
+var ViewCollection = Ember._ViewCollection;
+
+/**
+ A `ContainerView` is an `Ember.View` subclass that implements `Ember.MutableArray`
+ allowing programmatic management of its child views.
+
+ ## Setting Initial Child Views
+
+ The initial array of child views can be set in one of two ways. You can
+ provide a `childViews` property at creation time that contains instance of
+ `Ember.View`:
+
+ ```javascript
+ aContainer = Ember.ContainerView.create({
+ childViews: [Ember.View.create(), Ember.View.create()]
+ });
+ ```
+
+ You can also provide a list of property names whose values are instances of
+ `Ember.View`:
+
+ ```javascript
+ aContainer = Ember.ContainerView.create({
+ childViews: ['aView', 'bView', 'cView'],
+ aView: Ember.View.create(),
+ bView: Ember.View.create(),
+ cView: Ember.View.create()
+ });
+ ```
+
+ The two strategies can be combined:
+
+ ```javascript
+ aContainer = Ember.ContainerView.create({
+ childViews: ['aView', Ember.View.create()],
+ aView: Ember.View.create()
+ });
+ ```
+
+ Each child view's rendering will be inserted into the container's rendered
+ HTML in the same order as its position in the `childViews` property.
+
+ ## Adding and Removing Child Views
+
+ The container view implements `Ember.MutableArray` allowing programmatic management of its child views.
+
+ To remove a view, pass that view into a `removeObject` call on the container view.
+
+ Given an empty `<body>` the following code
+
+ ```javascript
+ aContainer = Ember.ContainerView.create({
+ classNames: ['the-container'],
+ childViews: ['aView', 'bView'],
+ aView: Ember.View.create({
+ template: Ember.Handlebars.compile("A")
+ }),
+ bView: Ember.View.create({
+ template: Ember.Handlebars.compile("B")
+ })
+ });
+
+ aContainer.appendTo('body');
+ ```
+
+ Results in the HTML
+
+ ```html
+ <div class="ember-view the-container">
+ <div class="ember-view">A</div>
+ <div class="ember-view">B</div>
+ </div>
+ ```
+
+ Removing a view
+
+ ```javascript
+ aContainer.toArray(); // [aContainer.aView, aContainer.bView]
+ aContainer.removeObject(aContainer.get('bView'));
+ aContainer.toArray(); // [aContainer.aView]
+ ```
+
+ Will result in the following HTML
+
+ ```html
+ <div class="ember-view the-container">
+ <div class="ember-view">A</div>
+ </div>
+ ```
+
+ Similarly, adding a child view is accomplished by adding `Ember.View` instances to the
+ container view.
+
+ Given an empty `<body>` the following code
+
+ ```javascript
+ aContainer = Ember.ContainerView.create({
+ classNames: ['the-container'],
+ childViews: ['aView', 'bView'],
+ aView: Ember.View.create({
+ template: Ember.Handlebars.compile("A")
+ }),
+ bView: Ember.View.create({
+ template: Ember.Handlebars.compile("B")
+ })
+ });
+
+ aContainer.appendTo('body');
+ ```
+
+ Results in the HTML
+
+ ```html
+ <div class="ember-view the-container">
+ <div class="ember-view">A</div>
+ <div class="ember-view">B</div>
+ </div>
+ ```
+
+ Adding a view
+
+ ```javascript
+ AnotherViewClass = Ember.View.extend({
+ template: Ember.Handlebars.compile("Another view")
+ });
+
+ aContainer.toArray(); // [aContainer.aView, aContainer.bView]
+ aContainer.pushObject(AnotherViewClass.create());
+ aContainer.toArray(); // [aContainer.aView, aContainer.bView, <AnotherViewClass instance>]
+ ```
+
+ Will result in the following HTML
+
+ ```html
+ <div class="ember-view the-container">
+ <div class="ember-view">A</div>
+ <div class="ember-view">B</div>
+ <div class="ember-view">Another view</div>
+ </div>
+ ```
+
+ ## Templates and Layout
+
+ A `template`, `templateName`, `defaultTemplate`, `layout`, `layoutName` or
+ `defaultLayout` property on a container view will not result in the template
+ or layout being rendered. The HTML contents of a `Ember.ContainerView`'s DOM
+ representation will only be the rendered HTML of its child views.
+
+ @class ContainerView
+ @namespace Ember
+ @extends Ember.View
+*/
+Ember.ContainerView = Ember.View.extend(Ember.MutableArray, {
+ states: states,
+
+ init: function() {
+ this._super();
+
+ var childViews = get(this, 'childViews');
+
+ // redefine view's childViews property that was obliterated
+ Ember.defineProperty(this, 'childViews', Ember.View.childViewsProperty);
+
+ var _childViews = this._childViews;
+
+ forEach(childViews, function(viewName, idx) {
+ var view;
+
+ if ('string' === typeof viewName) {
+ view = get(this, viewName);
+ view = this.createChildView(view);
+ set(this, viewName, view);
+ } else {
+ view = this.createChildView(viewName);
+ }
+
+ _childViews[idx] = view;
+ }, this);
+
+ var currentView = get(this, 'currentView');
+ if (currentView) {
+ if (!_childViews.length) { _childViews = this._childViews = this._childViews.slice(); }
+ _childViews.push(this.createChildView(currentView));
+ }
+ },
+
+ replace: function(idx, removedCount, addedViews) {
+ var addedCount = addedViews ? get(addedViews, 'length') : 0;
+ var self = this;
+ Ember.assert("You can't add a child to a container that is already a child of another view", Ember.A(addedViews).every(function(item) { return !get(item, '_parentView') || get(item, '_parentView') === self; }));
+
+ this.arrayContentWillChange(idx, removedCount, addedCount);
+ this.childViewsWillChange(this._childViews, idx, removedCount);
+
+ if (addedCount === 0) {
+ this._childViews.splice(idx, removedCount) ;
+ } else {
+ var args = [idx, removedCount].concat(addedViews);
+ if (addedViews.length && !this._childViews.length) { this._childViews = this._childViews.slice(); }
+ this._childViews.splice.apply(this._childViews, args);
+ }
+
+ this.arrayContentDidChange(idx, removedCount, addedCount);
+ this.childViewsDidChange(this._childViews, idx, removedCount, addedCount);
+
+ return this;
+ },
+
+ objectAt: function(idx) {
+ return this._childViews[idx];
+ },
+
+ length: Ember.computed(function () {
+ return this._childViews.length;
+ }).volatile(),
+
+ /**
+ Instructs each child view to render to the passed render buffer.
+
+ @private
+ @method render
+ @param {Ember.RenderBuffer} buffer the buffer to render to
+ */
+ render: function(buffer) {
+ this.forEachChildView(function(view) {
+ view.renderToBuffer(buffer);
+ });
+ },
+
+ instrumentName: 'container',
+
+ /**
+ When a child view is removed, destroy its element so that
+ it is removed from the DOM.
+
+ The array observer that triggers this action is set up in the
+ `renderToBuffer` method.
+
+ @private
+ @method childViewsWillChange
+ @param {Ember.Array} views the child views array before mutation
+ @param {Number} start the start position of the mutation
+ @param {Number} removed the number of child views removed
+ **/
+ childViewsWillChange: function(views, start, removed) {
+ this.propertyWillChange('childViews');
+
+ if (removed > 0) {
+ var changedViews = views.slice(start, start+removed);
+ // transition to preRender before clearing parentView
+ this.currentState.childViewsWillChange(this, views, start, removed);
+ this.initializeViews(changedViews, null, null);
+ }
+ },
+
+ removeChild: function(child) {
+ this.removeObject(child);
+ return this;
+ },
+
+ /**
+ When a child view is added, make sure the DOM gets updated appropriately.
+
+ If the view has already rendered an element, we tell the child view to
+ create an element and insert it into the DOM. If the enclosing container
+ view has already written to a buffer, but not yet converted that buffer
+ into an element, we insert the string representation of the child into the
+ appropriate place in the buffer.
+
+ @private
+ @method childViewsDidChange
+ @param {Ember.Array} views the array of child views afte the mutation has occurred
+ @param {Number} start the start position of the mutation
+ @param {Number} removed the number of child views removed
+ @param {Number} the number of child views added
+ */
+ childViewsDidChange: function(views, start, removed, added) {
+ if (added > 0) {
+ var changedViews = views.slice(start, start+added);
+ this.initializeViews(changedViews, this, get(this, 'templateData'));
+ this.currentState.childViewsDidChange(this, views, start, added);
+ }
+ this.propertyDidChange('childViews');
+ },
+
+ initializeViews: function(views, parentView, templateData) {
+ forEach(views, function(view) {
+ set(view, '_parentView', parentView);
+
+ if (!view.container && parentView) {
+ set(view, 'container', parentView.container);
+ }
+
+ if (!get(view, 'templateData')) {
+ set(view, 'templateData', templateData);
+ }
+ });
+ },
+
+ currentView: null,
+
+ _currentViewWillChange: Ember.beforeObserver('currentView', function() {
+ var currentView = get(this, 'currentView');
+ if (currentView) {
+ currentView.destroy();
+ }
+ }),
+
+ _currentViewDidChange: Ember.observer('currentView', function() {
+ var currentView = get(this, 'currentView');
+ if (currentView) {
+ Ember.assert("You tried to set a current view that already has a parent. Make sure you don't have multiple outlets in the same view.", !get(currentView, '_parentView'));
+ this.pushObject(currentView);
+ }
+ }),
+
+ _ensureChildrenAreInDOM: function () {
+ this.currentState.ensureChildrenAreInDOM(this);
+ }
+});
+
+Ember.merge(states._default, {
+ childViewsWillChange: Ember.K,
+ childViewsDidChange: Ember.K,
+ ensureChildrenAreInDOM: Ember.K
+});
+
+Ember.merge(states.inBuffer, {
+ childViewsDidChange: function(parentView, views, start, added) {
+ throw new Ember.Error('You cannot modify child views while in the inBuffer state');
+ }
+});
+
+Ember.merge(states.hasElement, {
+ childViewsWillChange: function(view, views, start, removed) {
+ for (var i=start; i<start+removed; i++) {
+ views[i].remove();
+ }
+ },
+
+ childViewsDidChange: function(view, views, start, added) {
+ Ember.run.scheduleOnce('render', view, '_ensureChildrenAreInDOM');
+ },
+
+ ensureChildrenAreInDOM: function(view) {
+ var childViews = view._childViews, i, len, childView, previous, buffer, viewCollection = new ViewCollection();
+
+ for (i = 0, len = childViews.length; i < len; i++) {
+ childView = childViews[i];
+
+ 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;
+ }
+ }
+
+ if (viewCollection.length) {
+ insertViewCollection(view, viewCollection, previous, buffer);
+ }
+ }
+});
+
+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');
+ });
+}
+
+
+})();
+
+
+
+(function() {
+/**
+@module ember
+@submodule ember-views
+*/
+
+var get = Ember.get, set = Ember.set, fmt = Ember.String.fmt;
+
+/**
+ `Ember.CollectionView` is an `Ember.View` descendent responsible for managing
+ a collection (an array or array-like object) by maintaining a child view object
+ and associated DOM representation for each item in the array and ensuring
+ that child views and their associated rendered HTML are updated when items in
+ the array are added, removed, or replaced.
+
+ ## Setting content
+
+ The managed collection of objects is referenced as the `Ember.CollectionView`
+ instance's `content` property.
+
+ ```javascript
+ someItemsView = Ember.CollectionView.create({
+ content: ['A', 'B','C']
+ })
+ ```
+
+ The view for each item in the collection will have its `content` property set
+ to the item.
+
+ ## Specifying itemViewClass
+
+ By default the view class for each item in the managed collection will be an
+ instance of `Ember.View`. You can supply a different class by setting the
+ `CollectionView`'s `itemViewClass` property.
+
+ Given an empty `<body>` and the following code:
+
+ ```javascript
+ someItemsView = Ember.CollectionView.create({
+ classNames: ['a-collection'],
+ content: ['A','B','C'],
+ itemViewClass: Ember.View.extend({
+ template: Ember.Handlebars.compile("the letter: {{view.content}}")
+ })
+ });
+
+ someItemsView.appendTo('body');
+ ```
+
+ Will result in the following HTML structure
+
+ ```html
+ <div class="ember-view a-collection">
+ <div class="ember-view">the letter: A</div>
+ <div class="ember-view">the letter: B</div>
+ <div class="ember-view">the letter: C</div>
+ </div>
+ ```
+
+ ## Automatic matching of parent/child tagNames
+
+ Setting the `tagName` property of a `CollectionView` to any of
+ "ul", "ol", "table", "thead", "tbody", "tfoot", "tr", or "select" will result
+ in the item views receiving an appropriately matched `tagName` property.
+
+ Given an empty `<body>` and the following code:
+
+ ```javascript
+ anUnorderedListView = Ember.CollectionView.create({
+ tagName: 'ul',
+ content: ['A','B','C'],
+ itemViewClass: Ember.View.extend({
+ template: Ember.Handlebars.compile("the letter: {{view.content}}")
+ })
+ });
+
+ anUnorderedListView.appendTo('body');
+ ```
+
+ Will result in the following HTML structure
+
+ ```html
+ <ul class="ember-view a-collection">
+ <li class="ember-view">the letter: A</li>
+ <li class="ember-view">the letter: B</li>
+ <li class="ember-view">the letter: C</li>
+ </ul>
+ ```
+
+ Additional `tagName` pairs can be provided by adding to
+ `Ember.CollectionView.CONTAINER_MAP `
+
+ ```javascript
+ Ember.CollectionView.CONTAINER_MAP['article'] = 'section'
+ ```
+
+ ## Programmatic creation of child views
+
+ For cases where additional customization beyond the use of a single
+ `itemViewClass` or `tagName` matching is required CollectionView's
+ `createChildView` method can be overidden:
+
+ ```javascript
+ CustomCollectionView = Ember.CollectionView.extend({
+ createChildView: function(viewClass, attrs) {
+ if (attrs.content.kind == 'album') {
+ viewClass = App.AlbumView;
+ } else {
+ viewClass = App.SongView;
+ }
+ return this._super(viewClass, attrs);
+ }
+ });
+ ```
+
+ ## Empty View
+
+ You can provide an `Ember.View` subclass to the `Ember.CollectionView`
+ instance as its `emptyView` property. If the `content` property of a
+ `CollectionView` is set to `null` or an empty array, an instance of this view
+ will be the `CollectionView`s only child.
+
+ ```javascript
+ aListWithNothing = Ember.CollectionView.create({
+ classNames: ['nothing']
+ content: null,
+ emptyView: Ember.View.extend({
+ template: Ember.Handlebars.compile("The collection is empty")
+ })
+ });
+
+ aListWithNothing.appendTo('body');
+ ```
+
+ Will result in the following HTML structure
+
+ ```html
+ <div class="ember-view nothing">
+ <div class="ember-view">
+ The collection is empty
+ </div>
+ </div>
+ ```
+
+ ## Adding and Removing items
+
+ The `childViews` property of a `CollectionView` should not be directly
+ manipulated. Instead, add, remove, replace items from its `content` property.
+ This will trigger appropriate changes to its rendered HTML.
+
+
+ @class CollectionView
+ @namespace Ember
+ @extends Ember.ContainerView
+ @since Ember 0.9
+*/
+Ember.CollectionView = Ember.ContainerView.extend({
+
+ /**
+ A list of items to be displayed by the `Ember.CollectionView`.
+
+ @property content
+ @type Ember.Array
+ @default null
+ */
+ content: null,
+
+ /**
+ This provides metadata about what kind of empty view class this
+ collection would like if it is being instantiated from another
+ system (like Handlebars)
+
+ @private
+ @property emptyViewClass
+ */
+ emptyViewClass: Ember.View,
+
+ /**
+ An optional view to display if content is set to an empty array.
+
+ @property emptyView
+ @type Ember.View
+ @default null
+ */
+ emptyView: null,
+
+ /**
+ @property itemViewClass
+ @type Ember.View
+ @default Ember.View
+ */
+ itemViewClass: Ember.View,
+
+ /**
+ Setup a CollectionView
+
+ @method init
+ */
+ init: function() {
+ var ret = this._super();
+ this._contentDidChange();
+ return ret;
+ },
+
+ /**
+ Invoked when the content property is about to change. Notifies observers that the
+ entire array content will change.
+
+ @private
+ @method _contentWillChange
+ */
+ _contentWillChange: Ember.beforeObserver('content', function() {
+ var content = this.get('content');
+
+ if (content) { content.removeArrayObserver(this); }
+ var len = content ? get(content, 'length') : 0;
+ this.arrayWillChange(content, 0, len);
+ }),
+
+ /**
+ Check to make sure that the content has changed, and if so,
+ update the children directly. This is always scheduled
+ asynchronously, to allow the element to be created before
+ bindings have synchronized and vice versa.
+
+ @private
+ @method _contentDidChange
+ */
+ _contentDidChange: Ember.observer('content', function() {
+ var content = get(this, 'content');
+
+ if (content) {
+ this._assertArrayLike(content);
+ content.addArrayObserver(this);
+ }
+
+ var len = content ? get(content, 'length') : 0;
+ this.arrayDidChange(content, 0, null, len);
+ }),
+
+ /**
+ Ensure that the content implements Ember.Array
+
+ @private
+ @method _assertArrayLike
+ */
+ _assertArrayLike: function(content) {
+ Ember.assert(fmt("an Ember.CollectionView's content must implement Ember.Array. You passed %@", [content]), Ember.Array.detect(content));
+ },
+
+ /**
+ Removes the content and content observers.
+
+ @method destroy
+ */
+ destroy: function() {
+ if (!this._super()) { return; }
+
+ var content = get(this, 'content');
+ if (content) { content.removeArrayObserver(this); }
+
+ if (this._createdEmptyView) {
+ this._createdEmptyView.destroy();
+ }
+
+ return this;
+ },
+
+ /**
+ Called when a mutation to the underlying content array will occur.
+
+ This method will remove any views that are no longer in the underlying
+ content array.
+
+ Invokes whenever the content array itself will change.
+
+ @method arrayWillChange
+ @param {Array} content the managed collection of objects
+ @param {Number} start the index at which the changes will occurr
+ @param {Number} removed number of object to be removed from content
+ */
+ arrayWillChange: function(content, start, removedCount) {
+ // If the contents were empty before and this template collection has an
+ // empty view remove it now.
+ var emptyView = get(this, 'emptyView');
+ if (emptyView && emptyView instanceof Ember.View) {
+ emptyView.removeFromParent();
+ }
+
+ // Loop through child views that correspond with the removed items.
+ // Note that we loop from the end of the array to the beginning because
+ // we are mutating it as we go.
+ var childViews = this._childViews, childView, idx, len;
+
+ len = this._childViews.length;
+
+ var removingAll = removedCount === len;
+
+ if (removingAll) {
+ this.currentState.empty(this);
+ this.invokeRecursively(function(view) {
+ view.removedFromDOM = true;
+ }, false);
+ }
+
+ for (idx = start + removedCount - 1; idx >= start; idx--) {
+ childView = childViews[idx];
+ childView.destroy();
+ }
+ },
+
+ /**
+ Called when a mutation to the underlying content array occurs.
+
+ This method will replay that mutation against the views that compose the
+ `Ember.CollectionView`, ensuring that the view reflects the model.
+
+ This array observer is added in `contentDidChange`.
+
+ @method arrayDidChange
+ @param {Array} content the managed collection of objects
+ @param {Number} start the index at which the changes occurred
+ @param {Number} removed number of object removed from content
+ @param {Number} added number of object added to content
+ */
+ arrayDidChange: function(content, start, removed, added) {
+ var addedViews = [], view, item, idx, len, itemViewClass,
+ emptyView;
+
+ len = content ? get(content, 'length') : 0;
+
+ if (len) {
+ itemViewClass = get(this, 'itemViewClass');
+
+ if ('string' === typeof itemViewClass) {
+ itemViewClass = get(itemViewClass) || itemViewClass;
+ }
+
+ Ember.assert(fmt("itemViewClass must be a subclass of Ember.View, not %@",
+ [itemViewClass]),
+ 'string' === typeof itemViewClass || Ember.View.detect(itemViewClass));
+
+ for (idx = start; idx < start+added; idx++) {
+ item = content.objectAt(idx);
+
+ view = this.createChildView(itemViewClass, {
+ content: item,
+ contentIndex: idx
+ });
+
+ addedViews.push(view);
+ }
+ } else {
+ emptyView = get(this, 'emptyView');
+
+ if (!emptyView) { return; }
+
+ if ('string' === typeof emptyView) {
+ emptyView = get(emptyView) || emptyView;
+ }
+
+ emptyView = this.createChildView(emptyView);
+ addedViews.push(emptyView);
+ set(this, 'emptyView', emptyView);
+
+ if (Ember.CoreView.detect(emptyView)) {
+ this._createdEmptyView = emptyView;
+ }
+ }
+
+ this.replace(start, 0, addedViews);
+ },
+
+ /**
+ Instantiates a view to be added to the childViews array during view
+ initialization. You generally will not call this method directly unless
+ you are overriding `createChildViews()`. Note that this method will
+ automatically configure the correct settings on the new view instance to
+ act as a child of the parent.
+
+ The tag name for the view will be set to the tagName of the viewClass
+ passed in.
+
+ @method createChildView
+ @param {Class} viewClass
+ @param {Hash} [attrs] Attributes to add
+ @return {Ember.View} new instance
+ */
+ createChildView: function(view, attrs) {
+ view = this._super(view, attrs);
+
+ var itemTagName = get(view, 'tagName');
+
+ if (itemTagName === null || itemTagName === undefined) {
+ itemTagName = Ember.CollectionView.CONTAINER_MAP[get(this, 'tagName')];
+ set(view, 'tagName', itemTagName);
+ }
+
+ return view;
+ }
+});
+
+/**
+ A map of parent tags to their default child tags. You can add
+ additional parent tags if you want collection views that use
+ a particular parent tag to default to a child tag.
+
+ @property CONTAINER_MAP
+ @type Hash
+ @static
+ @final
+*/
+Ember.CollectionView.CONTAINER_MAP = {
+ ul: 'li',
+ ol: 'li',
+ table: 'tr',
+ thead: 'tr',
+ tbody: 'tr',
+ tfoot: 'tr',
+ tr: 'td',
+ select: 'option'
+};
+
+})();
+
+
+
+(function() {
+var get = Ember.get;
+
+/**
+ The ComponentTemplateDeprecation mixin is used to provide a useful
+ deprecation warning when using either `template` or `templateName` with
+ a component. The `template` and `templateName` properties specified at
+ extend time are moved to `layout` and `layoutName` respectively.
+
+ `Ember.ComponentTemplateDeprecation` is used internally by Ember in
+ `Ember.Component`.
+
+ @class ComponentTemplateDeprecation
+ @namespace Ember
+*/
+Ember.ComponentTemplateDeprecation = Ember.Mixin.create({
+ /**
+ @private
+
+ Moves `templateName` to `layoutName` and `template` to `layout` at extend
+ time if a layout is not also specified.
+
+ Note that this currently modifies the mixin themselves, which is technically
+ dubious but is practically of little consequence. This may change in the
+ future.
+
+ @method willMergeMixin
+ */
+ willMergeMixin: function(props) {
+ // must call _super here to ensure that the ActionHandler
+ // mixin is setup properly (moves actions -> _actions)
+ //
+ // Calling super is only OK here since we KNOW that
+ // there is another Mixin loaded first.
+ this._super.apply(this, arguments);
+
+ var deprecatedProperty, replacementProperty,
+ layoutSpecified = (props.layoutName || props.layout || get(this, 'layoutName'));
+
+ if (props.templateName && !layoutSpecified) {
+ deprecatedProperty = 'templateName';
+ replacementProperty = 'layoutName';
+
+ props.layoutName = props.templateName;
+ delete props['templateName'];
+ }
+
+ if (props.template && !layoutSpecified) {
+ deprecatedProperty = 'template';
+ replacementProperty = 'layout';
+
+ props.layout = props.template;
+ delete props['template'];
+ }
+
+ if (deprecatedProperty) {
+ Ember.deprecate('Do not specify ' + deprecatedProperty + ' on a Component, use ' + replacementProperty + ' instead.', false);
+ }
+ }
+});
+
+
+})();
+
+
+
+(function() {
+var get = Ember.get, set = Ember.set, isNone = Ember.isNone,
+ a_slice = Array.prototype.slice;
+
+
+/**
+@module ember
+@submodule ember-views
+*/
+
+/**
+ An `Ember.Component` is a view that is completely
+ isolated. Property access in its templates go
+ to the view object and actions are targeted at
+ the view object. There is no access to the
+ surrounding context or outer controller; all
+ contextual information must be passed in.
+
+ The easiest way to create an `Ember.Component` is via
+ a template. If you name a template
+ `components/my-foo`, you will be able to use
+ `{{my-foo}}` in other templates, which will make
+ an instance of the isolated component.
+
+ ```handlebars
+ {{app-profile person=currentUser}}
+ ```
+
+ ```handlebars
+ <!-- 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 controller's 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:
+
+ ```handlebars
+ <!-- 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, Ember.ComponentTemplateDeprecation, {
+ init: function() {
+ this._super();
+ set(this, 'context', this);
+ set(this, 'controller', this);
+ },
+
+ defaultLayout: function(context, options){
+ Ember.Handlebars.helpers['yield'].call(context, options);
+ },
+
+ /**
+ A components template property is set by passing a block
+ during its invocation. It is executed within the parent context.
+
+ Example:
+
+ ```handlebars
+ {{#my-component}}
+ // something that is run in the context
+ // of the parent context
+ {{/my-component}}
+ ```
+
+ Specifying a template directly to a component is deprecated without
+ also specifying the layout property.
+
+ @deprecated
+ @property template
+ */
+ template: Ember.computed(function(key, value) {
+ if (value !== undefined) { return value; }
+
+ var templateName = get(this, 'templateName'),
+ template = this.templateForName(templateName, 'template');
+
+ Ember.assert("You specified the templateName " + templateName + " for " + this + ", but it did not exist.", !templateName || template);
+
+ return template || get(this, 'defaultTemplate');
+ }).property('templateName'),
+
+ /**
+ Specifying a components `templateName` is deprecated without also
+ providing the `layout` or `layoutName` properties.
+
+ @deprecated
+ @property templateName
+ */
+ templateName: null,
+
+ // during render, isolate keywords
+ cloneKeywords: function() {
+ return {
+ view: this,
+ controller: this
+ };
+ },
+
+ _yield: function(context, options) {
+ var view = options.data.view,
+ parentView = this._parentView,
+ template = get(this, 'template');
+
+ if (template) {
+ Ember.assert("A Component must have a parent view in order to yield.", parentView);
+
+ view.appendChild(Ember.View, {
+ isVirtual: true,
+ tagName: '',
+ _contextView: parentView,
+ template: template,
+ context: get(parentView, 'context'),
+ controller: get(parentView, 'controller'),
+ templateData: { keywords: parentView.cloneKeywords() }
+ });
+ }
+ },
+
+ /**
+ If the component is currently inserted into the DOM of a parent view, this
+ property will point to the controller of the parent view.
+
+ @property targetObject
+ @type Ember.Controller
+ @default null
+ */
+ targetObject: Ember.computed(function(key) {
+ var parentView = get(this, '_parentView');
+ return parentView ? get(parentView, 'controller') : null;
+ }).property('_parentView'),
+
+ /**
+ Triggers a named action on the controller context where the component is used if
+ this controller has registered for notifications of the action.
+
+ For example a component for playing or pausing music may translate click events
+ into action notifications of "play" or "stop" depending on some internal state
+ of the component:
+
+
+ ```javascript
+ App.PlayButtonComponent = Ember.Component.extend({
+ click: function(){
+ if (this.get('isPlaying')) {
+ this.sendAction('play');
+ } else {
+ this.sendAction('stop');
+ }
+ }
+ });
+ ```
+
+ When used inside a template these component actions are configured to
+ trigger actions in the outer application context:
+
+ ```handlebars
+ {{! application.hbs }}
+ {{play-button play="musicStarted" stop="musicStopped"}}
+ ```
+
+ When the component receives a browser `click` event it translate this
+ interaction into application-specific semantics ("play" or "stop") and
+ triggers the specified action name on the controller for the template
+ where the component is used:
+
+
+ ```javascript
+ App.ApplicationController = Ember.Controller.extend({
+ actions: {
+ musicStarted: function(){
+ // called when the play button is clicked
+ // and the music started playing
+ },
+ musicStopped: function(){
+ // called when the play button is clicked
+ // and the music stopped playing
+ }
+ }
+ });
+ ```
+
+ If no action name is passed to `sendAction` a default name of "action"
+ is assumed.
+
+ ```javascript
+ App.NextButtonComponent = Ember.Component.extend({
+ click: function(){
+ this.sendAction();
+ }
+ });
+ ```
+
+ ```handlebars
+ {{! application.hbs }}
+ {{next-button action="playNextSongInAlbum"}}
+ ```
+
+ ```javascript
+ App.ApplicationController = Ember.Controller.extend({
+ actions: {
+ playNextSongInAlbum: function(){
+ ...
+ }
+ }
+ });
+ ```
+
+ @method sendAction
+ @param [action] {String} the action to trigger
+ @param [context] {*} a context to send with the action
+ */
+ sendAction: function(action) {
+ var actionName,
+ contexts = a_slice.call(arguments, 1);
+
+ // Send the default action
+ if (action === undefined) {
+ actionName = get(this, 'action');
+ Ember.assert("The default action was triggered on the component " + this.toString() +
+ ", but the action name (" + actionName + ") was not a string.",
+ isNone(actionName) || typeof actionName === 'string');
+ } else {
+ actionName = get(this, action);
+ Ember.assert("The " + action + " action was triggered on the component " +
+ this.toString() + ", but the action name (" + actionName +
+ ") was not a string.",
+ isNone(actionName) || typeof actionName === 'string');
+ }
+
+ // If no action name for that action could be found, just abort.
+ if (actionName === undefined) { return; }
+
+ this.triggerAction({
+ action: actionName,
+ actionContext: contexts
+ });
+ }
+});
+
+})();
+
+
+
+(function() {
+
+})();
+
+
+
+(function() {
+/**
+`Ember.ViewTargetActionSupport` is a mixin that can be included in a
+view class to add a `triggerAction` method with semantics similar to
+the Handlebars `{{action}}` helper. It provides intelligent defaults
+for the action's target: the view's controller; and the context that is
+sent with the action: the view's context.
+
+Note: In normal Ember usage, the `{{action}}` helper is usually the best
+choice. This mixin is most often useful when you are doing more complex
+event handling in custom View subclasses.
+
+For example:
+
+```javascript
+App.SaveButtonView = Ember.View.extend(Ember.ViewTargetActionSupport, {
+ action: 'save',
+ click: function() {
+ this.triggerAction(); // Sends the `save` action, along with the current context
+ // to the current controller
+ }
+});
+```
+
+The `action` can be provided as properties of an optional object argument
+to `triggerAction` as well.
+
+```javascript
+App.SaveButtonView = Ember.View.extend(Ember.ViewTargetActionSupport, {
+ click: function() {
+ this.triggerAction({
+ action: 'save'
+ }); // Sends the `save` action, along with the current context
+ // to the current controller
+ }
+});
+```
+
+@class ViewTargetActionSupport
+@namespace Ember
+@extends Ember.TargetActionSupport
+*/
+Ember.ViewTargetActionSupport = Ember.Mixin.create(Ember.TargetActionSupport, {
+ /**
+ @property target
+ */
+ target: Ember.computed.alias('controller'),
+ /**
+ @property actionContext
+ */
+ actionContext: Ember.computed.alias('context')
+});
+
+})();
+
+
+
+(function() {
+
+})();
+
+
+
+(function() {
+/**
+Ember Views
+
+@module ember
+@submodule ember-views
+@requires ember-runtime
+@main ember-views
+*/
+
+})();
+
+(function() {
+define("metamorph",
+ [],
+ function() {
+ "use strict";
+ // ==========================================================================
+ // Project: metamorph
+ // Copyright: ©2014 Tilde, Inc. All rights reserved.
+ // ==========================================================================
+
+ var K = function() {},
+ guid = 0,
+ disableRange = (function(){
+ if ('undefined' !== typeof MetamorphENV) {
+ return MetamorphENV.DISABLE_RANGE_API;
+ } else if ('undefined' !== ENV) {
+ return ENV.DISABLE_RANGE_API;
+ } else {
+ return false;
+ }
+ })(),
+
+ // Feature-detect the W3C range API, the extended check is for IE9 which only partially supports ranges
+ supportsRange = (!disableRange) && typeof document !== 'undefined' && ('createRange' in document) && (typeof Range !== 'undefined') && Range.prototype.createContextualFragment,
+
+ // Internet Explorer prior to 9 does not allow setting innerHTML if the first element
+ // is a "zero-scope" element. This problem can be worked around by making
+ // the first node an invisible text node. We, like Modernizr, use ­
+ needsShy = typeof document !== 'undefined' && (function() {
+ var testEl = document.createElement('div');
+ testEl.innerHTML = "<div></div>";
+ testEl.firstChild.innerHTML = "<script></script>";
+ return testEl.firstChild.innerHTML === '';
+ })(),
+
+
+ // IE 8 (and likely earlier) likes to move whitespace preceeding
+ // a script tag to appear after it. This means that we can
+ // accidentally remove whitespace when updating a morph.
+ movesWhitespace = document && (function() {
+ var testEl = document.createElement('div');
+ testEl.innerHTML = "Test: <script type='text/x-placeholder'></script>Value";
+ return testEl.childNodes[0].nodeValue === 'Test:' &&
+ testEl.childNodes[2].nodeValue === ' Value';
+ })();
+
+ // Constructor that supports either Metamorph('foo') or new
+ // Metamorph('foo');
+ //
+ // Takes a string of HTML as the argument.
+
+ var Metamorph = function(html) {
+ var self;
+
+ if (this instanceof Metamorph) {
+ self = this;
+ } else {
+ self = new K();
+ }
+
+ self.innerHTML = html;
+ var myGuid = 'metamorph-'+(guid++);
+ self.start = myGuid + '-start';
+ self.end = myGuid + '-end';
+
+ return self;
+ };
+
+ K.prototype = Metamorph.prototype;
+
+ var rangeFor, htmlFunc, removeFunc, outerHTMLFunc, appendToFunc, afterFunc, prependFunc, startTagFunc, endTagFunc;
+
+ outerHTMLFunc = function() {
+ return this.startTag() + this.innerHTML + this.endTag();
+ };
+
+ startTagFunc = function() {
+ /*
+ * We replace chevron by its hex code in order to prevent escaping problems.
+ * Check this thread for more explaination:
+ * http://stackoverflow.com/questions/8231048/why-use-x3c-instead-of-when-gene…
+ */
+ return "<script id='" + this.start + "' type='text/x-placeholder'>\x3C/script>";
+ };
+
+ endTagFunc = function() {
+ /*
+ * We replace chevron by its hex code in order to prevent escaping problems.
+ * Check this thread for more explaination:
+ * http://stackoverflow.com/questions/8231048/why-use-x3c-instead-of-when-gene…
+ */
+ return "<script id='" + this.end + "' type='text/x-placeholder'>\x3C/script>";
+ };
+
+ // If we have the W3C range API, this process is relatively straight forward.
+ if (supportsRange) {
+
+ // Get a range for the current morph. Optionally include the starting and
+ // ending placeholders.
+ rangeFor = function(morph, outerToo) {
+ var range = document.createRange();
+ var before = document.getElementById(morph.start);
+ var after = document.getElementById(morph.end);
+
+ if (outerToo) {
+ range.setStartBefore(before);
+ range.setEndAfter(after);
+ } else {
+ range.setStartAfter(before);
+ range.setEndBefore(after);
+ }
+
+ return range;
+ };
+
+ htmlFunc = function(html, outerToo) {
+ // get a range for the current metamorph object
+ var range = rangeFor(this, outerToo);
+
+ // delete the contents of the range, which will be the
+ // nodes between the starting and ending placeholder.
+ range.deleteContents();
+
+ // create a new document fragment for the HTML
+ var fragment = range.createContextualFragment(html);
+
+ // insert the fragment into the range
+ range.insertNode(fragment);
+ };
+
+ /**
+ * @public
+ *
+ * Remove this object (including starting and ending
+ * placeholders).
+ *
+ * @method remove
+ */
+ removeFunc = function() {
+ // get a range for the current metamorph object including
+ // the starting and ending placeholders.
+ var range = rangeFor(this, true);
+
+ // delete the entire range.
+ range.deleteContents();
+ };
+
+ appendToFunc = function(node) {
+ var range = document.createRange();
+ range.setStart(node);
+ range.collapse(false);
+ var frag = range.createContextualFragment(this.outerHTML());
+ node.appendChild(frag);
+ };
+
+ afterFunc = function(html) {
+ var range = document.createRange();
+ var after = document.getElementById(this.end);
+
+ range.setStartAfter(after);
+ range.setEndAfter(after);
+
+ var fragment = range.createContextualFragment(html);
+ range.insertNode(fragment);
+ };
+
+ prependFunc = function(html) {
+ var range = document.createRange();
+ var start = document.getElementById(this.start);
+
+ range.setStartAfter(start);
+ range.setEndAfter(start);
+
+ var fragment = range.createContextualFragment(html);
+ range.insertNode(fragment);
+ };
+
+ } else {
+ /*
+ * This code is mostly taken from jQuery, with one exception. In jQuery's case, we
+ * have some HTML and we need to figure out how to convert it into some nodes.
+ *
+ * In this case, jQuery needs to scan the HTML looking for an opening tag and use
+ * that as the key for the wrap map. In our case, we know the parent node, and
+ * can use its type as the key for the wrap map.
+ **/
+ var wrapMap = {
+ select: [ 1, "<select multiple='multiple'>", "</select>" ],
+ fieldset: [ 1, "<fieldset>", "</fieldset>" ],
+ table: [ 1, "<table>", "</table>" ],
+ tbody: [ 2, "<table><tbody>", "</tbody></table>" ],
+ tr: [ 3, "<table><tbody><tr>", "</tr></tbody></table>" ],
+ colgroup: [ 2, "<table><tbody></tbody><colgroup>", "</colgroup></table>" ],
+ map: [ 1, "<map>", "</map>" ],
+ _default: [ 0, "", "" ]
+ };
+
+ var findChildById = function(element, id) {
+ if (element.getAttribute('id') === id) { return element; }
+
+ var len = element.childNodes.length, idx, node, found;
+ for (idx=0; idx<len; idx++) {
+ node = element.childNodes[idx];
+ found = node.nodeType === 1 && findChildById(node, id);
+ if (found) { return found; }
+ }
+ };
+
+ var setInnerHTML = function(element, html) {
+ var matches = [];
+ if (movesWhitespace) {
+ // Right now we only check for script tags with ids with the
+ // goal of targeting morphs.
+ html = html.replace(/(\s+)(<script id='([^']+)')/g, function(match, spaces, tag, id) {
+ matches.push([id, spaces]);
+ return tag;
+ });
+ }
+
+ element.innerHTML = html;
+
+ // If we have to do any whitespace adjustments do them now
+ if (matches.length > 0) {
+ var len = matches.length, idx;
+ for (idx=0; idx<len; idx++) {
+ var script = findChildById(element, matches[idx][0]),
+ node = document.createTextNode(matches[idx][1]);
+ script.parentNode.insertBefore(node, script);
+ }
+ }
+ };
+
+ /*
+ * Given a parent node and some HTML, generate a set of nodes. Return the first
+ * node, which will allow us to traverse the rest using nextSibling.
+ *
+ * We need to do this because innerHTML in IE does not really parse the nodes.
+ */
+ var firstNodeFor = function(parentNode, html) {
+ var arr = wrapMap[parentNode.tagName.toLowerCase()] || wrapMap._default;
+ var depth = arr[0], start = arr[1], end = arr[2];
+
+ if (needsShy) { html = '­'+html; }
+
+ var element = document.createElement('div');
+
+ setInnerHTML(element, start + html + end);
+
+ for (var i=0; i<=depth; i++) {
+ element = element.firstChild;
+ }
+
+ // Look for ­ to remove it.
+ if (needsShy) {
+ var shyElement = element;
+
+ // Sometimes we get nameless elements with the shy inside
+ while (shyElement.nodeType === 1 && !shyElement.nodeName) {
+ shyElement = shyElement.firstChild;
+ }
+
+ // At this point it's the actual unicode character.
+ if (shyElement.nodeType === 3 && shyElement.nodeValue.charAt(0) === "\u00AD") {
+ shyElement.nodeValue = shyElement.nodeValue.slice(1);
+ }
+ }
+
+ return element;
+ };
+
+ /*
+ * In some cases, Internet Explorer can create an anonymous node in
+ * the hierarchy with no tagName. You can create this scenario via:
+ *
+ * div = document.createElement("div");
+ * div.innerHTML = "<table>­<script></script><tr><td>hi</td></tr></table>";
+ * div.firstChild.firstChild.tagName //=> ""
+ *
+ * If our script markers are inside such a node, we need to find that
+ * node and use *it* as the marker.
+ */
+ var realNode = function(start) {
+ while (start.parentNode.tagName === "") {
+ start = start.parentNode;
+ }
+
+ return start;
+ };
+
+ /*
+ * When automatically adding a tbody, Internet Explorer inserts the
+ * tbody immediately before the first <tr>. Other browsers create it
+ * before the first node, no matter what.
+ *
+ * This means the the following code:
+ *
+ * div = document.createElement("div");
+ * div.innerHTML = "<table><script id='first'></script><tr><td>hi</td></tr><script id='last'></script></table>
+ *
+ * Generates the following DOM in IE:
+ *
+ * + div
+ * + table
+ * - script id='first'
+ * + tbody
+ * + tr
+ * + td
+ * - "hi"
+ * - script id='last'
+ *
+ * Which means that the two script tags, even though they were
+ * inserted at the same point in the hierarchy in the original
+ * HTML, now have different parents.
+ *
+ * This code reparents the first script tag by making it the tbody's
+ * first child.
+ *
+ */
+ var fixParentage = function(start, end) {
+ if (start.parentNode !== end.parentNode) {
+ end.parentNode.insertBefore(start, end.parentNode.firstChild);
+ }
+ };
+
+ htmlFunc = function(html, outerToo) {
+ // get the real starting node. see realNode for details.
+ var start = realNode(document.getElementById(this.start));
+ var end = document.getElementById(this.end);
+ var parentNode = end.parentNode;
+ var node, nextSibling, last;
+
+ // make sure that the start and end nodes share the same
+ // parent. If not, fix it.
+ fixParentage(start, end);
+
+ // remove all of the nodes after the starting placeholder and
+ // before the ending placeholder.
+ node = start.nextSibling;
+ while (node) {
+ nextSibling = node.nextSibling;
+ last = node === end;
+
+ // if this is the last node, and we want to remove it as well,
+ // set the `end` node to the next sibling. This is because
+ // for the rest of the function, we insert the new nodes
+ // before the end (note that insertBefore(node, null) is
+ // the same as appendChild(node)).
+ //
+ // if we do not want to remove it, just break.
+ if (last) {
+ if (outerToo) { end = node.nextSibling; } else { break; }
+ }
+
+ node.parentNode.removeChild(node);
+
+ // if this is the last node and we didn't break before
+ // (because we wanted to remove the outer nodes), break
+ // now.
+ if (last) { break; }
+
+ node = nextSibling;
+ }
+
+ // get the first node for the HTML string, even in cases like
+ // tables and lists where a simple innerHTML on a div would
+ // swallow some of the content.
+ node = firstNodeFor(start.parentNode, html);
+
+ if (outerToo) {
+ start.parentNode.removeChild(start);
+ }
+
+ // copy the nodes for the HTML between the starting and ending
+ // placeholder.
+ while (node) {
+ nextSibling = node.nextSibling;
+ parentNode.insertBefore(node, end);
+ node = nextSibling;
+ }
+ };
+
+ // remove the nodes in the DOM representing this metamorph.
+ //
+ // this includes the starting and ending placeholders.
+ removeFunc = function() {
+ var start = realNode(document.getElementById(this.start));
+ var end = document.getElementById(this.end);
+
+ this.html('');
+ start.parentNode.removeChild(start);
+ end.parentNode.removeChild(end);
+ };
+
+ appendToFunc = function(parentNode) {
+ var node = firstNodeFor(parentNode, this.outerHTML());
+ var nextSibling;
+
+ while (node) {
+ nextSibling = node.nextSibling;
+ parentNode.appendChild(node);
+ node = nextSibling;
+ }
+ };
+
+ afterFunc = function(html) {
+ // get the real starting node. see realNode for details.
+ var end = document.getElementById(this.end);
+ var insertBefore = end.nextSibling;
+ var parentNode = end.parentNode;
+ var nextSibling;
+ var node;
+
+ // get the first node for the HTML string, even in cases like
+ // tables and lists where a simple innerHTML on a div would
+ // swallow some of the content.
+ node = firstNodeFor(parentNode, html);
+
+ // copy the nodes for the HTML between the starting and ending
+ // placeholder.
+ while (node) {
+ nextSibling = node.nextSibling;
+ parentNode.insertBefore(node, insertBefore);
+ node = nextSibling;
+ }
+ };
+
+ prependFunc = function(html) {
+ var start = document.getElementById(this.start);
+ var parentNode = start.parentNode;
+ var nextSibling;
+ var node;
+
+ node = firstNodeFor(parentNode, html);
+ var insertBefore = start.nextSibling;
+
+ while (node) {
+ nextSibling = node.nextSibling;
+ parentNode.insertBefore(node, insertBefore);
+ node = nextSibling;
+ }
+ };
+ }
+
+ Metamorph.prototype.html = function(html) {
+ this.checkRemoved();
+ if (html === undefined) { return this.innerHTML; }
+
+ htmlFunc.call(this, html);
+
+ this.innerHTML = html;
+ };
+
+ Metamorph.prototype.replaceWith = function(html) {
+ this.checkRemoved();
+ htmlFunc.call(this, html, true);
+ };
+
+ Metamorph.prototype.remove = removeFunc;
+ Metamorph.prototype.outerHTML = outerHTMLFunc;
+ Metamorph.prototype.appendTo = appendToFunc;
+ Metamorph.prototype.after = afterFunc;
+ Metamorph.prototype.prepend = prependFunc;
+ Metamorph.prototype.startTag = startTagFunc;
+ Metamorph.prototype.endTag = endTagFunc;
+
+ Metamorph.prototype.isRemoved = function() {
+ var before = document.getElementById(this.start);
+ var after = document.getElementById(this.end);
+
+ return !before || !after;
+ };
+
+ Metamorph.prototype.checkRemoved = function() {
+ if (this.isRemoved()) {
+ throw new Error("Cannot perform operations on a Metamorph that is not in the DOM.");
+ }
+ };
+
+ return Metamorph;
+ });
+
+})();
+
+(function() {
+/**
+@module ember
+@submodule ember-handlebars-compiler
+*/
+
+// Eliminate dependency on any Ember to simplify precompilation workflow
+var objectCreate = Object.create || function(parent) {
+ function F() {}
+ F.prototype = parent;
+ return new F();
+};
+
+var Handlebars = (Ember.imports && Ember.imports.Handlebars) || (this && this.Handlebars);
+if (!Handlebars && typeof require === 'function') {
+ Handlebars = require('handlebars');
+}
+
+Ember.assert("Ember Handlebars requires Handlebars version 1.0 or 1.1. Include " +
+ "a SCRIPT tag in the HTML HEAD linking to the Handlebars file " +
+ "before you link to Ember.", Handlebars);
+
+Ember.assert("Ember Handlebars requires Handlebars version 1.0 or 1.1, " +
+ "COMPILER_REVISION expected: 4, got: " + Handlebars.COMPILER_REVISION +
+ " - Please note: Builds of master may have other COMPILER_REVISION values.",
+ Handlebars.COMPILER_REVISION === 4);
+
+/**
+ Prepares the Handlebars templating library for use inside Ember's view
+ system.
+
+ The `Ember.Handlebars` object is the standard Handlebars library, extended to
+ use Ember's `get()` method instead of direct property access, which allows
+ computed properties to be used inside templates.
+
+ To create an `Ember.Handlebars` template, call `Ember.Handlebars.compile()`.
+ This will return a function that can be used by `Ember.View` for rendering.
+
+ @class Handlebars
+ @namespace Ember
+*/
+Ember.Handlebars = objectCreate(Handlebars);
+
+/**
+ Register a bound helper or custom view helper.
+
+ ## Simple bound helper example
+
+ ```javascript
+ Ember.Handlebars.helper('capitalize', function(value) {
+ return value.toUpperCase();
+ });
+ ```
+
+ The above bound helper can be used inside of templates as follows:
+
+ ```handlebars
+ {{capitalize name}}
+ ```
+
+ In this case, when the `name` property of the template's context changes,
+ the rendered value of the helper will update to reflect this change.
+
+ For more examples of bound helpers, see documentation for
+ `Ember.Handlebars.registerBoundHelper`.
+
+ ## Custom view helper example
+
+ Assuming a view subclass named `App.CalendarView` were defined, a helper
+ for rendering instances of this view could be registered as follows:
+
+ ```javascript
+ Ember.Handlebars.helper('calendar', App.CalendarView):
+ ```
+
+ The above bound helper can be used inside of templates as follows:
+
+ ```handlebars
+ {{calendar}}
+ ```
+
+ Which is functionally equivalent to:
+
+ ```handlebars
+ {{view App.CalendarView}}
+ ```
+
+ Options in the helper will be passed to the view in exactly the same
+ manner as with the `view` helper.
+
+ @method helper
+ @for Ember.Handlebars
+ @param {String} name
+ @param {Function|Ember.View} function or view class constructor
+ @param {String} dependentKeys*
+*/
+Ember.Handlebars.helper = function(name, value) {
+ Ember.assert("You tried to register a component named '" + name + "', but component names must include a '-'", !Ember.Component.detect(value) || name.match(/-/));
+
+ if (Ember.View.detect(value)) {
+ Ember.Handlebars.registerHelper(name, Ember.Handlebars.makeViewHelper(value));
+ } else {
+ Ember.Handlebars.registerBoundHelper.apply(null, arguments);
+ }
+};
+
+/**
+ Returns a helper function that renders the provided ViewClass.
+
+ Used internally by Ember.Handlebars.helper and other methods
+ involving helper/component registration.
+
+ @private
+ @method makeViewHelper
+ @for Ember.Handlebars
+ @param {Function} ViewClass view class constructor
+*/
+Ember.Handlebars.makeViewHelper = function(ViewClass) {
+ return function(options) {
+ Ember.assert("You can only pass attributes (such as name=value) not bare values to a helper for a View found in '" + ViewClass.toString() + "'", arguments.length < 2);
+ return Ember.Handlebars.helpers.view.call(this, ViewClass, options);
+ };
+};
+
+/**
+@class helpers
+@namespace Ember.Handlebars
+*/
+Ember.Handlebars.helpers = objectCreate(Handlebars.helpers);
+
+/**
+ Override the the opcode compiler and JavaScript compiler for Handlebars.
+
+ @class Compiler
+ @namespace Ember.Handlebars
+ @private
+ @constructor
+*/
+Ember.Handlebars.Compiler = function() {};
+
+// Handlebars.Compiler doesn't exist in runtime-only
+if (Handlebars.Compiler) {
+ Ember.Handlebars.Compiler.prototype = objectCreate(Handlebars.Compiler.prototype);
+}
+
+Ember.Handlebars.Compiler.prototype.compiler = Ember.Handlebars.Compiler;
+
+/**
+ @class JavaScriptCompiler
+ @namespace Ember.Handlebars
+ @private
+ @constructor
+*/
+Ember.Handlebars.JavaScriptCompiler = function() {};
+
+// Handlebars.JavaScriptCompiler doesn't exist in runtime-only
+if (Handlebars.JavaScriptCompiler) {
+ Ember.Handlebars.JavaScriptCompiler.prototype = objectCreate(Handlebars.JavaScriptCompiler.prototype);
+ Ember.Handlebars.JavaScriptCompiler.prototype.compiler = Ember.Handlebars.JavaScriptCompiler;
+}
+
+
+Ember.Handlebars.JavaScriptCompiler.prototype.namespace = "Ember.Handlebars";
+
+Ember.Handlebars.JavaScriptCompiler.prototype.initializeBuffer = function() {
+ return "''";
+};
+
+/**
+ Override the default buffer for Ember Handlebars. By default, Handlebars
+ creates an empty String at the beginning of each invocation and appends to
+ it. Ember's Handlebars overrides this to append to a single shared buffer.
+
+ @private
+ @method appendToBuffer
+ @param string {String}
+*/
+Ember.Handlebars.JavaScriptCompiler.prototype.appendToBuffer = function(string) {
+ return "data.buffer.push("+string+");";
+};
+
+// Hacks ahead:
+// Handlebars presently has a bug where the `blockHelperMissing` hook
+// doesn't get passed the name of the missing helper name, but rather
+// gets passed the value of that missing helper evaluated on the current
+// context, which is most likely `undefined` and totally useless.
+//
+// So we alter the compiled template function to pass the name of the helper
+// instead, as expected.
+//
+// This can go away once the following is closed:
+// https://github.com/wycats/handlebars.js/issues/634
+
+var DOT_LOOKUP_REGEX = /helpers\.(.*?)\)/,
+ BRACKET_STRING_LOOKUP_REGEX = /helpers\['(.*?)'/,
+ INVOCATION_SPLITTING_REGEX = /(.*blockHelperMissing\.call\(.*)(stack[0-9]+)(,.*)/;
+
+Ember.Handlebars.JavaScriptCompiler.stringifyLastBlockHelperMissingInvocation = function(source) {
+ var helperInvocation = source[source.length - 1],
+ helperName = (DOT_LOOKUP_REGEX.exec(helperInvocation) || BRACKET_STRING_LOOKUP_REGEX.exec(helperInvocation))[1],
+ matches = INVOCATION_SPLITTING_REGEX.exec(helperInvocation);
+
+ source[source.length - 1] = matches[1] + "'" + helperName + "'" + matches[3];
+};
+var stringifyBlockHelperMissing = Ember.Handlebars.JavaScriptCompiler.stringifyLastBlockHelperMissingInvocation;
+
+var originalBlockValue = Ember.Handlebars.JavaScriptCompiler.prototype.blockValue;
+Ember.Handlebars.JavaScriptCompiler.prototype.blockValue = function() {
+ originalBlockValue.apply(this, arguments);
+ stringifyBlockHelperMissing(this.source);
+};
+
+var originalAmbiguousBlockValue = Ember.Handlebars.JavaScriptCompiler.prototype.ambiguousBlockValue;
+Ember.Handlebars.JavaScriptCompiler.prototype.ambiguousBlockValue = function() {
+ originalAmbiguousBlockValue.apply(this, arguments);
+ stringifyBlockHelperMissing(this.source);
+};
+
+/**
+ Rewrite simple mustaches from `{{foo}}` to `{{bind "foo"}}`. This means that
+ all simple mustaches in Ember's Handlebars will also set up an observer to
+ keep the DOM up to date when the underlying property changes.
+
+ @private
+ @method mustache
+ @for Ember.Handlebars.Compiler
+ @param mustache
+*/
+Ember.Handlebars.Compiler.prototype.mustache = function(mustache) {
+ if (!(mustache.params.length || mustache.hash)) {
+ var id = new Handlebars.AST.IdNode([{ part: '_triageMustache' }]);
+
+ // Update the mustache node to include a hash value indicating whether the original node
+ // was escaped. This will allow us to properly escape values when the underlying value
+ // changes and we need to re-render the value.
+ if (!mustache.escaped) {
+ mustache.hash = mustache.hash || new Handlebars.AST.HashNode([]);
+ mustache.hash.pairs.push(["unescaped", new Handlebars.AST.StringNode("true")]);
+ }
+ mustache = new Handlebars.AST.MustacheNode([id].concat([mustache.id]), mustache.hash, !mustache.escaped);
+ }
+
+ return Handlebars.Compiler.prototype.mustache.call(this, mustache);
+};
+
+/**
+ Used for precompilation of Ember Handlebars templates. This will not be used
+ during normal app execution.
+
+ @method precompile
+ @for Ember.Handlebars
+ @static
+ @param {String} string The template to precompile
+*/
+Ember.Handlebars.precompile = function(string) {
+ var ast = Handlebars.parse(string);
+
+ var options = {
+ knownHelpers: {
+ action: true,
+ unbound: true,
+ 'bind-attr': true,
+ template: true,
+ view: true,
+ _triageMustache: true
+ },
+ data: true,
+ stringParams: true
+ };
+
+ var environment = new Ember.Handlebars.Compiler().compile(ast, options);
+ return new Ember.Handlebars.JavaScriptCompiler().compile(environment, options, undefined, true);
+};
+
+// We don't support this for Handlebars runtime-only
+if (Handlebars.compile) {
+ /**
+ The entry point for Ember Handlebars. This replaces the default
+ `Handlebars.compile` and turns on template-local data and String
+ parameters.
+
+ @method compile
+ @for Ember.Handlebars
+ @static
+ @param {String} string The template to compile
+ @return {Function}
+ */
+ Ember.Handlebars.compile = function(string) {
+ var ast = Handlebars.parse(string);
+ var options = { data: true, stringParams: true };
+ var environment = new Ember.Handlebars.Compiler().compile(ast, options);
+ var templateSpec = new Ember.Handlebars.JavaScriptCompiler().compile(environment, options, undefined, true);
+
+ var template = Ember.Handlebars.template(templateSpec);
+ template.isMethod = false; //Make sure we don't wrap templates with ._super
+
+ return template;
+ };
+}
+
+
+})();
+
+(function() {
+var slice = Array.prototype.slice,
+ originalTemplate = Ember.Handlebars.template;
+
+/**
+ If a path starts with a reserved keyword, returns the root
+ that should be used.
+
+ @private
+ @method normalizePath
+ @for Ember
+ @param root {Object}
+ @param path {String}
+ @param data {Hash}
+*/
+var normalizePath = Ember.Handlebars.normalizePath = function(root, path, data) {
+ var keywords = (data && data.keywords) || {},
+ keyword, isKeyword;
+
+ // Get the first segment of the path. For example, if the
+ // path is "foo.bar.baz", returns "foo".
+ keyword = path.split('.', 1)[0];
+
+ // Test to see if the first path is a keyword that has been
+ // passed along in the view's data hash. If so, we will treat
+ // that object as the new root.
+ if (keywords.hasOwnProperty(keyword)) {
+ // Look up the value in the template's data hash.
+ root = keywords[keyword];
+ isKeyword = true;
+
+ // Handle cases where the entire path is the reserved
+ // word. In that case, return the object itself.
+ if (path === keyword) {
+ path = '';
+ } else {
+ // Strip the keyword from the path and look up
+ // the remainder from the newly found root.
+ path = path.substr(keyword.length+1);
+ }
+ }
+
+ return { root: root, path: path, isKeyword: isKeyword };
+};
+
+
+/**
+ Lookup both on root and on window. If the path starts with
+ a keyword, the corresponding object will be looked up in the
+ template's data hash and used to resolve the path.
+
+ @method get
+ @for Ember.Handlebars
+ @param {Object} root The object to look up the property on
+ @param {String} path The path to be lookedup
+ @param {Object} options The template's option hash
+*/
+var handlebarsGet = Ember.Handlebars.get = function(root, path, options) {
+ var data = options && options.data,
+ normalizedPath = normalizePath(root, path, data),
+ value;
+
+
+ root = normalizedPath.root;
+ path = normalizedPath.path;
+
+ value = Ember.get(root, path);
+
+ if (value === undefined && root !== Ember.lookup && Ember.isGlobalPath(path)) {
+ value = Ember.get(Ember.lookup, path);
+ }
+
+
+ return value;
+};
+
+/**
+ This method uses `Ember.Handlebars.get` to lookup a value, then ensures
+ that the value is escaped properly.
+
+ If `unescaped` is a truthy value then the escaping will not be performed.
+
+ @method getEscaped
+ @for Ember.Handlebars
+ @param {Object} root The object to look up the property on
+ @param {String} path The path to be lookedup
+ @param {Object} options The template's option hash
+*/
+Ember.Handlebars.getEscaped = function(root, path, options) {
+ var result = handlebarsGet(root, path, options);
+
+ if (result === null || result === undefined) {
+ result = "";
+ } else if (!(result instanceof Handlebars.SafeString)) {
+ result = String(result);
+ }
+ if (!options.hash.unescaped){
+ result = Handlebars.Utils.escapeExpression(result);
+ }
+
+ return result;
+};
+
+Ember.Handlebars.resolveParams = function(context, params, options) {
+ var resolvedParams = [], types = options.types, param, type;
+
+ for (var i=0, l=params.length; i<l; i++) {
+ param = params[i];
+ type = types[i];
+
+ if (type === 'ID') {
+ resolvedParams.push(handlebarsGet(context, param, options));
+ } else {
+ resolvedParams.push(param);
+ }
+ }
+
+ return resolvedParams;
+};
+
+Ember.Handlebars.resolveHash = function(context, hash, options) {
+ var resolvedHash = {}, types = options.hashTypes, type;
+
+ for (var key in hash) {
+ if (!hash.hasOwnProperty(key)) { continue; }
+
+ type = types[key];
+
+ if (type === 'ID') {
+ resolvedHash[key] = handlebarsGet(context, hash[key], options);
+ } else {
+ resolvedHash[key] = hash[key];
+ }
+ }
+
+ return resolvedHash;
+};
+
+/**
+ 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('helperMissing', function(path) {
+ var error, view = "";
+
+ 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));
+ }
+
+ error = "%@ Handlebars error: Could not find property '%@' on object %@.";
+ if (options.data) {
+ view = options.data.view;
+ }
+ throw new Ember.Error(Ember.String.fmt(error, [view, path, this]));
+});
+
+/**
+ 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);
+});
+
+/**
+ Register a bound handlebars helper. Bound helpers behave similarly to regular
+ handlebars helpers, with the added ability to re-render when the underlying data
+ changes.
+
+ ## Simple example
+
+ ```javascript
+ Ember.Handlebars.registerBoundHelper('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.
+
+ ## Example with options
+
+ Like normal handlebars helpers, bound helpers have access to the options
+ passed into the helper call.
+
+ ```javascript
+ Ember.Handlebars.registerBoundHelper('repeat', function(value, options) {
+ var count = options.hash.count;
+ var a = [];
+ while(a.length < count) {
+ a.push(value);
+ }
+ return a.join('');
+ });
+ ```
+
+ This helper could be used in a template as follows:
+
+ ```handlebars
+ {{repeat text count=3}}
+ ```
+
+ ## Example with bound options
+
+ Bound hash options are also supported. Example:
+
+ ```handlebars
+ {{repeat text countBinding="numRepeats"}}
+ ```
+
+ In this example, count will be bound to the value of
+ the `numRepeats` property on the context. If that property
+ changes, the helper will be re-rendered.
+
+ ## Example with extra dependencies
+
+ The `Ember.Handlebars.registerBoundHelper` method takes a variable length
+ third parameter which indicates extra dependencies on the passed in value.
+ This allows the handlebars helper to update when these dependencies change.
+
+ ```javascript
+ Ember.Handlebars.registerBoundHelper('capitalizeName', function(value) {
+ return value.get('name').toUpperCase();
+ }, 'name');
+ ```
+
+ ## Example with multiple bound properties
+
+ `Ember.Handlebars.registerBoundHelper` supports binding to
+ multiple properties, e.g.:
+
+ ```javascript
+ Ember.Handlebars.registerBoundHelper('concatenate', function() {
+ var values = Array.prototype.slice.call(arguments, 0, -1);
+ return values.join('||');
+ });
+ ```
+
+ Which allows for template syntax such as `{{concatenate prop1 prop2}}` or
+ `{{concatenate prop1 prop2 prop3}}`. If any of the properties change,
+ the helpr will re-render. Note that dependency keys cannot be
+ using in conjunction with multi-property helpers, since it is ambiguous
+ which property the dependent keys would belong to.
+
+ ## Use with unbound helper
+
+ The `{{unbound}}` helper can be used with bound helper invocations
+ to render them in their unbound form, e.g.
+
+ ```handlebars
+ {{unbound capitalize name}}
+ ```
+
+ In this example, if the name property changes, the helper
+ will not re-render.
+
+ ## Use with blocks not supported
+
+ Bound helpers do not support use with Handlebars blocks or
+ the addition of child views of any kind.
+
+ @method registerBoundHelper
+ @for Ember.Handlebars
+ @param {String} name
+ @param {Function} function
+ @param {String} dependentKeys*
+*/
+Ember.Handlebars.registerBoundHelper = function(name, fn) {
+ var boundHelperArgs = slice.call(arguments, 1),
+ boundFn = Ember.Handlebars.makeBoundHelper.apply(this, boundHelperArgs);
+ Ember.Handlebars.registerHelper(name, boundFn);
+};
+
+/**
+ 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);
+
+ function helper() {
+ var properties = slice.call(arguments, 0, -1),
+ numProperties = properties.length,
+ options = arguments[arguments.length - 1],
+ normalizedProperties = [],
+ data = options.data,
+ types = data.isUnbound ? slice.call(options.types, 1) : options.types,
+ hash = options.hash,
+ view = data.view,
+ contexts = options.contexts,
+ currentContext = (contexts && contexts.length) ? contexts[0] : this,
+ prefixPathForDependentKeys = '',
+ loc, len, hashOption,
+ boundOption, property,
+ normalizedValue = Ember._SimpleHandlebarsView.prototype.normalizedValue;
+
+ Ember.assert("registerBoundHelper-generated helpers do not support use with Handlebars blocks.", !options.fn);
+
+ // Detect bound options (e.g. countBinding="otherCount")
+ var boundOptions = hash.boundOptions = {};
+ for (hashOption in hash) {
+ if (Ember.IS_BINDING.test(hashOption)) {
+ // Lop off 'Binding' suffix.
+ boundOptions[hashOption.slice(0, -7)] = hash[hashOption];
+ }
+ }
+
+ // Expose property names on data.properties object.
+ var watchedProperties = [];
+ data.properties = [];
+ for (loc = 0; loc < numProperties; ++loc) {
+ data.properties.push(properties[loc]);
+ 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);
+ }
+ }
+ }
+
+ // Handle case when helper invocation is preceded by `unbound`, e.g.
+ // {{unbound myHelper foo}}
+ if (data.isUnbound) {
+ return evaluateUnboundHelper(this, fn, normalizedProperties, options);
+ }
+
+ var bindView = new Ember._SimpleHandlebarsView(null, null, !options.hash.unescaped, options.data);
+
+ // Override SimpleHandlebarsView's method for generating the view's content.
+ bindView.normalizedValue = function() {
+ var args = [], boundOption;
+
+ // 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);
+ }
+
+ 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);
+
+ // Run the supplied helper function.
+ return fn.apply(currentContext, args);
+ };
+
+ view.appendChild(bindView);
+
+ // 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));
+ }
+ }
+
+ // Observe each property.
+ for (loc = 0, len = watchedProperties.length; loc < len; ++loc) {
+ property = watchedProperties[loc];
+ view.registerObserver(property.root, property.path, bindView, bindView.rerender);
+ }
+
+ if (types[0] !== 'ID' || normalizedProperties.length === 0) {
+ return;
+ }
+
+ // Add dependent key observers to the first param
+ var normalized = normalizedProperties[0],
+ pathRoot = normalized.root,
+ path = normalized.path;
+
+ if(!Ember.isEmpty(path)) {
+ prefixPathForDependentKeys = path + '.';
+ }
+ for (var i=0, l=dependentKeys.length; i<l; i++) {
+ view.registerObserver(pathRoot, prefixPathForDependentKeys + dependentKeys[i], bindView, bindView.rerender);
+ }
+ }
+
+ helper._rawFunction = fn;
+ return helper;
+};
+
+/**
+ Renders the unbound form of an otherwise bound helper function.
+
+ @private
+ @method evaluateUnboundHelper
+ @param {Function} fn
+ @param {Object} context
+ @param {Array} normalizedProperties
+ @param {String} options
+*/
+function evaluateUnboundHelper(context, fn, normalizedProperties, options) {
+ var args = [],
+ hash = options.hash,
+ boundOptions = hash.boundOptions,
+ types = slice.call(options.types, 1),
+ loc,
+ len,
+ property,
+ propertyType,
+ boundOption;
+
+ for (boundOption in boundOptions) {
+ if (!boundOptions.hasOwnProperty(boundOption)) { continue; }
+ hash[boundOption] = Ember.Handlebars.get(context, boundOptions[boundOption], options);
+ }
+
+ for(loc = 0, len = normalizedProperties.length; loc < len; ++loc) {
+ property = normalizedProperties[loc];
+ propertyType = types[loc];
+ if(propertyType === "ID") {
+ args.push(Ember.Handlebars.get(property.root, property.path, options));
+ } else {
+ args.push(property.path);
+ }
+ }
+ args.push(options);
+ return fn.apply(context, args);
+}
+
+/**
+ Overrides Handlebars.template so that we can distinguish
+ user-created, top-level templates from inner contexts.
+
+ @private
+ @method template
+ @for Ember.Handlebars
+ @param {String} spec
+*/
+Ember.Handlebars.template = function(spec) {
+ var t = originalTemplate(spec);
+ t.isTop = true;
+ return t;
+};
+
+})();
+
+
+
+(function() {
+/**
+ 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>')
+ ```
+
+ @method htmlSafe
+ @for Ember.String
+ @static
+ @return {Handlebars.SafeString} a string that will not be html escaped by Handlebars
+*/
+Ember.String.htmlSafe = function(str) {
+ return new Handlebars.SafeString(str);
+};
+
+var htmlSafe = Ember.String.htmlSafe;
+
+if (Ember.EXTEND_PROTOTYPES === true || Ember.EXTEND_PROTOTYPES.String) {
+
+ /**
+ Mark a string as being safe for unescaped output with Handlebars.
+
+ ```javascript
+ '<div>someString</div>'.htmlSafe()
+ ```
+
+ See [Ember.String.htmlSafe](/api/classes/Ember.String.html#method_htmlSafe).
+
+ @method htmlSafe
+ @for String
+ @return {Handlebars.SafeString} a string that will not be html escaped by Handlebars
+ */
+ String.prototype.htmlSafe = function() {
+ return htmlSafe(this);
+ };
+}
+
+})();
+
+
+
+(function() {
+Ember.Handlebars.resolvePaths = function(options) {
+ var ret = [],
+ contexts = options.contexts,
+ roots = options.roots,
+ data = options.data;
+
+ for (var i=0, l=contexts.length; i<l; i++) {
+ ret.push( Ember.Handlebars.get(roots[i], contexts[i], { data: data }) );
+ }
+
+ return ret;
+};
+
+})();
+
+
+
+(function() {
+/*jshint newcap:false*/
+/**
+@module ember
+@submodule ember-handlebars
+*/
+
+var set = Ember.set, get = Ember.get;
+var Metamorph = requireModule('metamorph');
+
+function notifyMutationListeners() {
+ Ember.run.once(Ember.View, 'notifyMutationListeners');
+}
+
+// DOMManager should just abstract dom manipulation between jquery and metamorph
+var DOMManager = {
+ remove: function(view) {
+ view.morph.remove();
+ notifyMutationListeners();
+ },
+
+ prepend: function(view, html) {
+ view.morph.prepend(html);
+ notifyMutationListeners();
+ },
+
+ after: function(view, html) {
+ view.morph.after(html);
+ notifyMutationListeners();
+ },
+
+ html: function(view, html) {
+ view.morph.html(html);
+ notifyMutationListeners();
+ },
+
+ // This is messed up.
+ replace: function(view) {
+ var morph = view.morph;
+
+ view.transitionTo('preRender');
+
+ Ember.run.schedule('render', this, function renderMetamorphView() {
+ if (view.isDestroying) { return; }
+
+ view.clearRenderedChildren();
+ var buffer = view.renderToBuffer();
+
+ view.invokeRecursively(function(view) {
+ view.propertyWillChange('element');
+ });
+ view.triggerRecursively('willInsertElement');
+
+ morph.replaceWith(buffer.string());
+ view.transitionTo('inDOM');
+
+ view.invokeRecursively(function(view) {
+ view.propertyDidChange('element');
+ });
+ view.triggerRecursively('didInsertElement');
+
+ notifyMutationListeners();
+ });
+ },
+
+ empty: function(view) {
+ view.morph.html("");
+ notifyMutationListeners();
+ }
+};
+
+// The `morph` and `outerHTML` properties are internal only
+// and not observable.
+
+/**
+ @class _Metamorph
+ @namespace Ember
+ @private
+*/
+Ember._Metamorph = Ember.Mixin.create({
+ isVirtual: true,
+ tagName: '',
+
+ instrumentName: 'metamorph',
+
+ init: function() {
+ this._super();
+ this.morph = Metamorph();
+ 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);
+ },
+
+ beforeRender: function(buffer) {
+ buffer.push(this.morph.startTag());
+ buffer.pushOpeningTag();
+ },
+
+ afterRender: function(buffer) {
+ buffer.pushClosingTag();
+ buffer.push(this.morph.endTag());
+ },
+
+ createElement: function() {
+ var buffer = this.renderToBuffer();
+ this.outerHTML = buffer.string();
+ this.clearBuffer();
+ },
+
+ domManager: DOMManager
+});
+
+/**
+ @class _MetamorphView
+ @namespace Ember
+ @extends Ember.View
+ @uses Ember._Metamorph
+ @private
+*/
+Ember._MetamorphView = Ember.View.extend(Ember._Metamorph);
+
+/**
+ @class _SimpleMetamorphView
+ @namespace Ember
+ @extends Ember.CoreView
+ @uses Ember._Metamorph
+ @private
+*/
+Ember._SimpleMetamorphView = Ember.CoreView.extend(Ember._Metamorph);
+
+
+})();
+
+
+
+(function() {
+/*globals Handlebars */
+/*jshint newcap:false*/
+/**
+@module ember
+@submodule ember-handlebars
+*/
+
+var get = Ember.get, set = Ember.set, handlebarsGet = Ember.Handlebars.get;
+var Metamorph = requireModule('metamorph');
+function SimpleHandlebarsView(path, pathRoot, isEscaped, templateData) {
+ this.path = path;
+ this.pathRoot = pathRoot;
+ this.isEscaped = isEscaped;
+ this.templateData = templateData;
+
+ this.morph = Metamorph();
+ this.state = 'preRender';
+ this.updateId = null;
+ this._parentView = null;
+ this.buffer = null;
+}
+
+Ember._SimpleHandlebarsView = SimpleHandlebarsView;
+
+SimpleHandlebarsView.prototype = {
+ isVirtual: true,
+ isView: true,
+
+ destroy: function () {
+ if (this.updateId) {
+ Ember.run.cancel(this.updateId);
+ this.updateId = null;
+ }
+ if (this._parentView) {
+ this._parentView.removeChild(this);
+ }
+ this.morph = null;
+ this.state = 'destroyed';
+ },
+
+ propertyWillChange: Ember.K,
+
+ propertyDidChange: Ember.K,
+
+ normalizedValue: function() {
+ var path = this.path,
+ pathRoot = this.pathRoot,
+ result, templateData;
+
+ // Use the pathRoot as the result if no path is provided. This
+ // happens if the path is `this`, which gets normalized into
+ // a `pathRoot` of the current Handlebars context and a path
+ // of `''`.
+ if (path === '') {
+ result = pathRoot;
+ } else {
+ templateData = this.templateData;
+ result = handlebarsGet(pathRoot, path, { data: templateData });
+ }
+
+ return result;
+ },
+
+ renderToBuffer: function(buffer) {
+ var string = '';
+
+ string += this.morph.startTag();
+ string += this.render();
+ string += this.morph.endTag();
+
+ buffer.push(string);
+ },
+
+ render: function() {
+ // If not invoked via a triple-mustache ({{{foo}}}), escape
+ // the content of the template.
+ var escape = this.isEscaped;
+ var result = this.normalizedValue();
+
+ if (result === null || result === undefined) {
+ result = "";
+ } else if (!(result instanceof Handlebars.SafeString)) {
+ result = String(result);
+ }
+
+ if (escape) { result = Handlebars.Utils.escapeExpression(result); }
+ return result;
+ },
+
+ rerender: function() {
+ switch(this.state) {
+ case 'preRender':
+ case 'destroyed':
+ break;
+ case 'inBuffer':
+ throw new Ember.Error("Something you did tried to replace an {{expression}} before it was inserted into the DOM.");
+ case 'hasElement':
+ case 'inDOM':
+ this.updateId = Ember.run.scheduleOnce('render', this, 'update');
+ break;
+ }
+
+ return this;
+ },
+
+ update: function () {
+ this.updateId = null;
+ this.morph.html(this.render());
+ },
+
+ transitionTo: function(state) {
+ this.state = state;
+ }
+};
+
+var states = Ember.View.cloneStates(Ember.View.states), merge = Ember.merge;
+
+merge(states._default, {
+ rerenderIfNeeded: Ember.K
+});
+
+merge(states.inDOM, {
+ rerenderIfNeeded: function(view) {
+ if (view.normalizedValue() !== view._lastNormalizedValue) {
+ view.rerender();
+ }
+ }
+});
+
+/**
+ `Ember._HandlebarsBoundView` is a private view created by the Handlebars
+ `{{bind}}` helpers that is used to keep track of bound properties.
+
+ Every time a property is bound using a `{{mustache}}`, an anonymous subclass
+ of `Ember._HandlebarsBoundView` is created with the appropriate sub-template
+ and context set up. When the associated property changes, just the template
+ for this view will re-render.
+
+ @class _HandlebarsBoundView
+ @namespace Ember
+ @extends Ember._MetamorphView
+ @private
+*/
+Ember._HandlebarsBoundView = Ember._MetamorphView.extend({
+ instrumentName: 'boundHandlebars',
+ states: states,
+
+ /**
+ The function used to determine if the `displayTemplate` or
+ `inverseTemplate` should be rendered. This should be a function that takes
+ a value and returns a Boolean.
+
+ @property shouldDisplayFunc
+ @type Function
+ @default null
+ */
+ shouldDisplayFunc: null,
+
+ /**
+ Whether the template rendered by this view gets passed the context object
+ of its parent template, or gets passed the value of retrieving `path`
+ from the `pathRoot`.
+
+ For example, this is true when using the `{{#if}}` helper, because the
+ template inside the helper should look up properties relative to the same
+ object as outside the block. This would be `false` when used with `{{#with
+ foo}}` because the template should receive the object found by evaluating
+ `foo`.
+
+ @property preserveContext
+ @type Boolean
+ @default false
+ */
+ preserveContext: false,
+
+ /**
+ If `preserveContext` is true, this is the object that will be used
+ to render the template.
+
+ @property previousContext
+ @type Object
+ */
+ previousContext: null,
+
+ /**
+ The template to render when `shouldDisplayFunc` evaluates to `true`.
+
+ @property displayTemplate
+ @type Function
+ @default null
+ */
+ displayTemplate: null,
+
+ /**
+ The template to render when `shouldDisplayFunc` evaluates to `false`.
+
+ @property inverseTemplate
+ @type Function
+ @default null
+ */
+ inverseTemplate: null,
+
+
+ /**
+ The path to look up on `pathRoot` that is passed to
+ `shouldDisplayFunc` to determine which template to render.
+
+ In addition, if `preserveContext` is `false,` the object at this path will
+ be passed to the template when rendering.
+
+ @property path
+ @type String
+ @default null
+ */
+ path: null,
+
+ /**
+ The object from which the `path` will be looked up. Sometimes this is the
+ same as the `previousContext`, but in cases where this view has been
+ generated for paths that start with a keyword such as `view` or
+ `controller`, the path root will be that resolved object.
+
+ @property pathRoot
+ @type Object
+ */
+ pathRoot: null,
+
+ normalizedValue: function() {
+ var path = get(this, 'path'),
+ pathRoot = get(this, 'pathRoot'),
+ valueNormalizer = get(this, 'valueNormalizerFunc'),
+ result, templateData;
+
+ // Use the pathRoot as the result if no path is provided. This
+ // happens if the path is `this`, which gets normalized into
+ // a `pathRoot` of the current Handlebars context and a path
+ // of `''`.
+ if (path === '') {
+ result = pathRoot;
+ } else {
+ templateData = get(this, 'templateData');
+ result = handlebarsGet(pathRoot, path, { data: templateData });
+ }
+
+ return valueNormalizer ? valueNormalizer(result) : result;
+ },
+
+ rerenderIfNeeded: function() {
+ this.currentState.rerenderIfNeeded(this);
+ },
+
+ /**
+ Determines which template to invoke, sets up the correct state based on
+ that logic, then invokes the default `Ember.View` `render` implementation.
+
+ This method will first look up the `path` key on `pathRoot`,
+ then pass that value to the `shouldDisplayFunc` function. If that returns
+ `true,` the `displayTemplate` function will be rendered to DOM. Otherwise,
+ `inverseTemplate`, if specified, will be rendered.
+
+ For example, if this `Ember._HandlebarsBoundView` represented the `{{#with
+ foo}}` helper, it would look up the `foo` property of its context, and
+ `shouldDisplayFunc` would always return true. The object found by looking
+ up `foo` would be passed to `displayTemplate`.
+
+ @method render
+ @param {Ember.RenderBuffer} buffer
+ */
+ render: function(buffer) {
+ // If not invoked via a triple-mustache ({{{foo}}}), escape
+ // the content of the template.
+ var escape = get(this, 'isEscaped');
+
+ var shouldDisplay = get(this, 'shouldDisplayFunc'),
+ preserveContext = get(this, 'preserveContext'),
+ context = get(this, 'previousContext');
+
+ var _contextController = get(this, '_contextController');
+
+ var inverseTemplate = get(this, 'inverseTemplate'),
+ displayTemplate = get(this, 'displayTemplate');
+
+ var result = this.normalizedValue();
+ this._lastNormalizedValue = result;
+
+ // First, test the conditional to see if we should
+ // render the template or not.
+ if (shouldDisplay(result)) {
+ set(this, 'template', displayTemplate);
+
+ // If we are preserving the context (for example, if this
+ // is an #if block, call the template with the same object.
+ if (preserveContext) {
+ set(this, '_context', context);
+ } else {
+ // Otherwise, determine if this is a block bind or not.
+ // If so, pass the specified object to the template
+ if (displayTemplate) {
+ if (_contextController) {
+ set(_contextController, 'content', result);
+ result = _contextController;
+ }
+ set(this, '_context', result);
+ } else {
+ // This is not a bind block, just push the result of the
+ // expression to the render context and return.
+ if (result === null || result === undefined) {
+ result = "";
+ } else if (!(result instanceof Handlebars.SafeString)) {
+ result = String(result);
+ }
+
+ if (escape) { result = Handlebars.Utils.escapeExpression(result); }
+ buffer.push(result);
+ return;
+ }
+ }
+ } else if (inverseTemplate) {
+ set(this, 'template', inverseTemplate);
+
+ if (preserveContext) {
+ set(this, '_context', context);
+ } else {
+ set(this, '_context', result);
+ }
+ } else {
+ set(this, 'template', function() { return ''; });
+ }
+
+ return this._super(buffer);
+ }
+});
+
+})();
+
+
+
+(function() {
+/**
+@module ember
+@submodule ember-handlebars
+*/
+
+var get = Ember.get, set = Ember.set, fmt = Ember.String.fmt;
+var handlebarsGet = Ember.Handlebars.get, normalizePath = Ember.Handlebars.normalizePath;
+var handlebarsGetEscaped = Ember.Handlebars.getEscaped;
+var forEach = Ember.ArrayPolyfills.forEach;
+var o_create = Ember.create;
+
+var EmberHandlebars = Ember.Handlebars, helpers = EmberHandlebars.helpers;
+
+function exists(value) {
+ return !Ember.isNone(value);
+}
+
+// Binds a property into the DOM. This will create a hook in DOM that the
+// KVO system will look for and update if the property changes.
+function bind(property, options, preserveContext, shouldDisplay, valueNormalizer, childProperties) {
+ var data = options.data,
+ fn = options.fn,
+ inverse = options.inverse,
+ view = data.view,
+ currentContext = this,
+ normalized, observer, i;
+
+ normalized = normalizePath(currentContext, property, data);
+
+ // Set up observers for observable objects
+ if ('object' === typeof this) {
+ if (data.insideGroup) {
+ observer = function() {
+ Ember.run.once(view, 'rerender');
+ };
+
+ var template, context, result = handlebarsGet(currentContext, property, options);
+
+ result = valueNormalizer ? valueNormalizer(result) : result;
+
+ context = preserveContext ? currentContext : result;
+ if (shouldDisplay(result)) {
+ template = fn;
+ } else if (inverse) {
+ template = inverse;
+ }
+
+ template(context, { data: options.data });
+ } else {
+ // Create the view that will wrap the output of this template/property
+ // and add it to the nearest view's childViews array.
+ // See the documentation of Ember._HandlebarsBoundView for more.
+ var bindView = view.createChildView(Ember._HandlebarsBoundView, {
+ preserveContext: preserveContext,
+ shouldDisplayFunc: shouldDisplay,
+ valueNormalizerFunc: valueNormalizer,
+ displayTemplate: fn,
+ inverseTemplate: inverse,
+ path: property,
+ pathRoot: currentContext,
+ previousContext: currentContext,
+ isEscaped: !options.hash.unescaped,
+ templateData: options.data
+ });
+
+ if (options.hash.controller) {
+ bindView.set('_contextController', this.container.lookupFactory('controller:'+options.hash.controller).create({
+ container: currentContext.container,
+ parentController: currentContext,
+ target: currentContext
+ }));
+ }
+
+ view.appendChild(bindView);
+
+ observer = function() {
+ Ember.run.scheduleOnce('render', bindView, 'rerenderIfNeeded');
+ };
+ }
+
+ // Observes the given property on the context and
+ // tells the Ember._HandlebarsBoundView to re-render. If property
+ // is an empty string, we are printing the current context
+ // object ({{this}}) so updating it is not our responsibility.
+ if (normalized.path !== '') {
+ view.registerObserver(normalized.root, normalized.path, observer);
+ if (childProperties) {
+ for (i=0; i<childProperties.length; i++) {
+ view.registerObserver(normalized.root, normalized.path+'.'+childProperties[i], observer);
+ }
+ }
+ }
+ } else {
+ // The object is not observable, so just render it out and
+ // be done with it.
+ data.buffer.push(handlebarsGetEscaped(currentContext, property, options));
+ }
+}
+
+EmberHandlebars.bind = bind;
+
+function simpleBind(currentContext, property, options) {
+ var data = options.data,
+ view = data.view,
+ normalized, observer, pathRoot, output;
+
+ normalized = normalizePath(currentContext, property, data);
+ pathRoot = normalized.root;
+
+ // Set up observers for observable objects
+ if (pathRoot && ('object' === typeof pathRoot)) {
+ if (data.insideGroup) {
+ observer = function() {
+ Ember.run.once(view, 'rerender');
+ };
+
+ output = handlebarsGetEscaped(currentContext, property, options);
+
+ data.buffer.push(output);
+ } else {
+ var bindView = new Ember._SimpleHandlebarsView(
+ property, currentContext, !options.hash.unescaped, options.data
+ );
+
+ bindView._parentView = view;
+ view.appendChild(bindView);
+
+ observer = function() {
+ Ember.run.scheduleOnce('render', bindView, 'rerender');
+ };
+ }
+
+ // Observes the given property on the context and
+ // tells the Ember._HandlebarsBoundView to re-render. If property
+ // is an empty string, we are printing the current context
+ // object ({{this}}) so updating it is not our responsibility.
+ if (normalized.path !== '') {
+ view.registerObserver(normalized.root, normalized.path, observer);
+ }
+ } else {
+ // The object is not observable, so just render it out and
+ // be done with it.
+ output = handlebarsGetEscaped(currentContext, property, options);
+ data.buffer.push(output);
+ }
+}
+
+function shouldDisplayIfHelperContent(result) {
+ var truthy = result && get(result, 'isTruthy');
+ if (typeof truthy === 'boolean') { return truthy; }
+
+ if (Ember.isArray(result)) {
+ return get(result, 'length') !== 0;
+ } else {
+ return !!result;
+ }
+}
+
+/**
+ '_triageMustache' is used internally select between a binding, helper, or component for
+ the given context. Until this point, it would be hard to determine if the
+ mustache is a property reference or a regular helper reference. This triage
+ helper resolves that.
+
+ This would not be typically invoked by directly.
+
+ @private
+ @method _triageMustache
+ @for Ember.Handlebars.helpers
+ @param {String} property Property/helperID to triage
+ @param {Object} options hash of template/rendering options
+ @return {String} HTML string
+*/
+EmberHandlebars.registerHelper('_triageMustache', function(property, options) {
+ Ember.assert("You cannot pass more than one argument to the _triageMustache helper", arguments.length <= 2);
+
+ if (helpers[property]) {
+ return helpers[property].call(this, options);
+ }
+
+ var helper = Ember.Handlebars.resolveHelper(options.data.view.container, property);
+ if (helper) {
+ return helper.call(this, options);
+ }
+
+ return helpers.bind.call(this, property, options);
+});
+
+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;
+};
+
+/**
+ `bind` can be used to display a value, then update that value if it
+ changes. For example, if you wanted to print the `title` property of
+ `content`:
+
+ ```handlebars
+ {{bind "content.title"}}
+ ```
+
+ This will return the `title` property as a string, then create a new observer
+ at the specified path. If it changes, it will update the value in DOM. Note
+ that if you need to support IE7 and IE8 you must modify the model objects
+ properties using `Ember.get()` and `Ember.set()` for this to work as it
+ relies on Ember's KVO system. For all other browsers this will be handled for
+ you automatically.
+
+ @private
+ @method bind
+ @for Ember.Handlebars.helpers
+ @param {String} property Property to bind
+ @param {Function} fn Context to provide for rendering
+ @return {String} HTML string
+*/
+EmberHandlebars.registerHelper('bind', function bindHelper(property, options) {
+ Ember.assert("You cannot pass more than one argument to the bind helper", arguments.length <= 2);
+
+ var context = (options.contexts && options.contexts.length) ? options.contexts[0] : this;
+
+ if (!options.fn) {
+ return simpleBind(context, property, options);
+ }
+
+ return bind.call(context, property, options, false, exists);
+});
+
+/**
+ Use the `boundIf` helper to create a conditional that re-evaluates
+ whenever the truthiness of the bound value changes.
+
+ ```handlebars
+ {{#boundIf "content.shouldDisplayTitle"}}
+ {{content.title}}
+ {{/boundIf}}
+ ```
+
+ @private
+ @method boundIf
+ @for Ember.Handlebars.helpers
+ @param {String} property Property to bind
+ @param {Function} fn Context to provide for rendering
+ @return {String} HTML string
+*/
+EmberHandlebars.registerHelper('boundIf', function boundIfHelper(property, fn) {
+ var context = (fn.contexts && fn.contexts.length) ? fn.contexts[0] : this;
+
+ return bind.call(context, property, fn, true, shouldDisplayIfHelperContent, shouldDisplayIfHelperContent, ['isTruthy', 'length']);
+});
+
+
+/**
+ @private
+
+ Use the `unboundIf` helper to create a conditional that evaluates once.
+
+ ```handlebars
+ {{#unboundIf "content.shouldDisplayTitle"}}
+ {{content.title}}
+ {{/unboundIf}}
+ ```
+
+ @method unboundIf
+ @for Ember.Handlebars.helpers
+ @param {String} property Property to bind
+ @param {Function} fn Context to provide for rendering
+ @return {String} HTML string
+*/
+EmberHandlebars.registerHelper('unboundIf', function unboundIfHelper(property, fn) {
+ var context = (fn.contexts && fn.contexts.length) ? fn.contexts[0] : this,
+ data = fn.data,
+ template = fn.fn,
+ inverse = fn.inverse,
+ normalized, propertyValue, result;
+
+ normalized = normalizePath(context, property, data);
+ propertyValue = handlebarsGet(context, property, fn);
+
+ if (!shouldDisplayIfHelperContent(propertyValue)) {
+ template = inverse;
+ }
+
+ template(context, { data: data });
+});
+
+/**
+ 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.
+
+ NOTE: The alias should not reuse a name from the bound property path.
+ For example: `{{#with foo.bar as foo}}` is not supported because it attempts to alias using
+ the first part of the property path, `foo`. Instead, use `{{#with foo.bar as baz}}`.
+
+ ### `controller` option
+
+ Adding `controller='something'` instructs the `{{with}}` helper to create and use an instance of
+ the specified controller with the new context as its content.
+
+ This is very similar to using an `itemController` option with the `{{each}}` helper.
+
+ ```handlebars
+ {{#with users.posts controller='userBlogPosts'}}
+ {{!- The current context is wrapped in our controller instance }}
+ {{/with}}
+ ```
+
+ In the above example, the template provided to the `{{with}}` block is now wrapped in the
+ `userBlogPost` controller, which provides a very elegant way to decorate the context with custom
+ functions/properties.
+
+ @method with
+ @for Ember.Handlebars.helpers
+ @param {Function} context
+ @param {Hash} options
+ @return {String} HTML string
+*/
+EmberHandlebars.registerHelper('with', function withHelper(context, options) {
+ if (arguments.length === 4) {
+ var keywordName, path, rootPath, normalized, contextPath;
+
+ Ember.assert("If you pass more than one argument to the with helper, it must be in the form #with foo as bar", arguments[1] === "as");
+ options = arguments[3];
+ keywordName = arguments[2];
+ path = arguments[0];
+
+ Ember.assert("You must pass a block to the with helper", options.fn && options.fn !== Handlebars.VM.noop);
+
+ var localizedOptions = o_create(options);
+ localizedOptions.data = o_create(options.data);
+ localizedOptions.data.keywords = o_create(options.data.keywords || {});
+
+ if (Ember.isGlobalPath(path)) {
+ contextPath = path;
+ } else {
+ normalized = normalizePath(this, path, options.data);
+ path = normalized.path;
+ rootPath = normalized.root;
+
+ // This is a workaround for the fact that you cannot bind separate objects
+ // together. When we implement that functionality, we should use it here.
+ var contextKey = Ember.$.expando + Ember.guidFor(rootPath);
+ localizedOptions.data.keywords[contextKey] = rootPath;
+ // if the path is '' ("this"), just bind directly to the current context
+ contextPath = path ? contextKey + '.' + path : contextKey;
+ }
+
+ Ember.bind(localizedOptions.data.keywords, keywordName, contextPath);
+
+ return bind.call(this, path, localizedOptions, true, exists);
+ } else {
+ Ember.assert("You must pass exactly one argument to the with helper", arguments.length === 2);
+ Ember.assert("You must pass a block to the with helper", options.fn && options.fn !== Handlebars.VM.noop);
+ return helpers.bind.call(options.contexts[0], context, options);
+ }
+});
+
+
+/**
+ See [boundIf](/api/classes/Ember.Handlebars.helpers.html#method_boundIf)
+ and [unboundIf](/api/classes/Ember.Handlebars.helpers.html#method_unboundIf)
+
+ @method if
+ @for Ember.Handlebars.helpers
+ @param {Function} context
+ @param {Hash} options
+ @return {String} HTML string
+*/
+EmberHandlebars.registerHelper('if', function ifHelper(context, options) {
+ Ember.assert("You must pass exactly one argument to the if helper", arguments.length === 2);
+ Ember.assert("You must pass a block to the if helper", options.fn && options.fn !== Handlebars.VM.noop);
+ if (options.data.isUnbound) {
+ return helpers.unboundIf.call(options.contexts[0], context, options);
+ } else {
+ return helpers.boundIf.call(options.contexts[0], context, options);
+ }
+});
+
+/**
+ @method unless
+ @for Ember.Handlebars.helpers
+ @param {Function} context
+ @param {Hash} options
+ @return {String} HTML string
+*/
+EmberHandlebars.registerHelper('unless', function unlessHelper(context, options) {
+ Ember.assert("You must pass exactly one argument to the unless helper", arguments.length === 2);
+ Ember.assert("You must pass a block to the unless helper", options.fn && options.fn !== Handlebars.VM.noop);
+
+ var fn = options.fn, inverse = options.inverse;
+
+ options.fn = inverse;
+ options.inverse = fn;
+
+ if (options.data.isUnbound) {
+ return helpers.unboundIf.call(options.contexts[0], context, options);
+ } else {
+ return helpers.boundIf.call(options.contexts[0], context, options);
+ }
+});
+
+/**
+ `bind-attr` allows you to create a binding between DOM element attributes and
+ Ember objects. For example:
+
+ ```handlebars
+ <img {{bind-attr src="imageUrl" alt="imageTitle"}}>
+ ```
+
+ The above handlebars template will fill the `<img>`'s `src` attribute will
+ the value of the property referenced with `"imageUrl"` and its `alt`
+ attribute with the value of the property referenced with `"imageTitle"`.
+
+ If the rendering context of this template is the following object:
+
+ ```javascript
+ {
+ imageUrl: 'http://lolcats.info/haz-a-funny',
+ imageTitle: 'A humorous image of a cat'
+ }
+ ```
+
+ The resulting HTML output will be:
+
+ ```html
+ <img src="http://lolcats.info/haz-a-funny" alt="A humorous image of a cat">
+ ```
+
+ `bind-attr` cannot redeclare existing DOM element attributes. The use of `src`
+ in the following `bind-attr` example will be ignored and the hard coded value
+ of `src="/failwhale.gif"` will take precedence:
+
+ ```handlebars
+ <img src="/failwhale.gif" {{bind-attr src="imageUrl" alt="imageTitle"}}>
+ ```
+
+ ### `bind-attr` and the `class` attribute
+
+ `bind-attr` supports a special syntax for handling a number of cases unique
+ to the `class` DOM element attribute. The `class` attribute combines
+ multiple discrete values into a single attribute as a space-delimited
+ list of strings. Each string can be:
+
+ * a string return value of an object's property.
+ * a boolean return value of an object's property
+ * a hard-coded value
+
+ A string return value works identically to other uses of `bind-attr`. The
+ return value of the property will become the value of the attribute. For
+ example, the following view and template:
+
+ ```javascript
+ AView = Ember.View.extend({
+ someProperty: function() {
+ return "aValue";
+ }.property()
+ })
+ ```
+
+ ```handlebars
+ <img {{bind-attr class="view.someProperty}}>
+ ```
+
+ Result in the following rendered output:
+
+ ```html
+ <img class="aValue">
+ ```
+
+ A boolean return value will insert a specified class name if the property
+ returns `true` and remove the class name if the property returns `false`.
+
+ A class name is provided via the syntax
+ `somePropertyName:class-name-if-true`.
+
+ ```javascript
+ AView = Ember.View.extend({
+ someBool: true
+ })
+ ```
+
+ ```handlebars
+ <img {{bind-attr class="view.someBool:class-name-if-true"}}>
+ ```
+
+ Result in the following rendered output:
+
+ ```html
+ <img class="class-name-if-true">
+ ```
+
+ An additional section of the binding can be provided if you want to
+ replace the existing class instead of removing it when the boolean
+ value changes:
+
+ ```handlebars
+ <img {{bind-attr class="view.someBool:class-name-if-true:class-name-if-false"}}>
+ ```
+
+ A hard-coded value can be used by prepending `:` to the desired
+ class name: `:class-name-to-always-apply`.
+
+ ```handlebars
+ <img {{bind-attr class=":class-name-to-always-apply"}}>
+ ```
+
+ Results in the following rendered output:
+
+ ```html
+ <img class="class-name-to-always-apply">
+ ```
+
+ All three strategies - string return value, boolean return value, and
+ hard-coded value – can be combined in a single declaration:
+
+ ```handlebars
+ <img {{bind-attr class=":class-name-to-always-apply view.someBool:class-name-if-true view.someProperty"}}>
+ ```
+
+ @method bind-attr
+ @for Ember.Handlebars.helpers
+ @param {Hash} options
+ @return {String} HTML string
+*/
+EmberHandlebars.registerHelper('bind-attr', function bindAttrHelper(options) {
+
+ var attrs = options.hash;
+
+ Ember.assert("You must specify at least one hash argument to bind-attr", !!Ember.keys(attrs).length);
+
+ var view = options.data.view;
+ var ret = [];
+ var ctx = this;
+
+ // Generate a unique id for this element. This will be added as a
+ // data attribute to the element so it can be looked up when
+ // the bound property changes.
+ var dataId = ++Ember.uuid;
+
+ // Handle classes differently, as we can bind multiple classes
+ var classBindings = attrs['class'];
+ if (classBindings != null) {
+ var classResults = EmberHandlebars.bindClasses(this, classBindings, view, dataId, options);
+
+ ret.push('class="' + Handlebars.Utils.escapeExpression(classResults.join(' ')) + '"');
+ delete attrs['class'];
+ }
+
+ var attrKeys = Ember.keys(attrs);
+
+ // For each attribute passed, create an observer and emit the
+ // current value of the property as an attribute.
+ forEach.call(attrKeys, function(attr) {
+ var path = attrs[attr],
+ normalized;
+
+ Ember.assert(fmt("You must provide an expression as the value of bound attribute. You specified: %@=%@", [attr, path]), typeof path === 'string');
+
+ normalized = normalizePath(ctx, path, options.data);
+
+ var value = (path === 'this') ? normalized.root : handlebarsGet(ctx, path, options),
+ type = Ember.typeOf(value);
+
+ Ember.assert(fmt("Attributes must be numbers, strings or booleans, not %@", [value]), value === null || value === undefined || type === 'number' || type === 'string' || type === 'boolean');
+
+ var observer, invoker;
+
+ observer = function observer() {
+ var result = handlebarsGet(ctx, path, options);
+
+ Ember.assert(fmt("Attributes must be numbers, strings or booleans, not %@", [result]),
+ result === null || result === undefined || typeof result === 'number' ||
+ typeof result === 'string' || typeof result === 'boolean');
+
+ var elem = view.$("[data-bindattr-" + dataId + "='" + dataId + "']");
+
+ // If we aren't able to find the element, it means the element
+ // to which we were bound has been removed from the view.
+ // In that case, we can assume the template has been re-rendered
+ // and we need to clean up the observer.
+ if (!elem || elem.length === 0) {
+ Ember.removeObserver(normalized.root, normalized.path, invoker);
+ return;
+ }
+
+ Ember.View.applyAttributeBindings(elem, attr, result);
+ };
+
+ // Add an observer to the view for when the property changes.
+ // When the observer fires, find the element using the
+ // unique data id and update the attribute to the new value.
+ // Note: don't add observer when path is 'this' or path
+ // is whole keyword e.g. {{#each x in list}} ... {{bind-attr attr="x"}}
+ if (path !== 'this' && !(normalized.isKeyword && normalized.path === '' )) {
+ view.registerObserver(normalized.root, normalized.path, observer);
+ }
+
+ // if this changes, also change the logic in ember-views/lib/views/view.js
+ if ((type === 'string' || (type === 'number' && !isNaN(value)))) {
+ ret.push(attr + '="' + Handlebars.Utils.escapeExpression(value) + '"');
+ } else if (value && type === 'boolean') {
+ // The developer controls the attr name, so it should always be safe
+ ret.push(attr + '="' + attr + '"');
+ }
+ }, this);
+
+ // Add the unique identifier
+ // NOTE: We use all lower-case since Firefox has problems with mixed case in SVG
+ ret.push('data-bindattr-' + dataId + '="' + dataId + '"');
+ return new EmberHandlebars.SafeString(ret.join(' '));
+});
+
+/**
+ See `bind-attr`
+
+ @method bindAttr
+ @for Ember.Handlebars.helpers
+ @deprecated
+ @param {Function} context
+ @param {Hash} options
+ @return {String} HTML string
+*/
+EmberHandlebars.registerHelper('bindAttr', function bindAttrHelper() {
+ Ember.warn("The 'bindAttr' view helper is deprecated in favor of 'bind-attr'");
+ return EmberHandlebars.helpers['bind-attr'].apply(this, arguments);
+});
+
+/**
+ Helper that, given a space-separated string of property paths and a context,
+ returns an array of class names. Calling this method also has the side
+ effect of setting up observers at those property paths, such that if they
+ change, the correct class name will be reapplied to the DOM element.
+
+ For example, if you pass the string "fooBar", it will first look up the
+ "fooBar" value of the context. If that value is true, it will add the
+ "foo-bar" class to the current element (i.e., the dasherized form of
+ "fooBar"). If the value is a string, it will add that string as the class.
+ Otherwise, it will not add any new class name.
+
+ @private
+ @method bindClasses
+ @for Ember.Handlebars
+ @param {Ember.Object} context The context from which to lookup properties
+ @param {String} classBindings A string, space-separated, of class bindings
+ to use
+ @param {Ember.View} view The view in which observers should look for the
+ element to update
+ @param {Srting} bindAttrId Optional bindAttr id used to lookup elements
+ @return {Array} An array of class names to add
+*/
+EmberHandlebars.bindClasses = function(context, classBindings, view, bindAttrId, options) {
+ var ret = [], newClass, value, elem;
+
+ // Helper method to retrieve the property from the context and
+ // determine which class string to return, based on whether it is
+ // a Boolean or not.
+ var classStringForPath = function(root, parsedPath, options) {
+ var val,
+ path = parsedPath.path;
+
+ if (path === 'this') {
+ val = root;
+ } else if (path === '') {
+ val = true;
+ } else {
+ val = handlebarsGet(root, path, options);
+ }
+
+ return Ember.View._classStringForValue(path, val, parsedPath.className, parsedPath.falsyClassName);
+ };
+
+ // For each property passed, loop through and setup
+ // an observer.
+ forEach.call(classBindings.split(' '), function(binding) {
+
+ // Variable in which the old class value is saved. The observer function
+ // closes over this variable, so it knows which string to remove when
+ // the property changes.
+ var oldClass;
+
+ var observer, invoker;
+
+ var parsedPath = Ember.View._parsePropertyPath(binding),
+ path = parsedPath.path,
+ pathRoot = context,
+ normalized;
+
+ if (path !== '' && path !== 'this') {
+ normalized = normalizePath(context, path, options.data);
+
+ pathRoot = normalized.root;
+ path = normalized.path;
+ }
+
+ // Set up an observer on the context. If the property changes, toggle the
+ // class name.
+ observer = function() {
+ // Get the current value of the property
+ newClass = classStringForPath(context, parsedPath, options);
+ elem = bindAttrId ? view.$("[data-bindattr-" + bindAttrId + "='" + bindAttrId + "']") : view.$();
+
+ // If we can't find the element anymore, a parent template has been
+ // re-rendered and we've been nuked. Remove the observer.
+ if (!elem || elem.length === 0) {
+ Ember.removeObserver(pathRoot, path, invoker);
+ } else {
+ // If we had previously added a class to the element, remove it.
+ if (oldClass) {
+ elem.removeClass(oldClass);
+ }
+
+ // If necessary, add a new class. Make sure we keep track of it so
+ // it can be removed in the future.
+ if (newClass) {
+ elem.addClass(newClass);
+ oldClass = newClass;
+ } else {
+ oldClass = null;
+ }
+ }
+ };
+
+ if (path !== '' && path !== 'this') {
+ view.registerObserver(pathRoot, path, observer);
+ }
+
+ // We've already setup the observer; now we just need to figure out the
+ // correct behavior right now on the first pass through.
+ value = classStringForPath(context, parsedPath, options);
+
+ if (value) {
+ ret.push(value);
+
+ // Make sure we save the current value so that it can be removed if the
+ // observer fires.
+ oldClass = value;
+ }
+ });
+
+ return ret;
+};
+
+
+})();
+
+
+
+(function() {
+/*globals Handlebars */
+
+// TODO: Don't require the entire module
+/**
+@module ember
+@submodule ember-handlebars
+*/
+
+var get = Ember.get, set = Ember.set;
+var EmberHandlebars = Ember.Handlebars;
+var LOWERCASE_A_Z = /^[a-z]/;
+var VIEW_PREFIX = /^view\./;
+
+function makeBindings(thisContext, options) {
+ var hash = options.hash,
+ hashType = options.hashTypes;
+
+ for (var prop in hash) {
+ if (hashType[prop] === 'ID') {
+
+ var value = hash[prop];
+
+ if (Ember.IS_BINDING.test(prop)) {
+ Ember.warn("You're attempting to render a view by passing " + prop + "=" + value + " to a view helper, but this syntax is ambiguous. You should either surround " + value + " in quotes or remove `Binding` from " + prop + ".");
+ } else {
+ hash[prop + 'Binding'] = value;
+ hashType[prop + 'Binding'] = 'STRING';
+ delete hash[prop];
+ delete hashType[prop];
+ }
+ }
+ }
+
+ if (hash.hasOwnProperty('idBinding')) {
+ // id can't be bound, so just perform one-time lookup.
+ hash.id = EmberHandlebars.get(thisContext, hash.idBinding, options);
+ hashType.id = 'STRING';
+ delete hash.idBinding;
+ delete hashType.idBinding;
+ }
+}
+
+EmberHandlebars.ViewHelper = Ember.Object.create({
+
+ propertiesFromHTMLOptions: function(options) {
+ var hash = options.hash, data = options.data;
+ var extensions = {},
+ classes = hash['class'],
+ dup = false;
+
+ if (hash.id) {
+ extensions.elementId = hash.id;
+ dup = true;
+ }
+
+ if (hash.tag) {
+ extensions.tagName = hash.tag;
+ dup = true;
+ }
+
+ if (classes) {
+ classes = classes.split(' ');
+ extensions.classNames = classes;
+ dup = true;
+ }
+
+ if (hash.classBinding) {
+ extensions.classNameBindings = hash.classBinding.split(' ');
+ dup = true;
+ }
+
+ if (hash.classNameBindings) {
+ if (extensions.classNameBindings === undefined) extensions.classNameBindings = [];
+ extensions.classNameBindings = extensions.classNameBindings.concat(hash.classNameBindings.split(' '));
+ dup = true;
+ }
+
+ if (hash.attributeBindings) {
+ Ember.assert("Setting 'attributeBindings' via Handlebars is not allowed. Please subclass Ember.View and set it there instead.");
+ extensions.attributeBindings = null;
+ dup = true;
+ }
+
+ if (dup) {
+ hash = Ember.$.extend({}, hash);
+ delete hash.id;
+ delete hash.tag;
+ delete hash['class'];
+ delete hash.classBinding;
+ }
+
+ // Set the proper context for all bindings passed to the helper. This applies to regular attribute bindings
+ // as well as class name bindings. If the bindings are local, make them relative to the current context
+ // instead of the view.
+ var path;
+
+ // Evaluate the context of regular attribute bindings:
+ for (var prop in hash) {
+ if (!hash.hasOwnProperty(prop)) { continue; }
+
+ // Test if the property ends in "Binding"
+ if (Ember.IS_BINDING.test(prop) && typeof hash[prop] === 'string') {
+ path = this.contextualizeBindingPath(hash[prop], data);
+ if (path) { hash[prop] = path; }
+ }
+ }
+
+ // Evaluate the context of class name bindings:
+ if (extensions.classNameBindings) {
+ for (var b in extensions.classNameBindings) {
+ var full = extensions.classNameBindings[b];
+ if (typeof full === 'string') {
+ // Contextualize the path of classNameBinding so this:
+ //
+ // classNameBinding="isGreen:green"
+ //
+ // is converted to this:
+ //
+ // classNameBinding="_parentView.context.isGreen:green"
+ var parsedPath = Ember.View._parsePropertyPath(full);
+ path = this.contextualizeBindingPath(parsedPath.path, data);
+ if (path) { extensions.classNameBindings[b] = path + parsedPath.classNames; }
+ }
+ }
+ }
+
+ return Ember.$.extend(hash, extensions);
+ },
+
+ // Transform bindings from the current context to a context that can be evaluated within the view.
+ // Returns null if the path shouldn't be changed.
+ //
+ // TODO: consider the addition of a prefix that would allow this method to return `path`.
+ contextualizeBindingPath: function(path, data) {
+ var normalized = Ember.Handlebars.normalizePath(null, path, data);
+ if (normalized.isKeyword) {
+ return 'templateData.keywords.' + path;
+ } else if (Ember.isGlobalPath(path)) {
+ return null;
+ } else if (path === 'this' || path === '') {
+ return '_parentView.context';
+ } else {
+ return '_parentView.context.' + path;
+ }
+ },
+
+ helper: function(thisContext, path, options) {
+ var data = options.data,
+ fn = options.fn,
+ newView;
+
+ makeBindings(thisContext, options);
+
+ if ('string' === typeof path) {
+
+ // TODO: this is a lame conditional, this should likely change
+ // but something along these lines will likely need to be added
+ // as deprecation warnings
+ //
+ if (options.types[0] === 'STRING' && LOWERCASE_A_Z.test(path) && !VIEW_PREFIX.test(path)) {
+ Ember.assert("View requires a container", !!data.view.container);
+ newView = data.view.container.lookupFactory('view:' + path);
+ } else {
+ newView = EmberHandlebars.get(thisContext, path, options);
+ }
+
+ Ember.assert("Unable to find view at path '" + path + "'", !!newView);
+ } else {
+ newView = path;
+ }
+
+ Ember.assert(Ember.String.fmt('You must pass a view to the #view helper, not %@ (%@)', [path, newView]), Ember.View.detect(newView) || Ember.View.detectInstance(newView));
+
+ var viewOptions = this.propertiesFromHTMLOptions(options, thisContext);
+ var currentView = data.view;
+ viewOptions.templateData = data;
+ var newViewProto = newView.proto ? newView.proto() : newView;
+
+ if (fn) {
+ Ember.assert("You cannot provide a template block if you also specified a templateName", !get(viewOptions, 'templateName') && !get(newViewProto, 'templateName'));
+ viewOptions.template = fn;
+ }
+
+ // We only want to override the `_context` computed property if there is
+ // no specified controller. See View#_context for more information.
+ if (!newViewProto.controller && !newViewProto.controllerBinding && !viewOptions.controller && !viewOptions.controllerBinding) {
+ viewOptions._context = thisContext;
+ }
+
+ currentView.appendChild(newView, viewOptions);
+ }
+});
+
+/**
+ `{{view}}` inserts a new instance of `Ember.View` into a template passing its
+ options to the `Ember.View`'s `create` method and using the supplied block as
+ the view's own template.
+
+ An empty `<body>` and the following template:
+
+ ```handlebars
+ A span:
+ {{#view tagName="span"}}
+ hello.
+ {{/view}}
+ ```
+
+ Will result in HTML structure:
+
+ ```html
+ <body>
+ <!-- Note: the handlebars template script
+ also results in a rendered Ember.View
+ which is the outer <div> here -->
+
+ <div class="ember-view">
+ A span:
+ <span id="ember1" class="ember-view">
+ Hello.
+ </span>
+ </div>
+ </body>
+ ```
+
+ ### `parentView` setting
+
+ The `parentView` property of the new `Ember.View` instance created through
+ `{{view}}` will be set to the `Ember.View` instance of the template where
+ `{{view}}` was called.
+
+ ```javascript
+ aView = Ember.View.create({
+ template: Ember.Handlebars.compile("{{#view}} my parent: {{parentView.elementId}} {{/view}}")
+ });
+
+ aView.appendTo('body');
+ ```
+
+ Will result in HTML structure:
+
+ ```html
+ <div id="ember1" class="ember-view">
+ <div id="ember2" class="ember-view">
+ my parent: ember1
+ </div>
+ </div>
+ ```
+
+ ### Setting CSS id and class attributes
+
+ The HTML `id` attribute can be set on the `{{view}}`'s resulting element with
+ the `id` option. This option will _not_ be passed to `Ember.View.create`.
+
+ ```handlebars
+ {{#view tagName="span" id="a-custom-id"}}
+ hello.
+ {{/view}}
+ ```
+
+ Results in the following HTML structure:
+
+ ```html
+ <div class="ember-view">
+ <span id="a-custom-id" class="ember-view">
+ hello.
+ </span>
+ </div>
+ ```
+
+ The HTML `class` attribute can be set on the `{{view}}`'s resulting element
+ with the `class` or `classNameBindings` options. The `class` option will
+ directly set the CSS `class` attribute and will not be passed to
+ `Ember.View.create`. `classNameBindings` will be passed to `create` and use
+ `Ember.View`'s class name binding functionality:
+
+ ```handlebars
+ {{#view tagName="span" class="a-custom-class"}}
+ hello.
+ {{/view}}
+ ```
+
+ Results in the following HTML structure:
+
+ ```html
+ <div class="ember-view">
+ <span id="ember2" class="ember-view a-custom-class">
+ hello.
+ </span>
+ </div>
+ ```
+
+ ### Supplying a different view class
+
+ `{{view}}` can take an optional first argument before its supplied options to
+ specify a path to a custom view class.
+
+ ```handlebars
+ {{#view "MyApp.CustomView"}}
+ hello.
+ {{/view}}
+ ```
+
+ The first argument can also be a relative path accessible from the current
+ context.
+
+ ```javascript
+ MyApp = Ember.Application.create({});
+ MyApp.OuterView = Ember.View.extend({
+ innerViewClass: Ember.View.extend({
+ classNames: ['a-custom-view-class-as-property']
+ }),
+ template: Ember.Handlebars.compile('{{#view "view.innerViewClass"}} hi {{/view}}')
+ });
+
+ MyApp.OuterView.create().appendTo('body');
+ ```
+
+ Will result in the following HTML:
+
+ ```html
+ <div id="ember1" class="ember-view">
+ <div id="ember2" class="ember-view a-custom-view-class-as-property">
+ hi
+ </div>
+ </div>
+ ```
+
+ ### Blockless use
+
+ If you supply a custom `Ember.View` subclass that specifies its own template
+ or provide a `templateName` option to `{{view}}` it can be used without
+ supplying a block. Attempts to use both a `templateName` option and supply a
+ block will throw an error.
+
+ ```handlebars
+ {{view "MyApp.ViewWithATemplateDefined"}}
+ ```
+
+ ### `viewName` property
+
+ You can supply a `viewName` option to `{{view}}`. The `Ember.View` instance
+ will be referenced as a property of its parent view by this name.
+
+ ```javascript
+ aView = Ember.View.create({
+ template: Ember.Handlebars.compile('{{#view viewName="aChildByName"}} hi {{/view}}')
+ });
+
+ aView.appendTo('body');
+ aView.get('aChildByName') // the instance of Ember.View created by {{view}} helper
+ ```
+
+ @method view
+ @for Ember.Handlebars.helpers
+ @param {String} path
+ @param {Hash} options
+ @return {String} HTML string
+*/
+EmberHandlebars.registerHelper('view', function viewHelper(path, options) {
+ Ember.assert("The view helper only takes a single argument", arguments.length <= 2);
+
+ // If no path is provided, treat path param as options.
+ if (path && path.data && path.data.isRenderData) {
+ options = path;
+ path = "Ember.View";
+ }
+
+ return EmberHandlebars.ViewHelper.helper(this, path, options);
+});
+
+
+})();
+
+
+
+(function() {
+// TODO: Don't require all of this module
+/**
+@module ember
+@submodule ember-handlebars
+*/
+
+var get = Ember.get, handlebarsGet = Ember.Handlebars.get, fmt = Ember.String.fmt;
+
+/**
+ `{{collection}}` is a `Ember.Handlebars` helper for adding instances of
+ `Ember.CollectionView` to a template. See [Ember.CollectionView](/api/classes/Ember.CollectionView.html)
+ for additional information on how a `CollectionView` functions.
+
+ `{{collection}}`'s primary use is as a block helper with a `contentBinding`
+ option pointing towards an `Ember.Array`-compatible object. An `Ember.View`
+ instance will be created for each item in its `content` property. Each view
+ will have its own `content` property set to the appropriate item in the
+ collection.
+
+ The provided block will be applied as the template for each item's view.
+
+ Given an empty `<body>` the following template:
+
+ ```handlebars
+ {{#collection contentBinding="App.items"}}
+ Hi {{view.content.name}}
+ {{/collection}}
+ ```
+
+ And the following application code
+
+ ```javascript
+ App = Ember.Application.create()
+ App.items = [
+ Ember.Object.create({name: 'Dave'}),
+ Ember.Object.create({name: 'Mary'}),
+ Ember.Object.create({name: 'Sara'})
+ ]
+ ```
+
+ Will result in the HTML structure below
+
+ ```html
+ <div class="ember-view">
+ <div class="ember-view">Hi Dave</div>
+ <div class="ember-view">Hi Mary</div>
+ <div class="ember-view">Hi Sara</div>
+ </div>
+ ```
+
+ ### Blockless use in a collection
+
+ If you provide an `itemViewClass` option that has its own `template` you can
+ omit the block.
+
+ The following template:
+
+ ```handlebars
+ {{collection contentBinding="App.items" itemViewClass="App.AnItemView"}}
+ ```
+
+ And application code
+
+ ```javascript
+ App = Ember.Application.create();
+ App.items = [
+ Ember.Object.create({name: 'Dave'}),
+ Ember.Object.create({name: 'Mary'}),
+ Ember.Object.create({name: 'Sara'})
+ ];
+
+ App.AnItemView = Ember.View.extend({
+ template: Ember.Handlebars.compile("Greetings {{view.content.name}}")
+ });
+ ```
+
+ Will result in the HTML structure below
+
+ ```html
+ <div class="ember-view">
+ <div class="ember-view">Greetings Dave</div>
+ <div class="ember-view">Greetings Mary</div>
+ <div class="ember-view">Greetings Sara</div>
+ </div>
+ ```
+
+ ### Specifying a CollectionView subclass
+
+ By default the `{{collection}}` helper will create an instance of
+ `Ember.CollectionView`. You can supply a `Ember.CollectionView` subclass to
+ the helper by passing it as the first argument:
+
+ ```handlebars
+ {{#collection App.MyCustomCollectionClass contentBinding="App.items"}}
+ Hi {{view.content.name}}
+ {{/collection}}
+ ```
+
+ ### Forwarded `item.*`-named Options
+
+ As with the `{{view}}`, helper options passed to the `{{collection}}` will be
+ set on the resulting `Ember.CollectionView` as properties. Additionally,
+ options prefixed with `item` will be applied to the views rendered for each
+ item (note the camelcasing):
+
+ ```handlebars
+ {{#collection contentBinding="App.items"
+ itemTagName="p"
+ itemClassNames="greeting"}}
+ Howdy {{view.content.name}}
+ {{/collection}}
+ ```
+
+ Will result in the following HTML structure:
+
+ ```html
+ <div class="ember-view">
+ <p class="ember-view greeting">Howdy Dave</p>
+ <p class="ember-view greeting">Howdy Mary</p>
+ <p class="ember-view greeting">Howdy Sara</p>
+ </div>
+ ```
+
+ @method collection
+ @for Ember.Handlebars.helpers
+ @param {String} path
+ @param {Hash} options
+ @return {String} HTML string
+ @deprecated Use `{{each}}` helper instead.
+*/
+Ember.Handlebars.registerHelper('collection', function collectionHelper(path, options) {
+ Ember.deprecate("Using the {{collection}} helper without specifying a class has been deprecated as the {{each}} helper now supports the same functionality.", path !== 'collection');
+
+ // If no path is provided, treat path param as options.
+ if (path && path.data && path.data.isRenderData) {
+ options = path;
+ path = undefined;
+ Ember.assert("You cannot pass more than one argument to the collection helper", arguments.length === 1);
+ } else {
+ Ember.assert("You cannot pass more than one argument to the collection helper", arguments.length === 2);
+ }
+
+ var fn = options.fn;
+ var data = options.data;
+ var inverse = options.inverse;
+ var view = options.data.view;
+
+
+ var controller, container;
+ // If passed a path string, convert that into an object.
+ // Otherwise, just default to the standard class.
+ var collectionClass;
+ if (path) {
+ controller = data.keywords.controller;
+ container = controller && controller.container;
+ collectionClass = handlebarsGet(this, path, options) || container.lookupFactory('view:' + path);
+ Ember.assert(fmt("%@ #collection: Could not find collection class %@", [data.view, path]), !!collectionClass);
+ }
+ else {
+ collectionClass = Ember.CollectionView;
+ }
+
+ var hash = options.hash, itemHash = {}, match;
+
+ // Extract item view class if provided else default to the standard class
+ var collectionPrototype = collectionClass.proto(),
+ itemViewClass;
+
+ if (hash.itemView) {
+ controller = data.keywords.controller;
+ Ember.assert('You specified an itemView, but the current context has no ' +
+ 'container to look the itemView up in. This probably means ' +
+ 'that you created a view manually, instead of through the ' +
+ 'container. Instead, use container.lookup("view:viewName"), ' +
+ 'which will properly instantiate your view.',
+ controller && controller.container);
+ container = controller.container;
+ itemViewClass = container.lookupFactory('view:' + hash.itemView);
+ Ember.assert('You specified the itemView ' + hash.itemView + ", but it was " +
+ "not found at " + container.describe("view:" + hash.itemView) +
+ " (and it was not registered in the container)", !!itemViewClass);
+ } else if (hash.itemViewClass) {
+ itemViewClass = handlebarsGet(collectionPrototype, hash.itemViewClass, options);
+ } else {
+ itemViewClass = collectionPrototype.itemViewClass;
+ }
+
+ Ember.assert(fmt("%@ #collection: Could not find itemViewClass %@", [data.view, itemViewClass]), !!itemViewClass);
+
+ delete hash.itemViewClass;
+ delete hash.itemView;
+
+ // Go through options passed to the {{collection}} helper and extract options
+ // that configure item views instead of the collection itself.
+ for (var prop in hash) {
+ if (hash.hasOwnProperty(prop)) {
+ match = prop.match(/^item(.)(.*)$/);
+
+ if (match && prop !== 'itemController') {
+ // Convert itemShouldFoo -> shouldFoo
+ itemHash[match[1].toLowerCase() + match[2]] = hash[prop];
+ // Delete from hash as this will end up getting passed to the
+ // {{view}} helper method.
+ delete hash[prop];
+ }
+ }
+ }
+
+ if (fn) {
+ itemHash.template = fn;
+ delete options.fn;
+ }
+
+ var emptyViewClass;
+ if (inverse && inverse !== Ember.Handlebars.VM.noop) {
+ emptyViewClass = get(collectionPrototype, 'emptyViewClass');
+ emptyViewClass = emptyViewClass.extend({
+ template: inverse,
+ tagName: itemHash.tagName
+ });
+ } else if (hash.emptyViewClass) {
+ emptyViewClass = handlebarsGet(this, hash.emptyViewClass, options);
+ }
+ if (emptyViewClass) { hash.emptyView = emptyViewClass; }
+
+ if (!hash.keyword) {
+ itemHash._context = Ember.computed.alias('content');
+ }
+
+ var viewOptions = Ember.Handlebars.ViewHelper.propertiesFromHTMLOptions({ data: data, hash: itemHash }, this);
+ hash.itemViewClass = itemViewClass.extend(viewOptions);
+
+ return Ember.Handlebars.helpers.view.call(this, collectionClass, options);
+});
+
+
+})();
+
+
+
+(function() {
+/*globals Handlebars */
+/**
+@module ember
+@submodule ember-handlebars
+*/
+
+var handlebarsGet = Ember.Handlebars.get;
+
+/**
+ `unbound` allows you to output a property without binding. *Important:* The
+ output will not be updated if the property changes. Use with caution.
+
+ ```handlebars
+ <div>{{unbound somePropertyThatDoesntChange}}</div>
+ ```
+
+ `unbound` can also be used in conjunction with a bound helper to
+ render it in its unbound form:
+
+ ```handlebars
+ <div>{{unbound helperName somePropertyThatDoesntChange}}</div>
+ ```
+
+ @method unbound
+ @for Ember.Handlebars.helpers
+ @param {String} property
+ @return {String} HTML string
+*/
+Ember.Handlebars.registerHelper('unbound', function unboundHelper(property, fn) {
+ var options = arguments[arguments.length - 1], helper, context, out;
+
+ if (arguments.length > 2) {
+ // Unbound helper call.
+ options.data.isUnbound = true;
+ helper = Ember.Handlebars.helpers[arguments[0]] || Ember.Handlebars.helpers.helperMissing;
+ out = helper.apply(this, Array.prototype.slice.call(arguments, 1));
+ delete options.data.isUnbound;
+ return out;
+ }
+
+ context = (fn.contexts && fn.contexts.length) ? fn.contexts[0] : this;
+ return handlebarsGet(context, property, fn);
+});
+
+})();
+
+
+
+(function() {
+/*jshint debug:true*/
+/**
+@module ember
+@submodule ember-handlebars
+*/
+
+var get = Ember.get;
+var handlebarsGet = Ember.Handlebars.get, normalizePath = Ember.Handlebars.normalizePath;
+var a_slice = [].slice;
+
+/**
+ `log` allows you to output the value of variables in the current rendering
+ context. `log` also accepts primitive types such as strings or numbers.
+
+ ```handlebars
+ {{log "myVariable:" myVariable }}
+ ```
+
+ @method log
+ @for Ember.Handlebars.helpers
+ @param {String} property
+*/
+Ember.Handlebars.registerHelper('log', function logHelper() {
+ var params = a_slice.call(arguments, 0, -1),
+ options = arguments[arguments.length - 1],
+ logger = Ember.Logger.log,
+ values = [],
+ allowPrimitives = false;
+
+
+ allowPrimitives = true;
+
+
+ for (var i = 0; i < params.length; i++) {
+ var type = options.types[i];
+
+ if (type === 'ID' || !allowPrimitives) {
+ var context = (options.contexts && options.contexts[i]) || this,
+ normalized = normalizePath(context, params[i], options.data);
+
+ if (normalized.path === 'this') {
+ values.push(normalized.root);
+ } else {
+ values.push(handlebarsGet(normalized.root, normalized.path, options));
+ }
+ } else {
+ values.push(params[i]);
+ }
+ }
+
+ logger.apply(logger, values);
+});
+
+/**
+ Execute the `debugger` statement in the current context.
+
+ ```handlebars
+ {{debugger}}
+ ```
+
+ Before invoking the `debugger` statement, there
+ are a few helpful variables defined in the
+ body of this helper that you can inspect while
+ debugging that describe how and where this
+ helper was invoked:
+
+ - templateContext: this is most likely a controller
+ from which this template looks up / displays properties
+ - typeOfTemplateContext: a string description of
+ what the templateContext is
+
+ For example, if you're wondering why a value `{{foo}}`
+ isn't rendering as expected within a template, you
+ could place a `{{debugger}}` statement, and when
+ the `debugger;` breakpoint is hit, you can inspect
+ `templateContext`, determine if it's the object you
+ expect, and/or evaluate expressions in the console
+ to perform property lookups on the `templateContext`:
+
+ ```
+ > templateContext.get('foo') // -> "<value of {{foo}}>"
+ ```
+
+ @method debugger
+ @for Ember.Handlebars.helpers
+ @param {String} property
+*/
+Ember.Handlebars.registerHelper('debugger', function debuggerHelper(options) {
+
+ // These are helpful values you can inspect while debugging.
+ var templateContext = this;
+ var typeOfTemplateContext = Ember.inspect(templateContext);
+
+ debugger;
+});
+
+
+
+})();
+
+
+
+(function() {
+/**
+@module ember
+@submodule ember-handlebars
+*/
+
+var get = Ember.get, set = Ember.set;
+var fmt = Ember.String.fmt;
+
+Ember.Handlebars.EachView = Ember.CollectionView.extend(Ember._Metamorph, {
+ init: function() {
+ var itemController = get(this, 'itemController');
+ var binding;
+
+ if (itemController) {
+ var controller = get(this, 'controller.container').lookupFactory('controller:array').create({
+ _isVirtual: true,
+ parentController: get(this, 'controller'),
+ itemController: itemController,
+ target: get(this, 'controller'),
+ _eachView: this
+ });
+
+ this.disableContentObservers(function() {
+ set(this, 'content', controller);
+ binding = new Ember.Binding('content', '_eachView.dataSource').oneWay();
+ binding.connect(controller);
+ });
+
+ set(this, '_arrayController', controller);
+ } else {
+ this.disableContentObservers(function() {
+ binding = new Ember.Binding('content', 'dataSource').oneWay();
+ binding.connect(this);
+ });
+ }
+
+ return this._super();
+ },
+
+ _assertArrayLike: function(content) {
+ Ember.assert(fmt("The value that #each loops over must be an Array. You " +
+ "passed %@, but it should have been an ArrayController",
+ [content.constructor]),
+ !Ember.ControllerMixin.detect(content) ||
+ (content && content.isGenerated) ||
+ content instanceof Ember.ArrayController);
+ Ember.assert(fmt("The value that #each loops over must be an Array. You passed %@", [(Ember.ControllerMixin.detect(content) && content.get('model') !== undefined) ? fmt("'%@' (wrapped in %@)", [content.get('model'), content]) : content]), Ember.Array.detect(content));
+ },
+
+ disableContentObservers: function(callback) {
+ Ember.removeBeforeObserver(this, 'content', null, '_contentWillChange');
+ Ember.removeObserver(this, 'content', null, '_contentDidChange');
+
+ callback.call(this);
+
+ Ember.addBeforeObserver(this, 'content', null, '_contentWillChange');
+ Ember.addObserver(this, 'content', null, '_contentDidChange');
+ },
+
+ itemViewClass: Ember._MetamorphView,
+ emptyViewClass: Ember._MetamorphView,
+
+ createChildView: function(view, attrs) {
+ view = this._super(view, attrs);
+
+ // At the moment, if a container view subclass wants
+ // to insert keywords, it is responsible for cloning
+ // the keywords hash. This will be fixed momentarily.
+ var keyword = get(this, 'keyword');
+ var content = get(view, 'content');
+
+ if (keyword) {
+ var data = get(view, 'templateData');
+
+ data = Ember.copy(data);
+ data.keywords = view.cloneKeywords();
+ set(view, 'templateData', data);
+
+ // In this case, we do not bind, because the `content` of
+ // a #each item cannot change.
+ data.keywords[keyword] = content;
+ }
+
+ // If {{#each}} is looping over an array of controllers,
+ // point each child view at their respective controller.
+ if (content && content.isController) {
+ set(view, 'controller', content);
+ }
+
+ return view;
+ },
+
+ destroy: function() {
+ if (!this._super()) { return; }
+
+ var arrayController = get(this, '_arrayController');
+
+ if (arrayController) {
+ arrayController.destroy();
+ }
+
+ return this;
+ }
+});
+
+// Defeatureify doesn't seem to like nested functions that need to be removed
+function _addMetamorphCheck() {
+ Ember.Handlebars.EachView.reopen({
+ _checkMetamorph: Ember.on('didInsertElement', function() {
+ Ember.assert("The metamorph tags, " +
+ this.morph.start + " and " + this.morph.end +
+ ", have different parents.\nThe browser has fixed your template to output valid HTML (for example, check that you have properly closed all tags and have used a TBODY tag when creating a table with '{{#each}}')",
+ document.getElementById( this.morph.start ).parentNode ===
+ document.getElementById( this.morph.end ).parentNode
+ );
+ })
+ });
+}
+
+Ember.runInDebug( function() {
+ _addMetamorphCheck();
+});
+
+var GroupedEach = Ember.Handlebars.GroupedEach = function(context, path, options) {
+ var self = this,
+ normalized = Ember.Handlebars.normalizePath(context, path, options.data);
+
+ this.context = context;
+ this.path = path;
+ this.options = options;
+ this.template = options.fn;
+ this.containingView = options.data.view;
+ this.normalizedRoot = normalized.root;
+ this.normalizedPath = normalized.path;
+ this.content = this.lookupContent();
+
+ this.addContentObservers();
+ this.addArrayObservers();
+
+ this.containingView.on('willClearRender', function() {
+ self.destroy();
+ });
+};
+
+GroupedEach.prototype = {
+ contentWillChange: function() {
+ this.removeArrayObservers();
+ },
+
+ contentDidChange: function() {
+ this.content = this.lookupContent();
+ this.addArrayObservers();
+ this.rerenderContainingView();
+ },
+
+ contentArrayWillChange: Ember.K,
+
+ contentArrayDidChange: function() {
+ this.rerenderContainingView();
+ },
+
+ lookupContent: function() {
+ return Ember.Handlebars.get(this.normalizedRoot, this.normalizedPath, this.options);
+ },
+
+ addArrayObservers: function() {
+ if (!this.content) { return; }
+
+ this.content.addArrayObserver(this, {
+ willChange: 'contentArrayWillChange',
+ didChange: 'contentArrayDidChange'
+ });
+ },
+
+ removeArrayObservers: function() {
+ if (!this.content) { return; }
+
+ this.content.removeArrayObserver(this, {
+ willChange: 'contentArrayWillChange',
+ didChange: 'contentArrayDidChange'
+ });
+ },
+
+ addContentObservers: function() {
+ Ember.addBeforeObserver(this.normalizedRoot, this.normalizedPath, this, this.contentWillChange);
+ Ember.addObserver(this.normalizedRoot, this.normalizedPath, this, this.contentDidChange);
+ },
+
+ removeContentObservers: function() {
+ Ember.removeBeforeObserver(this.normalizedRoot, this.normalizedPath, this.contentWillChange);
+ Ember.removeObserver(this.normalizedRoot, this.normalizedPath, this.contentDidChange);
+ },
+
+ render: function() {
+ if (!this.content) { return; }
+
+ var content = this.content,
+ contentLength = get(content, 'length'),
+ data = this.options.data,
+ template = this.template;
+
+ data.insideEach = true;
+ for (var i = 0; i < contentLength; i++) {
+ template(content.objectAt(i), { data: data });
+ }
+ },
+
+ rerenderContainingView: function() {
+ var self = this;
+ Ember.run.scheduleOnce('render', this, function() {
+ // It's possible it's been destroyed after we enqueued a re-render call.
+ if (!self.destroyed) {
+ self.containingView.rerender();
+ }
+ });
+ },
+
+ destroy: function() {
+ this.removeContentObservers();
+ if (this.content) {
+ this.removeArrayObservers();
+ }
+ this.destroyed = true;
+ }
+};
+
+/**
+ The `{{#each}}` helper loops over elements in a collection, rendering its
+ block once for each item. It is an extension of the base Handlebars `{{#each}}`
+ helper:
+
+ ```javascript
+ Developers = [{name: 'Yehuda'},{name: 'Tom'}, {name: 'Paul'}];
+ ```
+
+ ```handlebars
+ {{#each Developers}}
+ {{name}}
+ {{/each}}
+ ```
+
+ `{{each}}` supports an alternative syntax with element naming:
+
+ ```handlebars
+ {{#each person in Developers}}
+ {{person.name}}
+ {{/each}}
+ ```
+
+ When looping over objects that do not have properties, `{{this}}` can be used
+ to render the object:
+
+ ```javascript
+ DeveloperNames = ['Yehuda', 'Tom', 'Paul']
+ ```
+
+ ```handlebars
+ {{#each DeveloperNames}}
+ {{this}}
+ {{/each}}
+ ```
+ ### {{else}} condition
+ `{{#each}}` can have a matching `{{else}}`. The contents of this block will render
+ if the collection is empty.
+
+ ```
+ {{#each person in Developers}}
+ {{person.name}}
+ {{else}}
+ <p>Sorry, nobody is available for this task.</p>
+ {{/each}}
+ ```
+ ### Specifying a View class for items
+ If you provide an `itemViewClass` option that references a view class
+ with its own `template` you can omit the block.
+
+ The following template:
+
+ ```handlebars
+ {{#view App.MyView }}
+ {{each view.items itemViewClass="App.AnItemView"}}
+ {{/view}}
+ ```
+
+ And application code
+
+ ```javascript
+ App = Ember.Application.create({
+ MyView: Ember.View.extend({
+ items: [
+ Ember.Object.create({name: 'Dave'}),
+ Ember.Object.create({name: 'Mary'}),
+ Ember.Object.create({name: 'Sara'})
+ ]
+ })
+ });
+
+ App.AnItemView = Ember.View.extend({
+ template: Ember.Handlebars.compile("Greetings {{name}}")
+ });
+ ```
+
+ Will result in the HTML structure below
+
+ ```html
+ <div class="ember-view">
+ <div class="ember-view">Greetings Dave</div>
+ <div class="ember-view">Greetings Mary</div>
+ <div class="ember-view">Greetings Sara</div>
+ </div>
+ ```
+
+ If an `itemViewClass` is defined on the helper, and therefore the helper is not
+ being used as a block, an `emptyViewClass` can also be provided optionally.
+ The `emptyViewClass` will match the behavior of the `{{else}}` condition
+ described above. That is, the `emptyViewClass` will render if the collection
+ is empty.
+
+ ### Representing each item with a Controller.
+ By default the controller lookup within an `{{#each}}` block will be
+ the controller of the template where the `{{#each}}` was used. If each
+ item needs to be presented by a custom controller you can provide a
+ `itemController` option which references a controller by lookup name.
+ Each item in the loop will be wrapped in an instance of this controller
+ and the item itself will be set to the `content` property of that controller.
+
+ This is useful in cases where properties of model objects need transformation
+ or synthesis for display:
+
+ ```javascript
+ App.DeveloperController = Ember.ObjectController.extend({
+ isAvailableForHire: function() {
+ return !this.get('content.isEmployed') && this.get('content.isSeekingWork');
+ }.property('isEmployed', 'isSeekingWork')
+ })
+ ```
+
+ ```handlebars
+ {{#each person in developers itemController="developer"}}
+ {{person.name}} {{#if person.isAvailableForHire}}Hire me!{{/if}}
+ {{/each}}
+ ```
+
+ Each itemController will receive a reference to the current controller as
+ a `parentController` property.
+
+ ### (Experimental) Grouped Each
+
+ When used in conjunction with the experimental [group helper](https://github.com/emberjs/group-helper),
+ you can inform Handlebars to re-render an entire group of items instead of
+ re-rendering them one at a time (in the event that they are changed en masse
+ or an item is added/removed).
+
+ ```handlebars
+ {{#group}}
+ {{#each people}}
+ {{firstName}} {{lastName}}
+ {{/each}}
+ {{/group}}
+ ```
+
+ This can be faster than the normal way that Handlebars re-renders items
+ in some cases.
+
+ If for some reason you have a group with more than one `#each`, you can make
+ one of the collections be updated in normal (non-grouped) fashion by setting
+ the option `groupedRows=true` (counter-intuitive, I know).
+
+ For example,
+
+ ```handlebars
+ {{dealershipName}}
+
+ {{#group}}
+ {{#each dealers}}
+ {{firstName}} {{lastName}}
+ {{/each}}
+
+ {{#each car in cars groupedRows=true}}
+ {{car.make}} {{car.model}} {{car.color}}
+ {{/each}}
+ {{/group}}
+ ```
+ Any change to `dealershipName` or the `dealers` collection will cause the
+ entire group to be re-rendered. However, changes to the `cars` collection
+ will be re-rendered individually (as normal).
+
+ Note that `group` behavior is also disabled by specifying an `itemViewClass`.
+
+ @method each
+ @for Ember.Handlebars.helpers
+ @param [name] {String} name for item (used with `in`)
+ @param [path] {String} path
+ @param [options] {Object} Handlebars key/value pairs of options
+ @param [options.itemViewClass] {String} a path to a view class used for each item
+ @param [options.itemController] {String} name of a controller to be created for each item
+ @param [options.groupedRows] {boolean} enable normal item-by-item rendering when inside a `#group` helper
+*/
+Ember.Handlebars.registerHelper('each', function eachHelper(path, options) {
+ if (arguments.length === 4) {
+ Ember.assert("If you pass more than one argument to the each helper, it must be in the form #each foo in bar", arguments[1] === "in");
+
+ var keywordName = arguments[0];
+
+ options = arguments[3];
+ path = arguments[2];
+ if (path === '') { path = "this"; }
+
+ options.hash.keyword = keywordName;
+ }
+
+ if (arguments.length === 1) {
+ options = path;
+ path = 'this';
+ }
+
+ options.hash.dataSourceBinding = path;
+ // Set up emptyView as a metamorph with no tag
+ //options.hash.emptyViewClass = Ember._MetamorphView;
+
+ if (options.data.insideGroup && !options.hash.groupedRows && !options.hash.itemViewClass) {
+ new Ember.Handlebars.GroupedEach(this, path, options).render();
+ } else {
+ return Ember.Handlebars.helpers.collection.call(this, 'Ember.Handlebars.EachView', options);
+ }
+});
+
+})();
+
+
+
+(function() {
+/**
+@module ember
+@submodule ember-handlebars
+*/
+
+/**
+ `template` allows you to render a template from inside another template.
+ This allows you to re-use the same template in multiple places. For example:
+
+ ```html
+ <script type="text/x-handlebars" data-template-name="logged_in_user">
+ {{#with loggedInUser}}
+ Last Login: {{lastLogin}}
+ User Info: {{template "user_info"}}
+ {{/with}}
+ </script>
+ ```
+
+ ```html
+ <script type="text/x-handlebars" data-template-name="user_info">
+ Name: <em>{{name}}</em>
+ Karma: <em>{{karma}}</em>
+ </script>
+ ```
+
+ ```handlebars
+ {{#if isUser}}
+ {{template "user_info"}}
+ {{else}}
+ {{template "unlogged_user_info"}}
+ {{/if}}
+ ```
+
+ This helper looks for templates in the global `Ember.TEMPLATES` hash. If you
+ add `<script>` tags to your page with the `data-template-name` attribute set,
+ they will be compiled and placed in this hash automatically.
+
+ You can also manually register templates by adding them to the hash:
+
+ ```javascript
+ Ember.TEMPLATES["my_cool_template"] = Ember.Handlebars.compile('<b>{{user}}</b>');
+ ```
+
+ @deprecated
+ @method template
+ @for Ember.Handlebars.helpers
+ @param {String} templateName the template to render
+*/
+
+Ember.Handlebars.registerHelper('template', function(name, options) {
+ 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);
+});
+
+})();
+
+
+
+(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);
+ }
+});
+
+function exists(value) {
+ return !Ember.isNone(value);
+}
+
+function renderPartial(context, name, options) {
+ var nameParts = name.split("/"),
+ lastPart = nameParts[nameParts.length - 1];
+
+ nameParts[nameParts.length - 1] = "_" + lastPart;
+
+ var view = options.data.view,
+ underscoredName = nameParts.join("/"),
+ template = view.templateForName(underscoredName),
+ deprecatedTemplate = !template && view.templateForName(name);
+
+ Ember.assert("Unable to find partial with name '"+name+"'.", template || deprecatedTemplate);
+
+ template = template || deprecatedTemplate;
+
+ template(context, { data: options.data });
+}
+
+})();
+
+
+
+(function() {
+/**
+@module ember
+@submodule ember-handlebars
+*/
+
+var get = Ember.get, set = Ember.set;
+
+/**
+ `{{yield}}` denotes an area of a template that will be rendered inside
+ of another template. It has two main uses:
+
+ ### Use with `layout`
+ When used in a Handlebars template that is assigned to an `Ember.View`
+ instance's `layout` property Ember will render the layout template first,
+ inserting the view's own rendered output at the `{{yield}}` location.
+
+ An empty `<body>` and the following application code:
+
+ ```javascript
+ AView = Ember.View.extend({
+ classNames: ['a-view-with-layout'],
+ layout: Ember.Handlebars.compile('<div class="wrapper">{{yield}}</div>'),
+ template: Ember.Handlebars.compile('<span>I am wrapped</span>')
+ });
+
+ aView = AView.create();
+ aView.appendTo('body');
+ ```
+
+ Will result in the following HTML output:
+
+ ```html
+ <body>
+ <div class='ember-view a-view-with-layout'>
+ <div class="wrapper">
+ <span>I am wrapped</span>
+ </div>
+ </div>
+ </body>
+ ```
+
+ The `yield` helper cannot be used outside of a template assigned to an
+ `Ember.View`'s `layout` property and will throw an error if attempted.
+
+ ```javascript
+ BView = Ember.View.extend({
+ classNames: ['a-view-with-layout'],
+ template: Ember.Handlebars.compile('{{yield}}')
+ });
+
+ bView = BView.create();
+ bView.appendTo('body');
+
+ // throws
+ // Uncaught Error: assertion failed:
+ // You called yield in a template that was not a layout
+ ```
+
+ ### 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>
+ ```
+
+ @method yield
+ @for Ember.Handlebars.helpers
+ @param {Hash} options
+ @return {String} HTML string
+*/
+Ember.Handlebars.registerHelper('yield', function yieldHelper(options) {
+ var view = options.data.view;
+
+ while (view && !get(view, 'layout')) {
+ if (view._contextView) {
+ view = view._contextView;
+ } else {
+ view = get(view, 'parentView');
+ }
+ }
+
+ Ember.assert("You called yield in a template that was not a layout", !!view);
+
+ view._yield(this, options);
+});
+
+})();
+
+
+
+(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);
+});
+
+})();
+
+
+
+(function() {
+
+})();
+
+
+
+(function() {
+
+})();
+
+
+
+(function() {
+/**
+@module ember
+@submodule ember-handlebars
+*/
+
+var set = Ember.set, get = Ember.get;
+
+/**
+ The internal class used to create text inputs when the `{{input}}`
+ helper is used with `type` of `checkbox`.
+
+ See [handlebars.helpers.input](/api/classes/Ember.Handlebars.helpers.html#method_input) for usage details.
+
+ ## Direct manipulation of `checked`
+
+ The `checked` attribute of an `Ember.Checkbox` object should always be set
+ through the Ember object or by interacting with its rendered element
+ representation via the mouse, keyboard, or touch. Updating the value of the
+ checkbox via jQuery will result in the checked value of the object and its
+ element losing synchronization.
+
+ ## Layout and LayoutName properties
+
+ Because HTML `input` elements are self closing `layout` and `layoutName`
+ properties will not be applied. See [Ember.View](/api/classes/Ember.View.html)'s
+ layout section for more information.
+
+ @class Checkbox
+ @namespace Ember
+ @extends Ember.View
+*/
+Ember.Checkbox = Ember.View.extend({
+ classNames: ['ember-checkbox'],
+
+ tagName: 'input',
+
+ attributeBindings: ['type', 'checked', 'indeterminate', 'disabled', 'tabindex', 'name',
+ 'autofocus', 'form'],
+
+ type: "checkbox",
+ checked: false,
+ disabled: false,
+ indeterminate: false,
+
+ init: function() {
+ this._super();
+ this.on("change", this, this._updateElementValue);
+ },
+
+ didInsertElement: function() {
+ this._super();
+ this.get('element').indeterminate = !!this.get('indeterminate');
+ },
+
+ _updateElementValue: function() {
+ set(this, 'checked', this.$().prop('checked'));
+ }
+});
+
+})();
+
+
+
+(function() {
+/**
+@module ember
+@submodule ember-handlebars
+*/
+
+var get = Ember.get, set = Ember.set;
+
+/**
+ Shared mixin used by `Ember.TextField` and `Ember.TextArea`.
+
+ @class TextSupport
+ @namespace Ember
+ @uses Ember.TargetActionSupport
+ @extends Ember.Mixin
+ @private
+*/
+Ember.TextSupport = Ember.Mixin.create(Ember.TargetActionSupport, {
+ value: "",
+
+ attributeBindings: ['placeholder', 'disabled', 'maxlength', 'tabindex', 'readonly',
+ 'autofocus', 'form', 'selectionDirection', 'spellcheck', 'required'],
+ placeholder: null,
+ disabled: false,
+ maxlength: null,
+
+ init: function() {
+ this._super();
+ this.on("focusOut", this, this._elementValueDidChange);
+ this.on("change", this, this._elementValueDidChange);
+ this.on("paste", this, this._elementValueDidChange);
+ this.on("cut", this, this._elementValueDidChange);
+ this.on("input", this, this._elementValueDidChange);
+ this.on("keyUp", this, this.interpretKeyEvents);
+ },
+
+ /**
+ The action to be sent when the user presses the return key.
+
+ This is similar to the `{{action}}` helper, but is fired when
+ the user presses the return key when editing a text field, and sends
+ the value of the field as the context.
+
+ @property action
+ @type String
+ @default null
+ */
+ action: null,
+
+ /**
+ The event that should send the action.
+
+ Options are:
+
+ * `enter`: the user pressed enter
+ * `keyPress`: the user pressed a key
+
+ @property onEvent
+ @type String
+ @default enter
+ */
+ onEvent: 'enter',
+
+ /**
+ Whether they `keyUp` event that triggers an `action` to be sent continues
+ propagating to other views.
+
+ By default, when the user presses the return key on their keyboard and
+ the text field has an `action` set, the action will be sent to the view's
+ controller and the key event will stop propagating.
+
+ If you would like parent views to receive the `keyUp` event even after an
+ action has been dispatched, set `bubbles` to true.
+
+ @property bubbles
+ @type Boolean
+ @default false
+ */
+ bubbles: false,
+
+ interpretKeyEvents: function(event) {
+ var map = Ember.TextSupport.KEY_EVENTS;
+ var method = map[event.keyCode];
+
+ this._elementValueDidChange();
+ if (method) { return this[method](event); }
+ },
+
+ _elementValueDidChange: function() {
+ set(this, 'value', this.$().val());
+ },
+
+ /**
+ The action to be sent when the user inserts a new line.
+
+ Called by the `Ember.TextSupport` mixin on keyUp if keycode matches 13.
+ Uses sendAction to send the `enter` action to the controller.
+
+ @method insertNewline
+ @param {Event} event
+ */
+ insertNewline: function(event) {
+ sendAction('enter', this, event);
+ sendAction('insert-newline', this, event);
+ },
+
+ /**
+ Called when the user hits escape.
+
+ Called by the `Ember.TextSupport` mixin on keyUp if keycode matches 27.
+ Uses sendAction to send the `escape-press` action to the controller.
+
+ @method cancel
+ @param {Event} event
+ */
+ cancel: function(event) {
+ sendAction('escape-press', this, event);
+ },
+
+ /**
+ Called when the text area is focused.
+
+ @method focusIn
+ @param {Event} event
+ */
+ focusIn: function(event) {
+ sendAction('focus-in', this, event);
+ },
+
+ /**
+ Called when the text area is blurred.
+
+ @method focusOut
+ @param {Event} event
+ */
+ focusOut: function(event) {
+ sendAction('focus-out', this, event);
+ },
+
+ /**
+ The action to be sent when the user presses a key. Enabled by setting
+ the `onEvent` property to `keyPress`.
+
+ Uses sendAction to send the `keyPress` action to the controller.
+
+ @method keyPress
+ @param {Event} event
+ */
+ keyPress: function(event) {
+ sendAction('key-press', this, event);
+ }
+
+});
+
+Ember.TextSupport.KEY_EVENTS = {
+ 13: 'insertNewline',
+ 27: 'cancel'
+};
+
+// In principle, this shouldn't be necessary, but the legacy
+// sectionAction semantics for TextField are different from
+// the component semantics so this method normalizes them.
+function sendAction(eventName, view, event) {
+ var action = get(view, eventName),
+ on = get(view, 'onEvent'),
+ value = get(view, 'value');
+
+ // back-compat support for keyPress as an event name even though
+ // it's also a method name that consumes the event (and therefore
+ // incompatible with sendAction semantics).
+ if (on === eventName || (on === 'keyPress' && eventName === 'key-press')) {
+ view.sendAction('action', value);
+ }
+
+ view.sendAction(eventName, value);
+
+ if (action || on === eventName) {
+ if(!get(view, 'bubbles')) {
+ event.stopPropagation();
+ }
+ }
+}
+
+})();
+
+
+
+(function() {
+/**
+@module ember
+@submodule ember-handlebars
+*/
+
+var get = Ember.get, set = Ember.set;
+
+/**
+
+ The internal class used to create text inputs when the `{{input}}`
+ helper is used with `type` of `text`.
+
+ See [Handlebars.helpers.input](/api/classes/Ember.Handlebars.helpers.html#method_input) for usage details.
+
+ ## Layout and LayoutName properties
+
+ Because HTML `input` elements are self closing `layout` and `layoutName`
+ properties will not be applied. See [Ember.View](/api/classes/Ember.View.html)'s
+ layout section for more information.
+
+ @class TextField
+ @namespace Ember
+ @extends Ember.Component
+ @uses Ember.TextSupport
+*/
+Ember.TextField = Ember.Component.extend(Ember.TextSupport, {
+
+ classNames: ['ember-text-field'],
+ tagName: "input",
+ attributeBindings: ['type', 'value', 'size', 'pattern', 'name', 'min', 'max',
+ 'accept', 'autocomplete', 'autosave', 'formaction',
+ 'formenctype', 'formmethod', 'formnovalidate', 'formtarget',
+ 'height', 'inputmode', 'list', 'multiple', 'pattern', 'step',
+ 'width'],
+
+ /**
+ The `value` attribute of the input element. As the user inputs text, this
+ property is updated live.
+
+ @property value
+ @type String
+ @default ""
+ */
+ value: "",
+
+ /**
+ The `type` attribute of the input element.
+
+ @property type
+ @type String
+ @default "text"
+ */
+ type: "text",
+
+ /**
+ The `size` of the text field in characters.
+
+ @property size
+ @type String
+ @default null
+ */
+ size: null,
+
+ /**
+ The `pattern` attribute of input element.
+
+ @property pattern
+ @type String
+ @default null
+ */
+ pattern: null,
+
+ /**
+ The `min` attribute of input element used with `type="number"` or `type="range"`.
+
+ @property min
+ @type String
+ @default null
+ */
+ min: null,
+
+ /**
+ The `max` attribute of input element used with `type="number"` or `type="range"`.
+
+ @property max
+ @type String
+ @default null
+ */
+ max: null
+});
+
+})();
+
+
+
+(function() {
+/**
+@module ember
+@submodule ember-handlebars
+*/
+
+var get = Ember.get, set = Ember.set;
+
+/**
+ The internal class used to create textarea element when the `{{textarea}}`
+ helper is used.
+
+ See [handlebars.helpers.textarea](/api/classes/Ember.Handlebars.helpers.html#method_textarea) for usage details.
+
+ ## Layout and LayoutName properties
+
+ Because HTML `textarea` elements do not contain inner HTML the `layout` and
+ `layoutName` properties will not be applied. See [Ember.View](/api/classes/Ember.View.html)'s
+ layout section for more information.
+
+ @class TextArea
+ @namespace Ember
+ @extends Ember.Component
+ @uses Ember.TextSupport
+*/
+Ember.TextArea = Ember.Component.extend(Ember.TextSupport, {
+ classNames: ['ember-text-area'],
+
+ tagName: "textarea",
+ attributeBindings: ['rows', 'cols', 'name', 'selectionEnd', 'selectionStart', 'wrap'],
+ rows: null,
+ cols: null,
+
+ _updateElementValue: Ember.observer('value', function() {
+ // We do this check so cursor position doesn't get affected in IE
+ var value = get(this, 'value'),
+ $el = this.$();
+ if ($el && value !== $el.val()) {
+ $el.val(value);
+ }
+ }),
+
+ init: function() {
+ this._super();
+ this.on("didInsertElement", this, this._updateElementValue);
+ }
+
+});
+
+})();
+
+
+
+(function() {
+/*jshint eqeqeq:false */
+
+/**
+@module ember
+@submodule ember-handlebars
+*/
+
+var set = Ember.set,
+ get = Ember.get,
+ indexOf = Ember.EnumerableUtils.indexOf,
+ indexesOf = Ember.EnumerableUtils.indexesOf,
+ forEach = Ember.EnumerableUtils.forEach,
+ replace = Ember.EnumerableUtils.replace,
+ isArray = Ember.isArray,
+ precompileTemplate = Ember.Handlebars.compile;
+
+Ember.SelectOption = Ember.View.extend({
+ tagName: 'option',
+ attributeBindings: ['value', 'selected'],
+
+ defaultTemplate: function(context, options) {
+ options = { data: options.data, hash: {} };
+ Ember.Handlebars.helpers.bind.call(context, "view.label", options);
+ },
+
+ init: function() {
+ this.labelPathDidChange();
+ this.valuePathDidChange();
+
+ this._super();
+ },
+
+ selected: Ember.computed(function() {
+ var content = get(this, 'content'),
+ selection = get(this, 'parentView.selection');
+ if (get(this, 'parentView.multiple')) {
+ return selection && indexOf(selection, content.valueOf()) > -1;
+ } else {
+ // Primitives get passed through bindings as objects... since
+ // `new Number(4) !== 4`, we use `==` below
+ return content == selection;
+ }
+ }).property('content', 'parentView.selection'),
+
+ labelPathDidChange: Ember.observer('parentView.optionLabelPath', function() {
+ var labelPath = get(this, 'parentView.optionLabelPath');
+
+ if (!labelPath) { return; }
+
+ Ember.defineProperty(this, 'label', Ember.computed(function() {
+ return get(this, labelPath);
+ }).property(labelPath));
+ }),
+
+ valuePathDidChange: Ember.observer('parentView.optionValuePath', function() {
+ var valuePath = get(this, 'parentView.optionValuePath');
+
+ if (!valuePath) { return; }
+
+ Ember.defineProperty(this, 'value', Ember.computed(function() {
+ return get(this, valuePath);
+ }).property(valuePath));
+ })
+});
+
+Ember.SelectOptgroup = Ember.CollectionView.extend({
+ tagName: 'optgroup',
+ attributeBindings: ['label'],
+
+ selectionBinding: 'parentView.selection',
+ multipleBinding: 'parentView.multiple',
+ optionLabelPathBinding: 'parentView.optionLabelPath',
+ optionValuePathBinding: 'parentView.optionValuePath',
+
+ itemViewClassBinding: 'parentView.optionView'
+});
+
+/**
+ The `Ember.Select` view class renders a
+ [select](https://developer.mozilla.org/en/HTML/Element/select) HTML element,
+ allowing the user to choose from a list of options.
+
+ The text and `value` property of each `<option>` element within the
+ `<select>` element are populated from the objects in the `Element.Select`'s
+ `content` property. The underlying data object of the selected `<option>` is
+ stored in the `Element.Select`'s `value` property.
+
+ ## The Content Property (array of strings)
+
+ The simplest version of an `Ember.Select` takes an array of strings as its
+ `content` property. The string will be used as both the `value` property and
+ the inner text of each `<option>` element inside the rendered `<select>`.
+
+ Example:
+
+ ```javascript
+ App.ApplicationController = Ember.Controller.extend({
+ names: ["Yehuda", "Tom"]
+ });
+ ```
+
+ ```handlebars
+ {{view Ember.Select content=names}}
+ ```
+
+ Would result in the following HTML:
+
+ ```html
+ <select class="ember-select">
+ <option value="Yehuda">Yehuda</option>
+ <option value="Tom">Tom</option>
+ </select>
+ ```
+
+ You can control which `<option>` is selected through the `Ember.Select`'s
+ `value` property:
+
+ ```javascript
+ App.ApplicationController = Ember.Controller.extend({
+ selectedName: 'Tom',
+ names: ["Yehuda", "Tom"]
+ });
+ ```
+
+ ```handlebars
+ {{view Ember.Select
+ content=names
+ value=selectedName
+ }}
+ ```
+
+ Would result in the following HTML with the `<option>` for 'Tom' selected:
+
+ ```html
+ <select class="ember-select">
+ <option value="Yehuda">Yehuda</option>
+ <option value="Tom" selected="selected">Tom</option>
+ </select>
+ ```
+
+ A user interacting with the rendered `<select>` to choose "Yehuda" would
+ update the value of `selectedName` to "Yehuda".
+
+ ## The Content Property (array of Objects)
+
+ An `Ember.Select` can also take an array of JavaScript or Ember objects as
+ its `content` property.
+
+ When using objects you need to tell the `Ember.Select` which property should
+ be accessed on each object to supply the `value` attribute of the `<option>`
+ and which property should be used to supply the element text.
+
+ The `optionValuePath` option is used to specify the path on each object to
+ the desired property for the `value` attribute. The `optionLabelPath`
+ specifies the path on each object to the desired property for the
+ element's text. Both paths must reference each object itself as `content`:
+
+ ```javascript
+ App.ApplicationController = Ember.Controller.extend({
+ programmers: [
+ {firstName: "Yehuda", id: 1},
+ {firstName: "Tom", id: 2}
+ ]
+ });
+ ```
+
+ ```handlebars
+ {{view Ember.Select
+ content=programmers
+ optionValuePath="content.id"
+ optionLabelPath="content.firstName"}}
+ ```
+
+ Would result in the following HTML:
+
+ ```html
+ <select class="ember-select">
+ <option value="1">Yehuda</option>
+ <option value="2">Tom</option>
+ </select>
+ ```
+
+ The `value` attribute of the selected `<option>` within an `Ember.Select`
+ can be bound to a property on another object:
+
+ ```javascript
+ App.ApplicationController = Ember.Controller.extend({
+ programmers: [
+ {firstName: "Yehuda", id: 1},
+ {firstName: "Tom", id: 2}
+ ],
+ currentProgrammer: {
+ id: 2
+ }
+ });
+ ```
+
+ ```handlebars
+ {{view Ember.Select
+ content=programmers
+ optionValuePath="content.id"
+ optionLabelPath="content.firstName"
+ value=currentProgrammer.id}}
+ ```
+
+ Would result in the following HTML with a selected option:
+
+ ```html
+ <select class="ember-select">
+ <option value="1">Yehuda</option>
+ <option value="2" selected="selected">Tom</option>
+ </select>
+ ```
+
+ Interacting with the rendered element by selecting the first option
+ ('Yehuda') will update the `id` of `currentProgrammer`
+ to match the `value` property of the newly selected `<option>`.
+
+ Alternatively, you can control selection through the underlying objects
+ used to render each object by binding the `selection` option. When the selected
+ `<option>` is changed, the property path provided to `selection`
+ will be updated to match the content object of the rendered `<option>`
+ element:
+
+ ```javascript
+ App.ApplicationController = Ember.Controller.extend({
+ selectedPerson: null,
+ programmers: [
+ {firstName: "Yehuda", id: 1},
+ {firstName: "Tom", id: 2}
+ ]
+ });
+ ```
+
+ ```handlebars
+ {{view Ember.Select
+ content=programmers
+ optionValuePath="content.id"
+ optionLabelPath="content.firstName"
+ selection=selectedPerson}}
+ ```
+
+ Would result in the following HTML with a selected option:
+
+ ```html
+ <select class="ember-select">
+ <option value="1">Yehuda</option>
+ <option value="2" selected="selected">Tom</option>
+ </select>
+ ```
+
+ Interacting with the rendered element by selecting the first option
+ ('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`
+
+ ## Supplying a Prompt
+
+ A `null` value for the `Ember.Select`'s `value` or `selection` property
+ results in there being no `<option>` with a `selected` attribute:
+
+ ```javascript
+ App.ApplicationController = Ember.Controller.extend({
+ selectedProgrammer: null,
+ programmers: [
+ "Yehuda",
+ "Tom"
+ ]
+ });
+ ```
+
+ ``` handlebars
+ {{view Ember.Select
+ content=programmers
+ value=selectedProgrammer
+ }}
+ ```
+
+ Would result in the following HTML:
+
+ ```html
+ <select class="ember-select">
+ <option value="Yehuda">Yehuda</option>
+ <option value="Tom">Tom</option>
+ </select>
+ ```
+
+ Although `selectedProgrammer` is `null` and no `<option>`
+ has a `selected` attribute the rendered HTML will display the
+ first item as though it were selected. You can supply a string
+ value for the `Ember.Select` to display when there is no selection
+ with the `prompt` option:
+
+ ```javascript
+ App.ApplicationController = Ember.Controller.extend({
+ selectedProgrammer: null,
+ programmers: [
+ "Yehuda",
+ "Tom"
+ ]
+ });
+ ```
+
+ ```handlebars
+ {{view Ember.Select
+ content=programmers
+ value=selectedProgrammer
+ prompt="Please select a name"
+ }}
+ ```
+
+ Would result in the following HTML:
+
+ ```html
+ <select class="ember-select">
+ <option>Please select a name</option>
+ <option value="Yehuda">Yehuda</option>
+ <option value="Tom">Tom</option>
+ </select>
+ ```
+
+ @class Select
+ @namespace Ember
+ @extends Ember.View
+*/
+Ember.Select = Ember.View.extend({
+ tagName: 'select',
+ classNames: ['ember-select'],
+ defaultTemplate: Ember.Handlebars.template(function anonymous(Handlebars,depth0,helpers,partials,data) {
+this.compilerInfo = [4,'>= 1.0.0'];
+helpers = this.merge(helpers, Ember.Handlebars.helpers); data = data || {};
+ var buffer = '', stack1, escapeExpression=this.escapeExpression, self=this;
+
+function program1(depth0,data) {
+
+ var buffer = '', stack1;
+ data.buffer.push("<option value=\"\">");
+ stack1 = helpers._triageMustache.call(depth0, "view.prompt", {hash:{},hashTypes:{},hashContexts:{},contexts:[depth0],types:["ID"],data:data});
+ if(stack1 || stack1 === 0) { data.buffer.push(stack1); }
+ data.buffer.push("</option>");
+ return buffer;
+ }
+
+function program3(depth0,data) {
+
+ var stack1;
+ stack1 = helpers.each.call(depth0, "view.groupedContent", {hash:{},hashTypes:{},hashContexts:{},inverse:self.noop,fn:self.program(4, program4, data),contexts:[depth0],types:["ID"],data:data});
+ if(stack1 || stack1 === 0) { data.buffer.push(stack1); }
+ else { data.buffer.push(''); }
+ }
+function program4(depth0,data) {
+
+
+ data.buffer.push(escapeExpression(helpers.view.call(depth0, "view.groupView", {hash:{
+ 'content': ("content"),
+ 'label': ("label")
+ },hashTypes:{'content': "ID",'label': "ID"},hashContexts:{'content': depth0,'label': depth0},contexts:[depth0],types:["ID"],data:data})));
+ }
+
+function program6(depth0,data) {
+
+ var stack1;
+ stack1 = helpers.each.call(depth0, "view.content", {hash:{},hashTypes:{},hashContexts:{},inverse:self.noop,fn:self.program(7, program7, data),contexts:[depth0],types:["ID"],data:data});
+ if(stack1 || stack1 === 0) { data.buffer.push(stack1); }
+ else { data.buffer.push(''); }
+ }
+function program7(depth0,data) {
+
+
+ data.buffer.push(escapeExpression(helpers.view.call(depth0, "view.optionView", {hash:{
+ 'content': ("")
+ },hashTypes:{'content': "ID"},hashContexts:{'content': depth0},contexts:[depth0],types:["ID"],data:data})));
+ }
+
+ stack1 = helpers['if'].call(depth0, "view.prompt", {hash:{},hashTypes:{},hashContexts:{},inverse:self.noop,fn:self.program(1, program1, data),contexts:[depth0],types:["ID"],data:data});
+ if(stack1 || stack1 === 0) { data.buffer.push(stack1); }
+ stack1 = helpers['if'].call(depth0, "view.optionGroupPath", {hash:{},hashTypes:{},hashContexts:{},inverse:self.program(6, program6, data),fn:self.program(3, program3, data),contexts:[depth0],types:["ID"],data:data});
+ if(stack1 || stack1 === 0) { data.buffer.push(stack1); }
+ return buffer;
+
+}),
+ attributeBindings: ['multiple', 'disabled', 'tabindex', 'name', 'required', 'autofocus',
+ 'form', 'size'],
+
+ /**
+ The `multiple` attribute of the select element. Indicates whether multiple
+ options can be selected.
+
+ @property multiple
+ @type Boolean
+ @default false
+ */
+ multiple: false,
+
+ /**
+ The `disabled` attribute of the select element. Indicates whether
+ the element is disabled from interactions.
+
+ @property disabled
+ @type Boolean
+ @default false
+ */
+ disabled: false,
+
+ /**
+ The `required` attribute of the select element. Indicates whether
+ a selected option is required for form validation.
+
+ @property required
+ @type Boolean
+ @default false
+ */
+ required: false,
+
+ /**
+ The list of options.
+
+ If `optionLabelPath` and `optionValuePath` are not overridden, this should
+ be a list of strings, which will serve simultaneously as labels and values.
+
+ Otherwise, this should be a list of objects. For instance:
+
+ ```javascript
+ Ember.Select.create({
+ content: Ember.A([
+ { id: 1, firstName: 'Yehuda' },
+ { id: 2, firstName: 'Tom' }
+ ]),
+ optionLabelPath: 'content.firstName',
+ optionValuePath: 'content.id'
+ });
+ ```
+
+ @property content
+ @type Array
+ @default null
+ */
+ content: null,
+
+ /**
+ When `multiple` is `false`, the element of `content` that is currently
+ selected, if any.
+
+ When `multiple` is `true`, an array of such elements.
+
+ @property selection
+ @type Object or Array
+ @default null
+ */
+ selection: null,
+
+ /**
+ In single selection mode (when `multiple` is `false`), value can be used to
+ get the current selection's value or set the selection by it's value.
+
+ It is not currently supported in multiple selection mode.
+
+ @property value
+ @type String
+ @default null
+ */
+ value: Ember.computed(function(key, value) {
+ if (arguments.length === 2) { return value; }
+ var valuePath = get(this, 'optionValuePath').replace(/^content\.?/, '');
+ return valuePath ? get(this, 'selection.' + valuePath) : get(this, 'selection');
+ }).property('selection'),
+
+ /**
+ If given, a top-most dummy option will be rendered to serve as a user
+ prompt.
+
+ @property prompt
+ @type String
+ @default null
+ */
+ prompt: null,
+
+ /**
+ The path of the option labels. See [content](/api/classes/Ember.Select.html#property_content).
+
+ @property optionLabelPath
+ @type String
+ @default 'content'
+ */
+ optionLabelPath: 'content',
+
+ /**
+ The path of the option values. See [content](/api/classes/Ember.Select.html#property_content).
+
+ @property optionValuePath
+ @type String
+ @default 'content'
+ */
+ optionValuePath: 'content',
+
+ /**
+ The path of the option group.
+ When this property is used, `content` should be sorted by `optionGroupPath`.
+
+ @property optionGroupPath
+ @type String
+ @default null
+ */
+ optionGroupPath: null,
+
+ /**
+ The view class for optgroup.
+
+ @property groupView
+ @type Ember.View
+ @default Ember.SelectOptgroup
+ */
+ groupView: Ember.SelectOptgroup,
+
+ groupedContent: Ember.computed(function() {
+ var groupPath = get(this, 'optionGroupPath');
+ var groupedContent = Ember.A();
+ var content = get(this, 'content') || [];
+
+ forEach(content, function(item) {
+ var label = get(item, groupPath);
+
+ if (get(groupedContent, 'lastObject.label') !== label) {
+ groupedContent.pushObject({
+ label: label,
+ content: Ember.A()
+ });
+ }
+
+ get(groupedContent, 'lastObject.content').push(item);
+ });
+
+ return groupedContent;
+ }).property('optionGroupPath', 'content.@each'),
+
+ /**
+ The view class for option.
+
+ @property optionView
+ @type Ember.View
+ @default Ember.SelectOption
+ */
+ optionView: Ember.SelectOption,
+
+ _change: function() {
+ if (get(this, 'multiple')) {
+ this._changeMultiple();
+ } else {
+ this._changeSingle();
+ }
+ },
+
+ selectionDidChange: Ember.observer('selection.@each', function() {
+ var selection = get(this, 'selection');
+ if (get(this, 'multiple')) {
+ if (!isArray(selection)) {
+ set(this, 'selection', Ember.A([selection]));
+ return;
+ }
+ this._selectionDidChangeMultiple();
+ } else {
+ this._selectionDidChangeSingle();
+ }
+ }),
+
+ valueDidChange: Ember.observer('value', function() {
+ var content = get(this, 'content'),
+ value = get(this, 'value'),
+ valuePath = get(this, 'optionValuePath').replace(/^content\.?/, ''),
+ selectedValue = (valuePath ? get(this, 'selection.' + valuePath) : get(this, 'selection')),
+ selection;
+
+ if (value !== selectedValue) {
+ selection = content ? content.find(function(obj) {
+ return value === (valuePath ? get(obj, valuePath) : obj);
+ }) : null;
+
+ this.set('selection', selection);
+ }
+ }),
+
+
+ _triggerChange: function() {
+ var selection = get(this, 'selection');
+ var value = get(this, 'value');
+
+ if (!Ember.isNone(selection)) { this.selectionDidChange(); }
+ if (!Ember.isNone(value)) { this.valueDidChange(); }
+
+ this._change();
+ },
+
+ _changeSingle: function() {
+ var selectedIndex = this.$()[0].selectedIndex,
+ content = get(this, 'content'),
+ prompt = get(this, 'prompt');
+
+ if (!content || !get(content, 'length')) { return; }
+ if (prompt && selectedIndex === 0) { set(this, 'selection', null); return; }
+
+ if (prompt) { selectedIndex -= 1; }
+ set(this, 'selection', content.objectAt(selectedIndex));
+ },
+
+
+ _changeMultiple: function() {
+ var options = this.$('option:selected'),
+ prompt = get(this, 'prompt'),
+ offset = prompt ? 1 : 0,
+ content = get(this, 'content'),
+ selection = get(this, 'selection');
+
+ if (!content) { return; }
+ if (options) {
+ var selectedIndexes = options.map(function() {
+ return this.index - offset;
+ }).toArray();
+ var newSelection = content.objectsAt(selectedIndexes);
+
+ if (isArray(selection)) {
+ replace(selection, 0, get(selection, 'length'), newSelection);
+ } else {
+ set(this, 'selection', newSelection);
+ }
+ }
+ },
+
+ _selectionDidChangeSingle: function() {
+ var el = this.get('element');
+ if (!el) { return; }
+
+ var content = get(this, 'content'),
+ selection = get(this, 'selection'),
+ selectionIndex = content ? indexOf(content, selection) : -1,
+ prompt = get(this, 'prompt');
+
+ if (prompt) { selectionIndex += 1; }
+ if (el) { el.selectedIndex = selectionIndex; }
+ },
+
+ _selectionDidChangeMultiple: function() {
+ var content = get(this, 'content'),
+ selection = get(this, 'selection'),
+ selectedIndexes = content ? indexesOf(content, selection) : [-1],
+ prompt = get(this, 'prompt'),
+ offset = prompt ? 1 : 0,
+ options = this.$('option'),
+ adjusted;
+
+ if (options) {
+ options.each(function() {
+ adjusted = this.index > -1 ? this.index - offset : -1;
+ this.selected = indexOf(selectedIndexes, adjusted) > -1;
+ });
+ }
+ },
+
+ init: function() {
+ this._super();
+ this.on("didInsertElement", this, this._triggerChange);
+ this.on("change", this, this._change);
+ }
+});
+
+})();
+
+
+
+(function() {
+/**
+@module ember
+@submodule ember-handlebars-compiler
+*/
+
+/**
+
+ The `{{input}}` helper inserts an HTML `<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.
+
+ ## 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:
+
+ <table>
+ <tr><td>`readonly`</td><td>`required`</td><td>`autofocus`</td></tr>
+ <tr><td>`value`</td><td>`placeholder`</td><td>`disabled`</td></tr>
+ <tr><td>`size`</td><td>`tabindex`</td><td>`maxlength`</td></tr>
+ <tr><td>`name`</td><td>`min`</td><td>`max`</td></tr>
+ <tr><td>`pattern`</td><td>`accept`</td><td>`autocomplete`</td></tr>
+ <tr><td>`autosave`</td><td>`formaction`</td><td>`formenctype`</td></tr>
+ <tr><td>`formmethod`</td><td>`formnovalidate`</td><td>`formtarget`</td></tr>
+ <tr><td>`height`</td><td>`inputmode`</td><td>`multiple`</td></tr>
+ <tr><td>`step`</td><td>`width`</td><td>`form`</td></tr>
+ <tr><td>`selectionDirection`</td><td>`spellcheck`</td><td> </td></tr>
+ </table>
+
+
+ When set to a quoted string, these values will be directly applied to the HTML
+ element. When left unquoted, these values will be bound to a property on the
+ template's current rendering context (most typically a controller instance).
+
+ ## Unbound:
+
+ ```handlebars
+ {{input value="http://www.facebook.com"}}
+ ```
+
+
+ ```html
+ <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`
+* `autofocus`
+* `form`
+
+
+ When set to a quoted string, these values will be directly applied to the HTML
+ element. When left unquoted, these values will be bound to a property on the
+ template's current rendering context (most typically a controller instance).
+
+ ## Unbound:
+
+ ```handlebars
+ {{input type="checkbox" name="isAdmin"}}
+ ```
+
+ ```html
+ <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') {
+ Ember.assert("{{input type='checkbox'}} does not support setting `value=someBooleanValue`; you must use `checked=someBooleanValue` instead.", options.hashTypes.value !== 'ID');
+ return Ember.Handlebars.helpers.view.call(this, Ember.Checkbox, options);
+ } else {
+ if (inputType) { hash.type = inputType; }
+ hash.onEvent = onEvent || 'enter';
+ return Ember.Handlebars.helpers.view.call(this, Ember.TextField, options);
+ }
+});
+
+/**
+ `{{textarea}}` inserts a new instance of `<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`
+ * `selectionEnd`
+ * `selectionStart`
+ * `selectionDirection`
+ * `wrap`
+ * `readonly`
+ * `autofocus`
+ * `form`
+ * `spellcheck`
+ * `required`
+
+ 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);
+});
+
+})();
+
+
+
+(function() {
+Ember.ComponentLookup = Ember.Object.extend({
+ lookupFactory: function(name, container) {
+
+ container = container || this.container;
+
+ var fullName = 'component:' + name,
+ templateFullName = 'template:components/' + name,
+ templateRegistered = container && container.has(templateFullName);
+
+ if (templateRegistered) {
+ container.injection(fullName, 'layout', templateFullName);
+ }
+
+ var Component = container.lookupFactory(fullName);
+
+ // Only treat as a component if either the component
+ // or a template has been registered.
+ if (templateRegistered || Component) {
+ if (!Component) {
+ container.register(fullName, Ember.Component);
+ Component = container.lookupFactory(fullName);
+ }
+ return Component;
+ }
+ }
+});
+
+})();
+
+
+
+(function() {
+/*globals Handlebars */
+/**
+@module ember
+@submodule ember-handlebars
+*/
+
+/**
+ Find templates stored in the head tag as script tags and make them available
+ to `Ember.CoreView` in the global `Ember.TEMPLATES` object. This will be run
+ as as jQuery DOM-ready callback.
+
+ Script tags with `text/x-handlebars` will be compiled
+ with Ember's Handlebars and are suitable for use as a view's template.
+ Those with type `text/x-raw-handlebars` will be compiled with regular
+ Handlebars and are suitable for use in views' computed properties.
+
+ @private
+ @method bootstrap
+ @for Ember.Handlebars
+ @static
+ @param ctx
+*/
+Ember.Handlebars.bootstrap = function(ctx) {
+ var selectors = 'script[type="text/x-handlebars"], script[type="text/x-raw-handlebars"]';
+
+ Ember.$(selectors, ctx)
+ .each(function() {
+ // Get a reference to the script tag
+ var script = Ember.$(this);
+
+ var compile = (script.attr('type') === 'text/x-raw-handlebars') ?
+ Ember.$.proxy(Handlebars.compile, Handlebars) :
+ Ember.$.proxy(Ember.Handlebars.compile, Ember.Handlebars),
+ // Get the name of the script, used by Ember.View's templateName property.
+ // First look for data-template-name attribute, then fall back to its
+ // id if no name is found.
+ templateName = script.attr('data-template-name') || script.attr('id') || 'application',
+ template = compile(script.html());
+
+ // Check if template of same name already exists
+ if (Ember.TEMPLATES[templateName] !== undefined) {
+ throw new Ember.Error('Template named "' + templateName + '" already exists.');
+ }
+
+ // For templates which have a name, we save them and then remove them from the DOM
+ Ember.TEMPLATES[templateName] = template;
+
+ // Remove script tag from DOM
+ script.remove();
+ });
+};
+
+function bootstrap() {
+ Ember.Handlebars.bootstrap( Ember.$(document) );
+}
+
+function registerComponentLookup(container) {
+ container.register('component-lookup:main', Ember.ComponentLookup);
+}
+
+/*
+ We tie this to application.load to ensure that we've at least
+ attempted to bootstrap at the point that the application is loaded.
+
+ We also tie this to document ready since we're guaranteed that all
+ the inline templates are present at this point.
+
+ There's no harm to running this twice, since we remove the templates
+ from the DOM after processing.
+*/
+
+Ember.onLoad('Ember.Application', function(Application) {
+ Application.initializer({
+ name: 'domTemplates',
+ initialize: bootstrap
+ });
+
+ Application.initializer({
+ name: 'registerComponentLookup',
+ after: 'domTemplates',
+ initialize: registerComponentLookup
+ });
+});
+
+})();
+
+
+
+(function() {
+/**
+Ember Handlebars
+
+@module ember
+@submodule ember-handlebars
+@requires ember-views
+*/
+
+Ember.runLoadHooks('Ember.Handlebars', Ember.Handlebars);
+
+})();
+
+(function() {
+define("route-recognizer",
+ ["exports"],
+ function(__exports__) {
+ "use strict";
+ var specials = [
+ '/', '.', '*', '+', '?', '|',
+ '(', ')', '[', ']', '{', '}', '\\'
+ ];
+
+ var escapeRegex = new RegExp('(\\' + specials.join('|\\') + ')', 'g');
+
+ function isArray(test) {
+ return Object.prototype.toString.call(test) === "[object Array]";
+ }
+
+ // A Segment represents a segment in the original route description.
+ // Each Segment type provides an `eachChar` and `regex` method.
+ //
+ // The `eachChar` method invokes the callback with one or more character
+ // specifications. A character specification consumes one or more input
+ // characters.
+ //
+ // The `regex` method returns a regex fragment for the segment. If the
+ // segment is a dynamic of star segment, the regex fragment also includes
+ // a capture.
+ //
+ // A character specification contains:
+ //
+ // * `validChars`: a String with a list of all valid characters, or
+ // * `invalidChars`: a String with a list of all invalid characters
+ // * `repeat`: true if the character specification can repeat
+
+ function StaticSegment(string) { this.string = string; }
+ StaticSegment.prototype = {
+ eachChar: function(callback) {
+ var string = this.string, ch;
+
+ for (var i=0, l=string.length; i<l; i++) {
+ ch = string.charAt(i);
+ callback({ validChars: ch });
+ }
+ },
+
+ regex: function() {
+ return this.string.replace(escapeRegex, '\\$1');
+ },
+
+ generate: function() {
+ return this.string;
+ }
+ };
+
+ function DynamicSegment(name) { this.name = name; }
+ DynamicSegment.prototype = {
+ eachChar: function(callback) {
+ callback({ invalidChars: "/", repeat: true });
+ },
+
+ regex: function() {
+ return "([^/]+)";
+ },
+
+ generate: function(params) {
+ return params[this.name];
+ }
+ };
+
+ function StarSegment(name) { this.name = name; }
+ StarSegment.prototype = {
+ eachChar: function(callback) {
+ callback({ invalidChars: "", repeat: true });
+ },
+
+ regex: function() {
+ return "(.+)";
+ },
+
+ generate: function(params) {
+ return params[this.name];
+ }
+ };
+
+ function EpsilonSegment() {}
+ EpsilonSegment.prototype = {
+ eachChar: function() {},
+ regex: function() { return ""; },
+ generate: function() { return ""; }
+ };
+
+ function parse(route, names, types) {
+ // normalize route as not starting with a "/". Recognition will
+ // also normalize.
+ if (route.charAt(0) === "/") { route = route.substr(1); }
+
+ var segments = route.split("/"), results = [];
+
+ for (var i=0, l=segments.length; i<l; i++) {
+ var segment = segments[i], match;
+
+ if (match = segment.match(/^:([^\/]+)$/)) {
+ results.push(new DynamicSegment(match[1]));
+ names.push(match[1]);
+ types.dynamics++;
+ } else if (match = segment.match(/^\*([^\/]+)$/)) {
+ results.push(new StarSegment(match[1]));
+ names.push(match[1]);
+ types.stars++;
+ } else if(segment === "") {
+ results.push(new EpsilonSegment());
+ } else {
+ results.push(new StaticSegment(segment));
+ types.statics++;
+ }
+ }
+
+ return results;
+ }
+
+ // A State has a character specification and (`charSpec`) and a list of possible
+ // subsequent states (`nextStates`).
+ //
+ // If a State is an accepting state, it will also have several additional
+ // properties:
+ //
+ // * `regex`: A regular expression that is used to extract parameters from paths
+ // that reached this accepting state.
+ // * `handlers`: Information on how to convert the list of captures into calls
+ // to registered handlers with the specified parameters
+ // * `types`: How many static, dynamic or star segments in this route. Used to
+ // decide which route to use if multiple registered routes match a path.
+ //
+ // Currently, State is implemented naively by looping over `nextStates` and
+ // comparing a character specification against a character. A more efficient
+ // implementation would use a hash of keys pointing at one or more next states.
+
+ function State(charSpec) {
+ this.charSpec = charSpec;
+ this.nextStates = [];
+ }
+
+ State.prototype = {
+ get: function(charSpec) {
+ var nextStates = this.nextStates;
+
+ for (var i=0, l=nextStates.length; i<l; i++) {
+ var child = nextStates[i];
+
+ var isEqual = child.charSpec.validChars === charSpec.validChars;
+ isEqual = isEqual && child.charSpec.invalidChars === charSpec.invalidChars;
+
+ if (isEqual) { return child; }
+ }
+ },
+
+ put: function(charSpec) {
+ var state;
+
+ // If the character specification already exists in a child of the current
+ // state, just return that state.
+ if (state = this.get(charSpec)) { return state; }
+
+ // Make a new state for the character spec
+ state = new State(charSpec);
+
+ // Insert the new state as a child of the current state
+ this.nextStates.push(state);
+
+ // If this character specification repeats, insert the new state as a child
+ // of itself. Note that this will not trigger an infinite loop because each
+ // transition during recognition consumes a character.
+ if (charSpec.repeat) {
+ state.nextStates.push(state);
+ }
+
+ // Return the new state
+ return state;
+ },
+
+ // Find a list of child states matching the next character
+ match: function(ch) {
+ // DEBUG "Processing `" + ch + "`:"
+ var nextStates = this.nextStates,
+ child, charSpec, chars;
+
+ // DEBUG " " + debugState(this)
+ var returned = [];
+
+ for (var i=0, l=nextStates.length; i<l; i++) {
+ child = nextStates[i];
+
+ charSpec = child.charSpec;
+
+ if (typeof (chars = charSpec.validChars) !== 'undefined') {
+ if (chars.indexOf(ch) !== -1) { returned.push(child); }
+ } else if (typeof (chars = charSpec.invalidChars) !== 'undefined') {
+ if (chars.indexOf(ch) === -1) { returned.push(child); }
+ }
+ }
+
+ return returned;
+ }
+
+ /** IF DEBUG
+ , debug: function() {
+ var charSpec = this.charSpec,
+ debug = "[",
+ chars = charSpec.validChars || charSpec.invalidChars;
+
+ if (charSpec.invalidChars) { debug += "^"; }
+ debug += chars;
+ debug += "]";
+
+ if (charSpec.repeat) { debug += "+"; }
+
+ return debug;
+ }
+ END IF **/
+ };
+
+ /** IF DEBUG
+ function debug(log) {
+ console.log(log);
+ }
+
+ function debugState(state) {
+ return state.nextStates.map(function(n) {
+ if (n.nextStates.length === 0) { return "( " + n.debug() + " [accepting] )"; }
+ return "( " + n.debug() + " <then> " + n.nextStates.map(function(s) { return s.debug() }).join(" or ") + " )";
+ }).join(", ")
+ }
+ END IF **/
+
+ // This is a somewhat naive strategy, but should work in a lot of cases
+ // A better strategy would properly resolve /posts/:id/new and /posts/edit/:id.
+ //
+ // This strategy generally prefers more static and less dynamic matching.
+ // Specifically, it
+ //
+ // * prefers fewer stars to more, then
+ // * prefers using stars for less of the match to more, then
+ // * prefers fewer dynamic segments to more, then
+ // * prefers more static segments to more
+ function sortSolutions(states) {
+ return states.sort(function(a, b) {
+ if (a.types.stars !== b.types.stars) { return a.types.stars - b.types.stars; }
+
+ if (a.types.stars) {
+ if (a.types.statics !== b.types.statics) { return b.types.statics - a.types.statics; }
+ if (a.types.dynamics !== b.types.dynamics) { return b.types.dynamics - a.types.dynamics; }
+ }
+
+ if (a.types.dynamics !== b.types.dynamics) { return a.types.dynamics - b.types.dynamics; }
+ if (a.types.statics !== b.types.statics) { return b.types.statics - a.types.statics; }
+
+ return 0;
+ });
+ }
+
+ function recognizeChar(states, ch) {
+ var nextStates = [];
+
+ for (var i=0, l=states.length; i<l; i++) {
+ var state = states[i];
+
+ nextStates = nextStates.concat(state.match(ch));
+ }
+
+ return nextStates;
+ }
+
+ var oCreate = Object.create || function(proto) {
+ function F() {}
+ F.prototype = proto;
+ return new F();
+ };
+
+ function RecognizeResults(queryParams) {
+ this.queryParams = queryParams || {};
+ }
+ RecognizeResults.prototype = oCreate({
+ splice: Array.prototype.splice,
+ slice: Array.prototype.slice,
+ push: Array.prototype.push,
+ length: 0,
+ queryParams: null
+ });
+
+ function findHandler(state, path, queryParams) {
+ var handlers = state.handlers, regex = state.regex;
+ var captures = path.match(regex), currentCapture = 1;
+ var result = new RecognizeResults(queryParams);
+
+ for (var i=0, l=handlers.length; i<l; i++) {
+ var handler = handlers[i], names = handler.names, params = {};
+
+ for (var j=0, m=names.length; j<m; j++) {
+ params[names[j]] = captures[currentCapture++];
+ }
+
+ result.push({ handler: handler.handler, params: params, isDynamic: !!names.length });
+ }
+
+ return result;
+ }
+
+ function addSegment(currentState, segment) {
+ segment.eachChar(function(ch) {
+ var state;
+
+ currentState = currentState.put(ch);
+ });
+
+ return currentState;
+ }
+
+ // The main interface
+
+ var RouteRecognizer = function() {
+ this.rootState = new State();
+ this.names = {};
+ };
+
+
+ RouteRecognizer.prototype = {
+ add: function(routes, options) {
+ var currentState = this.rootState, regex = "^",
+ types = { statics: 0, dynamics: 0, stars: 0 },
+ handlers = [], allSegments = [], name;
+
+ var isEmpty = true;
+
+ for (var i=0, l=routes.length; i<l; i++) {
+ var route = routes[i], names = [];
+
+ var segments = parse(route.path, names, types);
+
+ allSegments = allSegments.concat(segments);
+
+ for (var j=0, m=segments.length; j<m; j++) {
+ var segment = segments[j];
+
+ if (segment instanceof EpsilonSegment) { continue; }
+
+ isEmpty = false;
+
+ // Add a "/" for the new segment
+ currentState = currentState.put({ validChars: "/" });
+ regex += "/";
+
+ // Add a representation of the segment to the NFA and regex
+ currentState = addSegment(currentState, segment);
+ regex += segment.regex();
+ }
+
+ var handler = { handler: route.handler, names: names };
+ handlers.push(handler);
+ }
+
+ if (isEmpty) {
+ currentState = currentState.put({ validChars: "/" });
+ regex += "/";
+ }
+
+ currentState.handlers = handlers;
+ currentState.regex = new RegExp(regex + "$");
+ currentState.types = types;
+
+ if (name = options && options.as) {
+ this.names[name] = {
+ segments: allSegments,
+ handlers: handlers
+ };
+ }
+ },
+
+ handlersFor: function(name) {
+ var route = this.names[name], result = [];
+ if (!route) { throw new Error("There is no route named " + name); }
+
+ for (var i=0, l=route.handlers.length; i<l; i++) {
+ result.push(route.handlers[i]);
+ }
+
+ return result;
+ },
+
+ hasRoute: function(name) {
+ return !!this.names[name];
+ },
+
+ generate: function(name, params) {
+ var route = this.names[name], output = "";
+ if (!route) { throw new Error("There is no route named " + name); }
+
+ var segments = route.segments;
+
+ for (var i=0, l=segments.length; i<l; i++) {
+ var segment = segments[i];
+
+ if (segment instanceof EpsilonSegment) { continue; }
+
+ output += "/";
+ output += segment.generate(params);
+ }
+
+ if (output.charAt(0) !== '/') { output = '/' + output; }
+
+ if (params && params.queryParams) {
+ output += this.generateQueryString(params.queryParams, route.handlers);
+ }
+
+ return output;
+ },
+
+ generateQueryString: function(params, handlers) {
+ var pairs = [];
+ var keys = [];
+ for(var key in params) {
+ if (params.hasOwnProperty(key)) {
+ keys.push(key);
+ }
+ }
+ keys.sort();
+ for (var i = 0, len = keys.length; i < len; i++) {
+ key = keys[i];
+ var value = params[key];
+ if (value === false || value == null) {
+ continue;
+ }
+ var pair = key;
+ if (isArray(value)) {
+ for (var j = 0, l = value.length; j < l; j++) {
+ var arrayPair = key + '[]' + '=' + encodeURIComponent(value[j]);
+ pairs.push(arrayPair);
+ }
+ }
+ else if (value !== true) {
+ pair += "=" + encodeURIComponent(value);
+ pairs.push(pair);
+ } else {
+ 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]),
+ keyLength = key.length,
+ isArray = false,
+ value;
+ if (pair.length === 1) {
+ value = true;
+ } else {
+ //Handle arrays
+ if (keyLength > 2 && key.slice(keyLength -2) === '[]') {
+ isArray = true;
+ key = key.slice(0, keyLength - 2);
+ if(!queryParams[key]) {
+ queryParams[key] = [];
+ }
+ }
+ value = pair[1] ? decodeURIComponent(pair[1]) : '';
+ }
+ if (isArray) {
+ queryParams[key].push(value);
+ } else {
+ queryParams[key] = value;
+ }
+
+ }
+ return queryParams;
+ },
+
+ recognize: function(path) {
+ var states = [ this.rootState ],
+ pathLen, i, l, queryStart, queryParams = {},
+ isSlashDropped = false;
+
+ path = decodeURI(path);
+
+ queryStart = path.indexOf('?');
+ if (queryStart !== -1) {
+ var queryString = path.substr(queryStart + 1, path.length);
+ path = path.substr(0, queryStart);
+ queryParams = this.parseQueryString(queryString);
+ }
+
+ // DEBUG GROUP path
+
+ if (path.charAt(0) !== "/") { path = "/" + path; }
+
+ pathLen = path.length;
+ if (pathLen > 1 && path.charAt(pathLen - 1) === "/") {
+ path = path.substr(0, pathLen - 1);
+ isSlashDropped = true;
+ }
+
+ for (i=0, l=path.length; i<l; i++) {
+ states = recognizeChar(states, path.charAt(i));
+ if (!states.length) { break; }
+ }
+
+ // END DEBUG GROUP
+
+ var solutions = [];
+ for (i=0, l=states.length; i<l; i++) {
+ if (states[i].handlers) { solutions.push(states[i]); }
+ }
+
+ states = sortSolutions(solutions);
+
+ var state = solutions[0];
+
+ if (state && state.handlers) {
+ // if a trailing slash was dropped and a star segment is the last segment
+ // specified, put the trailing slash back
+ if (isSlashDropped && state.regex.source.slice(-5) === "(.+)$") {
+ path = path + "/";
+ }
+ return findHandler(state, path, queryParams);
+ }
+ }
+ };
+
+ __exports__["default"] = RouteRecognizer;
+
+ function Target(path, matcher, delegate) {
+ this.path = path;
+ this.matcher = matcher;
+ this.delegate = delegate;
+ }
+
+ Target.prototype = {
+ to: function(target, callback) {
+ var delegate = this.delegate;
+
+ if (delegate && delegate.willAddRoute) {
+ target = delegate.willAddRoute(this.matcher.target, target);
+ }
+
+ this.matcher.add(this.path, target);
+
+ if (callback) {
+ if (callback.length === 0) { throw new Error("You must have an argument in the function passed to `to`"); }
+ this.matcher.addChild(this.path, target, callback, this.delegate);
+ }
+ return this;
+ }
+ };
+
+ function Matcher(target) {
+ this.routes = {};
+ this.children = {};
+ this.target = target;
+ }
+
+ Matcher.prototype = {
+ add: function(path, handler) {
+ this.routes[path] = handler;
+ },
+
+ addChild: function(path, target, callback, delegate) {
+ var matcher = new Matcher(target);
+ this.children[path] = matcher;
+
+ var match = generateMatch(path, matcher, delegate);
+
+ if (delegate && delegate.contextEntered) {
+ delegate.contextEntered(target, match);
+ }
+
+ callback(match);
+ }
+ };
+
+ function generateMatch(startingPath, matcher, delegate) {
+ return function(path, nestedCallback) {
+ var fullPath = startingPath + path;
+
+ if (nestedCallback) {
+ nestedCallback(generateMatch(fullPath, matcher, delegate));
+ } else {
+ return new Target(startingPath + path, matcher, delegate);
+ }
+ };
+ }
+
+ function addRoute(routeArray, path, handler) {
+ var len = 0;
+ for (var i=0, l=routeArray.length; i<l; i++) {
+ len += routeArray[i].path.length;
+ }
+
+ path = path.substr(len);
+ var route = { path: path, handler: handler };
+ routeArray.push(route);
+ }
+
+ function eachRoute(baseRoute, matcher, callback, binding) {
+ var routes = matcher.routes;
+
+ for (var path in routes) {
+ if (routes.hasOwnProperty(path)) {
+ var routeArray = baseRoute.slice();
+ addRoute(routeArray, path, routes[path]);
+
+ if (matcher.children[path]) {
+ eachRoute(routeArray, matcher.children[path], callback, binding);
+ } else {
+ callback.call(binding, routeArray);
+ }
+ }
+ }
+ }
+
+ RouteRecognizer.prototype.map = function(callback, addRouteCallback) {
+ var matcher = new Matcher();
+
+ callback(generateMatch("", matcher, this.delegate));
+
+ eachRoute([], matcher, function(route) {
+ if (addRouteCallback) { addRouteCallback(this, route); }
+ else { this.add(route); }
+ }, this);
+ };
+ });
+
+})();
+
+
+
+(function() {
+define("router/handler-info",
+ ["./utils","rsvp","exports"],
+ function(__dependency1__, __dependency2__, __exports__) {
+ "use strict";
+ var bind = __dependency1__.bind;
+ var merge = __dependency1__.merge;
+ var oCreate = __dependency1__.oCreate;
+ var serialize = __dependency1__.serialize;
+ var promiseLabel = __dependency1__.promiseLabel;
+ var resolve = __dependency2__.resolve;
+
+ function HandlerInfo(props) {
+ if (props) {
+ merge(this, props);
+ }
+ }
+
+ HandlerInfo.prototype = {
+ name: null,
+ handler: null,
+ params: null,
+ context: null,
+
+ log: function(payload, message) {
+ if (payload.log) {
+ payload.log(this.name + ': ' + message);
+ }
+ },
+
+ promiseLabel: function(label) {
+ return promiseLabel("'" + this.name + "' " + label);
+ },
+
+ resolve: function(async, shouldContinue, payload) {
+ var checkForAbort = bind(this.checkForAbort, this, shouldContinue),
+ beforeModel = bind(this.runBeforeModelHook, this, async, payload),
+ model = bind(this.getModel, this, async, payload),
+ afterModel = bind(this.runAfterModelHook, this, async, payload),
+ becomeResolved = bind(this.becomeResolved, this, payload);
+
+ return resolve(undefined, this.promiseLabel("Start handler"))
+ .then(checkForAbort, null, this.promiseLabel("Check for abort"))
+ .then(beforeModel, null, this.promiseLabel("Before model"))
+ .then(checkForAbort, null, this.promiseLabel("Check if aborted during 'beforeModel' hook"))
+ .then(model, null, this.promiseLabel("Model"))
+ .then(checkForAbort, null, this.promiseLabel("Check if aborted in 'model' hook"))
+ .then(afterModel, null, this.promiseLabel("After model"))
+ .then(checkForAbort, null, this.promiseLabel("Check if aborted in 'afterModel' hook"))
+ .then(becomeResolved, null, this.promiseLabel("Become resolved"));
+ },
+
+ runBeforeModelHook: function(async, payload) {
+ if (payload.trigger) {
+ payload.trigger(true, 'willResolveModel', payload, this.handler);
+ }
+ return this.runSharedModelHook(async, payload, 'beforeModel', []);
+ },
+
+ runAfterModelHook: function(async, payload, resolvedModel) {
+ // Stash the resolved model on the payload.
+ // This makes it possible for users to swap out
+ // the resolved model in afterModel.
+ var name = this.name;
+ this.stashResolvedModel(payload, resolvedModel);
+
+ return this.runSharedModelHook(async, payload, 'afterModel', [resolvedModel])
+ .then(function() {
+ // Ignore the fulfilled value returned from afterModel.
+ // Return the value stashed in resolvedModels, which
+ // might have been swapped out in afterModel.
+ return payload.resolvedModels[name];
+ }, null, this.promiseLabel("Ignore fulfillment value and return model value"));
+ },
+
+ runSharedModelHook: function(async, payload, hookName, args) {
+ this.log(payload, "calling " + hookName + " hook");
+
+ if (this.queryParams) {
+ args.push(this.queryParams);
+ }
+ args.push(payload);
+
+ var handler = this.handler;
+ return async(function() {
+ return handler[hookName] && handler[hookName].apply(handler, args);
+ }, this.promiseLabel("Handle " + hookName));
+ },
+
+ getModel: function(payload) {
+ throw new Error("This should be overridden by a subclass of HandlerInfo");
+ },
+
+ checkForAbort: function(shouldContinue, promiseValue) {
+ return resolve(shouldContinue(), this.promiseLabel("Check for abort")).then(function() {
+ // We don't care about shouldContinue's resolve value;
+ // pass along the original value passed to this fn.
+ return promiseValue;
+ }, null, this.promiseLabel("Ignore fulfillment value and continue"));
+ },
+
+ stashResolvedModel: function(payload, resolvedModel) {
+ payload.resolvedModels = payload.resolvedModels || {};
+ payload.resolvedModels[this.name] = resolvedModel;
+ },
+
+ becomeResolved: function(payload, resolvedContext) {
+ var params = this.params || serialize(this.handler, resolvedContext, this.names);
+
+ if (payload) {
+ this.stashResolvedModel(payload, resolvedContext);
+ payload.params = payload.params || {};
+ payload.params[this.name] = params;
+ }
+
+ return new ResolvedHandlerInfo({
+ context: resolvedContext,
+ name: this.name,
+ handler: this.handler,
+ params: params
+ });
+ },
+
+ shouldSupercede: function(other) {
+ // Prefer this newer handlerInfo over `other` if:
+ // 1) The other one doesn't exist
+ // 2) The names don't match
+ // 3) This handler has a context that doesn't match
+ // the other one (or the other one doesn't have one).
+ // 4) This handler has parameters that don't match the other.
+ if (!other) { return true; }
+
+ var contextsMatch = (other.context === this.context);
+ return other.name !== this.name ||
+ (this.hasOwnProperty('context') && !contextsMatch) ||
+ (this.hasOwnProperty('params') && !paramsMatch(this.params, other.params));
+ }
+ };
+
+ function ResolvedHandlerInfo(props) {
+ HandlerInfo.call(this, props);
+ }
+
+ ResolvedHandlerInfo.prototype = oCreate(HandlerInfo.prototype);
+ ResolvedHandlerInfo.prototype.resolve = function(async, shouldContinue, payload) {
+ // A ResolvedHandlerInfo just resolved with itself.
+ if (payload && payload.resolvedModels) {
+ payload.resolvedModels[this.name] = this.context;
+ }
+ return resolve(this, this.promiseLabel("Resolve"));
+ };
+
+ // These are generated by URL transitions and
+ // named transitions for non-dynamic route segments.
+ function UnresolvedHandlerInfoByParam(props) {
+ HandlerInfo.call(this, props);
+ this.params = this.params || {};
+ }
+
+ UnresolvedHandlerInfoByParam.prototype = oCreate(HandlerInfo.prototype);
+ UnresolvedHandlerInfoByParam.prototype.getModel = function(async, payload) {
+ var fullParams = this.params;
+ if (payload && payload.queryParams) {
+ fullParams = {};
+ merge(fullParams, this.params);
+ fullParams.queryParams = payload.queryParams;
+ }
+
+ var hookName = typeof this.handler.deserialize === 'function' ?
+ 'deserialize' : 'model';
+
+ return this.runSharedModelHook(async, payload, hookName, [fullParams]);
+ };
+
+
+ // These are generated only for named transitions
+ // with dynamic route segments.
+ function UnresolvedHandlerInfoByObject(props) {
+ HandlerInfo.call(this, props);
+ }
+
+ UnresolvedHandlerInfoByObject.prototype = oCreate(HandlerInfo.prototype);
+ UnresolvedHandlerInfoByObject.prototype.getModel = function(async, payload) {
+ this.log(payload, this.name + ": resolving provided model");
+ return resolve(this.context);
+ };
+
+ function paramsMatch(a, b) {
+ if ((!a) ^ (!b)) {
+ // Only one is null.
+ return false;
+ }
+
+ if (!a) {
+ // Both must be null.
+ return true;
+ }
+
+ // Note: this assumes that both params have the same
+ // number of keys, but since we're comparing the
+ // same handlers, they should.
+ for (var k in a) {
+ if (a.hasOwnProperty(k) && a[k] !== b[k]) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ __exports__.HandlerInfo = HandlerInfo;
+ __exports__.ResolvedHandlerInfo = ResolvedHandlerInfo;
+ __exports__.UnresolvedHandlerInfoByParam = UnresolvedHandlerInfoByParam;
+ __exports__.UnresolvedHandlerInfoByObject = UnresolvedHandlerInfoByObject;
+ });
+define("router/router",
+ ["route-recognizer","rsvp","./utils","./transition-state","./transition","./transition-intent/named-transition-intent","./transition-intent/url-transition-intent","exports"],
+ function(__dependency1__, __dependency2__, __dependency3__, __dependency4__, __dependency5__, __dependency6__, __dependency7__, __exports__) {
+ "use strict";
+ var RouteRecognizer = __dependency1__["default"];
+ var resolve = __dependency2__.resolve;
+ var reject = __dependency2__.reject;
+ var async = __dependency2__.async;
+ var Promise = __dependency2__.Promise;
+ var trigger = __dependency3__.trigger;
+ var log = __dependency3__.log;
+ var slice = __dependency3__.slice;
+ var forEach = __dependency3__.forEach;
+ var merge = __dependency3__.merge;
+ var serialize = __dependency3__.serialize;
+ var extractQueryParams = __dependency3__.extractQueryParams;
+ var getChangelist = __dependency3__.getChangelist;
+ var promiseLabel = __dependency3__.promiseLabel;
+ var TransitionState = __dependency4__.TransitionState;
+ var logAbort = __dependency5__.logAbort;
+ var Transition = __dependency5__.Transition;
+ var TransitionAborted = __dependency5__.TransitionAborted;
+ var NamedTransitionIntent = __dependency6__.NamedTransitionIntent;
+ var URLTransitionIntent = __dependency7__.URLTransitionIntent;
+
+ var pop = Array.prototype.pop;
+
+ function Router() {
+ this.recognizer = new RouteRecognizer();
+ this.reset();
+ }
+
+ Router.prototype = {
+
+ /**
+ The main entry point into the router. The API is essentially
+ the same as the `map` method in `route-recognizer`.
+
+ This method extracts the String handler at the last `.to()`
+ call and uses it as the name of the whole route.
+
+ @param {Function} callback
+ */
+ map: function(callback) {
+ this.recognizer.delegate = this.delegate;
+
+ this.recognizer.map(callback, function(recognizer, routes) {
+ for (var i = routes.length - 1, proceed = true; i >= 0 && proceed; --i) {
+ var route = routes[i];
+ recognizer.add(routes, { as: route.handler });
+ proceed = route.path === '/' || route.path === '' || route.handler.slice(-6) === '.index';
+ }
+ });
+ },
+
+ hasRoute: function(route) {
+ return this.recognizer.hasRoute(route);
+ },
+
+ // NOTE: this doesn't really belong here, but here
+ // it shall remain until our ES6 transpiler can
+ // handle cyclical deps.
+ transitionByIntent: function(intent, isIntermediate) {
+
+ var wasTransitioning = !!this.activeTransition;
+ var oldState = wasTransitioning ? this.activeTransition.state : this.state;
+ var newTransition;
+ var router = this;
+
+ try {
+ var newState = intent.applyToState(oldState, this.recognizer, this.getHandler, isIntermediate);
+
+ if (handlerInfosEqual(newState.handlerInfos, oldState.handlerInfos)) {
+
+ // This is a no-op transition. See if query params changed.
+ var queryParamChangelist = getChangelist(oldState.queryParams, newState.queryParams);
+ if (queryParamChangelist) {
+
+ // This is a little hacky but we need some way of storing
+ // changed query params given that no activeTransition
+ // is guaranteed to have occurred.
+ this._changedQueryParams = queryParamChangelist.changed;
+ trigger(this, newState.handlerInfos, true, ['queryParamsDidChange', queryParamChangelist.changed, queryParamChangelist.all, queryParamChangelist.removed]);
+ this._changedQueryParams = null;
+
+ if (!wasTransitioning && this.activeTransition) {
+ // One of the handlers in queryParamsDidChange
+ // caused a transition. Just return that transition.
+ return this.activeTransition;
+ } else {
+ // Running queryParamsDidChange didn't change anything.
+ // Just update query params and be on our way.
+ oldState.queryParams = finalizeQueryParamChange(this, newState.handlerInfos, newState.queryParams);
+
+ // We have to return a noop transition that will
+ // perform a URL update at the end. This gives
+ // the user the ability to set the url update
+ // method (default is replaceState).
+ newTransition = new Transition(this);
+ newTransition.urlMethod = 'replace';
+ newTransition.promise = newTransition.promise.then(function(result) {
+ updateURL(newTransition, oldState, true);
+ if (router.didTransition) {
+ router.didTransition(router.currentHandlerInfos);
+ }
+ return result;
+ }, null, promiseLabel("Transition complete"));
+ return newTransition;
+ }
+ }
+
+ // No-op. No need to create a new transition.
+ return new Transition(this);
+ }
+
+ if (isIntermediate) {
+ setupContexts(this, newState);
+ return;
+ }
+
+ // Create a new transition to the destination route.
+ newTransition = new Transition(this, intent, newState);
+
+ // Abort and usurp any previously active transition.
+ if (this.activeTransition) {
+ this.activeTransition.abort();
+ }
+ this.activeTransition = newTransition;
+
+ // Transition promises by default resolve with resolved state.
+ // For our purposes, swap out the promise to resolve
+ // after the transition has been finalized.
+ newTransition.promise = newTransition.promise.then(function(result) {
+ return router.async(function() {
+ return finalizeTransition(newTransition, result.state);
+ }, "Finalize transition");
+ }, null, promiseLabel("Settle transition promise when transition is finalized"));
+
+ if (!wasTransitioning) {
+ trigger(this, this.state.handlerInfos, true, ['willTransition', newTransition]);
+ }
+
+ return newTransition;
+ } catch(e) {
+ return new Transition(this, intent, null, e);
+ }
+ },
+
+ /**
+ Clears the current and target route handlers and triggers exit
+ on each of them starting at the leaf and traversing up through
+ its ancestors.
+ */
+ reset: function() {
+ if (this.state) {
+ forEach(this.state.handlerInfos, function(handlerInfo) {
+ var handler = handlerInfo.handler;
+ if (handler.exit) {
+ handler.exit();
+ }
+ });
+ }
+
+ this.state = new TransitionState();
+ this.currentHandlerInfos = null;
+ },
+
+ activeTransition: null,
+
+ /**
+ var handler = handlerInfo.handler;
+ The entry point for handling a change to the URL (usually
+ via the back and forward button).
+
+ Returns an Array of handlers and the parameters associated
+ with those parameters.
+
+ @param {String} url a URL to process
+
+ @return {Array} an Array of `[handler, parameter]` tuples
+ */
+ handleURL: function(url) {
+ // Perform a URL-based transition, but don't change
+ // the URL afterward, since it already happened.
+ var args = slice.call(arguments);
+ if (url.charAt(0) !== '/') { args[0] = '/' + url; }
+
+ return doTransition(this, args).method('replaceQuery');
+ },
+
+ /**
+ Hook point for updating the URL.
+
+ @param {String} url a URL to update to
+ */
+ updateURL: function() {
+ throw new Error("updateURL is not implemented");
+ },
+
+ /**
+ Hook point for replacing the current URL, i.e. with replaceState
+
+ By default this behaves the same as `updateURL`
+
+ @param {String} url a URL to update to
+ */
+ replaceURL: function(url) {
+ this.updateURL(url);
+ },
+
+ /**
+ Transition into the specified named route.
+
+ If necessary, trigger the exit callback on any handlers
+ that are no longer represented by the target route.
+
+ @param {String} name the name of the route
+ */
+ transitionTo: function(name) {
+ return doTransition(this, arguments);
+ },
+
+ intermediateTransitionTo: function(name) {
+ doTransition(this, arguments, true);
+ },
+
+ refresh: function(pivotHandler) {
+
+
+ var state = this.activeTransition ? this.activeTransition.state : this.state;
+ var handlerInfos = state.handlerInfos;
+ var params = {};
+ for (var i = 0, len = handlerInfos.length; i < len; ++i) {
+ var handlerInfo = handlerInfos[i];
+ params[handlerInfo.name] = handlerInfo.params || {};
+ }
+
+ log(this, "Starting a refresh transition");
+ var intent = new NamedTransitionIntent({
+ name: handlerInfos[handlerInfos.length - 1].name,
+ pivotHandler: pivotHandler || handlerInfos[0].handler,
+ contexts: [], // TODO collect contexts...?
+ queryParams: this._changedQueryParams || state.queryParams || {}
+ });
+
+ return this.transitionByIntent(intent, false);
+ },
+
+ /**
+ Identical to `transitionTo` except that the current URL will be replaced
+ if possible.
+
+ This method is intended primarily for use with `replaceState`.
+
+ @param {String} name the name of the route
+ */
+ replaceWith: function(name) {
+ return doTransition(this, arguments).method('replace');
+ },
+
+ /**
+ Take a named route and context objects and generate a
+ URL.
+
+ @param {String} name the name of the route to generate
+ a URL for
+ @param {...Object} objects a list of objects to serialize
+
+ @return {String} a URL
+ */
+ generate: function(handlerName) {
+
+ var partitionedArgs = extractQueryParams(slice.call(arguments, 1)),
+ suppliedParams = partitionedArgs[0],
+ queryParams = partitionedArgs[1];
+
+ // Construct a TransitionIntent with the provided params
+ // and apply it to the present state of the router.
+ var intent = new NamedTransitionIntent({ name: handlerName, contexts: suppliedParams });
+ var state = intent.applyToState(this.state, this.recognizer, this.getHandler);
+ var params = {};
+
+ for (var i = 0, len = state.handlerInfos.length; i < len; ++i) {
+ var handlerInfo = state.handlerInfos[i];
+ var handlerParams = handlerInfo.params ||
+ serialize(handlerInfo.handler, handlerInfo.context, handlerInfo.names);
+ merge(params, handlerParams);
+ }
+ params.queryParams = queryParams;
+
+ return this.recognizer.generate(handlerName, params);
+ },
+
+ isActive: function(handlerName) {
+
+ var partitionedArgs = extractQueryParams(slice.call(arguments, 1)),
+ contexts = partitionedArgs[0],
+ queryParams = partitionedArgs[1],
+ activeQueryParams = this.state.queryParams;
+
+ var targetHandlerInfos = this.state.handlerInfos,
+ found = false, names, object, handlerInfo, handlerObj, i, len;
+
+ if (!targetHandlerInfos.length) { return false; }
+
+ var targetHandler = targetHandlerInfos[targetHandlerInfos.length - 1].name;
+ var recogHandlers = this.recognizer.handlersFor(targetHandler);
+
+ var index = 0;
+ for (len = recogHandlers.length; index < len; ++index) {
+ handlerInfo = targetHandlerInfos[index];
+ if (handlerInfo.name === handlerName) { break; }
+ }
+
+ if (index === recogHandlers.length) {
+ // The provided route name isn't even in the route hierarchy.
+ return false;
+ }
+
+ var state = new TransitionState();
+ state.handlerInfos = targetHandlerInfos.slice(0, index + 1);
+ recogHandlers = recogHandlers.slice(0, index + 1);
+
+ var intent = new NamedTransitionIntent({
+ name: targetHandler,
+ contexts: contexts
+ });
+
+ var newState = intent.applyToHandlers(state, recogHandlers, this.getHandler, targetHandler, true, true);
+
+ // Get a hash of QPs that will still be active on new route
+ var activeQPsOnNewHandler = {};
+ merge(activeQPsOnNewHandler, queryParams);
+ for (var key in activeQueryParams) {
+ if (activeQueryParams.hasOwnProperty(key) &&
+ activeQPsOnNewHandler.hasOwnProperty(key)) {
+ activeQPsOnNewHandler[key] = activeQueryParams[key];
+ }
+ }
+
+ return handlerInfosEqual(newState.handlerInfos, state.handlerInfos) &&
+ !getChangelist(activeQPsOnNewHandler, queryParams);
+ },
+
+ trigger: function(name) {
+ var args = slice.call(arguments);
+ trigger(this, this.currentHandlerInfos, false, args);
+ },
+
+ /**
+ @private
+
+ Pluggable hook for possibly running route hooks
+ in a try-catch escaping manner.
+
+ @param {Function} callback the callback that will
+ be asynchronously called
+
+ @return {Promise} a promise that fulfills with the
+ value returned from the callback
+ */
+ async: function(callback, label) {
+ return new Promise(function(resolve) {
+ resolve(callback());
+ }, label);
+ },
+
+ /**
+ Hook point for logging transition status updates.
+
+ @param {String} message The message to log.
+ */
+ log: null
+ };
+
+ /**
+ @private
+
+ Takes an Array of `HandlerInfo`s, figures out which ones are
+ exiting, entering, or changing contexts, and calls the
+ proper handler hooks.
+
+ For example, consider the following tree of handlers. Each handler is
+ followed by the URL segment it handles.
+
+ ```
+ |~index ("/")
+ | |~posts ("/posts")
+ | | |-showPost ("/:id")
+ | | |-newPost ("/new")
+ | | |-editPost ("/edit")
+ | |~about ("/about/:id")
+ ```
+
+ Consider the following transitions:
+
+ 1. A URL transition to `/posts/1`.
+ 1. Triggers the `*model` callbacks on the
+ `index`, `posts`, and `showPost` handlers
+ 2. Triggers the `enter` callback on the same
+ 3. Triggers the `setup` callback on the same
+ 2. A direct transition to `newPost`
+ 1. Triggers the `exit` callback on `showPost`
+ 2. Triggers the `enter` callback on `newPost`
+ 3. Triggers the `setup` callback on `newPost`
+ 3. A direct transition to `about` with a specified
+ context object
+ 1. Triggers the `exit` callback on `newPost`
+ and `posts`
+ 2. Triggers the `serialize` callback on `about`
+ 3. Triggers the `enter` callback on `about`
+ 4. Triggers the `setup` callback on `about`
+
+ @param {Router} transition
+ @param {TransitionState} newState
+ */
+ function setupContexts(router, newState, transition) {
+ var partition = partitionHandlers(router.state, newState);
+
+ forEach(partition.exited, function(handlerInfo) {
+ var handler = handlerInfo.handler;
+ delete handler.context;
+ if (handler.exit) { handler.exit(); }
+ });
+
+ var oldState = router.oldState = router.state;
+ router.state = newState;
+ var currentHandlerInfos = router.currentHandlerInfos = partition.unchanged.slice();
+
+ try {
+ forEach(partition.updatedContext, function(handlerInfo) {
+ return handlerEnteredOrUpdated(currentHandlerInfos, handlerInfo, false, transition);
+ });
+
+ forEach(partition.entered, function(handlerInfo) {
+ return handlerEnteredOrUpdated(currentHandlerInfos, handlerInfo, true, transition);
+ });
+ } catch(e) {
+ router.state = oldState;
+ router.currentHandlerInfos = oldState.handlerInfos;
+ throw e;
+ }
+
+ router.state.queryParams = finalizeQueryParamChange(router, currentHandlerInfos, newState.queryParams);
+ }
+
+
+ /**
+ @private
+
+ Helper method used by setupContexts. Handles errors or redirects
+ that may happen in enter/setup.
+ */
+ function handlerEnteredOrUpdated(currentHandlerInfos, handlerInfo, enter, transition) {
+
+ var handler = handlerInfo.handler,
+ context = handlerInfo.context;
+
+ if (enter && handler.enter) { handler.enter(transition); }
+ if (transition && transition.isAborted) {
+ throw new TransitionAborted();
+ }
+
+ handler.context = context;
+ if (handler.contextDidChange) { handler.contextDidChange(); }
+
+ if (handler.setup) { handler.setup(context, transition); }
+ if (transition && transition.isAborted) {
+ throw new TransitionAborted();
+ }
+
+ currentHandlerInfos.push(handlerInfo);
+
+ return true;
+ }
+
+
+ /**
+ @private
+
+ This function is called when transitioning from one URL to
+ another to determine which handlers are no longer active,
+ which handlers are newly active, and which handlers remain
+ active but have their context changed.
+
+ Take a list of old handlers and new handlers and partition
+ them into four buckets:
+
+ * unchanged: the handler was active in both the old and
+ new URL, and its context remains the same
+ * updated context: the handler was active in both the
+ old and new URL, but its context changed. The handler's
+ `setup` method, if any, will be called with the new
+ context.
+ * exited: the handler was active in the old URL, but is
+ no longer active.
+ * entered: the handler was not active in the old URL, but
+ is now active.
+
+ The PartitionedHandlers structure has four fields:
+
+ * `updatedContext`: a list of `HandlerInfo` objects that
+ represent handlers that remain active but have a changed
+ context
+ * `entered`: a list of `HandlerInfo` objects that represent
+ handlers that are newly active
+ * `exited`: a list of `HandlerInfo` objects that are no
+ longer active.
+ * `unchanged`: a list of `HanderInfo` objects that remain active.
+
+ @param {Array[HandlerInfo]} oldHandlers a list of the handler
+ information for the previous URL (or `[]` if this is the
+ first handled transition)
+ @param {Array[HandlerInfo]} newHandlers a list of the handler
+ information for the new URL
+
+ @return {Partition}
+ */
+ function partitionHandlers(oldState, newState) {
+ var oldHandlers = oldState.handlerInfos;
+ var newHandlers = newState.handlerInfos;
+
+ var handlers = {
+ updatedContext: [],
+ exited: [],
+ entered: [],
+ unchanged: []
+ };
+
+ var handlerChanged, contextChanged, queryParamsChanged, i, l;
+
+ for (i=0, l=newHandlers.length; i<l; i++) {
+ var oldHandler = oldHandlers[i], newHandler = newHandlers[i];
+
+ if (!oldHandler || oldHandler.handler !== newHandler.handler) {
+ handlerChanged = true;
+ }
+
+ if (handlerChanged) {
+ handlers.entered.push(newHandler);
+ if (oldHandler) { handlers.exited.unshift(oldHandler); }
+ } else if (contextChanged || oldHandler.context !== newHandler.context || queryParamsChanged) {
+ contextChanged = true;
+ handlers.updatedContext.push(newHandler);
+ } else {
+ handlers.unchanged.push(oldHandler);
+ }
+ }
+
+ for (i=newHandlers.length, l=oldHandlers.length; i<l; i++) {
+ handlers.exited.unshift(oldHandlers[i]);
+ }
+
+ return handlers;
+ }
+
+ function updateURL(transition, state, inputUrl) {
+ var urlMethod = transition.urlMethod;
+
+ if (!urlMethod) {
+ return;
+ }
+
+ var router = transition.router,
+ handlerInfos = state.handlerInfos,
+ handlerName = handlerInfos[handlerInfos.length - 1].name,
+ params = {};
+
+ for (var i = handlerInfos.length - 1; i >= 0; --i) {
+ var handlerInfo = handlerInfos[i];
+ merge(params, handlerInfo.params);
+ if (handlerInfo.handler.inaccessibleByURL) {
+ urlMethod = null;
+ }
+ }
+
+ if (urlMethod) {
+ params.queryParams = state.queryParams;
+ var url = router.recognizer.generate(handlerName, params);
+
+ if (urlMethod === 'replaceQuery') {
+ if (url !== inputUrl) {
+ router.replaceURL(url);
+ }
+ } else if (urlMethod === 'replace') {
+ router.replaceURL(url);
+ } else {
+ router.updateURL(url);
+ }
+ }
+ }
+
+ /**
+ @private
+
+ Updates the URL (if necessary) and calls `setupContexts`
+ to update the router's array of `currentHandlerInfos`.
+ */
+ function finalizeTransition(transition, newState) {
+
+ try {
+ log(transition.router, transition.sequence, "Resolved all models on destination route; finalizing transition.");
+
+ var router = transition.router,
+ handlerInfos = newState.handlerInfos,
+ seq = transition.sequence;
+
+ // Run all the necessary enter/setup/exit hooks
+ setupContexts(router, newState, transition);
+
+ // Check if a redirect occurred in enter/setup
+ if (transition.isAborted) {
+ // TODO: cleaner way? distinguish b/w targetHandlerInfos?
+ router.state.handlerInfos = router.currentHandlerInfos;
+ return reject(logAbort(transition));
+ }
+
+ updateURL(transition, newState, transition.intent.url);
+
+ transition.isActive = false;
+ router.activeTransition = null;
+
+ trigger(router, router.currentHandlerInfos, true, ['didTransition']);
+
+ if (router.didTransition) {
+ router.didTransition(router.currentHandlerInfos);
+ }
+
+ log(router, transition.sequence, "TRANSITION COMPLETE.");
+
+ // Resolve with the final handler.
+ return handlerInfos[handlerInfos.length - 1].handler;
+ } catch(e) {
+ if (!(e instanceof TransitionAborted)) {
+ //var erroneousHandler = handlerInfos.pop();
+ var infos = transition.state.handlerInfos;
+ transition.trigger(true, 'error', e, transition, infos[infos.length-1].handler);
+ transition.abort();
+ }
+
+ throw e;
+ }
+ }
+
+ /**
+ @private
+
+ Begins and returns a Transition based on the provided
+ arguments. Accepts arguments in the form of both URL
+ transitions and named transitions.
+
+ @param {Router} router
+ @param {Array[Object]} args arguments passed to transitionTo,
+ replaceWith, or handleURL
+ */
+ function doTransition(router, args, isIntermediate) {
+ // Normalize blank transitions to root URL transitions.
+ var name = args[0] || '/';
+
+ var lastArg = args[args.length-1];
+ var queryParams = {};
+ if (lastArg && lastArg.hasOwnProperty('queryParams')) {
+ queryParams = pop.call(args).queryParams;
+ }
+
+ var intent;
+ if (args.length === 0) {
+
+ log(router, "Updating query params");
+
+ // A query param update is really just a transition
+ // into the route you're already on.
+ var handlerInfos = router.state.handlerInfos;
+ intent = new NamedTransitionIntent({
+ name: handlerInfos[handlerInfos.length - 1].name,
+ contexts: [],
+ queryParams: queryParams
+ });
+
+ } else if (name.charAt(0) === '/') {
+
+ log(router, "Attempting URL transition to " + name);
+ intent = new URLTransitionIntent({ url: name });
+
+ } else {
+
+ log(router, "Attempting transition to " + name);
+ intent = new NamedTransitionIntent({
+ name: args[0],
+ contexts: slice.call(args, 1),
+ queryParams: queryParams
+ });
+ }
+
+ return router.transitionByIntent(intent, isIntermediate);
+ }
+
+ function handlerInfosEqual(handlerInfos, otherHandlerInfos) {
+ if (handlerInfos.length !== otherHandlerInfos.length) {
+ return false;
+ }
+
+ for (var i = 0, len = handlerInfos.length; i < len; ++i) {
+ if (handlerInfos[i] !== otherHandlerInfos[i]) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ function finalizeQueryParamChange(router, resolvedHandlers, newQueryParams) {
+ // We fire a finalizeQueryParamChange event which
+ // gives the new route hierarchy a chance to tell
+ // us which query params it's consuming and what
+ // their final values are. If a query param is
+ // no longer consumed in the final route hierarchy,
+ // its serialized segment will be removed
+ // from the URL.
+ var finalQueryParamsArray = [];
+ trigger(router, resolvedHandlers, true, ['finalizeQueryParamChange', newQueryParams, finalQueryParamsArray]);
+
+ var finalQueryParams = {};
+ for (var i = 0, len = finalQueryParamsArray.length; i < len; ++i) {
+ var qp = finalQueryParamsArray[i];
+ finalQueryParams[qp.key] = qp.value;
+ }
+ return finalQueryParams;
+ }
+
+ __exports__.Router = Router;
+ });
+define("router/transition-intent",
+ ["./utils","exports"],
+ function(__dependency1__, __exports__) {
+ "use strict";
+ var merge = __dependency1__.merge;
+
+ function TransitionIntent(props) {
+ if (props) {
+ merge(this, props);
+ }
+ this.data = this.data || {};
+ }
+
+ TransitionIntent.prototype.applyToState = function(oldState) {
+ // Default TransitionIntent is a no-op.
+ return oldState;
+ };
+
+ __exports__.TransitionIntent = TransitionIntent;
+ });
+define("router/transition-intent/named-transition-intent",
+ ["../transition-intent","../transition-state","../handler-info","../utils","exports"],
+ function(__dependency1__, __dependency2__, __dependency3__, __dependency4__, __exports__) {
+ "use strict";
+ var TransitionIntent = __dependency1__.TransitionIntent;
+ var TransitionState = __dependency2__.TransitionState;
+ var UnresolvedHandlerInfoByParam = __dependency3__.UnresolvedHandlerInfoByParam;
+ var UnresolvedHandlerInfoByObject = __dependency3__.UnresolvedHandlerInfoByObject;
+ var isParam = __dependency4__.isParam;
+ var forEach = __dependency4__.forEach;
+ var extractQueryParams = __dependency4__.extractQueryParams;
+ var oCreate = __dependency4__.oCreate;
+ var merge = __dependency4__.merge;
+
+ function NamedTransitionIntent(props) {
+ TransitionIntent.call(this, props);
+ }
+
+ NamedTransitionIntent.prototype = oCreate(TransitionIntent.prototype);
+ NamedTransitionIntent.prototype.applyToState = function(oldState, recognizer, getHandler, isIntermediate) {
+
+ var partitionedArgs = extractQueryParams([this.name].concat(this.contexts)),
+ pureArgs = partitionedArgs[0],
+ queryParams = partitionedArgs[1],
+ handlers = recognizer.handlersFor(pureArgs[0]);
+
+ var targetRouteName = handlers[handlers.length-1].handler;
+
+ return this.applyToHandlers(oldState, handlers, getHandler, targetRouteName, isIntermediate);
+ };
+
+ NamedTransitionIntent.prototype.applyToHandlers = function(oldState, handlers, getHandler, targetRouteName, isIntermediate, checkingIfActive) {
+
+ var i;
+ var newState = new TransitionState();
+ var objects = this.contexts.slice(0);
+
+ var invalidateIndex = handlers.length;
+ var nonDynamicIndexes = [];
+
+ // Pivot handlers are provided for refresh transitions
+ if (this.pivotHandler) {
+ for (i = 0; i < handlers.length; ++i) {
+ if (getHandler(handlers[i].handler) === this.pivotHandler) {
+ invalidateIndex = i;
+ break;
+ }
+ }
+ }
+
+ var pivotHandlerFound = !this.pivotHandler;
+
+ for (i = handlers.length - 1; i >= 0; --i) {
+ var result = handlers[i];
+ var name = result.handler;
+ var handler = getHandler(name);
+
+ var oldHandlerInfo = oldState.handlerInfos[i];
+ var newHandlerInfo = null;
+
+ if (result.names.length > 0) {
+ if (i >= invalidateIndex) {
+ newHandlerInfo = this.createParamHandlerInfo(name, handler, result.names, objects, oldHandlerInfo);
+ } else {
+ newHandlerInfo = this.getHandlerInfoForDynamicSegment(name, handler, result.names, objects, oldHandlerInfo, targetRouteName);
+ }
+ } else {
+ // This route has no dynamic segment.
+ // Therefore treat as a param-based handlerInfo
+ // with empty params. This will cause the `model`
+ // hook to be called with empty params, which is desirable.
+ newHandlerInfo = this.createParamHandlerInfo(name, handler, result.names, objects, oldHandlerInfo);
+ nonDynamicIndexes.unshift(i);
+ }
+
+ if (checkingIfActive) {
+ // If we're performing an isActive check, we want to
+ // serialize URL params with the provided context, but
+ // ignore mismatches between old and new context.
+ newHandlerInfo = newHandlerInfo.becomeResolved(null, newHandlerInfo.context);
+ var oldContext = oldHandlerInfo && oldHandlerInfo.context;
+ if (result.names.length > 0 && newHandlerInfo.context === oldContext) {
+ // If contexts match in isActive test, assume params also match.
+ // This allows for flexibility in not requiring that every last
+ // handler provide a `serialize` method
+ newHandlerInfo.params = oldHandlerInfo && oldHandlerInfo.params;
+ }
+ newHandlerInfo.context = oldContext;
+ }
+
+ var handlerToUse = oldHandlerInfo;
+ if (i >= invalidateIndex || newHandlerInfo.shouldSupercede(oldHandlerInfo)) {
+ invalidateIndex = Math.min(i, invalidateIndex);
+ handlerToUse = newHandlerInfo;
+ }
+
+ if (isIntermediate && !checkingIfActive) {
+ handlerToUse = handlerToUse.becomeResolved(null, handlerToUse.context);
+ }
+
+ newState.handlerInfos.unshift(handlerToUse);
+ }
+
+ if (objects.length > 0) {
+ throw new Error("More context objects were passed than there are dynamic segments for the route: " + targetRouteName);
+ }
+
+ if (!isIntermediate) {
+ this.invalidateNonDynamicHandlers(newState.handlerInfos, nonDynamicIndexes, invalidateIndex);
+ }
+
+ merge(newState.queryParams, oldState.queryParams);
+ merge(newState.queryParams, this.queryParams || {});
+
+ return newState;
+ };
+
+ NamedTransitionIntent.prototype.invalidateNonDynamicHandlers = function(handlerInfos, indexes, invalidateIndex) {
+ forEach(indexes, function(i) {
+ if (i >= invalidateIndex) {
+ var handlerInfo = handlerInfos[i];
+ handlerInfos[i] = new UnresolvedHandlerInfoByParam({
+ name: handlerInfo.name,
+ handler: handlerInfo.handler,
+ params: {}
+ });
+ }
+ });
+ };
+
+ NamedTransitionIntent.prototype.getHandlerInfoForDynamicSegment = function(name, handler, names, objects, oldHandlerInfo, targetRouteName) {
+
+ var numNames = names.length;
+ var objectToUse;
+ if (objects.length > 0) {
+
+ // Use the objects provided for this transition.
+ objectToUse = objects[objects.length - 1];
+ if (isParam(objectToUse)) {
+ return this.createParamHandlerInfo(name, handler, names, objects, oldHandlerInfo);
+ } else {
+ objects.pop();
+ }
+ } else if (oldHandlerInfo && oldHandlerInfo.name === name) {
+ // Reuse the matching oldHandlerInfo
+ return oldHandlerInfo;
+ } else {
+ // Ideally we should throw this error to provide maximal
+ // information to the user that not enough context objects
+ // were provided, but this proves too cumbersome in Ember
+ // in cases where inner template helpers are evaluated
+ // before parent helpers un-render, in which cases this
+ // error somewhat prematurely fires.
+ //throw new Error("Not enough context objects were provided to complete a transition to " + targetRouteName + ". Specifically, the " + name + " route needs an object that can be serialized into its dynamic URL segments [" + names.join(', ') + "]");
+ return oldHandlerInfo;
+ }
+
+ return new UnresolvedHandlerInfoByObject({
+ name: name,
+ handler: handler,
+ context: objectToUse,
+ names: names
+ });
+ };
+
+ NamedTransitionIntent.prototype.createParamHandlerInfo = function(name, handler, names, objects, oldHandlerInfo) {
+ var params = {};
+
+ // Soak up all the provided string/numbers
+ var numNames = names.length;
+ while (numNames--) {
+
+ // Only use old params if the names match with the new handler
+ var oldParams = (oldHandlerInfo && name === oldHandlerInfo.name && oldHandlerInfo.params) || {};
+
+ var peek = objects[objects.length - 1];
+ var paramName = names[numNames];
+ if (isParam(peek)) {
+ params[paramName] = "" + objects.pop();
+ } else {
+ // If we're here, this means only some of the params
+ // were string/number params, so try and use a param
+ // value from a previous handler.
+ if (oldParams.hasOwnProperty(paramName)) {
+ params[paramName] = oldParams[paramName];
+ } else {
+ throw new Error("You didn't provide enough string/numeric parameters to satisfy all of the dynamic segments for route " + name);
+ }
+ }
+ }
+
+ return new UnresolvedHandlerInfoByParam({
+ name: name,
+ handler: handler,
+ params: params
+ });
+ };
+
+ __exports__.NamedTransitionIntent = NamedTransitionIntent;
+ });
+define("router/transition-intent/url-transition-intent",
+ ["../transition-intent","../transition-state","../handler-info","../utils","exports"],
+ function(__dependency1__, __dependency2__, __dependency3__, __dependency4__, __exports__) {
+ "use strict";
+ var TransitionIntent = __dependency1__.TransitionIntent;
+ var TransitionState = __dependency2__.TransitionState;
+ var UnresolvedHandlerInfoByParam = __dependency3__.UnresolvedHandlerInfoByParam;
+ var oCreate = __dependency4__.oCreate;
+ var merge = __dependency4__.merge;
+
+ function URLTransitionIntent(props) {
+ TransitionIntent.call(this, props);
+ }
+
+ URLTransitionIntent.prototype = oCreate(TransitionIntent.prototype);
+ URLTransitionIntent.prototype.applyToState = function(oldState, recognizer, getHandler) {
+ var newState = new TransitionState();
+
+ var results = recognizer.recognize(this.url),
+ queryParams = {},
+ i, len;
+
+ if (!results) {
+ throw new UnrecognizedURLError(this.url);
+ }
+
+ var statesDiffer = false;
+
+ for (i = 0, len = results.length; i < len; ++i) {
+ var result = results[i];
+ var name = result.handler;
+ var handler = getHandler(name);
+
+ if (handler.inaccessibleByURL) {
+ throw new UnrecognizedURLError(this.url);
+ }
+
+ var newHandlerInfo = new UnresolvedHandlerInfoByParam({
+ name: name,
+ handler: handler,
+ params: result.params
+ });
+
+ var oldHandlerInfo = oldState.handlerInfos[i];
+ if (statesDiffer || newHandlerInfo.shouldSupercede(oldHandlerInfo)) {
+ statesDiffer = true;
+ newState.handlerInfos[i] = newHandlerInfo;
+ } else {
+ newState.handlerInfos[i] = oldHandlerInfo;
+ }
+ }
+
+ merge(newState.queryParams, results.queryParams);
+
+ return newState;
+ };
+
+ /**
+ Promise reject reasons passed to promise rejection
+ handlers for failed transitions.
+ */
+ function UnrecognizedURLError(message) {
+ this.message = (message || "UnrecognizedURLError");
+ this.name = "UnrecognizedURLError";
+ }
+
+ __exports__.URLTransitionIntent = URLTransitionIntent;
+ });
+define("router/transition-state",
+ ["./handler-info","./utils","rsvp","exports"],
+ function(__dependency1__, __dependency2__, __dependency3__, __exports__) {
+ "use strict";
+ var ResolvedHandlerInfo = __dependency1__.ResolvedHandlerInfo;
+ var forEach = __dependency2__.forEach;
+ var promiseLabel = __dependency2__.promiseLabel;
+ var resolve = __dependency3__.resolve;
+ var reject = __dependency3__.reject;
+
+ function TransitionState(other) {
+ this.handlerInfos = [];
+ this.queryParams = {};
+ this.params = {};
+ }
+
+ TransitionState.prototype = {
+ handlerInfos: null,
+ queryParams: null,
+ params: null,
+
+ promiseLabel: function(label) {
+ var targetName = '';
+ forEach(this.handlerInfos, function(handlerInfo) {
+ if (targetName !== '') {
+ targetName += '.';
+ }
+ targetName += handlerInfo.name;
+ });
+ return promiseLabel("'" + targetName + "': " + label);
+ },
+
+ resolve: function(async, shouldContinue, payload) {
+ var self = this;
+ // First, calculate params for this state. This is useful
+ // information to provide to the various route hooks.
+ var params = this.params;
+ forEach(this.handlerInfos, function(handlerInfo) {
+ params[handlerInfo.name] = handlerInfo.params || {};
+ });
+
+ payload = payload || {};
+ payload.resolveIndex = 0;
+
+ var currentState = this;
+ var wasAborted = false;
+
+ // The prelude RSVP.resolve() asyncs us into the promise land.
+ return resolve(null, this.promiseLabel("Start transition"))
+ .then(resolveOneHandlerInfo, null, this.promiseLabel('Resolve handler'))['catch'](handleError, this.promiseLabel('Handle error'));
+
+ function innerShouldContinue() {
+ return resolve(shouldContinue(), promiseLabel("Check if should continue"))['catch'](function(reason) {
+ // We distinguish between errors that occurred
+ // during resolution (e.g. beforeModel/model/afterModel),
+ // and aborts due to a rejecting promise from shouldContinue().
+ wasAborted = true;
+ return reject(reason);
+ }, promiseLabel("Handle abort"));
+ }
+
+ function handleError(error) {
+ // This is the only possible
+ // reject value of TransitionState#resolve
+ var handlerInfos = currentState.handlerInfos;
+ var errorHandlerIndex = payload.resolveIndex >= handlerInfos.length ?
+ handlerInfos.length - 1 : payload.resolveIndex;
+ return reject({
+ error: error,
+ handlerWithError: currentState.handlerInfos[errorHandlerIndex].handler,
+ wasAborted: wasAborted,
+ state: currentState
+ });
+ }
+
+ function proceed(resolvedHandlerInfo) {
+ // Swap the previously unresolved handlerInfo with
+ // the resolved handlerInfo
+ currentState.handlerInfos[payload.resolveIndex++] = resolvedHandlerInfo;
+
+ // Call the redirect hook. The reason we call it here
+ // vs. afterModel is so that redirects into child
+ // routes don't re-run the model hooks for this
+ // already-resolved route.
+ var handler = resolvedHandlerInfo.handler;
+ if (handler && handler.redirect) {
+ handler.redirect(resolvedHandlerInfo.context, payload);
+ }
+
+ // Proceed after ensuring that the redirect hook
+ // didn't abort this transition by transitioning elsewhere.
+ return innerShouldContinue().then(resolveOneHandlerInfo, null, promiseLabel('Resolve handler'));
+ }
+
+ function resolveOneHandlerInfo() {
+ if (payload.resolveIndex === currentState.handlerInfos.length) {
+ // This is is the only possible
+ // fulfill value of TransitionState#resolve
+ return {
+ error: null,
+ state: currentState
+ };
+ }
+
+ var handlerInfo = currentState.handlerInfos[payload.resolveIndex];
+
+ return handlerInfo.resolve(async, innerShouldContinue, payload)
+ .then(proceed, null, promiseLabel('Proceed'));
+ }
+ }
+ };
+
+ __exports__.TransitionState = TransitionState;
+ });
+define("router/transition",
+ ["rsvp","./handler-info","./utils","exports"],
+ function(__dependency1__, __dependency2__, __dependency3__, __exports__) {
+ "use strict";
+ var reject = __dependency1__.reject;
+ var resolve = __dependency1__.resolve;
+ var ResolvedHandlerInfo = __dependency2__.ResolvedHandlerInfo;
+ var trigger = __dependency3__.trigger;
+ var slice = __dependency3__.slice;
+ var log = __dependency3__.log;
+ var promiseLabel = __dependency3__.promiseLabel;
+
+ /**
+ @private
+
+ A Transition is a thennable (a promise-like object) that represents
+ an attempt to transition to another route. It can be aborted, either
+ explicitly via `abort` or by attempting another transition while a
+ previous one is still underway. An aborted transition can also
+ be `retry()`d later.
+ */
+ function Transition(router, intent, state, error) {
+ var transition = this;
+ this.state = state || router.state;
+ this.intent = intent;
+ this.router = router;
+ this.data = this.intent && this.intent.data || {};
+ this.resolvedModels = {};
+ this.queryParams = {};
+
+ if (error) {
+ this.promise = reject(error);
+ return;
+ }
+
+ if (state) {
+ this.params = state.params;
+ this.queryParams = state.queryParams;
+
+ var len = state.handlerInfos.length;
+ if (len) {
+ this.targetName = state.handlerInfos[state.handlerInfos.length-1].name;
+ }
+
+ for (var i = 0; i < len; ++i) {
+ var handlerInfo = state.handlerInfos[i];
+ if (!(handlerInfo instanceof ResolvedHandlerInfo)) {
+ break;
+ }
+ this.pivotHandler = handlerInfo.handler;
+ }
+
+ this.sequence = Transition.currentSequence++;
+ this.promise = state.resolve(router.async, checkForAbort, this)['catch'](function(result) {
+ if (result.wasAborted) {
+ return reject(logAbort(transition));
+ } else {
+ transition.trigger('error', result.error, transition, result.handlerWithError);
+ transition.abort();
+ return reject(result.error);
+ }
+ }, promiseLabel('Handle Abort'));
+ } else {
+ this.promise = resolve(this.state);
+ this.params = {};
+ }
+
+ function checkForAbort() {
+ if (transition.isAborted) {
+ return reject(undefined, promiseLabel("Transition aborted - reject"));
+ }
+ }
+ }
+
+ Transition.currentSequence = 0;
+
+ Transition.prototype = {
+ targetName: null,
+ urlMethod: 'update',
+ intent: null,
+ params: null,
+ pivotHandler: null,
+ resolveIndex: 0,
+ handlerInfos: null,
+ resolvedModels: null,
+ isActive: true,
+ state: null,
+
+ /**
+ @public
+
+ The Transition's internal promise. Calling `.then` on this property
+ is that same as calling `.then` on the Transition object itself, but
+ this property is exposed for when you want to pass around a
+ Transition's promise, but not the Transition object itself, since
+ Transition object can be externally `abort`ed, while the promise
+ cannot.
+ */
+ promise: null,
+
+ /**
+ @public
+
+ Custom state can be stored on a Transition's `data` object.
+ This can be useful for decorating a Transition within an earlier
+ hook and shared with a later hook. Properties set on `data` will
+ be copied to new transitions generated by calling `retry` on this
+ transition.
+ */
+ data: null,
+
+ /**
+ @public
+
+ A standard promise hook that resolves if the transition
+ succeeds and rejects if it fails/redirects/aborts.
+
+ Forwards to the internal `promise` property which you can
+ use in situations where you want to pass around a thennable,
+ but not the Transition itself.
+
+ @param {Function} success
+ @param {Function} failure
+ */
+ then: function(success, failure) {
+ return this.promise.then(success, failure);
+ },
+
+ /**
+ @public
+
+ Aborts the Transition. Note you can also implicitly abort a transition
+ by initiating another transition while a previous one is underway.
+ */
+ abort: function() {
+ if (this.isAborted) { return this; }
+ log(this.router, this.sequence, this.targetName + ": transition was aborted");
+ this.isAborted = true;
+ this.isActive = false;
+ this.router.activeTransition = null;
+ return this;
+ },
+
+ /**
+ @public
+
+ Retries a previously-aborted transition (making sure to abort the
+ transition if it's still active). Returns a new transition that
+ represents the new attempt to transition.
+ */
+ retry: function() {
+ // TODO: add tests for merged state retry()s
+ this.abort();
+ return this.router.transitionByIntent(this.intent, false);
+ },
+
+ /**
+ @public
+
+ Sets the URL-changing method to be employed at the end of a
+ successful transition. By default, a new Transition will just
+ use `updateURL`, but passing 'replace' to this method will
+ cause the URL to update using 'replaceWith' instead. Omitting
+ a parameter will disable the URL change, allowing for transitions
+ that don't update the URL at completion (this is also used for
+ handleURL, since the URL has already changed before the
+ transition took place).
+
+ @param {String} method the type of URL-changing method to use
+ at the end of a transition. Accepted values are 'replace',
+ falsy values, or any other non-falsy value (which is
+ interpreted as an updateURL transition).
+
+ @return {Transition} this transition
+ */
+ method: function(method) {
+ this.urlMethod = method;
+ return this;
+ },
+
+ /**
+ @public
+
+ Fires an event on the current list of resolved/resolving
+ handlers within this transition. Useful for firing events
+ on route hierarchies that haven't fully been entered yet.
+
+ Note: This method is also aliased as `send`
+
+ @param {Boolean} [ignoreFailure=false] a boolean specifying whether unhandled events throw an error
+ @param {String} name the name of the event to fire
+ */
+ trigger: function (ignoreFailure) {
+ var args = slice.call(arguments);
+ if (typeof ignoreFailure === 'boolean') {
+ args.shift();
+ } else {
+ // Throw errors on unhandled trigger events by default
+ ignoreFailure = false;
+ }
+ trigger(this.router, this.state.handlerInfos.slice(0, this.resolveIndex + 1), ignoreFailure, args);
+ },
+
+ /**
+ @public
+
+ Transitions are aborted and their promises rejected
+ when redirects occur; this method returns a promise
+ that will follow any redirects that occur and fulfill
+ with the value fulfilled by any redirecting transitions
+ that occur.
+
+ @return {Promise} a promise that fulfills with the same
+ value that the final redirecting transition fulfills with
+ */
+ followRedirects: function() {
+ var router = this.router;
+ return this.promise['catch'](function(reason) {
+ if (router.activeTransition) {
+ return router.activeTransition.followRedirects();
+ }
+ return reject(reason);
+ });
+ },
+
+ toString: function() {
+ return "Transition (sequence " + this.sequence + ")";
+ },
+
+ /**
+ @private
+ */
+ log: function(message) {
+ log(this.router, this.sequence, message);
+ }
+ };
+
+ // Alias 'trigger' as 'send'
+ Transition.prototype.send = Transition.prototype.trigger;
+
+ /**
+ @private
+
+ Logs and returns a TransitionAborted error.
+ */
+ function logAbort(transition) {
+ log(transition.router, transition.sequence, "detected abort.");
+ return new TransitionAborted();
+ }
+
+ function TransitionAborted(message) {
+ this.message = (message || "TransitionAborted");
+ this.name = "TransitionAborted";
+ }
+
+ __exports__.Transition = Transition;
+ __exports__.logAbort = logAbort;
+ __exports__.TransitionAborted = TransitionAborted;
+ });
+define("router/utils",
+ ["exports"],
+ function(__exports__) {
+ "use strict";
+ var slice = Array.prototype.slice;
+
+ function isArray(test) {
+ return Object.prototype.toString.call(test) === "[object Array]";
+ }
+
+ function merge(hash, other) {
+ for (var prop in other) {
+ if (other.hasOwnProperty(prop)) { hash[prop] = other[prop]; }
+ }
+ }
+
+ var oCreate = Object.create || function(proto) {
+ function F() {}
+ F.prototype = proto;
+ return new F();
+ };
+
+ /**
+ @private
+
+ Extracts query params from the end of an array
+ **/
+ function extractQueryParams(array) {
+ var len = (array && array.length), head, queryParams;
+
+ if(len && len > 0 && array[len - 1] && array[len - 1].hasOwnProperty('queryParams')) {
+ queryParams = array[len - 1].queryParams;
+ head = slice.call(array, 0, len - 1);
+ return [head, queryParams];
+ } else {
+ return [array, null];
+ }
+ }
+
+ /**
+ @private
+
+ Coerces query param properties and array elements into strings.
+ **/
+ function coerceQueryParamsToString(queryParams) {
+ for (var key in queryParams) {
+ if (typeof queryParams[key] === 'number') {
+ queryParams[key] = '' + queryParams[key];
+ } else if (isArray(queryParams[key])) {
+ for (var i = 0, l = queryParams[key].length; i < l; i++) {
+ queryParams[key][i] = '' + queryParams[key][i];
+ }
+ }
+ }
+ }
+ /**
+ @private
+ */
+ function log(router, sequence, msg) {
+ if (!router.log) { return; }
+
+ if (arguments.length === 3) {
+ router.log("Transition #" + sequence + ": " + msg);
+ } else {
+ msg = sequence;
+ router.log(msg);
+ }
+ }
+
+ function bind(fn, context) {
+ var boundArgs = arguments;
+ return function(value) {
+ var args = slice.call(boundArgs, 2);
+ args.push(value);
+ return fn.apply(context, args);
+ };
+ }
+
+ function isParam(object) {
+ return (typeof object === "string" || object instanceof String || typeof object === "number" || object instanceof Number);
+ }
+
+
+ function forEach(array, callback) {
+ for (var i=0, l=array.length; i<l && false !== callback(array[i]); i++) { }
+ }
+
+ /**
+ @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;
+ }
+
+ function trigger(router, handlerInfos, ignoreFailure, args) {
+ if (router.triggerEvent) {
+ router.triggerEvent(handlerInfos, ignoreFailure, args);
+ return;
+ }
+
+ var name = args.shift();
+
+ if (!handlerInfos) {
+ if (ignoreFailure) { return; }
+ throw new Error("Could not trigger event '" + name + "'. There are no active handlers");
+ }
+
+ var eventWasHandled = false;
+
+ for (var i=handlerInfos.length-1; i>=0; i--) {
+ var handlerInfo = handlerInfos[i],
+ handler = handlerInfo.handler;
+
+ if (handler.events && handler.events[name]) {
+ if (handler.events[name].apply(handler, args) === true) {
+ eventWasHandled = true;
+ } else {
+ return;
+ }
+ }
+ }
+
+ if (!eventWasHandled && !ignoreFailure) {
+ throw new Error("Nothing handled the event '" + name + "'.");
+ }
+ }
+
+ function getChangelist(oldObject, newObject) {
+ var key;
+ var results = {
+ all: {},
+ changed: {},
+ removed: {}
+ };
+
+ merge(results.all, newObject);
+
+ var didChange = false;
+ coerceQueryParamsToString(oldObject);
+ coerceQueryParamsToString(newObject);
+
+ // Calculate removals
+ for (key in oldObject) {
+ if (oldObject.hasOwnProperty(key)) {
+ if (!newObject.hasOwnProperty(key)) {
+ didChange = true;
+ results.removed[key] = oldObject[key];
+ }
+ }
+ }
+
+ // Calculate changes
+ for (key in newObject) {
+ if (newObject.hasOwnProperty(key)) {
+ if (isArray(oldObject[key]) && isArray(newObject[key])) {
+ if (oldObject[key].length !== newObject[key].length) {
+ results.changed[key] = newObject[key];
+ didChange = true;
+ } else {
+ for (var i = 0, l = oldObject[key].length; i < l; i++) {
+ if (oldObject[key][i] !== newObject[key][i]) {
+ results.changed[key] = newObject[key];
+ didChange = true;
+ }
+ }
+ }
+ }
+ else {
+ if (oldObject[key] !== newObject[key]) {
+ results.changed[key] = newObject[key];
+ didChange = true;
+ }
+ }
+ }
+ }
+
+ return didChange && results;
+ }
+
+ function promiseLabel(label) {
+ return 'Router: ' + label;
+ }
+
+ __exports__.trigger = trigger;
+ __exports__.log = log;
+ __exports__.oCreate = oCreate;
+ __exports__.merge = merge;
+ __exports__.extractQueryParams = extractQueryParams;
+ __exports__.bind = bind;
+ __exports__.isParam = isParam;
+ __exports__.forEach = forEach;
+ __exports__.slice = slice;
+ __exports__.serialize = serialize;
+ __exports__.getChangelist = getChangelist;
+ __exports__.coerceQueryParamsToString = coerceQueryParamsToString;
+ __exports__.promiseLabel = promiseLabel;
+ });
+define("router",
+ ["./router/router","exports"],
+ function(__dependency1__, __exports__) {
+ "use strict";
+ var Router = __dependency1__.Router;
+
+ __exports__.Router = Router;
+ });
+})();
+
+
+
+(function() {
+/**
+@module ember
+@submodule ember-routing
+*/
+
+function DSL(name) {
+ this.parent = name;
+ this.matches = [];
+}
+
+DSL.prototype = {
+ resource: function(name, options, callback) {
+ Ember.assert("'basic' cannot be used as a resource name.", name !== 'basic');
+
+ if (arguments.length === 2 && typeof options === 'function') {
+ callback = options;
+ options = {};
+ }
+
+ if (arguments.length === 1) {
+ options = {};
+ }
+
+ if (typeof options.path !== 'string') {
+ options.path = "/" + name;
+ }
+
+ if (callback) {
+ var dsl = new DSL(name);
+ route(dsl, 'loading');
+ route(dsl, 'error', { path: "/_unused_dummy_error_path_route_" + name + "/:error" });
+ callback.call(dsl);
+ this.push(options.path, name, dsl.generate());
+ } else {
+ this.push(options.path, name, null);
+ }
+
+
+ },
+
+ push: function(url, name, callback) {
+ var parts = name.split('.');
+ if (url === "" || url === "/" || parts[parts.length-1] === "index") { this.explicitIndex = true; }
+
+ this.matches.push([url, name, callback]);
+ },
+
+ route: function(name, options) {
+ Ember.assert("'basic' cannot be used as a route name.", name !== 'basic');
+
+ route(this, name, options);
+ },
+
+ generate: function() {
+ var dslMatches = this.matches;
+
+ if (!this.explicitIndex) {
+ this.route("index", { path: "/" });
+ }
+
+ return function(match) {
+ for (var i=0, l=dslMatches.length; i<l; i++) {
+ var dslMatch = dslMatches[i];
+ var matchObj = match(dslMatch[0]).to(dslMatch[1], dslMatch[2]);
+ }
+ };
+ }
+};
+
+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);
+}
+
+DSL.map = function(callback) {
+ var dsl = new DSL();
+ callback.call(dsl);
+ return dsl;
+};
+
+Ember.RouterDSL = DSL;
+
+})();
+
+
+
+(function() {
+var get = Ember.get;
+
+/**
+@module ember
+@submodule ember-routing
+*/
+
+/**
+
+ Finds a controller instance.
+
+ @for Ember
+ @method controllerFor
+ @private
+*/
+Ember.controllerFor = function(container, controllerName, lookupOptions) {
+ return container.lookup('controller:' + controllerName, lookupOptions);
+};
+
+/**
+ Generates a controller factory
+
+ 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;
+
+ if (context && Ember.isArray(context)) {
+ controllerType = 'array';
+ } else if (context) {
+ controllerType = 'object';
+ } else {
+ controllerType = 'basic';
+ }
+
+ factoryName = 'controller:' + controllerType;
+
+ Factory = container.lookupFactory(factoryName).extend({
+ isGenerated: true,
+ toString: function() {
+ return "(generated " + controllerName + " controller)";
+ }
+ });
+
+ fullName = 'controller:' + controllerName;
+
+ container.register(fullName, Factory);
+
+ return Factory;
+};
+
+/**
+ 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;
+};
+
+})();
+
+
+
+(function() {
+/**
+@module ember
+@submodule ember-routing
+*/
+
+var routerJsModule = requireModule("router");
+var Router = routerJsModule.Router;
+var Transition = routerJsModule.Transition;
+var get = Ember.get, set = Ember.set, fmt = Ember.String.fmt;
+var defineProperty = Ember.defineProperty;
+var slice = Array.prototype.slice;
+var forEach = Ember.EnumerableUtils.forEach;
+
+var DefaultView = Ember._MetamorphView;
+/**
+ The `Ember.Router` class manages the application state and URLs. Refer to
+ the [routing guide](http://emberjs.com/guides/routing/) for documentation.
+
+ @class Router
+ @namespace Ember
+ @extends Ember.Object
+*/
+Ember.Router = Ember.Object.extend(Ember.Evented, {
+ /**
+ The `location` property determines the type of URL's that your
+ application will use.
+
+ The following location types are currently available:
+
+ * `hash`
+ * `history`
+ * `none`
+
+ @property location
+ @default 'hash'
+ @see {Ember.Location}
+ */
+ location: 'hash',
+
+ init: function() {
+ this.router = this.constructor.router || this.constructor.map(Ember.K);
+ this._activeViews = {};
+ this._setupLocation();
+
+ if (get(this, 'namespace.LOG_TRANSITIONS_INTERNAL')) {
+ this.router.log = Ember.Logger.debug;
+ }
+ },
+
+ /**
+ Represents the current URL.
+
+ @method url
+ @returns {String} The current URL.
+ */
+ url: Ember.computed(function() {
+ return get(this, 'location').getURL();
+ }),
+
+ /**
+ Initializes the current router instance and sets up the change handling
+ event listeners used by the instances `location` implementation.
+
+ A property named `initialURL` will be used to determine the initial URL.
+ If no value is found `/` will be used.
+
+ @method startRouting
+ @private
+ */
+ startRouting: function() {
+ this.router = this.router || this.constructor.map(Ember.K);
+
+ var router = this.router,
+ location = get(this, 'location'),
+ container = this.container,
+ self = this,
+ initialURL = get(this, 'initialURL');
+
+
+ // Allow the Location class to cancel the router setup while it refreshes
+ // the page
+ if (get(location, 'cancelRouterSetup')) {
+ return;
+ }
+
+
+ this._setupRouter(router, location);
+
+ container.register('view:default', DefaultView);
+ container.register('view:toplevel', Ember.View.extend());
+
+ location.onUpdateURL(function(url) {
+ self.handleURL(url);
+ });
+
+ if (typeof initialURL === "undefined") {
+ initialURL = location.getURL();
+ }
+
+ this.handleURL(initialURL);
+ },
+
+ /**
+ Handles updating the paths and notifying any listeners of the URL
+ change.
+
+ Triggers the router level `didTransition` hook.
+
+ @method didTransition
+ @private
+ */
+ didTransition: function(infos) {
+ updatePaths(this);
+
+ this._cancelLoadingEvent();
+
+ this.notifyPropertyChange('url');
+
+ // Put this in the runloop so url will be accurate. Seems
+ // less surprising than didTransition being out of sync.
+ Ember.run.once(this, this.trigger, 'didTransition');
+
+ if (get(this, 'namespace').LOG_TRANSITIONS) {
+ Ember.Logger.log("Transitioned into '" + Ember.Router._routePath(infos) + "'");
+ }
+ },
+
+ handleURL: function(url) {
+ return this._doTransition('handleURL', [url]);
+ },
+
+ transitionTo: function() {
+ return this._doTransition('transitionTo', arguments);
+ },
+
+ intermediateTransitionTo: function() {
+ this.router.intermediateTransitionTo.apply(this.router, arguments);
+
+ updatePaths(this);
+
+ var infos = this.router.currentHandlerInfos;
+ if (get(this, 'namespace').LOG_TRANSITIONS) {
+ Ember.Logger.log("Intermediate-transitioned into '" + Ember.Router._routePath(infos) + "'");
+ }
+ },
+
+ replaceWith: function() {
+ return this._doTransition('replaceWith', arguments);
+ },
+
+ generate: function() {
+ var url = this.router.generate.apply(this.router, arguments);
+ return this.location.formatURL(url);
+ },
+
+ /**
+ Determines if the supplied route is currently active.
+
+ @method isActive
+ @param routeName
+ @returns {Boolean}
+ @private
+ */
+ isActive: function(routeName) {
+ var router = this.router;
+ return router.isActive.apply(router, arguments);
+ },
+
+ send: function(name, context) {
+ this.router.trigger.apply(this.router, arguments);
+ },
+
+ /**
+ Does this router instance have the given route.
+
+ @method hasRoute
+ @returns {Boolean}
+ @private
+ */
+ hasRoute: function(route) {
+ return this.router.hasRoute(route);
+ },
+
+ /**
+ Resets the state of the router by clearing the current route
+ handlers and deactivating them.
+
+ @private
+ @method reset
+ */
+ reset: function() {
+ this.router.reset();
+ },
+
+ _lookupActiveView: function(templateName) {
+ var active = this._activeViews[templateName];
+ return active && active[0];
+ },
+
+ _connectActiveView: function(templateName, view) {
+ var existing = this._activeViews[templateName];
+
+ if (existing) {
+ existing[0].off('willDestroyElement', this, existing[1]);
+ }
+
+ var disconnect = function() {
+ delete this._activeViews[templateName];
+ };
+
+ this._activeViews[templateName] = [view, disconnect];
+ view.one('willDestroyElement', this, disconnect);
+ },
+
+ _setupLocation: function() {
+ var location = get(this, 'location'),
+ rootURL = get(this, 'rootURL');
+
+ if ('string' === typeof location && this.container) {
+ var resolvedLocation = this.container.lookup('location:' + location);
+
+ if ('undefined' !== typeof resolvedLocation) {
+ location = set(this, 'location', resolvedLocation);
+ } else {
+ // Allow for deprecated registration of custom location API's
+ var options = {implementation: location};
+
+ location = set(this, 'location', Ember.Location.create(options));
+ }
+ }
+
+ if (typeof rootURL === 'string') {
+ location.rootURL = rootURL;
+ }
+
+ // ensure that initState is called AFTER the rootURL is set on
+ // the location instance
+ if (typeof location.initState === 'function') { location.initState(); }
+ },
+
+ _getHandlerFunction: function() {
+ var seen = {}, container = this.container,
+ DefaultRoute = container.lookupFactory('route:basic'),
+ self = this;
+
+ return function(name) {
+ var routeName = 'route:' + name,
+ handler = container.lookup(routeName);
+
+ if (seen[name]) { return handler; }
+
+ seen[name] = true;
+
+ if (!handler) {
+ container.register(routeName, DefaultRoute.extend());
+ handler = container.lookup(routeName);
+
+ if (get(self, 'namespace.LOG_ACTIVE_GENERATION')) {
+ Ember.Logger.info("generated -> " + routeName, { fullName: routeName });
+ }
+ }
+
+ handler.routeName = name;
+ return handler;
+ };
+ },
+
+ _setupRouter: function(router, location) {
+ var lastURL, emberRouter = this;
+
+ router.getHandler = this._getHandlerFunction();
+
+ var doUpdateURL = function() {
+ location.setURL(lastURL);
+ };
+
+ router.updateURL = function(path) {
+ lastURL = path;
+ Ember.run.once(doUpdateURL);
+ };
+
+ if (location.replaceURL) {
+ var doReplaceURL = function() {
+ location.replaceURL(lastURL);
+ };
+
+ router.replaceURL = function(path) {
+ lastURL = path;
+ Ember.run.once(doReplaceURL);
+ };
+ }
+
+ router.didTransition = function(infos) {
+ emberRouter.didTransition(infos);
+ };
+ },
+
+ _doTransition: function(method, args) {
+ // Normalize blank route to root URL.
+ args = slice.call(args);
+ args[0] = args[0] || '/';
+
+ var name = args[0], self = this,
+ isQueryParamsOnly = false, queryParams;
+
+
+ if (!isQueryParamsOnly && name.charAt(0) !== '/') {
+ Ember.assert("The route " + name + " was not found", this.router.hasRoute(name));
+ }
+
+ if (queryParams) {
+ // router.js expects queryParams to be passed in in
+ // their final serialized form, so we need to translate.
+
+ if (!name) {
+ // Need to determine destination route name.
+ var handlerInfos = this.router.activeTransition ?
+ this.router.activeTransition.state.handlerInfos :
+ this.router.state.handlerInfos;
+ name = handlerInfos[handlerInfos.length - 1].name;
+ args.unshift(name);
+ }
+
+ var qpMappings = this._queryParamNamesFor(name);
+
+
+ Ember.Router._translateQueryParams(queryParams, qpMappings.translations, name);
+ var value;
+ for (var key in queryParams) {
+ var descopedParam = Ember.Router._descopeQueryParam(key);
+ if (key in qpMappings.queryParams) {
+ value = queryParams[key];
+ delete queryParams[key];
+ queryParams[qpMappings.queryParams[key]] = value;
+ } else if (descopedParam in qpMappings.validQueryParams) {
+ value = queryParams[key];
+ delete queryParams[key];
+ queryParams[descopedParam] = value;
+ }
+ }
+ }
+
+ var transitionPromise = this.router[method].apply(this.router, args);
+
+ transitionPromise.then(null, function(error) {
+ if (error && error.name === "UnrecognizedURLError") {
+ Ember.assert("The URL '" + error.message + "' did not match any routes in your application");
+ }
+ }, 'Ember: Check for Router unrecognized URL error');
+
+ // We want to return the configurable promise object
+ // so that callers of this function can use `.method()` on it,
+ // which obviously doesn't exist for normal RSVP promises.
+ return transitionPromise;
+ },
+
+ _scheduleLoadingEvent: function(transition, originRoute) {
+ this._cancelLoadingEvent();
+ this._loadingStateTimer = Ember.run.scheduleOnce('routerTransitions', this, '_fireLoadingEvent', transition, originRoute);
+ },
+
+ _fireLoadingEvent: function(transition, originRoute) {
+ if (!this.router.activeTransition) {
+ // Don't fire an event if we've since moved on from
+ // the transition that put us in a loading state.
+ return;
+ }
+
+ transition.trigger(true, 'loading', transition, originRoute);
+ },
+
+ _cancelLoadingEvent: function () {
+ if (this._loadingStateTimer) {
+ Ember.run.cancel(this._loadingStateTimer);
+ }
+ this._loadingStateTimer = null;
+ },
+
+ _queryParamNamesFor: function(routeName) {
+
+ // TODO: add caching
+
+ var handlerInfos = this.router.recognizer.handlersFor(routeName);
+ var result = { queryParams: Ember.create(null), translations: Ember.create(null), validQueryParams: Ember.create(null) };
+ var routerjs = this.router;
+ forEach(handlerInfos, function(recogHandler) {
+ var route = routerjs.getHandler(recogHandler.handler);
+ getQueryParamsForRoute(route, result);
+ });
+
+ descopeQueryParams(result.queryParams);
+
+ for (var k in result.queryParams) {
+ result.validQueryParams[result.queryParams[k]] = true;
+ }
+ return result;
+ },
+
+ _queryParamNamesForSingle: function(routeName) {
+
+ // TODO: add caching
+
+ var result = { queryParams: Ember.create(null), translations: Ember.create(null) };
+ var route = this.router.getHandler(routeName);
+
+ getQueryParamsForRoute(route, result);
+
+ // Descope non duplicate params.
+ if (routeName !== 'application') {
+ var allParams = this._queryParamNamesFor(routeName);
+ for (var k in result.queryParams) {
+ result.queryParams[k] = allParams.queryParams[k];
+ }
+ }
+
+ return result;
+ },
+
+ /**
+ @private
+
+ Utility function for fetching all the current query params
+ values from a controller.
+ */
+ _queryParamOverrides: function(results, queryParams, callback) {
+ for (var name in queryParams) {
+ var parts = name.split(':');
+
+ var controller = controllerOrProtoFor(parts[0], this.container);
+ Ember.assert(fmt("Could not lookup controller '%@' while setting up query params", [controller]), controller);
+
+ // Now assign the final URL-serialized key-value pair,
+ // e.g. "foo[propName]": "value"
+ results[queryParams[name]] = get(controller, parts[1]);
+
+ if (callback) {
+ // Give callback a chance to override.
+ callback(name, queryParams[name], name);
+ }
+ }
+ }
+});
+
+/**
+ @private
+ */
+function getQueryParamsForRoute(route, result) {
+ var controllerName = route.controllerName || route.routeName,
+ controller = controllerOrProtoFor(controllerName, route.container),
+ queryParams = get(controller, 'queryParams');
+
+ if (queryParams) {
+ forEach(queryParams, function(propName) {
+
+ var parts = propName.split(':');
+
+ var urlKeyName;
+ if (parts.length > 1) {
+ urlKeyName = parts[1];
+ } else {
+ // TODO: use _queryParamScope here?
+ if (controllerName !== 'application') {
+ urlKeyName = controllerName + '[' + propName + ']';
+ } else {
+ urlKeyName = propName;
+ }
+ }
+
+ var controllerFullname = controllerName + ':' + propName;
+
+ result.queryParams[controllerFullname] = urlKeyName;
+ result.translations[parts[0]] = controllerFullname;
+ });
+ }
+}
+
+function controllerOrProtoFor(controllerName, container) {
+ var fullName = 'controller:' + controllerName;
+ if (container.cache.has(fullName)) {
+ return container.lookup(fullName);
+ } else {
+ // Controller hasn't been instantiated yet; just return its proto.
+ var controllerClass = container.lookupFactory(fullName);
+ if (controllerClass && typeof controllerClass.proto === 'function') {
+ return controllerClass.proto();
+ } else {
+ return {};
+ }
+ }
+}
+
+function descopeQueryParams(params) {
+ var paramCounts = {},
+ descopedParam,
+ k;
+
+ // Loop through params and count the occurance of descoped param
+ for (k in params) {
+ descopedParam = Ember.Router._descopeQueryParam(params[k]);
+
+ if (!paramCounts[descopedParam]) {
+ paramCounts[descopedParam] = 1;
+ } else {
+ paramCounts[descopedParam] = paramCounts[descopedParam] + 1;
+ }
+ }
+
+ // Loop through again descoping params if the descoped key only occurs once
+ for (k in params) {
+ descopedParam = Ember.Router._descopeQueryParam(params[k]);
+
+ if (paramCounts[descopedParam] === 1) {
+ params[k] = descopedParam;
+ }
+ }
+}
+
+/**
+ Helper function for iterating root-ward, starting
+ from (but not including) the provided `originRoute`.
+
+ Returns true if the last callback fired requested
+ to bubble upward.
+
+ @private
+ */
+function forEachRouteAbove(originRoute, transition, callback) {
+ var handlerInfos = transition.state.handlerInfos,
+ originRouteFound = false;
+
+ for (var i = handlerInfos.length - 1; i >= 0; --i) {
+ var handlerInfo = handlerInfos[i],
+ route = handlerInfo.handler;
+
+ if (!originRouteFound) {
+ if (originRoute === route) {
+ originRouteFound = true;
+ }
+ continue;
+ }
+
+ if (callback(route, handlerInfos[i + 1].handler) !== true) {
+ return false;
+ }
+ }
+ return true;
+}
+
+// These get invoked when an action bubbles above ApplicationRoute
+// and are not meant to be overridable.
+var defaultActionHandlers = {
+
+ willResolveModel: function(transition, originRoute) {
+ originRoute.router._scheduleLoadingEvent(transition, originRoute);
+ },
+
+ error: function(error, transition, originRoute) {
+ // Attempt to find an appropriate error substate to enter.
+ var router = originRoute.router;
+
+ var tryTopLevel = forEachRouteAbove(originRoute, transition, function(route, childRoute) {
+ var childErrorRouteName = findChildRouteName(route, childRoute, 'error');
+ if (childErrorRouteName) {
+ router.intermediateTransitionTo(childErrorRouteName, error);
+ return;
+ }
+ return true;
+ });
+
+ if (tryTopLevel) {
+ // Check for top-level error state to enter.
+ if (routeHasBeenDefined(originRoute.router, 'application_error')) {
+ router.intermediateTransitionTo('application_error', error);
+ return;
+ }
+ } else {
+ // Don't fire an assertion if we found an error substate.
+ return;
+ }
+
+ Ember.Logger.error('Error while loading route: ' + (error && error.stack));
+ },
+
+ loading: function(transition, originRoute) {
+ // Attempt to find an appropriate loading substate to enter.
+ var router = originRoute.router;
+
+ var tryTopLevel = forEachRouteAbove(originRoute, transition, function(route, childRoute) {
+ var childLoadingRouteName = findChildRouteName(route, childRoute, 'loading');
+
+ if (childLoadingRouteName) {
+ router.intermediateTransitionTo(childLoadingRouteName);
+ return;
+ }
+
+ // Don't bubble above pivot route.
+ if (transition.pivotHandler !== route) {
+ return true;
+ }
+ });
+
+ if (tryTopLevel) {
+ // Check for top-level loading state to enter.
+ if (routeHasBeenDefined(originRoute.router, 'application_loading')) {
+ router.intermediateTransitionTo('application_loading');
+ return;
+ }
+ }
+ }
+};
+
+function findChildRouteName(parentRoute, originatingChildRoute, name) {
+ var router = parentRoute.router,
+ childName,
+ targetChildRouteName = originatingChildRoute.routeName.split('.').pop(),
+ namespace = parentRoute.routeName === 'application' ? '' : parentRoute.routeName + '.';
+
+
+ // Second, try general loading state, e.g. 'loading'
+ childName = namespace + name;
+ if (routeHasBeenDefined(router, childName)) {
+ return childName;
+ }
+}
+
+function routeHasBeenDefined(router, name) {
+ var container = router.container;
+ return router.hasRoute(name) &&
+ (container.has('template:' + name) || container.has('route:' + name));
+}
+
+function triggerEvent(handlerInfos, ignoreFailure, args) {
+ var name = args.shift();
+
+ if (!handlerInfos) {
+ if (ignoreFailure) { return; }
+ throw new Ember.Error("Can't trigger action '" + name + "' because your app hasn't finished transitioning into its first route. To trigger an action on destination routes during a transition, you can call `.send()` on the `Transition` object passed to the `model/beforeModel/afterModel` hooks.");
+ }
+
+ var eventWasHandled = false;
+
+ for (var i = handlerInfos.length - 1; i >= 0; i--) {
+ var handlerInfo = handlerInfos[i],
+ handler = handlerInfo.handler;
+
+ if (handler._actions && handler._actions[name]) {
+ if (handler._actions[name].apply(handler, args) === true) {
+ eventWasHandled = true;
+ } else {
+ return;
+ }
+ }
+ }
+
+ if (defaultActionHandlers[name]) {
+ defaultActionHandlers[name].apply(null, args);
+ return;
+ }
+
+ if (!eventWasHandled && !ignoreFailure) {
+ throw new Ember.Error("Nothing handled the action '" + name + "'. If you did handle the action, this error can be caused by returning true from an action handler in a controller, causing the action to bubble.");
+ }
+}
+
+function updatePaths(router) {
+ var appController = router.container.lookup('controller:application');
+
+ if (!appController) {
+ // appController might not exist when top-level loading/error
+ // substates have been entered since ApplicationRoute hasn't
+ // actually been entered at that point.
+ return;
+ }
+
+ var infos = router.router.currentHandlerInfos,
+ path = Ember.Router._routePath(infos);
+
+ if (!('currentPath' in appController)) {
+ defineProperty(appController, 'currentPath');
+ }
+
+ set(appController, 'currentPath', path);
+
+ if (!('currentRouteName' in appController)) {
+ defineProperty(appController, 'currentRouteName');
+ }
+
+ set(appController, 'currentRouteName', infos[infos.length - 1].name);
+}
+
+Ember.Router.reopenClass({
+ router: null,
+ map: function(callback) {
+ var router = this.router;
+ if (!router) {
+ router = new Router();
+ router.callbacks = [];
+ router.triggerEvent = triggerEvent;
+ this.reopenClass({ router: router });
+ }
+
+ var dsl = Ember.RouterDSL.map(function() {
+ this.resource('application', { path: "/" }, function() {
+ for (var i=0; i < router.callbacks.length; i++) {
+ router.callbacks[i].call(this);
+ }
+ callback.call(this);
+ });
+ });
+
+ router.callbacks.push(callback);
+ router.map(dsl.generate());
+ return router;
+ },
+
+ _routePath: function(handlerInfos) {
+ var path = [];
+
+ // We have to handle coalescing resource names that
+ // are prefixed with their parent's names, e.g.
+ // ['foo', 'foo.bar.baz'] => 'foo.bar.baz', not 'foo.foo.bar.baz'
+
+ function intersectionMatches(a1, a2) {
+ for (var i = 0, len = a1.length; i < len; ++i) {
+ if (a1[i] !== a2[i]) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ for (var i=1, l=handlerInfos.length; i<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(".");
+ },
+
+ _translateQueryParams: function(queryParams, translations, routeName) {
+ for (var name in queryParams) {
+ if (!queryParams.hasOwnProperty(name)) { continue; }
+
+ if (name in translations) {
+ queryParams[translations[name]] = queryParams[name];
+ delete queryParams[name];
+ } else {
+ Ember.assert(fmt("You supplied an unknown query param controller property '%@' for route '%@'. Only the following query param properties can be set for this route: %@", [name, routeName, Ember.keys(translations)]), name in queryParams);
+ }
+ }
+ },
+
+ _descopeQueryParam: function(param) {
+ var regex = /\[(.+)\]/,
+ result = param.match(regex);
+
+ if (!result) {
+ result = param;
+ } else {
+ result = result[1];
+ }
+
+ return result;
+ }
+});
+
+
+
+})();
+
+
+
+(function() {
+/**
+@module ember
+@submodule ember-routing
+*/
+
+var get = Ember.get, set = Ember.set,
+ getProperties = Ember.getProperties,
+ classify = Ember.String.classify,
+ fmt = Ember.String.fmt,
+ a_forEach = Ember.EnumerableUtils.forEach,
+ a_replace = Ember.EnumerableUtils.replace;
+
+
+/**
+ The `Ember.Route` class is used to define individual routes. Refer to
+ the [routing guide](http://emberjs.com/guides/routing/) for documentation.
+
+ @class Route
+ @namespace Ember
+ @extends Ember.Object
+ @uses Ember.ActionHandler
+*/
+Ember.Route = Ember.Object.extend(Ember.ActionHandler, {
+
+ /**
+ @private
+
+ @method exit
+ */
+ exit: function() {
+ this.deactivate();
+ this.teardownViews();
+ },
+
+ /**
+ @private
+
+ @method enter
+ */
+ enter: function() {
+ this.activate();
+ },
+
+ /**
+ The name of the view to use by default when rendering this routes template.
+
+ When rendering a template, the route will, by default, determine the
+ template and view to use from the name of the route itself. If you need to
+ define a specific view, set this property.
+
+ This is useful when multiple routes would benefit from using the same view
+ because it doesn't require a custom `renderTemplate` method. For example,
+ the following routes will all render using the `App.PostsListView` view:
+
+ ```js
+ var PostsList = Ember.Route.extend({
+ viewName: 'postsList',
+ });
+
+ App.PostsIndexRoute = PostsList.extend();
+ App.PostsArchivedRoute = PostsList.extend();
+ ```
+
+ @property viewName
+ @type String
+ @default null
+ */
+ viewName: null,
+
+ /**
+ The name of the template to use by default when rendering this routes
+ template.
+
+ This is similar with `viewName`, but is useful when you just want a custom
+ template without a view.
+
+ ```js
+ var PostsList = Ember.Route.extend({
+ templateName: 'posts/list'
+ });
+
+ App.PostsIndexRoute = PostsList.extend();
+ App.PostsArchivedRoute = PostsList.extend();
+ ```
+
+ @property templateName
+ @type String
+ @default null
+ */
+ templateName: null,
+
+ /**
+ The name of the controller to associate with this route.
+
+ By default, Ember will lookup a route's controller that matches the name
+ of the route (i.e. `App.PostController` for `App.PostRoute`). However,
+ if you would like to define a specific controller to use, you can do so
+ using this property.
+
+ This is useful in many ways, as the controller specified will be:
+
+ * passed to the `setupController` method.
+ * used as the controller for the view being rendered by the route.
+ * returned from a call to `controllerFor` for the route.
+
+ @property controllerName
+ @type String
+ @default null
+ */
+ controllerName: null,
+
+ /**
+ The collection of functions, keyed by name, available on this route as
+ action targets.
+
+ These functions will be invoked when a matching `{{action}}` is triggered
+ from within a template and the application's current route is this route.
+
+ Actions can also be invoked from other parts of your application via `Route#send`
+ or `Controller#send`.
+
+ 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.:
+
+ ```js
+ App.CanDisplayBanner = Ember.Mixin.create({
+ actions: {
+ displayBanner: function(msg) {
+ // ...
+ }
+ }
+ });
+
+ App.WelcomeRoute = Ember.Route.extend(App.CanDisplayBanner, {
+ actions: {
+ playMusic: function() {
+ // ...
+ }
+ }
+ });
+
+ // `WelcomeRoute`, when active, will be able to respond
+ // to both actions, since the actions hash is merged rather
+ // then replaced when extending mixins / parent classes.
+ this.send('displayBanner');
+ this.send('playMusic');
+ ```
+
+ Within a 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
+ @type Hash
+ @default null
+ */
+ _actions: {
+ finalizeQueryParamChange: function(params, finalParams) {
+ }
+ },
+
+ /**
+ @deprecated
+
+ Please use `actions` instead.
+ @method events
+ */
+ events: null,
+
+ mergedProperties: ['events'],
+
+ /**
+ This hook is executed when the router completely exits this route. It is
+ not executed when the model for the route changes.
+
+ @method deactivate
+ */
+ deactivate: Ember.K,
+
+ /**
+ This hook is executed when the router enters the route. It is not executed
+ when the model for the route changes.
+
+ @method activate
+ */
+ activate: Ember.K,
+
+ /**
+ Transition the application into another route. The route may
+ be either a single route or route path:
+
+ ```javascript
+ this.transitionTo('blogPosts');
+ this.transitionTo('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
+ this.transitionTo('blogPost', aPost);
+ ```
+
+ If a literal is passed (such as a number or a string), it will
+ be treated as an identifier instead. In this case, the `model`
+ hook of the route will be triggered:
+
+ ```javascript
+ this.transitionTo('blogPost', 1);
+ ```
+
+ Multiple models will be applied last to first recursively up the
+ resource tree.
+
+ ```javascript
+ App.Router.map(function() {
+ this.resource('blogPost', {path:':blogPostId'}, function(){
+ this.resource('blogComment', {path: ':blogCommentId'});
+ });
+ });
+
+ this.transitionTo('blogComment', aPost, aComment);
+ this.transitionTo('blogComment', 1, 13);
+ ```
+
+ It is also possible to pass a URL (a string that starts with a
+ `/`). This is intended for testing and debugging purposes and
+ should rarely be used in production code.
+
+ ```javascript
+ this.transitionTo('/');
+ this.transitionTo('/blog/post/1/comment/13');
+ ```
+
+ See also 'replaceWith'.
+
+ 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);
+ }
+ }
+ });
+ ```
+
+ @method transitionTo
+ @param {String} name the name of the route or a URL
+ @param {...Object} models the model(s) or identifier(s) to be used while
+ transitioning to the route.
+ @return {Transition} the transition object associated with this
+ attempted transition
+ */
+ transitionTo: function(name, context) {
+ var router = this.router;
+ return router.transitionTo.apply(router, arguments);
+ },
+
+ /**
+ Perform a synchronous transition into another route without attempting
+ to resolve promises, update the URL, or abort any currently active
+ asynchronous transitions (i.e. regular transitions caused by
+ `transitionTo` or URL changes).
+
+ 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);
+ },
+
+ /**
+ Refresh the model on this route and any child routes, firing the
+ `beforeModel`, `model`, and `afterModel` hooks in a similar fashion
+ to how routes are entered when transitioning in from other route.
+ The current route params (e.g. `article_id`) will be passed in
+ to the respective model hooks, and if a different model is returned,
+ `setupController` and associated route hooks will re-fire as well.
+
+ An example usage of this method is re-querying the server for the
+ latest information using the same parameters as when the route
+ was first entered.
+
+ Note that this will cause `model` hooks to fire even on routes
+ that were provided a model object when the route was initially
+ entered.
+
+ @method refresh
+ @return {Transition} the transition object associated with this
+ attempted transition
+ */
+ refresh: function() {
+ return this.router.router.refresh(this).method('replace');
+ },
+
+ /**
+ 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');
+ }
+ }
+ });
+ ```
+
+ @method replaceWith
+ @param {String} name the name of the route or a URL
+ @param {...Object} models the model(s) or identifier(s) to be used while
+ transitioning to the route.
+ @return {Transition} the transition object associated with this
+ attempted transition
+ */
+ replaceWith: function() {
+ var router = this.router;
+ return router.replaceWith.apply(router, arguments);
+ },
+
+ /**
+ 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
+ */
+ send: function() {
+ return this.router.send.apply(this.router, arguments);
+ },
+
+ /**
+ This hook is the entry point for router.js
+
+ @private
+ @method setup
+ */
+ setup: function(context, transition) {
+ var controllerName = this.controllerName || this.routeName,
+ controller = this.controllerFor(controllerName, true);
+ if (!controller) {
+ controller = this.generateController(controllerName, context);
+ }
+
+ // Assign the route's controller so that it can more easily be
+ // referenced in action handlers
+ this.controller = controller;
+
+
+ if (this.setupControllers) {
+ Ember.deprecate("Ember.Route.setupControllers is deprecated. Please use Ember.Route.setupController(controller, model) instead.");
+ this.setupControllers(controller, context);
+ } else {
+
+
+ this.setupController(controller, context);
+
+ }
+
+ if (this.renderTemplates) {
+ Ember.deprecate("Ember.Route.renderTemplates is deprecated. Please use Ember.Route.renderTemplate(controller, model) instead.");
+ this.renderTemplates(context);
+ } else {
+ this.renderTemplate(controller, context);
+ }
+ },
+
+ /**
+ 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:
+
+ 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.
+
+ 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);
+ });
+ }
+ }
+ });
+ ```
+
+ @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.
+ */
+ 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: Ember.K,
+
+ /**
+ A hook you can implement to optionally redirect to another route.
+
+ If you call `this.transitionTo` from inside of this hook, this route
+ will not be entered in favor of the other hook.
+
+ `redirect` and `afterModel` behave very similarly and are
+ called almost at the same time, but they have an important
+ distinction in the case that, from one of these hooks, a
+ redirect into a child route of this route occurs: redirects
+ from `afterModel` essentially invalidate the current attempt
+ to enter this route, and will result in this route's `beforeModel`,
+ `model`, and `afterModel` hooks being fired again within
+ the new, redirecting transition. Redirects that occur within
+ the `redirect` hook, on the other hand, will _not_ cause
+ these hooks to be fired again the second time around; in
+ other words, by the time the `redirect` hook has been called,
+ both the resolved model and attempted entry into this route
+ are considered to be fully validated.
+
+ @method redirect
+ @param {Object} model the model for this route
+ */
+ redirect: Ember.K,
+
+ /**
+ Called when the context is changed by router.js.
+
+ @private
+ @method contextDidChange
+ */
+ contextDidChange: function() {
+ this.currentModel = this.context;
+ },
+
+ /**
+ A hook you can implement to convert the URL into the model for
+ this route.
+
+ ```js
+ App.Router.map(function() {
+ this.resource('post', {path: '/posts/:post_id'});
+ });
+ ```
+
+ The model for the `post` route is `App.Post.find(params.post_id)`.
+
+ By default, if your route has a dynamic segment ending in `_id`:
+
+ * The model class is determined from the segment (`post_id`'s
+ class is `App.Post`)
+ * The find method is called on the model class with the value of
+ the dynamic segment.
+
+ 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);
+ }
+ });
+ ```
+
+ @method model
+ @param {Object} params the parameters extracted from the URL
+ @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.
+ */
+ model: function(params, transition) {
+ var match, name, sawParams, value;
+
+ for (var prop in params) {
+ if (prop === 'queryParams') { continue; }
+
+ if (match = prop.match(/^(.*)_id$/)) {
+ name = match[1];
+ value = params[prop];
+ }
+ sawParams = true;
+ }
+
+ if (!name && sawParams) { return Ember.copy(params); }
+ else if (!name) {
+
+ if (transition.resolveIndex !== transition.state.handlerInfos.length-1) { return; }
+
+ var parentModel = transition.state.handlerInfos[transition.resolveIndex-1].context;
+
+ return parentModel;
+ }
+
+ return this.findModel(name, value);
+ },
+
+ /**
+ @private
+
+ Router.js hook.
+ */
+ deserialize: function(params, transition) {
+
+ return this.model(params, transition);
+
+ },
+
+ /**
+
+ @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);
+ },
+
+ /**
+ 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);
+
+ if (!modelClass) { return; }
+
+ Ember.assert(classify(name) + ' has no method `find`.', typeof modelClass.find === 'function');
+
+ return modelClass.find(value);
+ }
+ };
+ }),
+
+ /**
+ A hook you can implement to convert the route's model into parameters
+ for the URL.
+
+ ```js
+ App.Router.map(function() {
+ this.resource('post', {path: '/posts/:post_id'});
+ });
+
+ App.PostRoute = Ember.Route.extend({
+ model: function(params) {
+ // the server returns `{ id: 12 }`
+ return jQuery.getJSON("/posts/" + params.post_id);
+ },
+
+ serialize: function(model) {
+ // this will make the URL `/posts/12`
+ return { post_id: model.id };
+ }
+ });
+ ```
+
+ 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)`
+
+ This method is called when `transitionTo` is called with a context
+ in order to populate the URL.
+
+ @method serialize
+ @param {Object} model the route's model
+ @param {Array} params an Array of parameter names for the current
+ route (in the example, `['post_id']`.
+ @return {Object} the serialized parameters
+ */
+ serialize: function(model, params) {
+ if (params.length < 1) { return; }
+ if (!model) { return; }
+
+ var name = params[0], object = {};
+
+ if (/_id$/.test(name) && params.length === 1) {
+ object[name] = get(model, "id");
+ } else {
+ object = getProperties(model, params);
+ }
+
+ return object;
+ },
+
+ /**
+ A hook you can use to setup the controller for the current route.
+
+ This method is called with the controller for the current route and the
+ model supplied by the `model` hook.
+
+ By default, the `setupController` hook sets the `content` property of
+ the controller to the `model`.
+
+ If you implement the `setupController` hook in your Route, it will
+ prevent this default behavior. If you want to preserve that behavior
+ when implementing your `setupController` function, make sure to call
+ `_super`:
+
+ ```js
+ App.PhotosRoute = Ember.Route.extend({
+ model: function() {
+ return App.Photo.find();
+ },
+
+ setupController: function (controller, model) {
+ // Call _super for default behavior
+ this._super(controller, model);
+ // Implement your custom setup after
+ this.controllerFor('application').set('showingPhotos', true);
+ }
+ });
+ ```
+
+ 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.
+
+ 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.
+
+ * if the model is an `Ember.Array` (including record arrays from Ember
+ Data), the controller is an `Ember.ArrayController`.
+ * otherwise, the controller is an `Ember.ObjectController`.
+
+ As an example, consider the router:
+
+ ```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);
+ }
+ });
+ ```
+
+ @method setupController
+ @param {Controller} controller instance
+ @param {Object} model
+ */
+ setupController: function(controller, context, transition) {
+ if (controller && (context !== undefined)) {
+ set(controller, 'model', context);
+ }
+ },
+
+ /**
+ Returns the controller for a particular route or name.
+
+ The controller instance must already have been created, either through entering the
+ associated route or using `generateController`.
+
+ ```js
+ App.PostRoute = Ember.Route.extend({
+ setupController: function(controller, post) {
+ this._super(controller, post);
+ this.controllerFor('posts').set('currentPost', post);
+ }
+ });
+ ```
+
+ @method controllerFor
+ @param {String} name the name of the route or controller
+ @return {Ember.Controller}
+ */
+ controllerFor: function(name, _skipAssert) {
+ var container = this.container,
+ route = container.lookup('route:'+name),
+ controller;
+
+ if (route && route.controllerName) {
+ name = route.controllerName;
+ }
+
+ controller = container.lookup('controller:' + name);
+
+ // 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);
+
+ return controller;
+ },
+
+ /**
+ Generates a controller for a route.
+
+ If the optional model is passed then the controller type is determined automatically,
+ e.g., an ArrayController for arrays.
+
+ 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'));
+ }
+ });
+ ```
+
+ @method modelFor
+ @param {String} name the name of the route
+ @return {Object} the model object
+ */
+ modelFor: function(name) {
+
+ 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];
+ }
+ }
+
+ return route && route.currentModel;
+ },
+
+ /**
+ A hook you can use to render the template for the current route.
+
+ This method is called with the controller for the current route and the
+ model supplied by the `model` hook. By default, it renders the route's
+ template, configured with the controller for the route.
+
+ This method can be overridden to set up and render additional or
+ alternative templates.
+
+ ```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
+ });
+ }
+ });
+ ```
+
+ @method renderTemplate
+ @param {Object} controller the route's controller
+ @param {Object} model the route's model
+ */
+ renderTemplate: function(controller, model) {
+ this.render();
+ },
+
+ /**
+ Renders a template into an outlet.
+
+ This method has a number of defaults, based on the name of the
+ route specified in the router.
+
+ For example:
+
+ ```js
+ App.Router.map(function() {
+ this.route('index');
+ this.resource('post', {path: '/posts/:post_id'});
+ });
+
+ App.PostRoute = App.Route.extend({
+ renderTemplate: function() {
+ this.render();
+ }
+ });
+ ```
+
+ The name of the `PostRoute`, as defined by the router, is `post`.
+
+ By default, render will:
+
+ * render the `post` template
+ * with the `post` view (`PostView`) for event handling, if one exists
+ * and the `post` controller (`PostController`), if one exists
+ * into the `main` outlet of the `application` template
+
+ You can override this behavior:
+
+ ```js
+ App.PostRoute = App.Route.extend({
+ renderTemplate: function() {
+ this.render('myPost', { // the template to render
+ into: 'index', // the template to render into
+ outlet: 'detail', // the name of the outlet in that template
+ controller: 'blogPost' // the controller to use for the template
+ });
+ }
+ });
+ ```
+
+ Remember that the controller's `content` will be the route's model. In
+ this case, the default model will be `App.Post.find(params.post_id)`.
+
+ @method render
+ @param {String} name the name of the template to render
+ @param {Object} options the options
+ */
+ render: function(name, options) {
+ Ember.assert("The name in the given arguments is undefined", arguments.length > 0 ? !Ember.isNone(arguments[0]) : true);
+
+ var namePassed = !!name;
+
+ if (typeof name === 'object' && !options) {
+ options = name;
+ name = this.routeName;
+ }
+
+ options = options || {};
+
+ var templateName;
+
+ if (name) {
+ name = name.replace(/\//g, '.');
+ templateName = name;
+ } else {
+ name = this.routeName;
+ templateName = this.templateName || name;
+ }
+
+ var viewName = options.view || this.viewName || name;
+
+ var container = this.container,
+ view = container.lookup('view:' + viewName),
+ template = view ? view.get('template') : null;
+
+ if (!template) {
+ template = container.lookup('template:' + templateName);
+ }
+
+ if (!view && !template) {
+ Ember.assert("Could not find \"" + name + "\" template or view.", !namePassed);
+ if (get(this.router, 'namespace.LOG_VIEW_LOOKUPS')) {
+ Ember.Logger.info("Could not find \"" + name + "\" template or view. Nothing will be rendered", { fullName: 'template:' + name });
+ }
+ return;
+ }
+
+ options = normalizeOptions(this, name, template, options);
+ view = setupView(view, container, options);
+
+ if (options.outlet === 'main') { this.lastRenderedTemplate = name; }
+
+ appendView(this, view, options);
+ },
+
+ /**
+ Disconnects a view that has been rendered into an outlet.
+
+ You may pass any or all of the following options to `disconnectOutlet`:
+
+ * `outlet`: the name of the outlet to clear (default: 'main')
+ * `parentView`: the name of the view containing the outlet to clear
+ (default: the view rendered by the parent route)
+
+ Example:
+
+ ```js
+ App.ApplicationRoute = App.Route.extend({
+ actions: {
+ showModal: function(evt) {
+ this.render(evt.modalName, {
+ outlet: 'modal',
+ into: 'application'
+ });
+ },
+ hideModal: function(evt) {
+ this.disconnectOutlet({
+ outlet: 'modal',
+ parentView: 'application'
+ });
+ }
+ }
+ });
+ ```
+
+ Alternatively, you can pass the `outlet` name directly as a string.
+
+ Example:
+
+ ```js
+ hideModal: function(evt) {
+ this.disconnectOutlet('modal');
+ }
+ ```
+
+ @method disconnectOutlet
+ @param {Object|String} options the options hash or outlet name
+ */
+ disconnectOutlet: function(options) {
+ if (!options || typeof options === "string") {
+ var outletName = options;
+ options = {};
+ options.outlet = outletName;
+ }
+ options.parentView = options.parentView ? options.parentView.replace(/\//g, '.') : parentTemplate(this);
+ options.outlet = options.outlet || 'main';
+
+ var parentView = this.router._lookupActiveView(options.parentView);
+ if (parentView) { parentView.disconnectOutlet(options.outlet); }
+ },
+
+ willDestroy: function() {
+ this.teardownViews();
+ },
+
+ /**
+ @private
+
+ @method teardownViews
+ */
+ teardownViews: function() {
+ // Tear down the top level view
+ if (this.teardownTopLevelView) { this.teardownTopLevelView(); }
+
+ // Tear down any outlets rendered with 'into'
+ var teardownOutletViews = this.teardownOutletViews || [];
+ a_forEach(teardownOutletViews, function(teardownOutletView) {
+ teardownOutletView();
+ });
+
+ delete this.teardownTopLevelView;
+ delete this.teardownOutletViews;
+ delete this.lastRenderedTemplate;
+ }
+});
+
+
+
+function parentRoute(route) {
+ var handlerInfos = route.router.router.state.handlerInfos;
+
+ if (!handlerInfos) { return; }
+
+ var parent, current;
+
+ for (var i=0, l=handlerInfos.length; i<l; i++) {
+ current = handlerInfos[i].handler;
+ if (current === route) { return parent; }
+ parent = current;
+ }
+}
+
+function parentTemplate(route) {
+ var parent = parentRoute(route), template;
+
+ if (!parent) { return; }
+
+ if (template = parent.lastRenderedTemplate) {
+ return template;
+ } else {
+ return parentTemplate(parent);
+ }
+}
+
+function normalizeOptions(route, name, template, options) {
+ options = options || {};
+ options.into = options.into ? options.into.replace(/\//g, '.') : parentTemplate(route);
+ options.outlet = options.outlet || 'main';
+ options.name = name;
+ options.template = template;
+ options.LOG_VIEW_LOOKUPS = get(route.router, 'namespace.LOG_VIEW_LOOKUPS');
+
+ Ember.assert("An outlet ("+options.outlet+") was specified but was not found.", options.outlet === 'main' || options.into);
+
+ var controller = options.controller, namedController;
+
+ if (options.controller) {
+ controller = options.controller;
+ } else if (namedController = route.container.lookup('controller:' + name)) {
+ controller = namedController;
+ } else {
+ controller = route.controllerName || route.routeName;
+ }
+
+ if (typeof controller === 'string') {
+ var controllerName = controller;
+ controller = route.container.lookup('controller:' + controllerName);
+ if (!controller) {
+ throw new Ember.Error("You passed `controller: '" + controllerName + "'` into the `render` method, but no such controller could be found.");
+ }
+ }
+
+ options.controller = controller;
+
+ return options;
+}
+
+function setupView(view, container, options) {
+ 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 });
+ }
+ }
+
+ if (!get(view, 'templateName')) {
+ set(view, 'template', options.template);
+
+ set(view, '_debugTemplateName', options.name);
+ }
+
+ set(view, 'renderedName', options.name);
+ set(view, 'controller', options.controller);
+
+ return view;
+}
+
+function appendView(route, view, options) {
+ if (options.into) {
+ var parentView = route.router._lookupActiveView(options.into);
+ var teardownOutletView = generateOutletTeardown(parentView, options.outlet);
+ if (!route.teardownOutletViews) { route.teardownOutletViews = []; }
+ a_replace(route.teardownOutletViews, 0, 0, [teardownOutletView]);
+ parentView.connectOutlet(options.outlet, view);
+ } else {
+ var rootElement = get(route, 'router.namespace.rootElement');
+ // tear down view if one is already rendered
+ if (route.teardownTopLevelView) {
+ route.teardownTopLevelView();
+ }
+ route.router._connectActiveView(options.name, view);
+ route.teardownTopLevelView = generateTopLevelTeardown(view);
+ view.appendTo(rootElement);
+ }
+}
+
+function generateTopLevelTeardown(view) {
+ return function() { view.destroy(); };
+}
+
+function generateOutletTeardown(parentView, outlet) {
+ return function() { parentView.disconnectOutlet(outlet); };
+}
+
+})();
+
+
+
+(function() {
+
+})();
+
+
+
+(function() {
+Ember.onLoad('Ember.Handlebars', function() {
+ var handlebarsResolve = Ember.Handlebars.resolveParams,
+ map = Ember.ArrayPolyfills.map,
+ get = Ember.get,
+ handlebarsGet = Ember.Handlebars.get;
+
+ 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;
+});
+
+})();
+
+
+
+(function() {
+/**
+@module ember
+@submodule ember-routing
+*/
+
+var get = Ember.get, set = Ember.set, fmt = Ember.String.fmt;
+
+var slice = Array.prototype.slice;
+var numberOfContextsAcceptedByHandler = function(handler, handlerInfos) {
+ var req = 0;
+ for (var i = 0, l = handlerInfos.length; i < l; i++) {
+ req = req + handlerInfos[i].names.length;
+ if (handlerInfos[i].handler === handler)
+ break;
+ }
+
+ // query params adds an additional context
+ return req;
+};
+
+Ember.onLoad('Ember.Handlebars', function(Handlebars) {
+
+ var QueryParams = Ember.Object.extend({
+ values: null
+ });
+
+ var resolveParams = Ember.Router.resolveParams,
+ translateQueryParams = Ember.Router._translateQueryParams,
+ resolvePaths = Ember.Router.resolvePaths,
+ isSimpleClick = Ember.ViewUtils.isSimpleClick;
+
+ function getResolvedPaths(options) {
+
+ var types = options.options.types,
+ data = options.options.data;
+
+ return resolvePaths(options.context, options.params, { types: types, data: data });
+ }
+
+ /**
+ `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.
+
+ 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.
+
+ @class LinkView
+ @namespace Ember
+ @extends Ember.View
+ @see {Handlebars.helpers.link-to}
+ **/
+ var LinkView = Ember.LinkView = Ember.View.extend({
+ tagName: 'a',
+ currentWhen: null,
+
+ /**
+ Sets the `title` attribute of the `LinkView`'s HTML element.
+
+ @property title
+ @default null
+ **/
+ title: null,
+
+ /**
+ 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
+ **/
+ activeClass: 'active',
+
+ /**
+ 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
+ **/
+ replace: false,
+
+ /**
+ 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);
+ }
+
+ var queryParamsObject = this.queryParamsObject;
+ if (queryParamsObject) {
+ var values = queryParamsObject.values;
+
+ // Install observers for all of the hash options
+ // provided in the (query-params) subexpression.
+ for (var k in values) {
+ if (!values.hasOwnProperty(k)) { continue; }
+
+ if (queryParamsObject.types[k] === 'ID') {
+ normalizedPath = Ember.Handlebars.normalizePath(helperParameters.context, values[k], helperParameters.options.data);
+ this.registerObserver(normalizedPath.root, normalizedPath.path, this, this._paramsChanged);
+ }
+ }
+ }
+ },
+
+ afterRender: function(){
+ this._super.apply(this, arguments);
+ this._setupPathObservers();
+ },
+
+ /**
+ 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
+ **/
+ concreteView: Ember.computed(function() {
+ return get(this, 'parentView');
+ }).property('parentView'),
+
+ /**
+
+ 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 || routeArgs[0],
+ maximumContexts = numberOfContextsAcceptedByHandler(currentWhen, router.router.recognizer.handlersFor(currentWhen));
+
+ // if we don't have enough contexts revert back to full route name
+ // this is because the leaf route will use one of the contexts
+ if (contexts.length > maximumContexts)
+ currentWhen = routeArgs[0];
+
+ var isActive = router.isActive.apply(router, [currentWhen].concat(contexts));
+
+ if (isActive) { return get(this, 'activeClass'); }
+ }).property('resolvedParams', 'routeArgs'),
+
+ /**
+ Accessed as a classname binding to apply the `LinkView`'s `loadingClass`
+ CSS `class` to the element when the link is loading.
+
+ A `LinkView` is considered loading when it has at least one
+ parameter whose value is currently null or undefined. During
+ this time, clicking the link will perform no transition and
+ emit a warning that the link is still in a loading state.
+
+ @property loading
+ **/
+ loading: Ember.computed(function computeLinkViewLoading() {
+ if (!get(this, 'routeArgs')) { return get(this, 'loadingClass'); }
+ }).property('routeArgs'),
+
+ /**
+ Returns the application's main router from the container.
+
+ @private
+ @property router
+ **/
+ router: Ember.computed(function() {
+ return get(this, 'controller').container.lookup('router:main');
+ }),
+
+ /**
+ Event handler that invokes the link, activating the associated route.
+
+ @private
+ @method _invoke
+ @param {Event} event
+ */
+ _invoke: function(event) {
+ if (!isSimpleClick(event)) { return true; }
+
+ if (this.preventDefault !== false) { event.preventDefault(); }
+ if (this.bubbles === false) { event.stopPropagation(); }
+
+ if (get(this, '_isDisabled')) { return false; }
+
+ if (get(this, 'loading')) {
+ Ember.Logger.warn("This link-to is in an inactive loading state because at least one of its parameters presently has a null/undefined value, or the provided route name is invalid.");
+ return false;
+ }
+
+ var router = get(this, 'router'),
+ routeArgs = get(this, 'routeArgs');
+
+ var transition;
+ if (get(this, 'replace')) {
+ transition = router.replaceWith.apply(router, routeArgs);
+ } else {
+ transition = router.transitionTo.apply(router, routeArgs);
+ }
+
+ // Schedule eager URL update, but after we've given the transition
+ // a chance to synchronously redirect.
+
+ // We need to always generate the URL instead of using the href because
+ // the href will include any rootURL set, but the router expects a URL
+ // without it! Note that we don't use the first level router because it
+ // calls location.formatURL(), which also would add the rootURL!
+ var url = router.router.generate.apply(router.router, get(this, 'routeArgs'));
+ Ember.run.scheduleOnce('routerTransitions', this, this._eagerUpdateUrl, transition, url);
+
+ },
+
+ /**
+ @private
+ */
+ _eagerUpdateUrl: function(transition, href) {
+ if (!transition.isActive || !transition.urlMethod) {
+ // transition was aborted, already ran to completion,
+ // or it has a null url-updated method.
+ return;
+ }
+
+ if (href.indexOf('#') === 0) {
+ href = href.slice(1);
+ }
+
+ // Re-use the routerjs hooks set up by the Ember router.
+ var routerjs = get(this, 'router.router');
+ if (transition.urlMethod === 'update') {
+ routerjs.updateURL(href);
+ } else if (transition.urlMethod === 'replace') {
+ routerjs.replaceURL(href);
+ }
+
+ // Prevent later update url refire.
+ transition.method(null);
+ },
+
+ /**
+ Computed property that returns an array of the
+ resolved parameters passed to the `link-to` helper,
+ e.g.:
+
+ ```hbs
+ {{link-to a b '123' c}}
+ ```
+
+ will generate a `resolvedParams` of:
+
+ ```js
+ [aObject, bObject, '123', cObject]
+ ```
+
+ @private
+ @property
+ @return {Array}
+ */
+ resolvedParams: Ember.computed(function() {
+ var parameters = this.parameters,
+ options = parameters.options,
+ types = options.types,
+ data = options.data;
+
+ if (parameters.params.length === 0) {
+ var appController = this.container.lookup('controller:application');
+ return [get(appController, 'currentRouteName')];
+ } else {
+ return resolveParams(parameters.context, parameters.params, { types: types, data: data });
+ }
+ }).property('router.url'),
+
+ /**
+ Computed property that returns the current route name and
+ any dynamic segments.
+
+ @private
+ @property
+ @return {Array} An array with the route name and any dynamic segments
+ */
+ routeArgs: Ember.computed(function computeLinkViewRouteArgs() {
+ var resolvedParams = get(this, 'resolvedParams').slice(0),
+ router = get(this, 'router'),
+ namedRoute = resolvedParams[0];
+
+ if (!namedRoute) { return; }
+
+ Ember.assert(fmt("The attempt to link-to route '%@' failed. " +
+ "The router did not find '%@' in its possible routes: '%@'",
+ [namedRoute, namedRoute, Ember.keys(router.router.recognizer.names).join("', '")]),
+ router.hasRoute(namedRoute));
+
+ //normalize route name
+ var handlers = router.router.recognizer.handlersFor(namedRoute);
+ var normalizedPath = handlers[handlers.length - 1].handler;
+ if (namedRoute !== normalizedPath) {
+ this.set('currentWhen', namedRoute);
+ namedRoute = handlers[handlers.length - 1].handler;
+ resolvedParams[0] = namedRoute;
+ }
+
+ for (var i = 1, len = resolvedParams.length; i < len; ++i) {
+ var param = resolvedParams[i];
+ if (param === null || typeof param === 'undefined') {
+ // If contexts aren't present, consider the linkView unloaded.
+ return;
+ }
+ }
+
+
+ return resolvedParams;
+ }).property('resolvedParams', 'queryParams'),
+
+ queryParamsObject: null,
+ queryParams: Ember.computed(function computeLinkViewQueryParams() {
+
+ var queryParamsObject = get(this, 'queryParamsObject'),
+ suppliedParams = {};
+
+ if (queryParamsObject) {
+ Ember.merge(suppliedParams, queryParamsObject.values);
+ }
+
+ var resolvedParams = get(this, 'resolvedParams'),
+ router = get(this, 'router'),
+ routeName = resolvedParams[0],
+ paramsForRoute = router._queryParamNamesFor(routeName),
+ queryParams = paramsForRoute.queryParams,
+ translations = paramsForRoute.translations,
+ paramsForRecognizer = {};
+
+ // Normalize supplied params into their long-form name
+ // e.g. 'foo' -> 'controllername:foo'
+ translateQueryParams(suppliedParams, translations, routeName);
+
+ var helperParameters = this.parameters;
+ router._queryParamOverrides(paramsForRecognizer, queryParams, function(name, resultsName) {
+ if (!(name in suppliedParams)) { return; }
+
+ var parts = name.split(':');
+
+ var type = queryParamsObject.types[parts[1]];
+
+ var value;
+ if (type === 'ID') {
+ var normalizedPath = Ember.Handlebars.normalizePath(helperParameters.context, suppliedParams[name], helperParameters.options.data);
+ value = Ember.Handlebars.get(normalizedPath.root, normalizedPath.path, helperParameters.options);
+ } else {
+ value = suppliedParams[name];
+ }
+
+ delete suppliedParams[name];
+
+ paramsForRecognizer[resultsName] = value;
+ });
+
+ return paramsForRecognizer;
+ }).property('resolvedParams.[]'),
+
+ /**
+ Sets the element's `href` attribute to the url for
+ the `LinkView`'s targeted route.
+
+ If the `LinkView`'s `tagName` is changed to a value other
+ than `a`, this property will be ignored.
+
+ @property href
+ **/
+ href: Ember.computed(function computeLinkViewHref() {
+ if (get(this, 'tagName') !== 'a') { return; }
+
+ var router = get(this, 'router'),
+ routeArgs = get(this, 'routeArgs');
+
+ return routeArgs ? router.generate.apply(router, routeArgs) : get(this, 'loadingHref');
+ }).property('routeArgs'),
+
+ /**
+ The default href value to use while a link-to is loading.
+ Only applies when tagName is 'a'
+
+ @property loadingHref
+ @type String
+ @default #
+ */
+ loadingHref: '#'
+ });
+
+ LinkView.toString = function() { return "LinkView"; };
+
+ /**
+ The `{{link-to}}` helper renders a link to the supplied
+ `routeName` passing an optionally supplied model to the
+ route as its `model` context of the route. The block
+ for `{{link-to}}` becomes the innerHTML of the rendered
+ element:
+
+ ```handlebars
+ {{#link-to 'photoGallery'}}
+ Great Hamster Photos
+ {{/link-to}}
+ ```
+
+ ```html
+ <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
+ @for Ember.Handlebars.helpers
+ @param {String} routeName
+ @param {Object} [context]*
+ @param [options] {Object} Handlebars key/value pairs of options, you can override any property of Ember.LinkView
+ @return {String} HTML string
+ @see {Ember.LinkView}
+ */
+ Ember.Handlebars.registerHelper('link-to', function linkToHelper(name) {
+ var options = slice.call(arguments, -1)[0],
+ params = slice.call(arguments, 0, -1),
+ hash = options.hash;
+
+ if (params[params.length - 1] instanceof QueryParams) {
+ hash.queryParamsObject = params.pop();
+ }
+
+ hash.disabledBinding = hash.disabledWhen;
+
+ if (!options.fn) {
+ var linkTitle = params.shift();
+ var linkType = options.types.shift();
+ var context = this;
+ if (linkType === 'ID') {
+ options.linkTextPath = linkTitle;
+ options.fn = function() {
+ return Ember.Handlebars.getEscaped(context, linkTitle, options);
+ };
+ } else {
+ options.fn = function() {
+ return linkTitle;
+ };
+ }
+ }
+
+ hash.parameters = {
+ context: this,
+ options: options,
+ params: params
+ };
+
+ return Ember.Handlebars.helpers.view.call(this, LinkView, options);
+ });
+
+
+
+ /**
+ See [link-to](/api/classes/Ember.Handlebars.helpers.html#method_link-to)
+
+ @method linkTo
+ @for Ember.Handlebars.helpers
+ @deprecated
+ @param {String} routeName
+ @param {Object} [context]*
+ @return {String} HTML string
+ */
+ Ember.Handlebars.registerHelper('linkTo', function linkToHelper() {
+ Ember.warn("The 'linkTo' view helper is deprecated in favor of 'link-to'");
+ return Ember.Handlebars.helpers['link-to'].apply(this, arguments);
+ });
+});
+
+
+
+})();
+
+
+
+(function() {
+/**
+@module ember
+@submodule ember-routing
+*/
+
+var get = Ember.get, set = Ember.set;
+Ember.onLoad('Ember.Handlebars', function(Handlebars) {
+ /**
+ @module ember
+ @submodule ember-routing
+ */
+
+ Handlebars.OutletView = Ember.ContainerView.extend(Ember._Metamorph);
+
+ /**
+ The `outlet` helper is a placeholder that the router will fill in with
+ the appropriate template based on the current state of the application.
+
+ ``` handlebars
+ {{outlet}}
+ ```
+
+ By default, a template based on Ember's naming conventions will be rendered
+ into the `outlet` (e.g. `App.PostsRoute` will render the `posts` template).
+
+ You can render a different template by using the `render()` method in the
+ route's `renderTemplate` hook. The following will render the `favoritePost`
+ template into the `outlet`.
+
+ ``` javascript
+ App.PostsRoute = Ember.Route.extend({
+ renderTemplate: function() {
+ this.render('favoritePost');
+ }
+ });
+ ```
+
+ You can create custom named outlets for more control.
+
+ ``` handlebars
+ {{outlet 'favoritePost'}}
+ {{outlet 'posts'}}
+ ```
+
+ Then you can define what template is rendered into each outlet in your
+ route.
+
+
+ ``` javascript
+ App.PostsRoute = Ember.Route.extend({
+ renderTemplate: function() {
+ this.render('favoritePost', { outlet: 'favoritePost' });
+ this.render('posts', { outlet: 'posts' });
+ }
+ });
+ ```
+
+ You can specify the view that the outlet uses to contain and manage the
+ templates rendered into it.
+
+ ``` handlebars
+ {{outlet view='sectionContainer'}}
+ ```
+
+ ``` javascript
+ App.SectionContainer = Ember.ContainerView.extend({
+ tagName: 'section',
+ classNames: ['special']
+ });
+ ```
+
+ @method outlet
+ @for Ember.Handlebars.helpers
+ @param {String} property the property on the controller
+ that holds the view for this outlet
+ @return {String} HTML string
+ */
+ Handlebars.registerHelper('outlet', function outletHelper(property, options) {
+
+ var outletSource,
+ container,
+ viewName,
+ viewClass,
+ viewFullName;
+
+ if (property && property.data && property.data.isRenderData) {
+ options = property;
+ property = 'main';
+ }
+
+ container = options.data.view.container;
+
+ outletSource = options.data.view;
+ while (!outletSource.get('template.isTop')) {
+ outletSource = outletSource.get('_parentView');
+ }
+
+ // provide controller override
+ viewName = options.hash.view;
+
+ if (viewName) {
+ viewFullName = 'view:' + viewName;
+ Ember.assert("Using a quoteless view parameter with {{outlet}} is not supported. Please update to quoted usage '{{outlet \"" + viewName + "\"}}.", options.hashTypes.view !== 'ID');
+ Ember.assert("The view name you supplied '" + viewName + "' did not resolve to a view.", container.has(viewFullName));
+ }
+
+ viewClass = viewName ? container.lookupFactory(viewFullName) : options.hash.viewClass || Handlebars.OutletView;
+
+ options.data.view.set('outletSource', outletSource);
+ options.hash.currentViewBinding = '_view.outletSource._outlets.' + property;
+
+ return Handlebars.helpers.view.call(this, viewClass, options);
+ });
+});
+
+})();
+
+
+
+(function() {
+/**
+@module ember
+@submodule ember-routing
+*/
+
+var get = Ember.get, set = Ember.set;
+Ember.onLoad('Ember.Handlebars', function(Handlebars) {
+
+ /**
+ Calling ``{{render}}`` from within a template will insert another
+ template that matches the provided name. The inserted template will
+ access its properties on its own controller (rather than the controller
+ of the parent template).
+
+ If a view class with the same name exists, the view class also will be used.
+
+ Note: A given controller may only be used *once* in your app in this manner.
+ A singleton instance of the controller will be created for you.
+
+ Example:
+
+ ```javascript
+ App.NavigationController = Ember.Controller.extend({
+ who: "world"
+ });
+ ```
+
+ ```handlebars
+ <!-- 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
+ @for Ember.Handlebars.helpers
+ @param {String} name
+ @param {Object?} contextString
+ @param {Hash} options
+ @return {String} HTML string
+ */
+ Ember.Handlebars.registerHelper('render', function renderHelper(name, contextString, options) {
+ var length = arguments.length;
+
+ var contextProvided = length === 3,
+ container, router, controller, view, context, lookupOptions;
+
+ container = (options || contextString).data.keywords.controller.container;
+ router = container.lookup('router:main');
+
+ if (length === 2) {
+ // use the singleton controller
+ options = contextString;
+ contextString = undefined;
+ Ember.assert("You can only use the {{render}} helper once without a model object as its second argument, as in {{render \"post\" post}}.", !router || !router._lookupActiveView(name));
+ } else if (length === 3) {
+ // create a new controller
+ context = Ember.Handlebars.get(options.contexts[1], contextString, options);
+ } else {
+ throw Ember.Error("You must pass a templateName to render");
+ }
+
+ Ember.deprecate("Using a quoteless parameter with {{render}} is deprecated. Please update to quoted usage '{{render \"" + name + "\"}}.", options.types[0] !== 'ID');
+
+ // # legacy namespace
+ name = name.replace(/\//g, '.');
+ // \ legacy slash as namespace support
+
+
+ view = container.lookup('view:' + name) || container.lookup('view:default');
+
+ // provide controller override
+ var controllerName = options.hash.controller || name;
+ var controllerFullName = 'controller:' + controllerName;
+
+ if (options.hash.controller) {
+ Ember.assert("The controller name you supplied '" + controllerName + "' did not resolve to a controller.", container.has(controllerFullName));
+ }
+
+ var parentController = options.data.keywords.controller;
+
+ // choose name
+ if (length > 2) {
+ var factory = container.lookupFactory(controllerFullName) ||
+ Ember.generateControllerFactory(container, controllerName, context);
+
+ controller = factory.create({
+ model: context,
+ parentController: parentController,
+ target: parentController
+ });
+
+ } else {
+ controller = container.lookup(controllerFullName) ||
+ Ember.generateController(container, controllerName);
+
+ controller.setProperties({
+ target: parentController,
+ parentController: parentController
+ });
+ }
+
+ var root = options.contexts[1];
+
+ if (root) {
+ view.registerObserver(root, contextString, function() {
+ controller.set('model', Ember.Handlebars.get(root, contextString, options));
+ });
+ }
+
+ options.hash.viewName = Ember.String.camelize(name);
+
+ var templateName = 'template:' + name;
+ Ember.assert("You used `{{render '" + name + "'}}`, but '" + name + "' can not be found as either a template or a view.", container.has("view:" + name) || container.has(templateName) || options.fn);
+ options.hash.template = container.lookup(templateName);
+
+ options.hash.controller = controller;
+
+ if (router && !context) {
+ router._connectActiveView(name, view);
+ }
+
+ Ember.Handlebars.helpers.view.call(this, view, options);
+ });
+});
+
+})();
+
+
+
+(function() {
+/**
+@module ember
+@submodule ember-routing
+*/
+Ember.onLoad('Ember.Handlebars', function(Handlebars) {
+
+ var resolveParams = Ember.Router.resolveParams,
+ isSimpleClick = Ember.ViewUtils.isSimpleClick;
+
+ var EmberHandlebars = Ember.Handlebars,
+ handlebarsGet = EmberHandlebars.get,
+ SafeString = EmberHandlebars.SafeString,
+ forEach = Ember.ArrayPolyfills.forEach,
+ get = Ember.get,
+ a_slice = Array.prototype.slice;
+
+ function args(options, actionName) {
+ var ret = [];
+ if (actionName) { ret.push(actionName); }
+
+ var types = options.options.types.slice(1),
+ data = options.options.data;
+
+ return ret.concat(resolveParams(options.context, options.params, { types: types, data: data }));
+ }
+
+ var ActionHelper = EmberHandlebars.ActionHelper = {
+ registeredActions: {}
+ };
+
+ var keys = ["alt", "shift", "meta", "ctrl"];
+
+ var POINTER_EVENT_TYPE_REGEX = /^click|mouse|touch/;
+
+ var isAllowedEvent = function(event, allowedKeys) {
+ if (typeof allowedKeys === "undefined") {
+ if (POINTER_EVENT_TYPE_REGEX.test(event.type)) {
+ return isSimpleClick(event);
+ } else {
+ allowedKeys = '';
+ }
+ }
+
+ if (allowedKeys.indexOf("any") >= 0) {
+ return true;
+ }
+
+ var allowed = true;
+
+ forEach.call(keys, function(key) {
+ if (event[key + "Key"] && allowedKeys.indexOf(key) === -1) {
+ allowed = false;
+ }
+ });
+
+ return allowed;
+ };
+
+ ActionHelper.registerAction = function(actionNameOrPath, options, allowedKeys) {
+ var actionId = ++Ember.uuid;
+
+ ActionHelper.registeredActions[actionId] = {
+ eventName: options.eventName,
+ handler: function handleRegisteredAction(event) {
+ if (!isAllowedEvent(event, allowedKeys)) { return true; }
+
+ if (options.preventDefault !== false) {
+ event.preventDefault();
+ }
+
+ if (options.bubbles === false) {
+ event.stopPropagation();
+ }
+
+ var target = options.target,
+ actionName;
+
+ if (target.target) {
+ target = handlebarsGet(target.root, target.target, target.options);
+ } else {
+ target = target.root;
+ }
+
+
+ if (options.boundProperty) {
+ actionName = handlebarsGet(target, actionNameOrPath, options.options);
+
+ if(typeof actionName === 'undefined' || typeof actionName === 'function') {
+ Ember.assert("You specified a quoteless path to the {{action}} helper '" + actionNameOrPath + "' which did not resolve to an actionName. Perhaps you meant to use a quoted actionName? (e.g. {{action '" + actionNameOrPath + "'}}).", true);
+ actionName = actionNameOrPath;
+ }
+ }
+
+ if (!actionName) {
+ actionName = actionNameOrPath;
+ }
+
+ Ember.run(function runRegisteredAction() {
+ if (target.send) {
+ target.send.apply(target, args(options.parameters, actionName));
+ } else {
+ Ember.assert("The action '" + actionName + "' did not exist on " + target, typeof target[actionName] === 'function');
+ target[actionName].apply(target, args(options.parameters));
+ }
+ });
+ }
+ };
+
+ options.view.on('willClearRender', function() {
+ delete ActionHelper.registeredActions[actionId];
+ });
+
+ return actionId;
+ };
+
+ /**
+ The `{{action}}` helper registers an HTML element within a template for DOM
+ event handling and forwards that interaction to the templates's controller
+ or supplied `target` option (see 'Specifying a Target').
+
+ If the controller does not implement the event, the event is sent
+ to the current route, and it bubbles up the route hierarchy from there.
+
+ User interaction with that element will invoke the supplied action name on
+ the appropriate target. Specifying a non-quoted action name will result in
+ a bound property lookup at the time the event will be triggered.
+
+ Given the following application Handlebars template on the page
+
+ ```handlebars
+ <div {{action 'anActionName'}}>
+ click me
+ </div>
+ ```
+
+ And application code
+
+ ```javascript
+ App.ApplicationController = Ember.Controller.extend({
+ actions: {
+ anActionName: function() {
+ }
+ }
+ });
+ ```
+
+ Will result in the following rendered HTML
+
+ ```html
+ <div class="ember-view">
+ <div data-ember-action="1">
+ click me
+ </div>
+ </div>
+ ```
+
+ Clicking "click me" will trigger the `anActionName` action of the
+ `App.ApplicationController`. In this case, no additional parameters will be passed.
+
+ If you provide additional parameters to the helper:
+
+ ```handlebars
+ <button {{action 'edit' post}}>Edit</button>
+ ```
+
+ Those parameters will be passed along as arguments to the JavaScript
+ function implementing the action.
+
+ ### Event Propagation
+
+ Events triggered through the action helper will automatically have
+ `.preventDefault()` called on them. You do not need to do so in your event
+ handlers. If you need to allow event propagation (to handle file inputs for
+ example) you can supply the `preventDefault=false` option to the `{{action}}` helper:
+
+ ```handlebars
+ <div {{action "sayHello" preventDefault=false}}>
+ <input type="file" />
+ <input type="checkbox" />
+ </div>
+ ```
+
+ To disable bubbling, pass `bubbles=false` to the helper:
+
+ ```handlebars
+ <button {{action 'edit' post bubbles=false}}>Edit</button>
+ ```
+
+ If you need the default handler to trigger you should either register your
+ own event handler, or use event methods on your view class. See [Ember.View](/api/classes/Ember.View.html)
+ 'Responding to Browser Events' for more information.
+
+ ### Specifying DOM event type
+
+ By default the `{{action}}` helper registers for DOM `click` events. You can
+ supply an `on` option to the helper to specify a different DOM event name:
+
+ ```handlebars
+ <div {{action "anActionName" on="doubleClick"}}>
+ click me
+ </div>
+ ```
+
+ See `Ember.View` 'Responding to Browser Events' for a list of
+ acceptable DOM event names.
+
+ NOTE: Because `{{action}}` depends on Ember's event dispatch system it will
+ only function if an `Ember.EventDispatcher` instance is available. An
+ `Ember.EventDispatcher` instance will be created when a new `Ember.Application`
+ is created. Having an instance of `Ember.Application` will satisfy this
+ requirement.
+
+ ### Specifying whitelisted modifier keys
+
+ By default the `{{action}}` helper will ignore click event with pressed modifier
+ keys. You can supply an `allowedKeys` option to specify which keys should not be ignored.
+
+ ```handlebars
+ <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>
+ ```
+
+ ### Specifying a Target
+
+ There are several possible target objects for `{{action}}` helpers:
+
+ In a typical Ember application, where views are managed through use of the
+ `{{outlet}}` helper, actions will bubble to the current controller, then
+ to the current route, and then up the route hierarchy.
+
+ Alternatively, a `target` option can be provided to the helper to change
+ which object will receive the method call. This option must be a path
+ to an object, accessible in the current context:
+
+ ```handlebars
+ {{! the application template }}
+ <div {{action "anActionName" target=view}}>
+ click me
+ </div>
+ ```
+
+ ```javascript
+ App.ApplicationView = Ember.View.extend({
+ actions: {
+ anActionName: function(){}
+ }
+ });
+
+ ```
+
+ ### Additional Parameters
+
+ You may specify additional parameters to the `{{action}}` helper. These
+ parameters are passed along as the arguments to the JavaScript function
+ implementing the action.
+
+ ```handlebars
+ {{#each person in people}}
+ <div {{action "edit" person}}>
+ click me
+ </div>
+ {{/each}}
+ ```
+
+ Clicking "click me" will trigger the `edit` method on the current controller
+ with the value of `person` as a parameter.
+
+ @method action
+ @for Ember.Handlebars.helpers
+ @param {String} actionName
+ @param {Object} [context]*
+ @param {Hash} options
+ */
+ EmberHandlebars.registerHelper('action', function actionHelper(actionName) {
+ var options = arguments[arguments.length - 1],
+ contexts = a_slice.call(arguments, 1, -1);
+
+ var hash = options.hash,
+ controller = options.data.keywords.controller;
+
+ // create a hash to pass along to registerAction
+ var action = {
+ eventName: hash.on || "click",
+ parameters: {
+ context: this,
+ options: options,
+ params: contexts
+ },
+ view: options.data.view,
+ bubbles: hash.bubbles,
+ preventDefault: hash.preventDefault,
+ target: { options: options },
+ boundProperty: options.types[0] === "ID"
+ };
+
+ if (hash.target) {
+ action.target.root = this;
+ action.target.target = hash.target;
+ } else if (controller) {
+ action.target.root = controller;
+ }
+
+ var actionId = ActionHelper.registerAction(actionName, action, hash.allowedKeys);
+ return new SafeString('data-ember-action="' + actionId + '"');
+ });
+});
+
+})();
+
+
+
+(function() {
+
+})();
+
+
+
+(function() {
+/**
+@module ember
+@submodule ember-routing
+*/
+
+var get = Ember.get, set = Ember.set,
+ map = Ember.EnumerableUtils.map;
+
+var queuedQueryParamChanges = {};
+
+Ember.ControllerMixin.reopen({
+ /**
+ Transition the application into another route. The route may
+ be either a single route or route path:
+
+ ```javascript
+ aController.transitionToRoute('blogPosts');
+ aController.transitionToRoute('blogPosts.recentEntries');
+ ```
+
+ Optionally supply a model for the route in question. The model
+ will be serialized into the URL using the `serialize` hook of
+ the route:
+
+ ```javascript
+ aController.transitionToRoute('blogPost', aPost);
+ ```
+
+ If a literal is passed (such as a number or a string), it will
+ be treated as an identifier instead. In this case, the `model`
+ hook of the route will be triggered:
+
+ ```javascript
+ aController.transitionToRoute('blogPost', 1);
+ ```
+
+ Multiple models will be applied last to first recursively up the
+ resource tree.
+
+ ```javascript
+ App.Router.map(function() {
+ this.resource('blogPost', {path:':blogPostId'}, function(){
+ this.resource('blogComment', {path: ':blogCommentId'});
+ });
+ });
+
+ aController.transitionToRoute('blogComment', aPost, aComment);
+ aController.transitionToRoute('blogComment', 1, 13);
+ ```
+
+ It is also possible to pass a URL (a string that starts with a
+ `/`). This is intended for testing and debugging purposes and
+ should rarely be used in production code.
+
+ ```javascript
+ aController.transitionToRoute('/');
+ aController.transitionToRoute('/blog/post/1/comment/13');
+ ```
+
+ See also [replaceRoute](/api/classes/Ember.ControllerMixin.html#method_replaceRoute).
+
+ @param {String} name the name of the route or a URL
+ @param {...Object} models the model(s) or identifier(s) to be used
+ while transitioning to the route.
+ @for Ember.ControllerMixin
+ @method transitionToRoute
+ */
+ transitionToRoute: function() {
+ // target may be either another controller or a router
+ var target = get(this, 'target'),
+ method = target.transitionToRoute || target.transitionTo;
+ return method.apply(target, arguments);
+ },
+
+ /**
+ @deprecated
+ @for Ember.ControllerMixin
+ @method transitionTo
+ */
+ transitionTo: function() {
+ Ember.deprecate("transitionTo is deprecated. Please use transitionToRoute.");
+ return this.transitionToRoute.apply(this, arguments);
+ },
+
+ /**
+ Transition into another route while replacing the current URL, if possible.
+ This will replace the current history entry instead of adding a new one.
+ Beside that, it is identical to `transitionToRoute` in all other respects.
+
+ ```javascript
+ aController.replaceRoute('blogPosts');
+ aController.replaceRoute('blogPosts.recentEntries');
+ ```
+
+ Optionally supply a model for the route in question. The model
+ will be serialized into the URL using the `serialize` hook of
+ the route:
+
+ ```javascript
+ aController.replaceRoute('blogPost', aPost);
+ ```
+
+ If a literal is passed (such as a number or a string), it will
+ be treated as an identifier instead. In this case, the `model`
+ hook of the route will be triggered:
+
+ ```javascript
+ aController.replaceRoute('blogPost', 1);
+ ```
+
+ Multiple models will be applied last to first recursively up the
+ resource tree.
+
+ ```javascript
+ App.Router.map(function() {
+ this.resource('blogPost', {path:':blogPostId'}, function(){
+ this.resource('blogComment', {path: ':blogCommentId'});
+ });
+ });
+
+ aController.replaceRoute('blogComment', aPost, aComment);
+ aController.replaceRoute('blogComment', 1, 13);
+ ```
+
+ It is also possible to pass a URL (a string that starts with a
+ `/`). This is intended for testing and debugging purposes and
+ should rarely be used in production code.
+
+ ```javascript
+ aController.replaceRoute('/');
+ aController.replaceRoute('/blog/post/1/comment/13');
+ ```
+
+ @param {String} name the name of the route or a URL
+ @param {...Object} models the model(s) or identifier(s) to be used
+ while transitioning to the route.
+ @for Ember.ControllerMixin
+ @method replaceRoute
+ */
+ replaceRoute: function() {
+ // target may be either another controller or a router
+ var target = get(this, 'target'),
+ method = target.replaceRoute || target.replaceWith;
+ return method.apply(target, arguments);
+ },
+
+ /**
+ @deprecated
+ @for Ember.ControllerMixin
+ @method replaceWith
+ */
+ replaceWith: function() {
+ Ember.deprecate("replaceWith is deprecated. Please use replaceRoute.");
+ return this.replaceRoute.apply(this, arguments);
+ }
+});
+
+
+})();
+
+
+
+(function() {
+/**
+@module ember
+@submodule ember-routing
+*/
+
+var get = Ember.get, set = Ember.set;
+
+Ember.View.reopen({
+
+ /**
+ Sets the private `_outlets` object on the view.
+
+ @method init
+ */
+ init: function() {
+ set(this, '_outlets', {});
+ this._super();
+ },
+
+ /**
+ Manually fill any of a view's `{{outlet}}` areas with the
+ supplied view.
+
+ Example
+
+ ```javascript
+ var MyView = Ember.View.extend({
+ template: Ember.Handlebars.compile('Child view: {{outlet "main"}} ')
+ });
+ var myView = MyView.create();
+ myView.appendTo('body');
+ // The html for myView now looks like:
+ // <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
+ */
+ connectOutlet: function(outletName, view) {
+ if (this._pendingDisconnections) {
+ delete this._pendingDisconnections[outletName];
+ }
+
+ if (this._hasEquivalentView(outletName, view)) {
+ view.destroy();
+ return;
+ }
+
+ var outlets = get(this, '_outlets'),
+ container = get(this, 'container'),
+ router = container && container.lookup('router:main'),
+ renderedName = get(view, 'renderedName');
+
+ set(outlets, outletName, view);
+
+ if (router && renderedName) {
+ router._connectActiveView(renderedName, view);
+ }
+ },
+
+ /**
+ Determines if the view has already been created by checking if
+ the view has the same constructor, template, and context as the
+ view in the `_outlets` object.
+
+ @private
+ @method _hasEquivalentView
+ @param {String} outletName The name of the outlet we are checking
+ @param {Object} view An Ember.View
+ @return {Boolean}
+ */
+ _hasEquivalentView: function(outletName, view) {
+ var existingView = get(this, '_outlets.'+outletName);
+ return existingView &&
+ existingView.constructor === view.constructor &&
+ existingView.get('template') === view.get('template') &&
+ existingView.get('context') === view.get('context');
+ },
+
+ /**
+ Removes an outlet from the view.
+
+ Example
+
+ ```javascript
+ var MyView = Ember.View.extend({
+ template: Ember.Handlebars.compile('Child view: {{outlet "main"}} ')
+ });
+ var myView = MyView.create();
+ myView.appendTo('body');
+ // myView's html:
+ // <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
+ */
+ disconnectOutlet: function(outletName) {
+ if (!this._pendingDisconnections) {
+ this._pendingDisconnections = {};
+ }
+ this._pendingDisconnections[outletName] = true;
+ Ember.run.once(this, '_finishDisconnections');
+ },
+
+ /**
+ Gets an outlet that is pending disconnection and then
+ nullifys the object on the `_outlet` object.
+
+ @private
+ @method _finishDisconnections
+ */
+ _finishDisconnections: function() {
+ if (this.isDestroyed) return; // _outlets will be gone anyway
+ var outlets = get(this, '_outlets');
+ var pendingDisconnections = this._pendingDisconnections;
+ this._pendingDisconnections = null;
+
+ for (var outletName in pendingDisconnections) {
+ set(outlets, outletName, null);
+ }
+ }
+});
+
+})();
+
+
+
+(function() {
+/**
+@module ember
+@submodule ember-views
+*/
+
+// Add a new named queue after the 'actions' queue (where RSVP promises
+// resolve), which is used in router transitions to prevent unnecessary
+// loading state entry if all context promises resolve on the
+// 'actions' queue first.
+
+var queues = Ember.run.queues,
+ indexOf = Ember.ArrayPolyfills.indexOf;
+queues.splice(indexOf.call(queues, 'actions') + 1, 0, 'routerTransitions');
+
+})();
+
+
+
+(function() {
+/**
+@module ember
+@submodule ember-routing
+*/
+
+var get = Ember.get, set = Ember.set;
+
+/**
+ Ember.Location returns an instance of the correct implementation of
+ the `location` API.
+
+ ## Implementations
+
+ You can pass an implementation name (`hash`, `history`, `none`) to force a
+ particular implementation to be used in your application.
+
+ ### HashLocation
+
+ Using `HashLocation` results in URLs with a `#` (hash sign) separating the
+ server side URL portion of the URL from the portion that is used by Ember.
+ This relies upon the `hashchange` event existing in the browser.
+
+ Example:
+
+ ```javascript
+ App.Router.map(function() {
+ this.resource('posts', function() {
+ this.route('new');
+ });
+ });
+
+ App.Router.reopen({
+ location: 'hash'
+ });
+ ```
+
+ This will result in a posts.new url of `/#/posts/new`.
+
+ ### HistoryLocation
+
+ Using `HistoryLocation` results in URLs that are indistinguishable from a
+ standard URL. This relies upon the browser's `history` API.
+
+ Example:
+
+ ```javascript
+ App.Router.map(function() {
+ this.resource('posts', function() {
+ this.route('new');
+ });
+ });
+
+ App.Router.reopen({
+ location: 'history'
+ });
+ ```
+
+ This will result in a posts.new url of `/posts/new`.
+
+ Keep in mind that your server must serve the Ember app at all the routes you
+ define.
+
+ ### AutoLocation
+
+ Using `AutoLocation`, the router will use the best Location class supported by
+ the browser it is running in.
+
+ Browsers that support the `history` API will use `HistoryLocation`, those that
+ do not, but still support the `hashchange` event will use `HashLocation`, and
+ in the rare case neither is supported will use `NoneLocation`.
+
+ Example:
+
+ ```javascript
+ App.Router.map(function() {
+ this.resource('posts', function() {
+ this.route('new');
+ });
+ });
+
+ App.Router.reopen({
+ location: 'auto'
+ });
+ ```
+
+ This will result in a posts.new url of `/posts/new` for modern browsers that
+ support the `history` api or `/#/posts/new` for older ones, like Internet
+ Explorer 9 and below.
+
+ When a user visits a link to your application, they will be automatically
+ upgraded or downgraded to the appropriate `Location` class, with the URL
+ transformed accordingly, if needed.
+
+ Keep in mind that since some of your users will use `HistoryLocation`, your
+ server must serve the Ember app at all the routes you define.
+
+ ### NoneLocation
+
+ Using `NoneLocation` causes Ember to not store the applications URL state
+ in the actual URL. This is generally used for testing purposes, and is one
+ of the changes made when calling `App.setupForTesting()`.
+
+ ## Location API
+
+ Each location implementation must provide the following methods:
+
+ * implementation: returns the string name used to reference the implementation.
+ * getURL: returns the current URL.
+ * setURL(path): sets the current URL.
+ * replaceURL(path): replace the current URL (optional).
+ * onUpdateURL(callback): triggers the callback when the URL changes.
+ * formatURL(url): formats `url` to be placed into `href` attribute.
+
+ Calling setURL or replaceURL will not trigger onUpdateURL callbacks.
+
+ @class Location
+ @namespace Ember
+ @static
+*/
+Ember.Location = {
+ /**
+ This is deprecated in favor of using the container to lookup the location
+ implementation as desired.
+
+ For example:
+
+ ```javascript
+ // Given a location registered as follows:
+ container.register('location:history-test', HistoryTestLocation);
+
+ // You could create a new instance via:
+ container.lookup('location:history-test');
+ ```
+
+ @method create
+ @param {Object} options
+ @return {Object} an instance of an implementation of the `location` API
+ @deprecated Use the container to lookup the location implementation that you
+ need.
+ */
+ create: function(options) {
+ var implementation = options && options.implementation;
+ Ember.assert("Ember.Location.create: you must specify a 'implementation' option", !!implementation);
+
+ var implementationClass = this.implementations[implementation];
+ Ember.assert("Ember.Location.create: " + implementation + " is not a valid implementation", !!implementationClass);
+
+ return implementationClass.create.apply(implementationClass, arguments);
+ },
+
+ /**
+ This is deprecated in favor of using the container to register the
+ location implementation as desired.
+
+ Example:
+
+ ```javascript
+ Application.initializer({
+ name: "history-test-location",
+
+ initialize: function(container, application) {
+ application.register('location:history-test', HistoryTestLocation);
+ }
+ });
+ ```
+
+ @method registerImplementation
+ @param {String} name
+ @param {Object} implementation of the `location` API
+ @deprecated Register your custom location implementation with the
+ container directly.
+ */
+ registerImplementation: function(name, implementation) {
+ Ember.deprecate('Using the Ember.Location.registerImplementation is no longer supported. Register your custom location implementation with the container instead.', false);
+
+ this.implementations[name] = implementation;
+ },
+
+ implementations: {},
+
+ /**
+ Returns the current `location.hash` by parsing location.href since browsers
+ inconsistently URL-decode `location.hash`.
+
+ https://bugzilla.mozilla.org/show_bug.cgi?id=483304
+
+ @private
+ @method getHash
+ */
+ getHash: function () {
+ var href = window.location.href,
+ hashIndex = href.indexOf('#');
+
+ if (hashIndex === -1) {
+ return '';
+ } else {
+ return href.substr(hashIndex);
+ }
+ }
+};
+
+})();
+
+
+
+(function() {
+/**
+@module ember
+@submodule ember-routing
+*/
+
+var get = Ember.get, set = Ember.set;
+
+/**
+ Ember.NoneLocation does not interact with the browser. It is useful for
+ testing, or when you need to manage state with your Router, but temporarily
+ don't want it to muck with the URL (for example when you embed your
+ application in a larger page).
+
+ @class NoneLocation
+ @namespace Ember
+ @extends Ember.Object
+*/
+Ember.NoneLocation = Ember.Object.extend({
+ implementation: 'none',
+ path: '',
+
+ /**
+ Returns the current path.
+
+ @private
+ @method getURL
+ @return {String} path
+ */
+ getURL: function() {
+ return get(this, 'path');
+ },
+
+ /**
+ Set the path and remembers what was set. Using this method
+ to change the path will not invoke the `updateURL` callback.
+
+ @private
+ @method setURL
+ @param path {String}
+ */
+ setURL: function(path) {
+ set(this, 'path', path);
+ },
+
+ /**
+ Register a callback to be invoked when the path changes. These
+ callbacks will execute when the user presses the back or forward
+ button, but not after `setURL` is invoked.
+
+ @private
+ @method onUpdateURL
+ @param callback {Function}
+ */
+ onUpdateURL: function(callback) {
+ this.updateCallback = callback;
+ },
+
+ /**
+ Sets the path and calls the `updateURL` callback.
+
+ @private
+ @method handleURL
+ @param callback {Function}
+ */
+ handleURL: function(url) {
+ set(this, 'path', url);
+ this.updateCallback(url);
+ },
+
+ /**
+ Given a URL, formats it to be placed into the page as part
+ of an element's `href` attribute.
+
+ This is used, for example, when using the {{action}} helper
+ to generate a URL based on an event.
+
+ @private
+ @method formatURL
+ @param url {String}
+ @return {String} url
+ */
+ formatURL: function(url) {
+ // The return value is not overly meaningful, but we do not want to throw
+ // errors when test code renders templates containing {{action href=true}}
+ // helpers.
+ return url;
+ }
+});
+
+})();
+
+
+
+(function() {
+/**
+@module ember
+@submodule ember-routing
+*/
+
+var get = Ember.get, set = Ember.set,
+ getHash = Ember.Location.getHash;
+
+/**
+ `Ember.HashLocation` implements the location API using the browser's
+ hash. At present, it relies on a `hashchange` event existing in the
+ browser.
+
+ @class HashLocation
+ @namespace Ember
+ @extends Ember.Object
+*/
+Ember.HashLocation = Ember.Object.extend({
+ implementation: 'hash',
+
+ init: function() {
+ set(this, 'location', get(this, 'location') || window.location);
+ },
+
+ /**
+ Returns the current `location.hash`, minus the '#' at the front.
+
+ @private
+ @method getURL
+ */
+ getURL: function() {
+ return getHash().substr(1);
+ },
+
+ /**
+ Set the `location.hash` and remembers what was set. This prevents
+ `onUpdateURL` callbacks from triggering when the hash was set by
+ `HashLocation`.
+
+ @private
+ @method setURL
+ @param path {String}
+ */
+ setURL: function(path) {
+ get(this, 'location').hash = path;
+ set(this, 'lastSetURL', path);
+ },
+
+ /**
+ Uses location.replace to update the url without a page reload
+ or history modification.
+
+ @private
+ @method replaceURL
+ @param path {String}
+ */
+ replaceURL: function(path) {
+ get(this, 'location').replace('#' + path);
+ set(this, 'lastSetURL', path);
+ },
+
+ /**
+ Register a callback to be invoked when the hash changes. These
+ callbacks will execute when the user presses the back or forward
+ button, but not after `setURL` is invoked.
+
+ @private
+ @method onUpdateURL
+ @param callback {Function}
+ */
+ onUpdateURL: function(callback) {
+ var self = this;
+ var guid = Ember.guidFor(this);
+
+ Ember.$(window).on('hashchange.ember-location-'+guid, function() {
+ Ember.run(function() {
+ var path = self.getURL();
+ if (get(self, 'lastSetURL') === path) { return; }
+
+ set(self, 'lastSetURL', null);
+
+ callback(path);
+ });
+ });
+ },
+
+ /**
+ Given a URL, formats it to be placed into the page as part
+ of an element's `href` attribute.
+
+ This is used, for example, when using the {{action}} helper
+ to generate a URL based on an event.
+
+ @private
+ @method formatURL
+ @param url {String}
+ */
+ formatURL: function(url) {
+ return '#'+url;
+ },
+
+ /**
+ Cleans up the HashLocation event listener.
+
+ @private
+ @method willDestroy
+ */
+ willDestroy: function() {
+ var guid = Ember.guidFor(this);
+
+ Ember.$(window).off('hashchange.ember-location-'+guid);
+ }
+});
+
+})();
+
+
+
+(function() {
+/**
+@module ember
+@submodule ember-routing
+*/
+
+var get = Ember.get, set = Ember.set;
+var popstateFired = false;
+var supportsHistoryState = window.history && 'state' in window.history;
+
+/**
+ Ember.HistoryLocation implements the location API using the browser's
+ history.pushState API.
+
+ @class HistoryLocation
+ @namespace Ember
+ @extends Ember.Object
+*/
+Ember.HistoryLocation = Ember.Object.extend({
+ implementation: 'history',
+
+ init: function() {
+ set(this, 'location', get(this, 'location') || window.location);
+ set(this, 'baseURL', Ember.$('base').attr('href') || '');
+ },
+
+ /**
+ Used to set state on first call to setURL
+
+ @private
+ @method initState
+ */
+ initState: function() {
+ set(this, 'history', get(this, 'history') || window.history);
+ this.replaceState(this.formatURL(this.getURL()));
+ },
+
+ /**
+ Will be pre-pended to path upon state change
+
+ @property rootURL
+ @default '/'
+ */
+ rootURL: '/',
+
+ /**
+ Returns the current `location.pathname` without `rootURL`.
+
+ @private
+ @method getURL
+ @return url {String}
+ */
+ getURL: function() {
+ var rootURL = get(this, 'rootURL'),
+ location = get(this, 'location'),
+ path = location.pathname,
+ baseURL = get(this, 'baseURL');
+
+ rootURL = rootURL.replace(/\/$/, '');
+ baseURL = baseURL.replace(/\/$/, '');
+ var url = path.replace(baseURL, '').replace(rootURL, '');
+
+
+ return url;
+ },
+
+ /**
+ Uses `history.pushState` to update the url without a page reload.
+
+ @private
+ @method setURL
+ @param path {String}
+ */
+ setURL: function(path) {
+ var state = this.getState();
+ path = this.formatURL(path);
+
+ if (state && state.path !== path) {
+ this.pushState(path);
+ }
+ },
+
+ /**
+ Uses `history.replaceState` to update the url without a page reload
+ or history modification.
+
+ @private
+ @method replaceURL
+ @param path {String}
+ */
+ replaceURL: function(path) {
+ var state = this.getState();
+ path = this.formatURL(path);
+
+ if (state && state.path !== path) {
+ this.replaceState(path);
+ }
+ },
+
+ /**
+ Get the current `history.state`
+ Polyfill checks for native browser support and falls back to retrieving
+ from a private _historyState variable
+
+ @private
+ @method getState
+ @return state {Object}
+ */
+ getState: function() {
+ return supportsHistoryState ? get(this, 'history').state : this._historyState;
+ },
+
+ /**
+ Pushes a new state.
+
+ @private
+ @method pushState
+ @param path {String}
+ */
+ pushState: function(path) {
+ var state = { path: path };
+
+ get(this, 'history').pushState(state, null, path);
+
+ // store state if browser doesn't support `history.state`
+ if (!supportsHistoryState) {
+ this._historyState = state;
+ }
+
+ // used for webkit workaround
+ this._previousURL = this.getURL();
+ },
+
+ /**
+ Replaces the current state.
+
+ @private
+ @method replaceState
+ @param path {String}
+ */
+ replaceState: function(path) {
+ var state = { path: path };
+
+ get(this, 'history').replaceState(state, null, path);
+
+ // store state if browser doesn't support `history.state`
+ if (!supportsHistoryState) {
+ this._historyState = state;
+ }
+
+ // used for webkit workaround
+ this._previousURL = this.getURL();
+ },
+
+ /**
+ Register a callback to be invoked whenever the browser
+ history changes, including using forward and back buttons.
+
+ @private
+ @method onUpdateURL
+ @param callback {Function}
+ */
+ onUpdateURL: function(callback) {
+ var guid = Ember.guidFor(this),
+ self = this;
+
+ Ember.$(window).on('popstate.ember-location-'+guid, function(e) {
+ // Ignore initial page load popstate event in Chrome
+ if (!popstateFired) {
+ popstateFired = true;
+ if (self.getURL() === self._previousURL) { return; }
+ }
+ callback(self.getURL());
+ });
+ },
+
+ /**
+ Used when using `{{action}}` helper. The url is always appended to the rootURL.
+
+ @private
+ @method formatURL
+ @param url {String}
+ @return formatted url {String}
+ */
+ formatURL: function(url) {
+ var rootURL = get(this, 'rootURL'),
+ baseURL = get(this, 'baseURL');
+
+ if (url !== '') {
+ rootURL = rootURL.replace(/\/$/, '');
+ baseURL = baseURL.replace(/\/$/, '');
+ } else if(baseURL.match(/^\//) && rootURL.match(/^\//)) {
+ baseURL = baseURL.replace(/\/$/, '');
+ }
+
+ return baseURL + rootURL + url;
+ },
+
+ /**
+ Cleans up the HistoryLocation event listener.
+
+ @private
+ @method willDestroy
+ */
+ willDestroy: function() {
+ var guid = Ember.guidFor(this);
+
+ Ember.$(window).off('popstate.ember-location-'+guid);
+ }
+});
+
+})();
+
+
+
+(function() {
+
+ /**
+ @module ember
+ @submodule ember-routing
+ */
+
+ var get = Ember.get, set = Ember.set;
+ var documentMode = document.documentMode,
+ history = window.history,
+ location = window.location,
+ getHash = Ember.Location.getHash;
+
+ /**
+ Ember.AutoLocation will select the best location option based off browser
+ support with the priority order: history, hash, none.
+
+ Clean pushState paths accessed by hashchange-only browsers will be redirected
+ to the hash-equivalent and vice versa so future transitions are consistent.
+
+ Keep in mind that since some of your users will use `HistoryLocation`, your
+ server must serve the Ember app at all the routes you define.
+
+ @class AutoLocation
+ @namespace Ember
+ @static
+ */
+ var AutoLocation = Ember.AutoLocation = {
+
+ /**
+ Will be pre-pended to path upon state change.
+
+ @property rootURL
+ @default '/'
+ */
+ rootURL: '/',
+
+ /**
+ @private
+
+ Exposed for testing
+
+ @property location
+ @default window.location
+ */
+ _location: location,
+
+ /**
+ @private
+
+ Returns location.origin or builds it if device doesn't support it.
+
+ @method getOrigin
+ */
+ getOrigin: function () {
+ var location = this._location,
+ origin = location.origin;
+
+ // Older browsers, especially IE, don't have origin
+ if (!origin) {
+ origin = location.protocol + '//' + location.hostname;
+
+ if (location.port) {
+ origin += ':' + location.port;
+ }
+ }
+
+ return origin;
+ },
+
+ /**
+ @private
+
+ We assume that if the history object has a pushState method, the host should
+ support HistoryLocation.
+
+ @property supportsHistory
+ */
+ supportsHistory: (function () {
+ // Boosted from Modernizr: https://github.com/Modernizr/Modernizr/blob/master/feature-detects/history.…
+ // The stock browser on Android 2.2 & 2.3 returns positive on history support
+ // Unfortunately support is really buggy and there is no clean way to detect
+ // these bugs, so we fall back to a user agent sniff :(
+ var userAgent = window.navigator.userAgent;
+
+ // We only want Android 2, stock browser, and not Chrome which identifies
+ // itself as 'Mobile Safari' as well
+ if (userAgent.indexOf('Android 2') !== -1 &&
+ userAgent.indexOf('Mobile Safari') !== -1 &&
+ userAgent.indexOf('Chrome') === -1) {
+ return false;
+ }
+
+ return !!(history && 'pushState' in history);
+ })(),
+
+ /**
+ @private
+
+ IE8 running in IE7 compatibility mode gives false positive, so we must also
+ check documentMode.
+
+ @property supportsHashChange
+ */
+ supportsHashChange: ('onhashchange' in window && (documentMode === undefined || documentMode > 7 )),
+
+ create: function (options) {
+ if (options && options.rootURL) {
+ this.rootURL = options.rootURL;
+ }
+
+ var implementationClass, historyPath, hashPath,
+ cancelRouterSetup = false,
+ currentPath = this.getFullPath();
+
+ if (this.supportsHistory) {
+ historyPath = this.getHistoryPath();
+
+ // Since we support history paths, let's be sure we're using them else
+ // switch the location over to it.
+ if (currentPath === historyPath) {
+ implementationClass = Ember.HistoryLocation;
+ } else {
+ cancelRouterSetup = true;
+ this.replacePath(historyPath);
+ }
+
+ } else if (this.supportsHashChange) {
+ hashPath = this.getHashPath();
+
+ // Be sure we're using a hashed path, otherwise let's switch over it to so
+ // we start off clean and consistent.
+ if (currentPath === hashPath) {
+ implementationClass = Ember.HashLocation;
+ } else {
+ cancelRouterSetup = true;
+ this.replacePath(hashPath);
+ }
+ }
+
+ // If none has been set
+ if (!implementationClass) {
+ implementationClass = Ember.NoneLocation;
+ }
+
+ var implementation = implementationClass.create.apply(implementationClass, arguments);
+
+ if (cancelRouterSetup) {
+ set(implementation, 'cancelRouterSetup', true);
+ }
+
+ return implementation;
+ },
+
+ /**
+ @private
+
+ Redirects the browser using location.replace, prepending the locatin.origin
+ to prevent phishing attempts
+
+ @method replacePath
+ */
+ replacePath: function (path) {
+ this._location.replace(this.getOrigin() + path);
+ },
+
+ /**
+ @private
+
+ Returns the current `location.pathname`, normalized for IE inconsistencies.
+
+ @method getPath
+ */
+ getPath: function () {
+ var pathname = location.pathname;
+ // Various versions of IE/Opera don't always return a leading slash
+ if (pathname.charAt(0) !== '/') {
+ pathname = '/' + pathname;
+ }
+
+ return pathname;
+ },
+
+ /**
+ @private
+
+ Returns the full pathname including the hash string.
+
+ @method getFullPath
+ */
+ getFullPath: function () {
+ return this.getPath() + getHash().substr(1);
+ },
+
+ /**
+ @private
+
+ Returns the current path as it should appear for HistoryLocation supported
+ browsers. This may very well differ from the real current path (e.g. if it
+ starts off as a hashed URL)
+
+ @method getHistoryPath
+ */
+ getHistoryPath: function () {
+ var path = this.getPath(),
+ hashPath = getHash().substr(1),
+ url = path + hashPath;
+
+ // Removes any stacked double stashes
+ return url.replace(/\/\//, '/');
+ },
+
+ /**
+ @private
+
+ Returns the current path as it should appear for HashLocation supported
+ browsers. This may very well differ from the real current path.
+
+ @method getHashPath
+ */
+ getHashPath: function () {
+ var historyPath = this.getHistoryPath(),
+ exp = new RegExp('(' + this.rootURL + ')(.+)'),
+ url = historyPath.replace(exp, '$1#/$2');
+
+ // Remove any stacked double stashes
+ url = url.replace(/\/\//, '/');
+
+ return url;
+ }
+
+ };
+
+})();
+
+
+
+(function() {
+
+})();
+
+
+
+(function() {
+/**
+Ember Routing
+
+@module ember
+@submodule ember-routing
+@requires ember-views
+*/
+
+})();
+
+(function() {
+function visit(vertex, fn, visited, path) {
+ var name = vertex.name,
+ vertices = vertex.incoming,
+ names = vertex.incomingNames,
+ len = names.length,
+ i;
+ if (!visited) {
+ visited = {};
+ }
+ if (!path) {
+ path = [];
+ }
+ if (visited.hasOwnProperty(name)) {
+ return;
+ }
+ path.push(name);
+ visited[name] = true;
+ for (i = 0; i < len; i++) {
+ visit(vertices[names[i]], fn, visited, path);
+ }
+ fn(vertex, path);
+ path.pop();
+}
+
+function DAG() {
+ this.names = [];
+ this.vertices = {};
+}
+
+DAG.prototype.add = function(name) {
+ if (!name) { return; }
+ if (this.vertices.hasOwnProperty(name)) {
+ return this.vertices[name];
+ }
+ var vertex = {
+ name: name, incoming: {}, incomingNames: [], hasOutgoing: false, value: null
+ };
+ this.vertices[name] = vertex;
+ this.names.push(name);
+ return vertex;
+};
+
+DAG.prototype.map = function(name, value) {
+ this.add(name).value = value;
+};
+
+DAG.prototype.addEdge = function(fromName, toName) {
+ if (!fromName || !toName || fromName === toName) {
+ return;
+ }
+ var from = this.add(fromName), to = this.add(toName);
+ if (to.incoming.hasOwnProperty(fromName)) {
+ return;
+ }
+ function checkCycle(vertex, path) {
+ if (vertex.name === toName) {
+ throw new Ember.Error("cycle detected: " + toName + " <- " + path.join(" <- "));
+ }
+ }
+ visit(from, checkCycle);
+ from.hasOutgoing = true;
+ to.incoming[fromName] = from;
+ to.incomingNames.push(fromName);
+};
+
+DAG.prototype.topsort = function(fn) {
+ var visited = {},
+ vertices = this.vertices,
+ names = this.names,
+ len = names.length,
+ i, vertex;
+ for (i = 0; i < len; i++) {
+ vertex = vertices[names[i]];
+ if (!vertex.hasOutgoing) {
+ visit(vertex, fn, visited);
+ }
+ }
+};
+
+DAG.prototype.addEdges = function(name, value, before, after) {
+ var i;
+ this.map(name, value);
+ if (before) {
+ if (typeof before === 'string') {
+ this.addEdge(name, before);
+ } else {
+ for (i = 0; i < before.length; i++) {
+ this.addEdge(name, before[i]);
+ }
+ }
+ }
+ if (after) {
+ if (typeof after === 'string') {
+ this.addEdge(after, name);
+ } else {
+ for (i = 0; i < after.length; i++) {
+ this.addEdge(after[i], name);
+ }
+ }
+ }
+};
+
+Ember.DAG = DAG;
+
+})();
+
+
+
+(function() {
+/**
+@module ember
+@submodule ember-application
+*/
+
+var get = Ember.get,
+ classify = Ember.String.classify,
+ capitalize = Ember.String.capitalize,
+ decamelize = Ember.String.decamelize;
+
+Ember.Resolver = Ember.Object.extend({
+ /**
+ This will be set to the Application instance when it is
+ created.
+
+ @property namespace
+ */
+ namespace: null,
+ normalize: function(fullName) {
+ throw new Error("Invalid call to `resolver.normalize(fullName)`. Please override the 'normalize' method in subclass of `Ember.AbstractResolver` to prevent falling through to this error.");
+ },
+ resolve: function(fullName) {
+ throw new Error("Invalid call to `resolver.resolve(parsedName)`. Please override the 'resolve' method in subclass of `Ember.AbstractResolver` to prevent falling through to this error.");
+ },
+ parseName: function(parsedName) {
+ throw new Error("Invalid call to `resolver.resolveByType(parsedName)`. Please override the 'resolveByType' method in subclass of `Ember.AbstractResolver` to prevent falling through to this error.");
+ },
+ lookupDescription: function(fullName) {
+ throw new Error("Invalid call to `resolver.lookupDescription(fullName)`. Please override the 'lookupDescription' method in subclass of `Ember.AbstractResolver` to prevent falling through to this error.");
+ },
+ makeToString: function(factory, fullName) {
+ throw new Error("Invalid call to `resolver.makeToString(factory, fullName)`. Please override the 'makeToString' method in subclass of `Ember.AbstractResolver` to prevent falling through to this error.");
+ },
+ resolveOther: function(parsedName) {
+ throw new Error("Invalid call to `resolver.resolveDefault(parsedName)`. Please override the 'resolveDefault' method in subclass of `Ember.AbstractResolver` to prevent falling through to this error.");
+ }
+});
+
+
+
+/**
+ The DefaultResolver defines the default lookup rules to resolve
+ container lookups before consulting the container for registered
+ items:
+
+ * templates are looked up on `Ember.TEMPLATES`
+ * other names are looked up on the application after converting
+ the name. For example, `controller:post` looks up
+ `App.PostController` by default.
+ * there are some nuances (see examples below)
+
+ ### How Resolving Works
+
+ The container calls this object's `resolve` method with the
+ `fullName` argument.
+
+ It first parses the fullName into an object using `parseName`.
+
+ Then it checks for the presence of a type-specific instance
+ method of the form `resolve[Type]` and calls it if it exists.
+ For example if it was resolving 'template:post', it would call
+ the `resolveTemplate` method.
+
+ Its last resort is to call the `resolveOther` method.
+
+ The methods of this object are designed to be easy to override
+ in a subclass. For example, you could enhance how a template
+ is resolved like so:
+
+ ```javascript
+ App = Ember.Application.create({
+ Resolver: Ember.DefaultResolver.extend({
+ resolveTemplate: function(parsedName) {
+ var resolvedTemplate = this._super(parsedName);
+ if (resolvedTemplate) { return resolvedTemplate; }
+ return Ember.TEMPLATES['not_found'];
+ }
+ })
+ });
+ ```
+
+ Some examples of how names are resolved:
+
+ ```
+ 'template:post' //=> Ember.TEMPLATES['post']
+ 'template:posts/byline' //=> Ember.TEMPLATES['posts/byline']
+ 'template:posts.byline' //=> Ember.TEMPLATES['posts/byline']
+ 'template:blogPost' //=> Ember.TEMPLATES['blogPost']
+ // OR
+ // Ember.TEMPLATES['blog_post']
+ 'controller:post' //=> App.PostController
+ 'controller:posts.index' //=> App.PostsIndexController
+ 'controller:blog/post' //=> Blog.PostController
+ 'controller:basic' //=> Ember.Controller
+ 'route:post' //=> App.PostRoute
+ 'route:posts.index' //=> App.PostsIndexRoute
+ 'route:blog/post' //=> Blog.PostRoute
+ 'route:basic' //=> Ember.Route
+ 'view:post' //=> App.PostView
+ 'view:posts.index' //=> App.PostsIndexView
+ 'view:blog/post' //=> Blog.PostView
+ 'view:basic' //=> Ember.View
+ 'foo:post' //=> App.PostFoo
+ 'model:post' //=> App.Post
+ ```
+
+ @class DefaultResolver
+ @namespace Ember
+ @extends Ember.Object
+*/
+Ember.DefaultResolver = Ember.Object.extend({
+ /**
+ This will be set to the Application instance when it is
+ created.
+
+ @property namespace
+ */
+ namespace: null,
+
+ normalize: function(fullName) {
+ var split = fullName.split(':', 2),
+ type = split[0],
+ name = split[1];
+
+ Ember.assert("Tried to normalize a container name without a colon (:) in it. You probably tried to lookup a name that did not contain a type, a colon, and a name. A proper lookup name would be `view:post`.", split.length === 2);
+
+ if (type !== 'template') {
+ var result = name;
+
+ if (result.indexOf('.') > -1) {
+ result = result.replace(/\.(.)/g, function(m) { return m.charAt(1).toUpperCase(); });
+ }
+
+ if (name.indexOf('_') > -1) {
+ result = result.replace(/_(.)/g, function(m) { return m.charAt(1).toUpperCase(); });
+ }
+
+ return type + ':' + result;
+ } else {
+ return fullName;
+ }
+ },
+
+
+ /**
+ This method is called via the container's resolver method.
+ It parses the provided `fullName` and then looks up and
+ returns the appropriate template or class.
+
+ @method resolve
+ @param {String} fullName the lookup string
+ @return {Object} the resolved factory
+ */
+ resolve: function(fullName) {
+ var parsedName = this.parseName(fullName),
+ resolveMethodName = parsedName.resolveMethodName;
+
+ if (!(parsedName.name && parsedName.type)) {
+ throw new TypeError("Invalid fullName: `" + fullName + "`, must be of the form `type:name` ");
+ }
+
+ if (this[resolveMethodName]) {
+ var resolved = this[resolveMethodName](parsedName);
+ if (resolved) { return resolved; }
+ }
+ return this.resolveOther(parsedName);
+ },
+ /**
+ Convert the string name of the form "type:name" to
+ a Javascript object with the parsed aspects of the name
+ broken out.
+
+ @protected
+ @param {String} fullName the lookup string
+ @method parseName
+ */
+ parseName: function(fullName) {
+ var nameParts = fullName.split(":"),
+ type = nameParts[0], fullNameWithoutType = nameParts[1],
+ name = fullNameWithoutType,
+ namespace = get(this, 'namespace'),
+ root = namespace;
+
+ if (type !== 'template' && name.indexOf('/') !== -1) {
+ var parts = name.split('/');
+ name = parts[parts.length - 1];
+ var namespaceName = capitalize(parts.slice(0, -1).join('.'));
+ root = Ember.Namespace.byName(namespaceName);
+
+ Ember.assert('You are looking for a ' + name + ' ' + type + ' in the ' + namespaceName + ' namespace, but the namespace could not be found', root);
+ }
+
+ return {
+ fullName: fullName,
+ type: type,
+ fullNameWithoutType: fullNameWithoutType,
+ name: name,
+ root: root,
+ resolveMethodName: "resolve" + classify(type)
+ };
+ },
+
+ /**
+ Returns a human-readable description for a fullName. Used by the
+ Application namespace in assertions to describe the
+ precise name of the class that Ember is looking for, rather than
+ container keys.
+
+ @protected
+ @param {String} fullName the lookup string
+ @method lookupDescription
+ */
+ lookupDescription: function(fullName) {
+ var parsedName = this.parseName(fullName);
+
+ if (parsedName.type === 'template') {
+ return "template at " + parsedName.fullNameWithoutType.replace(/\./g, '/');
+ }
+
+ var description = parsedName.root + "." + classify(parsedName.name);
+ if (parsedName.type !== 'model') { description += classify(parsedName.type); }
+
+ return description;
+ },
+
+ makeToString: function(factory, fullName) {
+ return factory.toString();
+ },
+ /**
+ Given a parseName object (output from `parseName`), apply
+ the conventions expected by `Ember.Router`
+
+ @protected
+ @param {Object} parsedName a parseName object with the parsed
+ fullName lookup string
+ @method useRouterNaming
+ */
+ useRouterNaming: function(parsedName) {
+ parsedName.name = parsedName.name.replace(/\./g, '_');
+ if (parsedName.name === 'basic') {
+ parsedName.name = '';
+ }
+ },
+ /**
+ Look up the template in Ember.TEMPLATES
+
+ @protected
+ @param {Object} parsedName a parseName object with the parsed
+ fullName lookup string
+ @method resolveTemplate
+ */
+ resolveTemplate: function(parsedName) {
+ var templateName = parsedName.fullNameWithoutType.replace(/\./g, '/');
+
+ if (Ember.TEMPLATES[templateName]) {
+ return Ember.TEMPLATES[templateName];
+ }
+
+ templateName = decamelize(templateName);
+ if (Ember.TEMPLATES[templateName]) {
+ return Ember.TEMPLATES[templateName];
+ }
+ },
+ /**
+ Lookup the view using `resolveOther`
+
+ @protected
+ @param {Object} parsedName a parseName object with the parsed
+ fullName lookup string
+ @method resolveView
+ */
+ resolveView: function(parsedName) {
+ this.useRouterNaming(parsedName);
+ return this.resolveOther(parsedName);
+ },
+ /**
+ Lookup the controller using `resolveOther`
+
+ @protected
+ @param {Object} parsedName a parseName object with the parsed
+ fullName lookup string
+ @method resolveController
+ */
+ resolveController: function(parsedName) {
+ this.useRouterNaming(parsedName);
+ return this.resolveOther(parsedName);
+ },
+ /**
+ Lookup the route using `resolveOther`
+
+ @protected
+ @param {Object} parsedName a parseName object with the parsed
+ fullName lookup string
+ @method resolveRoute
+ */
+ resolveRoute: function(parsedName) {
+ this.useRouterNaming(parsedName);
+ return this.resolveOther(parsedName);
+ },
+
+ /**
+ Lookup the model on the Application namespace
+
+ @protected
+ @param {Object} parsedName a parseName object with the parsed
+ fullName lookup string
+ @method resolveModel
+ */
+ resolveModel: function(parsedName) {
+ var className = classify(parsedName.name),
+ factory = get(parsedName.root, className);
+
+ if (factory) { return factory; }
+ },
+ /**
+ Look up the specified object (from parsedName) on the appropriate
+ namespace (usually on the Application)
+
+ @protected
+ @param {Object} parsedName a parseName object with the parsed
+ fullName lookup string
+ @method resolveHelper
+ */
+ resolveHelper: function(parsedName) {
+ return this.resolveOther(parsedName) || Ember.Handlebars.helpers[parsedName.fullNameWithoutType];
+ },
+ /**
+ Look up the specified object (from parsedName) on the appropriate
+ namespace (usually on the Application)
+
+ @protected
+ @param {Object} parsedName a parseName object with the parsed
+ fullName lookup string
+ @method resolveOther
+ */
+ resolveOther: function(parsedName) {
+ var className = classify(parsedName.name) + classify(parsedName.type),
+ factory = get(parsedName.root, className);
+ if (factory) { return factory; }
+ }
+});
+
+})();
+
+
+
+(function() {
+/**
+@module ember
+@submodule ember-application
+*/
+
+var get = Ember.get, set = Ember.set;
+
+function DeprecatedContainer(container) {
+ this._container = container;
+}
+
+DeprecatedContainer.deprecate = function(method) {
+ return function() {
+ var container = this._container;
+
+ Ember.deprecate('Using the defaultContainer is no longer supported. [defaultContainer#' + method + '] see: http://git.io/EKPpnA', false);
+ return container[method].apply(container, arguments);
+ };
+};
+
+DeprecatedContainer.prototype = {
+ _container: null,
+ lookup: DeprecatedContainer.deprecate('lookup'),
+ resolve: DeprecatedContainer.deprecate('resolve'),
+ register: DeprecatedContainer.deprecate('register')
+};
+
+/**
+ An instance of `Ember.Application` is the starting point for every Ember
+ application. It helps to instantiate, initialize and coordinate the many
+ objects that make up your app.
+
+ Each Ember app has one and only one `Ember.Application` object. In fact, the
+ very first thing you should do in your application is create the instance:
+
+ ```javascript
+ window.App = Ember.Application.create();
+ ```
+
+ Typically, the application object is the only global variable. All other
+ classes in your app should be properties on the `Ember.Application` instance,
+ which highlights its first role: a global namespace.
+
+ For example, if you define a view class, it might look like this:
+
+ ```javascript
+ App.MyView = Ember.View.extend();
+ ```
+
+ By default, calling `Ember.Application.create()` will automatically initialize
+ your application by calling the `Ember.Application.initialize()` method. If
+ you need to delay initialization, you can call your app's `deferReadiness()`
+ method. When you are ready for your app to be initialized, call its
+ `advanceReadiness()` method.
+
+ You can define a `ready` method on the `Ember.Application` instance, which
+ will be run by Ember when the application is initialized.
+
+ Because `Ember.Application` inherits from `Ember.Namespace`, any classes
+ you create will have useful string representations when calling `toString()`.
+ See the `Ember.Namespace` documentation for more information.
+
+ While you can think of your `Ember.Application` as a container that holds the
+ other classes in your application, there are several other responsibilities
+ going on under-the-hood that you may want to understand.
+
+ ### Event Delegation
+
+ Ember uses a technique called _event delegation_. This allows the framework
+ to set up a global, shared event listener instead of requiring each view to
+ do it manually. For example, instead of each view registering its own
+ `mousedown` listener on its associated element, Ember sets up a `mousedown`
+ listener on the `body`.
+
+ If a `mousedown` event occurs, Ember will look at the target of the event and
+ start walking up the DOM node tree, finding corresponding views and invoking
+ their `mouseDown` method as it goes.
+
+ `Ember.Application` has a number of default events that it listens for, as
+ well as a mapping from lowercase events to camel-cased view method names. For
+ example, the `keypress` event causes the `keyPress` method on the view to be
+ called, the `dblclick` event causes `doubleClick` to be called, and so on.
+
+ If there is a bubbling browser event that Ember does not listen for by
+ default, you can specify custom events and their corresponding view method
+ names by setting the application's `customEvents` property:
+
+ ```javascript
+ App = Ember.Application.create({
+ customEvents: {
+ // add support for the paste event
+ paste: "paste"
+ }
+ });
+ ```
+
+ By default, the application sets up these event listeners on the document
+ body. However, in cases where you are embedding an Ember application inside
+ an existing page, you may want it to set up the listeners on an element
+ inside the body.
+
+ For example, if only events inside a DOM element with the ID of `ember-app`
+ should be delegated, set your application's `rootElement` property:
+
+ ```javascript
+ window.App = Ember.Application.create({
+ rootElement: '#ember-app'
+ });
+ ```
+
+ The `rootElement` can be either a DOM element or a jQuery-compatible selector
+ string. Note that *views appended to the DOM outside the root element will
+ not receive events.* If you specify a custom root element, make sure you only
+ append views inside it!
+
+ To learn more about the advantages of event delegation and the Ember view
+ layer, and a list of the event listeners that are setup by default, visit the
+ [Ember View Layer guide](http://emberjs.com/guides/understanding-ember/the-view-layer/#toc_ev….
+
+ ### Initializers
+
+ Libraries on top of Ember can add initializers, like so:
+
+ ```javascript
+ Ember.Application.initializer({
+ name: 'api-adapter',
+
+ initialize: function(container, application) {
+ application.register('api-adapter:main', ApiAdapter);
+ }
+ });
+ ```
+
+ Initializers provide an opportunity to access the container, which
+ organizes the different components of an Ember application. Additionally
+ they provide a chance to access the instantiated application. Beyond
+ being used for libraries, initializers are also a great way to organize
+ dependency injection or setup in your own application.
+
+ ### Routing
+
+ In addition to creating your application's router, `Ember.Application` is
+ also responsible for telling the router when to start routing. Transitions
+ between routes can be logged with the `LOG_TRANSITIONS` flag, and more
+ detailed intra-transition logging can be logged with
+ the `LOG_TRANSITIONS_INTERNAL` flag:
+
+ ```javascript
+ window.App = Ember.Application.create({
+ LOG_TRANSITIONS: true, // basic logging of successful transitions
+ LOG_TRANSITIONS_INTERNAL: true // detailed logging of all routing steps
+ });
+ ```
+
+ By default, the router will begin trying to translate the current URL into
+ application state once the browser emits the `DOMContentReady` event. If you
+ need to defer routing, you can call the application's `deferReadiness()`
+ method. Once routing can begin, call the `advanceReadiness()` method.
+
+ If there is any setup required before routing begins, you can implement a
+ `ready()` method on your app that will be invoked immediately before routing
+ begins.
+ ```
+
+ @class Application
+ @namespace Ember
+ @extends Ember.Namespace
+*/
+
+var Application = Ember.Application = Ember.Namespace.extend(Ember.DeferredMixin, {
+
+ /**
+ The root DOM element of the Application. This can be specified as an
+ element or a
+ [jQuery-compatible selector string](http://api.jquery.com/category/selectors/).
+
+ This is the element that will be passed to the Application's,
+ `eventDispatcher`, which sets up the listeners for event delegation. Every
+ view in your application should be a child of the element you specify here.
+
+ @property rootElement
+ @type DOMElement
+ @default 'body'
+ */
+ rootElement: 'body',
+
+ /**
+ The `Ember.EventDispatcher` responsible for delegating events to this
+ application's views.
+
+ The event dispatcher is created by the application at initialization time
+ and sets up event listeners on the DOM element described by the
+ application's `rootElement` property.
+
+ See the documentation for `Ember.EventDispatcher` for more information.
+
+ @property eventDispatcher
+ @type Ember.EventDispatcher
+ @default null
+ */
+ eventDispatcher: null,
+
+ /**
+ The DOM events for which the event dispatcher should listen.
+
+ By default, the application's `Ember.EventDispatcher` listens
+ for a set of standard DOM events, such as `mousedown` and
+ `keyup`, and delegates them to your application's `Ember.View`
+ instances.
+
+ If you would like additional bubbling events to be delegated to your
+ views, set your `Ember.Application`'s `customEvents` property
+ to a hash containing the DOM event name as the key and the
+ corresponding view method name as the value. For example:
+
+ ```javascript
+ App = Ember.Application.create({
+ customEvents: {
+ // add support for the paste event
+ paste: "paste"
+ }
+ });
+ ```
+
+ @property customEvents
+ @type Object
+ @default null
+ */
+ customEvents: null,
+
+ // Start off the number of deferrals at 1. This will be
+ // decremented by the Application's own `initialize` method.
+ _readinessDeferrals: 1,
+
+ init: function() {
+ if (!this.$) { this.$ = Ember.$; }
+ this.__container__ = this.buildContainer();
+
+ this.Router = this.defaultRouter();
+
+ this._super();
+
+ this.scheduleInitialize();
+
+ Ember.libraries.registerCoreLibrary('Handlebars', Ember.Handlebars.VERSION);
+ Ember.libraries.registerCoreLibrary('jQuery', Ember.$().jquery);
+
+ if ( Ember.LOG_VERSION ) {
+ Ember.LOG_VERSION = false; // we only need to see this once per Application#init
+ var maxNameLength = Math.max.apply(this, Ember.A(Ember.libraries).mapBy("name.length"));
+
+ Ember.debug('-------------------------------');
+ Ember.libraries.each(function(name, version) {
+ var spaces = new Array(maxNameLength - name.length + 1).join(" ");
+ Ember.debug([name, spaces, ' : ', version].join(""));
+ });
+ Ember.debug('-------------------------------');
+ }
+ },
+
+ /**
+ Build the container for the current application.
+
+ Also register a default application view in case the application
+ itself does not.
+
+ @private
+ @method buildContainer
+ @return {Ember.Container} the configured container
+ */
+ buildContainer: function() {
+ var container = this.__container__ = Application.buildContainer(this);
+
+ return container;
+ },
+
+ /**
+ If the application has not opted out of routing and has not explicitly
+ defined a router, supply a default router for the application author
+ to configure.
+
+ This allows application developers to do:
+
+ ```javascript
+ var App = Ember.Application.create();
+
+ App.Router.map(function() {
+ this.resource('posts');
+ });
+ ```
+
+ @private
+ @method defaultRouter
+ @return {Ember.Router} the default router
+ */
+
+ defaultRouter: function() {
+ if (this.Router === false) { return; }
+ var container = this.__container__;
+
+ if (this.Router) {
+ container.unregister('router:main');
+ container.register('router:main', this.Router);
+ }
+
+ return container.lookupFactory('router:main');
+ },
+
+ /**
+ Automatically initialize the application once the DOM has
+ become ready.
+
+ The initialization itself is scheduled on the actions queue
+ which ensures that application loading finishes before
+ booting.
+
+ If you are asynchronously loading code, you should call
+ `deferReadiness()` to defer booting, and then call
+ `advanceReadiness()` once all of your code has finished
+ loading.
+
+ @private
+ @method scheduleInitialize
+ */
+ scheduleInitialize: function() {
+ var self = this;
+
+ if (!this.$ || this.$.isReady) {
+ Ember.run.schedule('actions', self, '_initialize');
+ } else {
+ this.$().ready(function runInitialize() {
+ Ember.run(self, '_initialize');
+ });
+ }
+ },
+
+ /**
+ Use this to defer readiness until some condition is true.
+
+ Example:
+
+ ```javascript
+ App = Ember.Application.create();
+ App.deferReadiness();
+
+ jQuery.getJSON("/auth-token", function(token) {
+ App.token = token;
+ App.advanceReadiness();
+ });
+ ```
+
+ This allows you to perform asynchronous setup logic and defer
+ booting your application until the setup has finished.
+
+ However, if the setup requires a loading UI, it might be better
+ to use the router for this purpose.
+
+ @method deferReadiness
+ */
+ deferReadiness: function() {
+ Ember.assert("You must call deferReadiness on an instance of Ember.Application", this instanceof Ember.Application);
+ Ember.assert("You cannot defer readiness since the `ready()` hook has already been called.", this._readinessDeferrals > 0);
+ this._readinessDeferrals++;
+ },
+
+ /**
+ Call `advanceReadiness` after any asynchronous setup logic has completed.
+ Each call to `deferReadiness` must be matched by a call to `advanceReadiness`
+ or the application will never become ready and routing will not begin.
+
+ @method advanceReadiness
+ @see {Ember.Application#deferReadiness}
+ */
+ advanceReadiness: function() {
+ Ember.assert("You must call advanceReadiness on an instance of Ember.Application", this instanceof Ember.Application);
+ this._readinessDeferrals--;
+
+ if (this._readinessDeferrals === 0) {
+ Ember.run.once(this, this.didBecomeReady);
+ }
+ },
+
+ /**
+ Registers a factory that can be used for dependency injection (with
+ `App.inject`) or for service lookup. Each factory is registered with
+ a full name including two parts: `type:name`.
+
+ A simple example:
+
+ ```javascript
+ var App = Ember.Application.create();
+ App.Orange = Ember.Object.extend();
+ App.register('fruit:favorite', App.Orange);
+ ```
+
+ Ember will resolve factories from the `App` namespace automatically.
+ For example `App.CarsController` will be discovered and returned if
+ an application requests `controller:cars`.
+
+ An example of registering a controller with a non-standard name:
+
+ ```javascript
+ var App = Ember.Application.create(),
+ Session = Ember.Controller.extend();
+
+ App.register('controller:session', Session);
+
+ // The Session controller can now be treated like a normal controller,
+ // despite its non-standard name.
+ App.ApplicationController = Ember.Controller.extend({
+ needs: ['session']
+ });
+ ```
+
+ Registered factories are **instantiated** by having `create`
+ called on them. Additionally they are **singletons**, each time
+ they are looked up they return the same instance.
+
+ Some examples modifying that default behavior:
+
+ ```javascript
+ var App = Ember.Application.create();
+
+ App.Person = Ember.Object.extend();
+ App.Orange = Ember.Object.extend();
+ App.Email = Ember.Object.extend();
+ App.session = Ember.Object.create();
+
+ App.register('model:user', App.Person, {singleton: false });
+ App.register('fruit:favorite', App.Orange);
+ App.register('communication:main', App.Email, {singleton: false});
+ App.register('session', App.session, {instantiate: false});
+ ```
+
+ @method register
+ @param fullName {String} type:name (e.g., 'model:user')
+ @param factory {Function} (e.g., App.Person)
+ @param options {Object} (optional) disable instantiation or singleton usage
+ **/
+ register: function() {
+ var container = this.__container__;
+ container.register.apply(container, arguments);
+ },
+
+ /**
+ Define a dependency injection onto a specific factory or all factories
+ of a type.
+
+ When Ember instantiates a controller, view, or other framework component
+ it can attach a dependency to that component. This is often used to
+ provide services to a set of framework components.
+
+ An example of providing a session object to all controllers:
+
+ ```javascript
+ var App = Ember.Application.create(),
+ Session = Ember.Object.extend({ isAuthenticated: false });
+
+ // A factory must be registered before it can be injected
+ App.register('session:main', Session);
+
+ // Inject 'session:main' onto all factories of the type 'controller'
+ // with the name 'session'
+ App.inject('controller', 'session', 'session:main');
+
+ App.IndexController = Ember.Controller.extend({
+ isLoggedIn: Ember.computed.alias('session.isAuthenticated')
+ });
+ ```
+
+ Injections can also be performed on specific factories.
+
+ ```javascript
+ App.inject(<full_name or type>, <property name>, <full_name>)
+ App.inject('route', 'source', 'source:main')
+ App.inject('route:application', 'email', 'model:email')
+ ```
+
+ It is important to note that injections can only be performed on
+ classes that are instantiated by Ember itself. Instantiating a class
+ directly (via `create` or `new`) bypasses the dependency injection
+ system.
+
+ Ember-Data instantiates its models in a unique manner, and consequently
+ injections onto models (or all models) will not work as expected. Injections
+ on models can be enabled by setting `Ember.MODEL_FACTORY_INJECTIONS`
+ to `true`.
+
+ @method inject
+ @param factoryNameOrType {String}
+ @param property {String}
+ @param injectionName {String}
+ **/
+ inject: function() {
+ var container = this.__container__;
+ container.injection.apply(container, arguments);
+ },
+
+ /**
+ Calling initialize manually is not supported.
+
+ Please see Ember.Application#advanceReadiness and
+ Ember.Application#deferReadiness.
+
+ @private
+ @deprecated
+ @method initialize
+ **/
+ initialize: function() {
+ Ember.deprecate('Calling initialize manually is not supported. Please see Ember.Application#advanceReadiness and Ember.Application#deferReadiness');
+ },
+
+ /**
+ Initialize the application. This happens automatically.
+
+ Run any initializers and run the application load hook. These hooks may
+ choose to defer readiness. For example, an authentication hook might want
+ to defer readiness until the auth token has been retrieved.
+
+ @private
+ @method _initialize
+ */
+ _initialize: function() {
+ if (this.isDestroyed) { return; }
+
+ // At this point, the App.Router must already be assigned
+ if (this.Router) {
+ var container = this.__container__;
+ container.unregister('router:main');
+ container.register('router:main', this.Router);
+ }
+
+ this.runInitializers();
+ Ember.runLoadHooks('application', this);
+
+ // At this point, any initializers or load hooks that would have wanted
+ // to defer readiness have fired. In general, advancing readiness here
+ // will proceed to didBecomeReady.
+ this.advanceReadiness();
+
+ return this;
+ },
+
+ /**
+ Reset the application. This is typically used only in tests. It cleans up
+ the application in the following order:
+
+ 1. Deactivate existing routes
+ 2. Destroy all objects in the container
+ 3. Create a new application container
+ 4. Re-route to the existing url
+
+ Typical Example:
+
+ ```javascript
+
+ var App;
+
+ Ember.run(function() {
+ App = Ember.Application.create();
+ });
+
+ module("acceptance test", {
+ setup: function() {
+ App.reset();
+ }
+ });
+
+ test("first test", function() {
+ // App is freshly reset
+ });
+
+ test("first test", function() {
+ // App is again freshly reset
+ });
+ ```
+
+ Advanced Example:
+
+ Occasionally you may want to prevent the app from initializing during
+ setup. This could enable extra configuration, or enable asserting prior
+ to the app becoming ready.
+
+ ```javascript
+
+ var App;
+
+ Ember.run(function() {
+ App = Ember.Application.create();
+ });
+
+ module("acceptance test", {
+ setup: function() {
+ Ember.run(function() {
+ App.reset();
+ App.deferReadiness();
+ });
+ }
+ });
+
+ test("first test", function() {
+ ok(true, 'something before app is initialized');
+
+ Ember.run(function() {
+ App.advanceReadiness();
+ });
+ ok(true, 'something after app is initialized');
+ });
+ ```
+
+ @method reset
+ **/
+ reset: function() {
+ this._readinessDeferrals = 1;
+
+ function handleReset() {
+ var router = this.__container__.lookup('router:main');
+ router.reset();
+
+ Ember.run(this.__container__, 'destroy');
+
+ this.buildContainer();
+
+ Ember.run.schedule('actions', this, function() {
+ this._initialize();
+ });
+ }
+
+ Ember.run.join(this, handleReset);
+ },
+
+ /**
+ @private
+ @method runInitializers
+ */
+ runInitializers: function() {
+ var initializers = get(this.constructor, 'initializers'),
+ container = this.__container__,
+ graph = new Ember.DAG(),
+ namespace = this,
+ name, initializer;
+
+ for (name in initializers) {
+ initializer = initializers[name];
+ graph.addEdges(initializer.name, initializer.initialize, initializer.before, initializer.after);
+ }
+
+ graph.topsort(function (vertex) {
+ var initializer = vertex.value;
+ Ember.assert("No application initializer named '"+vertex.name+"'", initializer);
+ initializer(container, namespace);
+ });
+ },
+
+ /**
+ @private
+ @method didBecomeReady
+ */
+ didBecomeReady: function() {
+ this.setupEventDispatcher();
+ this.ready(); // user hook
+ this.startRouting();
+
+ if (!Ember.testing) {
+ // Eagerly name all classes that are already loaded
+ Ember.Namespace.processAll();
+ Ember.BOOTED = true;
+ }
+
+ this.resolve(this);
+ },
+
+ /**
+ Setup up the event dispatcher to receive events on the
+ application's `rootElement` with any registered
+ `customEvents`.
+
+ @private
+ @method setupEventDispatcher
+ */
+ setupEventDispatcher: function() {
+ var customEvents = get(this, 'customEvents'),
+ rootElement = get(this, 'rootElement'),
+ dispatcher = this.__container__.lookup('event_dispatcher:main');
+
+ set(this, 'eventDispatcher', dispatcher);
+ dispatcher.setup(customEvents, rootElement);
+ },
+
+ /**
+ trigger a new call to `route` whenever the URL changes.
+ If the application has a router, use it to route to the current URL, and
+
+ @private
+ @method startRouting
+ @property router {Ember.Router}
+ */
+ startRouting: function() {
+ var router = this.__container__.lookup('router:main');
+ if (!router) { return; }
+
+ router.startRouting();
+ },
+
+ handleURL: function(url) {
+ var router = this.__container__.lookup('router:main');
+
+ router.handleURL(url);
+ },
+
+ /**
+ Called when the Application has become ready.
+ The call will be delayed until the DOM has become ready.
+
+ @event ready
+ */
+ ready: Ember.K,
+
+ /**
+ @deprecated Use 'Resolver' instead
+ Set this to provide an alternate class to `Ember.DefaultResolver`
+
+
+ @property resolver
+ */
+ resolver: null,
+
+ /**
+ Set this to provide an alternate class to `Ember.DefaultResolver`
+
+ @property resolver
+ */
+ Resolver: null,
+
+ willDestroy: function() {
+ Ember.BOOTED = false;
+ // Ensure deactivation of routes before objects are destroyed
+ this.__container__.lookup('router:main').reset();
+
+ this.__container__.destroy();
+ },
+
+ initializer: function(options) {
+ this.constructor.initializer(options);
+ }
+});
+
+Ember.Application.reopenClass({
+ initializers: {},
+ initializer: function(initializer) {
+ // If this is the first initializer being added to a subclass, we are going to reopen the class
+ // to make sure we have a new `initializers` object, which extends from the parent class' using
+ // prototypal inheritance. Without this, attempting to add initializers to the subclass would
+ // pollute the parent class as well as other subclasses.
+ if (this.superclass.initializers !== undefined && this.superclass.initializers === this.initializers) {
+ this.reopenClass({
+ initializers: Ember.create(this.initializers)
+ });
+ }
+
+ Ember.assert("The initializer '" + initializer.name + "' has already been registered", !this.initializers[initializer.name]);
+ Ember.assert("An initializer cannot be registered with both a before and an after", !(initializer.before && initializer.after));
+ Ember.assert("An initializer cannot be registered without an initialize function", Ember.canInvoke(initializer, 'initialize'));
+
+ this.initializers[initializer.name] = initializer;
+ },
+
+ /**
+ This creates a container with the default Ember naming conventions.
+
+ It also configures the container:
+
+ * registered views are created every time they are looked up (they are
+ not singletons)
+ * registered templates are not factories; the registered value is
+ returned directly.
+ * the router receives the application as its `namespace` property
+ * all controllers receive the router as their `target` and `controllers`
+ properties
+ * all controllers receive the application as their `namespace` property
+ * the application view receives the application controller as its
+ `controller` property
+ * the application view receives the application template as its
+ `defaultTemplate` property
+
+ @private
+ @method buildContainer
+ @static
+ @param {Ember.Application} namespace the application to build the
+ container for.
+ @return {Ember.Container} the built container
+ */
+ buildContainer: function(namespace) {
+ var container = new Ember.Container();
+
+ Ember.Container.defaultContainer = new DeprecatedContainer(container);
+
+ container.set = Ember.set;
+ container.resolver = resolverFor(namespace);
+ container.normalize = container.resolver.normalize;
+ container.describe = container.resolver.describe;
+ container.makeToString = container.resolver.makeToString;
+
+ container.optionsForType('component', { singleton: false });
+ container.optionsForType('view', { singleton: false });
+ container.optionsForType('template', { instantiate: false });
+ container.optionsForType('helper', { instantiate: false });
+
+ container.register('application:main', namespace, { instantiate: false });
+
+ container.register('controller:basic', Ember.Controller, { instantiate: false });
+ container.register('controller:object', Ember.ObjectController, { instantiate: false });
+ container.register('controller:array', Ember.ArrayController, { instantiate: false });
+ container.register('route:basic', Ember.Route, { instantiate: false });
+ container.register('event_dispatcher:main', Ember.EventDispatcher);
+
+ container.register('router:main', Ember.Router);
+ container.injection('router:main', 'namespace', 'application:main');
+
+
+ container.register('location:auto', Ember.AutoLocation);
+
+
+ container.register('location:hash', Ember.HashLocation);
+ container.register('location:history', Ember.HistoryLocation);
+ container.register('location:none', Ember.NoneLocation);
+
+ container.injection('controller', 'target', 'router:main');
+ container.injection('controller', 'namespace', 'application:main');
+
+ container.injection('route', 'router', 'router:main');
+
+ // DEBUGGING
+ container.register('resolver-for-debugging:main', container.resolver.__resolver__, { instantiate: false });
+ container.injection('container-debug-adapter:main', 'resolver', 'resolver-for-debugging:main');
+ container.injection('data-adapter:main', 'containerDebugAdapter', 'container-debug-adapter:main');
+ // Custom resolver authors may want to register their own ContainerDebugAdapter with this key
+ container.register('container-debug-adapter:main', Ember.ContainerDebugAdapter);
+
+ return container;
+ }
+});
+
+/**
+ This function defines the default lookup rules for container lookups:
+
+ * templates are looked up on `Ember.TEMPLATES`
+ * other names are looked up on the application after classifying the name.
+ For example, `controller:post` looks up `App.PostController` by default.
+ * if the default lookup fails, look for registered classes on the container
+
+ This allows the application to register default injections in the container
+ that could be overridden by the normal naming convention.
+
+ @private
+ @method resolverFor
+ @param {Ember.Namespace} namespace the namespace to look for classes
+ @return {*} the resolved value for a given lookup
+*/
+function resolverFor(namespace) {
+ if (namespace.get('resolver')) {
+ Ember.deprecate('Application.resolver is deprecated in favor of Application.Resolver', false);
+ }
+
+ var ResolverClass = namespace.get('resolver') || namespace.get('Resolver') || Ember.DefaultResolver;
+ var resolver = ResolverClass.create({
+ namespace: namespace
+ });
+
+ function resolve(fullName) {
+ return resolver.resolve(fullName);
+ }
+
+ resolve.describe = function(fullName) {
+ return resolver.lookupDescription(fullName);
+ };
+
+ resolve.makeToString = function(factory, fullName) {
+ return resolver.makeToString(factory, fullName);
+ };
+
+ resolve.normalize = function(fullName) {
+ if (resolver.normalize) {
+ return resolver.normalize(fullName);
+ } else {
+ Ember.deprecate('The Resolver should now provide a \'normalize\' function', false);
+ return fullName;
+ }
+ };
+
+ resolve.__resolver__ = resolver;
+
+ return resolve;
+}
+
+Ember.runLoadHooks('Ember.Application', Ember.Application);
+
+})();
+
+
+
+(function() {
+
+})();
+
+
+
+(function() {
+/**
+@module ember
+@submodule ember-application
+*/
+
+var get = Ember.get, set = Ember.set;
+
+function verifyNeedsDependencies(controller, container, needs) {
+ var dependency, i, l, missing = [];
+
+ for (i=0, l=needs.length; i<l; i++) {
+ dependency = needs[i];
+
+ Ember.assert(Ember.inspect(controller) + "#needs must not specify dependencies with periods in their names (" + dependency + ")", dependency.indexOf('.') === -1);
+
+ if (dependency.indexOf(':') === -1) {
+ dependency = "controller:" + dependency;
+ }
+
+ // Structure assert to still do verification but not string concat in production
+ if (!container.has(dependency)) {
+ missing.push(dependency);
+ }
+ }
+ if (missing.length) {
+ throw new Ember.Error(Ember.inspect(controller) + " needs [ " + missing.join(', ') + " ] but " + (missing.length > 1 ? 'they' : 'it') + " could not be found");
+ }
+}
+
+var defaultControllersComputedProperty = Ember.computed(function() {
+ var controller = this;
+
+ return {
+ needs: get(controller, 'needs'),
+ container: get(controller, 'container'),
+ unknownProperty: function(controllerName) {
+ var needs = this.needs,
+ dependency, i, l;
+ for (i=0, l=needs.length; i<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
+*/
+Ember.ControllerMixin.reopen({
+ concatenatedProperties: ['needs'],
+
+ /**
+ 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 []
+ */
+ needs: [],
+
+ init: function() {
+ var needs = get(this, 'needs'),
+ length = get(needs, 'length');
+
+ 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');
+ }
+
+ this._super.apply(this, arguments);
+ },
+
+ /**
+ @method controllerFor
+ @see {Ember.Route#controllerFor}
+ @deprecated Use `needs` instead
+ */
+ controllerFor: function(controllerName) {
+ Ember.deprecate("Controller#controllerFor is deprecated, please use Controller#needs instead");
+ return Ember.controllerFor(get(this, 'container'), controllerName);
+ },
+
+ /**
+ Stores the instances of other controllers available from within
+ this controller. Any controller listed by name in the `needs`
+ property will be accessible by name through this property.
+
+ ```javascript
+ App.CommentsController = Ember.ArrayController.extend({
+ needs: ['post'],
+ postTitle: function(){
+ var currentPost = this.get('controllers.post'); // instance of App.PostController
+ return currentPost.get('title');
+ }.property('controllers.post.title')
+ });
+ ```
+
+ @see {Ember.ControllerMixin#needs}
+ @property {Object} controllers
+ @default null
+ */
+ controllers: defaultControllersComputedProperty
+});
+
+})();
+
+
+
+(function() {
+
+})();
+
+
+
+(function() {
+/**
+Ember Application
+
+@module ember
+@submodule ember-application
+@requires ember-views, ember-routing
+*/
+
+})();
+
+(function() {
+/**
+@module ember
+@submodule ember-extension-support
+*/
+/**
+ The `ContainerDebugAdapter` helps the container and resolver interface
+ with tools that debug Ember such as the
+ [Ember Extension](https://github.com/tildeio/ember-extension)
+ for Chrome and Firefox.
+
+ This class can be extended by a custom resolver implementer
+ to override some of the methods with library-specific code.
+
+ The methods likely to be overridden are:
+
+ * `canCatalogEntriesByType`
+ * `catalogEntriesByType`
+
+ The adapter will need to be registered
+ in the application's container as `container-debug-adapter:main`
+
+ Example:
+
+ ```javascript
+ Application.initializer({
+ name: "containerDebugAdapter",
+
+ initialize: function(container, application) {
+ application.register('container-debug-adapter:main', require('app/container-debug-adapter'));
+ }
+ });
+ ```
+
+ @class ContainerDebugAdapter
+ @namespace Ember
+ @extends Ember.Object
+*/
+Ember.ContainerDebugAdapter = Ember.Object.extend({
+ /**
+ The container of the application being debugged.
+ This property will be injected
+ on creation.
+
+ @property container
+ @default null
+ */
+ container: null,
+
+ /**
+ The resolver instance of the application
+ being debugged. This property will be injected
+ on creation.
+
+ @property resolver
+ @default null
+ */
+ resolver: null,
+
+ /**
+ Returns true if it is possible to catalog a list of available
+ classes in the resolver for a given type.
+
+ @method canCatalogEntriesByType
+ @param {string} type The type. e.g. "model", "controller", "route"
+ @return {boolean} whether a list is available for this type.
+ */
+ canCatalogEntriesByType: function(type) {
+ if (type === 'model' || type === 'template') return false;
+ return true;
+ },
+
+ /**
+ Returns the available classes a given type.
+
+ @method catalogEntriesByType
+ @param {string} type The type. e.g. "model", "controller", "route"
+ @return {Array} An array of strings.
+ */
+ catalogEntriesByType: function(type) {
+ var namespaces = Ember.A(Ember.Namespace.NAMESPACES), types = Ember.A(), self = this;
+ var typeSuffixRegex = new RegExp(Ember.String.classify(type) + "$");
+
+ namespaces.forEach(function(namespace) {
+ if (namespace !== Ember) {
+ for (var key in namespace) {
+ if (!namespace.hasOwnProperty(key)) { continue; }
+ if (typeSuffixRegex.test(key)) {
+ var klass = namespace[key];
+ if (Ember.typeOf(klass) === 'class') {
+ types.push(Ember.String.dasherize(key.replace(typeSuffixRegex, '')));
+ }
+ }
+ }
+ }
+ });
+ return types;
+ }
+});
+
+
+})();
+
+
+
+(function() {
+/**
+@module ember
+@submodule ember-extension-support
+*/
+/**
+ The `DataAdapter` helps a data persistence library
+ interface with tools that debug Ember such
+ as the [Ember Extension](https://github.com/tildeio/ember-extension)
+ for Chrome and Firefox.
+
+ This class will be extended by a persistence library
+ which will override some of the methods with
+ library-specific code.
+
+ The methods likely to be overridden are:
+
+ * `getFilters`
+ * `detect`
+ * `columnsForType`
+ * `getRecords`
+ * `getRecordColumnValues`
+ * `getRecordKeywords`
+ * `getRecordFilterValues`
+ * `getRecordColor`
+ * `observeRecord`
+
+ The adapter will need to be registered
+ in the application's container as `dataAdapter:main`
+
+ Example:
+
+ ```javascript
+ Application.initializer({
+ name: "data-adapter",
+
+ initialize: function(container, application) {
+ application.register('data-adapter:main', DS.DataAdapter);
+ }
+ });
+ ```
+
+ @class DataAdapter
+ @namespace Ember
+ @extends Ember.Object
+*/
+Ember.DataAdapter = Ember.Object.extend({
+ init: function() {
+ this._super();
+ this.releaseMethods = Ember.A();
+ },
+
+ /**
+ The container of the application being debugged.
+ This property will be injected
+ on creation.
+
+ @property container
+ @default null
+ */
+ container: null,
+
+
+ /**
+ The container-debug-adapter which is used
+ to list all models.
+
+ @property containerDebugAdapter
+ @default undefined
+ **/
+ containerDebugAdapter: undefined,
+
+ /**
+ Number of attributes to send
+ as columns. (Enough to make the record
+ identifiable).
+
+ @private
+ @property attributeLimit
+ @default 3
+ */
+ attributeLimit: 3,
+
+ /**
+ Stores all methods that clear observers.
+ These methods will be called on destruction.
+
+ @private
+ @property releaseMethods
+ */
+ releaseMethods: Ember.A(),
+
+ /**
+ Specifies how records can be filtered.
+ Records returned will need to have a `filterValues`
+ property with a key for every name in the returned array.
+
+ @public
+ @method getFilters
+ @return {Array} List of objects defining filters.
+ The object should have a `name` and `desc` property.
+ */
+ getFilters: function() {
+ return Ember.A();
+ },
+
+ /**
+ Fetch the model types and observe them for changes.
+
+ @public
+ @method watchModelTypes
+
+ @param {Function} typesAdded Callback to call to add types.
+ Takes an array of objects containing wrapped types (returned from `wrapModelType`).
+
+ @param {Function} typesUpdated Callback to call when a type has changed.
+ Takes an array of objects containing wrapped types.
+
+ @return {Function} Method to call to remove all observers
+ */
+ watchModelTypes: function(typesAdded, typesUpdated) {
+ var modelTypes = this.getModelTypes(),
+ self = this, typesToSend, releaseMethods = Ember.A();
+
+ typesToSend = modelTypes.map(function(type) {
+ var klass = type.klass;
+ var wrapped = self.wrapModelType(klass, type.name);
+ releaseMethods.push(self.observeModelType(klass, typesUpdated));
+ return wrapped;
+ });
+
+ typesAdded(typesToSend);
+
+ var release = function() {
+ releaseMethods.forEach(function(fn) { fn(); });
+ self.releaseMethods.removeObject(release);
+ };
+ this.releaseMethods.pushObject(release);
+ return release;
+ },
+
+ _nameToClass: function(type) {
+ if (typeof type === 'string') {
+ type = this.container.lookupFactory('model:' + type);
+ }
+ return type;
+ },
+
+ /**
+ Fetch the records of a given type and observe them for changes.
+
+ @public
+ @method watchRecords
+
+ @param {Function} recordsAdded Callback to call to add records.
+ Takes an array of objects containing wrapped records.
+ The object should have the following properties:
+ columnValues: {Object} key and value of a table cell
+ object: {Object} the actual record object
+
+ @param {Function} recordsUpdated Callback to call when a record has changed.
+ Takes an array of objects containing wrapped records.
+
+ @param {Function} recordsRemoved Callback to call when a record has removed.
+ Takes the following parameters:
+ index: the array index where the records were removed
+ count: the number of records removed
+
+ @return {Function} Method to call to remove all observers
+ */
+ watchRecords: function(type, recordsAdded, recordsUpdated, recordsRemoved) {
+ var self = this, releaseMethods = Ember.A(), records = this.getRecords(type), release;
+
+ var recordUpdated = function(updatedRecord) {
+ recordsUpdated([updatedRecord]);
+ };
+
+ var recordsToSend = records.map(function(record) {
+ releaseMethods.push(self.observeRecord(record, recordUpdated));
+ return self.wrapRecord(record);
+ });
+
+
+ var contentDidChange = function(array, idx, removedCount, addedCount) {
+ for (var i = idx; i < idx + addedCount; i++) {
+ var record = array.objectAt(i);
+ var wrapped = self.wrapRecord(record);
+ releaseMethods.push(self.observeRecord(record, recordUpdated));
+ recordsAdded([wrapped]);
+ }
+
+ if (removedCount) {
+ recordsRemoved(idx, removedCount);
+ }
+ };
+
+ var observer = { didChange: contentDidChange, willChange: Ember.K };
+ records.addArrayObserver(self, observer);
+
+ release = function() {
+ releaseMethods.forEach(function(fn) { fn(); });
+ records.removeArrayObserver(self, observer);
+ self.releaseMethods.removeObject(release);
+ };
+
+ recordsAdded(recordsToSend);
+
+ this.releaseMethods.pushObject(release);
+ return release;
+ },
+
+ /**
+ Clear all observers before destruction
+ @private
+ */
+ willDestroy: function() {
+ this._super();
+ this.releaseMethods.forEach(function(fn) {
+ fn();
+ });
+ },
+
+ /**
+ Detect whether a class is a model.
+
+ Test that against the model class
+ of your persistence library
+
+ @private
+ @method detect
+ @param {Class} klass The class to test
+ @return boolean Whether the class is a model class or not
+ */
+ detect: function(klass) {
+ return false;
+ },
+
+ /**
+ Get the columns for a given model type.
+
+ @private
+ @method columnsForType
+ @param {Class} type The model type
+ @return {Array} An array of columns of the following format:
+ name: {String} name of the column
+ desc: {String} Humanized description (what would show in a table column name)
+ */
+ columnsForType: function(type) {
+ return Ember.A();
+ },
+
+ /**
+ Adds observers to a model type class.
+
+ @private
+ @method observeModelType
+ @param {Class} type The model type class
+ @param {Function} typesUpdated Called when a type is modified.
+ @return {Function} The function to call to remove observers
+ */
+
+ observeModelType: function(type, typesUpdated) {
+ var self = this, records = this.getRecords(type);
+
+ var onChange = function() {
+ typesUpdated([self.wrapModelType(type)]);
+ };
+ var observer = {
+ didChange: function() {
+ Ember.run.scheduleOnce('actions', this, onChange);
+ },
+ willChange: Ember.K
+ };
+
+ records.addArrayObserver(this, observer);
+
+ var release = function() {
+ records.removeArrayObserver(self, observer);
+ };
+
+ return release;
+ },
+
+
+ /**
+ Wraps a given model type and observes changes to it.
+
+ @private
+ @method wrapModelType
+ @param {Class} type A model class
+ @param {String} Optional name of the class
+ @return {Object} contains the wrapped type and the function to remove observers
+ Format:
+ type: {Object} the wrapped type
+ The wrapped type has the following format:
+ name: {String} name of the type
+ count: {Integer} number of records available
+ columns: {Columns} array of columns to describe the record
+ object: {Class} the actual Model type class
+ release: {Function} The function to remove observers
+ */
+ wrapModelType: function(type, name) {
+ var release, records = this.getRecords(type),
+ typeToSend, self = this;
+
+ typeToSend = {
+ name: name || type.toString(),
+ count: Ember.get(records, 'length'),
+ columns: this.columnsForType(type),
+ object: type
+ };
+
+
+ return typeToSend;
+ },
+
+
+ /**
+ Fetches all models defined in the application.
+
+ @private
+ @method getModelTypes
+ @return {Array} Array of model types
+ */
+ getModelTypes: function() {
+ var types, self = this,
+ containerDebugAdapter = this.get('containerDebugAdapter');
+
+ if (containerDebugAdapter.canCatalogEntriesByType('model')) {
+ types = containerDebugAdapter.catalogEntriesByType('model');
+ } else {
+ types = this._getObjectsOnNamespaces();
+ }
+ // New adapters return strings instead of classes
+ return types.map(function(name) {
+ return {
+ klass: self._nameToClass(name),
+ name: name
+ };
+ }).filter(function(type) {
+ return self.detect(type.klass);
+ });
+ },
+
+ /**
+ Loops over all namespaces and all objects
+ attached to them
+
+ @private
+ @method _getObjectsOnNamespaces
+ @return {Array} Array of model type strings
+ */
+ _getObjectsOnNamespaces: function() {
+ var namespaces = Ember.A(Ember.Namespace.NAMESPACES), types = Ember.A();
+
+ namespaces.forEach(function(namespace) {
+ for (var key in namespace) {
+ if (!namespace.hasOwnProperty(key)) { continue; }
+ var name = Ember.String.dasherize(key);
+ if (!(namespace instanceof Ember.Application) && namespace.toString()) {
+ name = namespace + '/' + name;
+ }
+ types.push(name);
+ }
+ });
+ return types;
+ },
+
+ /**
+ Fetches all loaded records for a given type.
+
+ @private
+ @method getRecords
+ @return {Array} An array of records.
+ This array will be observed for changes,
+ so it should update when new records are added/removed.
+ */
+ getRecords: function(type) {
+ return Ember.A();
+ },
+
+ /**
+ Wraps a record and observers changes to it.
+
+ @private
+ @method wrapRecord
+ @param {Object} record The record instance.
+ @return {Object} The wrapped record. Format:
+ columnValues: {Array}
+ searchKeywords: {Array}
+ */
+ wrapRecord: function(record) {
+ var recordToSend = { object: record }, columnValues = {}, self = this;
+
+ recordToSend.columnValues = this.getRecordColumnValues(record);
+ recordToSend.searchKeywords = this.getRecordKeywords(record);
+ recordToSend.filterValues = this.getRecordFilterValues(record);
+ recordToSend.color = this.getRecordColor(record);
+
+ return recordToSend;
+ },
+
+ /**
+ Gets the values for each column.
+
+ @private
+ @method getRecordColumnValues
+ @return {Object} Keys should match column names defined
+ by the model type.
+ */
+ getRecordColumnValues: function(record) {
+ return {};
+ },
+
+ /**
+ Returns keywords to match when searching records.
+
+ @private
+ @method getRecordKeywords
+ @return {Array} Relevant keywords for search.
+ */
+ getRecordKeywords: function(record) {
+ return Ember.A();
+ },
+
+ /**
+ Returns the values of filters defined by `getFilters`.
+
+ @private
+ @method getRecordFilterValues
+ @param {Object} record The record instance
+ @return {Object} The filter values
+ */
+ getRecordFilterValues: function(record) {
+ return {};
+ },
+
+ /**
+ Each record can have a color that represents its state.
+
+ @private
+ @method getRecordColor
+ @param {Object} record The record instance
+ @return {String} The record's color
+ Possible options: black, red, blue, green
+ */
+ getRecordColor: function(record) {
+ return null;
+ },
+
+ /**
+ Observes all relevant properties and re-sends the wrapped record
+ when a change occurs.
+
+ @private
+ @method observerRecord
+ @param {Object} record The record instance
+ @param {Function} recordUpdated The callback to call when a record is updated.
+ @return {Function} The function to call to remove all observers.
+ */
+ observeRecord: function(record, recordUpdated) {
+ return function(){};
+ }
+
+});
+
+
+})();
+
+
+
+(function() {
+/**
+Ember Extension Support
+
+@module ember
+@submodule ember-extension-support
+@requires ember-application
+*/
+
+})();
+
+define("container/container",
+ ["container/inheriting_dict","exports"],
+ function(__dependency1__, __exports__) {
+ "use strict";
+ var InheritingDict = __dependency1__["default"];
+
+ // A lightweight container that helps to assemble and decouple components.
+ // Public api for the container is still in flux.
+ // The public api, specified on the application namespace should be considered the stable api.
+ function Container(parent) {
+ this.parent = parent;
+ this.children = [];
+
+ this.resolver = parent && parent.resolver || function() {};
+
+ this.registry = new InheritingDict(parent && parent.registry);
+ this.cache = new InheritingDict(parent && parent.cache);
+ this.factoryCache = new InheritingDict(parent && parent.factoryCache);
+ this.resolveCache = new InheritingDict(parent && parent.resolveCache);
+ this.typeInjections = new InheritingDict(parent && parent.typeInjections);
+ this.injections = {};
+
+ this.factoryTypeInjections = new InheritingDict(parent && parent.factoryTypeInjections);
+ this.factoryInjections = {};
+
+ this._options = new InheritingDict(parent && parent._options);
+ this._typeOptions = new InheritingDict(parent && parent._typeOptions);
+ }
+
+ Container.prototype = {
+
+ /**
+ @property parent
+ @type Container
+ @default null
+ */
+ parent: null,
+
+ /**
+ @property children
+ @type Array
+ @default []
+ */
+ children: null,
+
+ /**
+ @property resolver
+ @type function
+ */
+ resolver: null,
+
+ /**
+ @property registry
+ @type InheritingDict
+ */
+ registry: null,
+
+ /**
+ @property cache
+ @type InheritingDict
+ */
+ cache: null,
+
+ /**
+ @property typeInjections
+ @type InheritingDict
+ */
+ typeInjections: null,
+
+ /**
+ @property injections
+ @type Object
+ @default {}
+ */
+ injections: null,
+
+ /**
+ @private
+
+ @property _options
+ @type InheritingDict
+ @default null
+ */
+ _options: null,
+
+ /**
+ @private
+
+ @property _typeOptions
+ @type InheritingDict
+ */
+ _typeOptions: null,
+
+ /**
+ Returns a new child of the current container. These children are configured
+ to correctly inherit from the current container.
+
+ @method child
+ @return {Container}
+ */
+ child: function() {
+ var container = new Container(this);
+ this.children.push(container);
+ return container;
+ },
+
+ /**
+ Sets a key-value pair on the current container. If a parent container,
+ has the same key, once set on a child, the parent and child will diverge
+ as expected.
+
+ @method set
+ @param {Object} object
+ @param {String} key
+ @param {any} value
+ */
+ set: function(object, key, value) {
+ object[key] = value;
+ },
+
+ /**
+ Registers a factory for later injection.
+
+ Example:
+
+ ```javascript
+ var container = new Container();
+
+ container.register('model:user', Person, {singleton: false });
+ container.register('fruit:favorite', Orange);
+ container.register('communication:main', Email, {singleton: false});
+ ```
+
+ @method register
+ @param {String} fullName
+ @param {Function} factory
+ @param {Object} options
+ */
+ register: function(fullName, factory, options) {
+ validateFullName(fullName);
+
+ if (factory === undefined) {
+ throw new TypeError('Attempting to register an unknown factory: `' + fullName + '`');
+ }
+
+ var normalizedName = this.normalize(fullName);
+
+ if (this.cache.has(normalizedName)) {
+ throw new Error('Cannot re-register: `' + fullName +'`, as it has already been looked up.');
+ }
+
+ this.registry.set(normalizedName, factory);
+ this._options.set(normalizedName, options || {});
+ },
+
+ /**
+ Unregister a fullName
+
+ ```javascript
+ var container = new Container();
+ container.register('model:user', User);
+
+ container.lookup('model:user') instanceof User //=> true
+
+ container.unregister('model:user')
+ container.lookup('model:user') === undefined //=> true
+ ```
+
+ @method unregister
+ @param {String} fullName
+ */
+ unregister: function(fullName) {
+ validateFullName(fullName);
+
+ var normalizedName = this.normalize(fullName);
+
+ this.registry.remove(normalizedName);
+ this.cache.remove(normalizedName);
+ this.factoryCache.remove(normalizedName);
+ this.resolveCache.remove(normalizedName);
+ this._options.remove(normalizedName);
+ },
+
+ /**
+ Given a fullName return the corresponding factory.
+
+ By default `resolve` will retrieve the factory from
+ its container's registry.
+
+ ```javascript
+ var container = new Container();
+ container.register('api:twitter', Twitter);
+
+ container.resolve('api:twitter') // => Twitter
+ ```
+
+ Optionally the container can be provided with a custom resolver.
+ If provided, `resolve` will first provide the custom resolver
+ the oppertunity to resolve the fullName, otherwise it will fallback
+ to the registry.
+
+ ```javascript
+ var container = new Container();
+ container.resolver = function(fullName) {
+ // lookup via the module system of choice
+ };
+
+ // the twitter factory is added to the module system
+ container.resolve('api:twitter') // => Twitter
+ ```
+
+ @method resolve
+ @param {String} fullName
+ @return {Function} fullName's factory
+ */
+ resolve: function(fullName) {
+ validateFullName(fullName);
+
+ var normalizedName = this.normalize(fullName);
+ var cached = this.resolveCache.get(normalizedName);
+
+ if (cached) { return cached; }
+
+ var resolved = this.resolver(normalizedName) || this.registry.get(normalizedName);
+
+ this.resolveCache.set(normalizedName, resolved);
+
+ return resolved;
+ },
+
+ /**
+ A hook that can be used to describe how the resolver will
+ attempt to find the factory.
+
+ For example, the default Ember `.describe` returns the full
+ class name (including namespace) where Ember's resolver expects
+ to find the `fullName`.
+
+ @method describe
+ @param {String} fullName
+ @return {string} described fullName
+ */
+ describe: function(fullName) {
+ return fullName;
+ },
+
+ /**
+ A hook to enable custom fullName normalization behaviour
+
+ @method normalize
+ @param {String} fullName
+ @return {string} normalized fullName
+ */
+ normalize: function(fullName) {
+ return fullName;
+ },
+
+ /**
+ @method makeToString
+
+ @param {any} factory
+ @param {string} fullName
+ @return {function} toString function
+ */
+ makeToString: function(factory, fullName) {
+ return factory.toString();
+ },
+
+ /**
+ Given a fullName return a corresponding instance.
+
+ The default behaviour is for lookup to return a singleton instance.
+ The singleton is scoped to the container, allowing multiple containers
+ to all have their own locally scoped singletons.
+
+ ```javascript
+ var container = new Container();
+ container.register('api:twitter', Twitter);
+
+ var twitter = container.lookup('api:twitter');
+
+ twitter instanceof Twitter; // => true
+
+ // by default the container will return singletons
+ var twitter2 = container.lookup('api:twitter');
+ twitter instanceof Twitter; // => true
+
+ twitter === twitter2; //=> true
+ ```
+
+ If singletons are not wanted an optional flag can be provided at lookup.
+
+ ```javascript
+ var container = new Container();
+ container.register('api:twitter', Twitter);
+
+ var twitter = container.lookup('api:twitter', { singleton: false });
+ var twitter2 = container.lookup('api:twitter', { singleton: false });
+
+ twitter === twitter2; //=> false
+ ```
+
+ @method lookup
+ @param {String} fullName
+ @param {Object} options
+ @return {any}
+ */
+ lookup: function(fullName, options) {
+ validateFullName(fullName);
+ return lookup(this, this.normalize(fullName), options);
+ },
+
+ /**
+ Given a fullName return the corresponding factory.
+
+ @method lookupFactory
+ @param {String} fullName
+ @return {any}
+ */
+ lookupFactory: function(fullName) {
+ validateFullName(fullName);
+ return factoryFor(this, this.normalize(fullName));
+ },
+
+ /**
+ Given a fullName check if the container is aware of its factory
+ or singleton instance.
+
+ @method has
+ @param {String} fullName
+ @return {Boolean}
+ */
+ has: function(fullName) {
+ validateFullName(fullName);
+ return has(this, this.normalize(fullName));
+ },
+
+ /**
+ Allow registering options for all factories of a type.
+
+ ```javascript
+ var container = new Container();
+
+ // if all of type `connection` must not be singletons
+ container.optionsForType('connection', { singleton: false });
+
+ container.register('connection:twitter', TwitterConnection);
+ container.register('connection:facebook', FacebookConnection);
+
+ var twitter = container.lookup('connection:twitter');
+ var twitter2 = container.lookup('connection:twitter');
+
+ twitter === twitter2; // => false
+
+ var facebook = container.lookup('connection:facebook');
+ var facebook2 = container.lookup('connection:facebook');
+
+ facebook === facebook2; // => false
+ ```
+
+ @method optionsForType
+ @param {String} type
+ @param {Object} options
+ */
+ optionsForType: function(type, options) {
+ if (this.parent) { illegalChildOperation('optionsForType'); }
+
+ this._typeOptions.set(type, options);
+ },
+
+ /**
+ @method options
+ @param {String} type
+ @param {Object} options
+ */
+ options: function(type, options) {
+ this.optionsForType(type, options);
+ },
+
+ /**
+ Used only via `injection`.
+
+ Provides a specialized form of injection, specifically enabling
+ all objects of one type to be injected with a reference to another
+ object.
+
+ For example, provided each object of type `controller` needed a `router`.
+ one would do the following:
+
+ ```javascript
+ var container = new Container();
+
+ container.register('router:main', Router);
+ container.register('controller:user', UserController);
+ container.register('controller:post', PostController);
+
+ container.typeInjection('controller', 'router', 'router:main');
+
+ var user = container.lookup('controller:user');
+ var post = container.lookup('controller:post');
+
+ user.router instanceof Router; //=> true
+ post.router instanceof Router; //=> true
+
+ // both controllers share the same router
+ user.router === post.router; //=> true
+ ```
+
+ @private
+ @method typeInjection
+ @param {String} type
+ @param {String} property
+ @param {String} fullName
+ */
+ typeInjection: function(type, property, fullName) {
+ validateFullName(fullName);
+ if (this.parent) { illegalChildOperation('typeInjection'); }
+
+ var fullNameType = fullName.split(':')[0];
+ if(fullNameType === type) {
+ throw new Error('Cannot inject a `' + fullName + '` on other ' + type + '(s). Register the `' + fullName + '` as a different type and perform the typeInjection.');
+ }
+ addTypeInjection(this.typeInjections, type, property, fullName);
+ },
+
+ /**
+ Defines injection rules.
+
+ These rules are used to inject dependencies onto objects when they
+ are instantiated.
+
+ Two forms of injections are possible:
+
+ * Injecting one fullName on another fullName
+ * Injecting one fullName on a type
+
+ Example:
+
+ ```javascript
+ var container = new Container();
+
+ container.register('source:main', Source);
+ container.register('model:user', User);
+ container.register('model:post', Post);
+
+ // injecting one fullName on another fullName
+ // eg. each user model gets a post model
+ container.injection('model:user', 'post', 'model:post');
+
+ // injecting one fullName on another type
+ container.injection('model', 'source', 'source:main');
+
+ var user = container.lookup('model:user');
+ var post = container.lookup('model:post');
+
+ user.source instanceof Source; //=> true
+ post.source instanceof Source; //=> true
+
+ user.post instanceof Post; //=> true
+
+ // and both models share the same source
+ user.source === post.source; //=> true
+ ```
+
+ @method injection
+ @param {String} factoryName
+ @param {String} property
+ @param {String} injectionName
+ */
+ injection: function(fullName, property, injectionName) {
+ if (this.parent) { illegalChildOperation('injection'); }
+
+ validateFullName(injectionName);
+ var normalizedInjectionName = this.normalize(injectionName);
+
+ if (fullName.indexOf(':') === -1) {
+ return this.typeInjection(fullName, property, normalizedInjectionName);
+ }
+
+ validateFullName(fullName);
+ var normalizedName = this.normalize(fullName);
+
+ addInjection(this.injections, normalizedName, property, normalizedInjectionName);
+ },
+
+
+ /**
+ Used only via `factoryInjection`.
+
+ Provides a specialized form of injection, specifically enabling
+ all factory of one type to be injected with a reference to another
+ object.
+
+ For example, provided each factory of type `model` needed a `store`.
+ one would do the following:
+
+ ```javascript
+ var container = new Container();
+
+ container.register('store:main', SomeStore);
+
+ container.factoryTypeInjection('model', 'store', 'store:main');
+
+ var store = container.lookup('store:main');
+ var UserFactory = container.lookupFactory('model:user');
+
+ UserFactory.store instanceof SomeStore; //=> true
+ ```
+
+ @private
+ @method factoryTypeInjection
+ @param {String} type
+ @param {String} property
+ @param {String} fullName
+ */
+ factoryTypeInjection: function(type, property, fullName) {
+ if (this.parent) { illegalChildOperation('factoryTypeInjection'); }
+
+ addTypeInjection(this.factoryTypeInjections, type, property, this.normalize(fullName));
+ },
+
+ /**
+ Defines factory injection rules.
+
+ Similar to regular injection rules, but are run against factories, via
+ `Container#lookupFactory`.
+
+ These rules are used to inject objects onto factories when they
+ are looked up.
+
+ Two forms of injections are possible:
+
+ * Injecting one fullName on another fullName
+ * Injecting one fullName on a type
+
+ Example:
+
+ ```javascript
+ var container = new Container();
+
+ container.register('store:main', Store);
+ container.register('store:secondary', OtherStore);
+ container.register('model:user', User);
+ container.register('model:post', Post);
+
+ // injecting one fullName on another type
+ container.factoryInjection('model', 'store', 'store:main');
+
+ // injecting one fullName on another fullName
+ container.factoryInjection('model:post', 'secondaryStore', 'store:secondary');
+
+ var UserFactory = container.lookupFactory('model:user');
+ var PostFactory = container.lookupFactory('model:post');
+ var store = container.lookup('store:main');
+
+ UserFactory.store instanceof Store; //=> true
+ UserFactory.secondaryStore instanceof OtherStore; //=> false
+
+ PostFactory.store instanceof Store; //=> true
+ PostFactory.secondaryStore instanceof OtherStore; //=> true
+
+ // and both models share the same source instance
+ UserFactory.store === PostFactory.store; //=> true
+ ```
+
+ @method factoryInjection
+ @param {String} factoryName
+ @param {String} property
+ @param {String} injectionName
+ */
+ factoryInjection: function(fullName, property, injectionName) {
+ if (this.parent) { illegalChildOperation('injection'); }
+
+ var normalizedName = this.normalize(fullName);
+ var normalizedInjectionName = this.normalize(injectionName);
+
+ validateFullName(injectionName);
+
+ if (fullName.indexOf(':') === -1) {
+ return this.factoryTypeInjection(normalizedName, property, normalizedInjectionName);
+ }
+
+ validateFullName(fullName);
+
+ addInjection(this.factoryInjections, normalizedName, property, normalizedInjectionName);
+ },
+
+ /**
+ A depth first traversal, destroying the container, its descendant containers and all
+ their managed objects.
+
+ @method destroy
+ */
+ destroy: function() {
+ for (var i=0, l=this.children.length; i<l; i++) {
+ this.children[i].destroy();
+ }
+
+ this.children = [];
+
+ eachDestroyable(this, function(item) {
+ item.destroy();
+ });
+
+ this.parent = undefined;
+ this.isDestroyed = true;
+ },
+
+ /**
+ @method reset
+ */
+ reset: function() {
+ for (var i=0, l=this.children.length; i<l; i++) {
+ resetCache(this.children[i]);
+ }
+ resetCache(this);
+ }
+ };
+
+ function has(container, fullName){
+ if (container.cache.has(fullName)) {
+ return true;
+ }
+
+ return !!container.resolve(fullName);
+ }
+
+ function lookup(container, fullName, options) {
+ options = options || {};
+
+ if (container.cache.has(fullName) && options.singleton !== false) {
+ return container.cache.get(fullName);
+ }
+
+ var value = instantiate(container, fullName);
+
+ if (value === undefined) { return; }
+
+ if (isSingleton(container, fullName) && options.singleton !== false) {
+ container.cache.set(fullName, value);
+ }
+
+ return value;
+ }
+
+ function illegalChildOperation(operation) {
+ throw new Error(operation + " is not currently supported on child containers");
+ }
+
+ function isSingleton(container, fullName) {
+ var singleton = option(container, fullName, 'singleton');
+
+ return singleton !== false;
+ }
+
+ function buildInjections(container, injections) {
+ var hash = {};
+
+ if (!injections) { return hash; }
+
+ var injection, injectable;
+
+ for (var i=0, l=injections.length; i<l; i++) {
+ injection = injections[i];
+ injectable = lookup(container, injection.fullName);
+
+ if (injectable !== undefined) {
+ hash[injection.property] = injectable;
+ } else {
+ throw new Error('Attempting to inject an unknown injection: `' + injection.fullName + '`');
+ }
+ }
+
+ return hash;
+ }
+
+ function option(container, fullName, optionName) {
+ var options = container._options.get(fullName);
+
+ if (options && options[optionName] !== undefined) {
+ return options[optionName];
+ }
+
+ var type = fullName.split(":")[0];
+ options = container._typeOptions.get(type);
+
+ if (options) {
+ return options[optionName];
+ }
+ }
+
+ function factoryFor(container, fullName) {
+ var name = 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;
+ }
+ }
+
+ 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;
+ }
+
+ function instantiate(container, fullName) {
+ var factory = factoryFor(container, fullName);
+
+ if (option(container, fullName, 'instantiate') === false) {
+ return factory;
+ }
+
+ if (factory) {
+ 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));
+ }
+ }
+ }
+
+ function eachDestroyable(container, callback) {
+ container.cache.eachLocal(function(key, value) {
+ if (option(container, key, 'instantiate') === false) { return; }
+ callback(value);
+ });
+ }
+
+ function resetCache(container) {
+ container.cache.eachLocal(function(key, value) {
+ if (option(container, key, 'instantiate') === false) { return; }
+ value.destroy();
+ });
+ container.cache.dict = {};
+ }
+
+ function addTypeInjection(rules, type, property, fullName) {
+ var injections = rules.get(type);
+
+ if (!injections) {
+ injections = [];
+ rules.set(type, injections);
+ }
+
+ injections.push({
+ property: property,
+ fullName: fullName
+ });
+ }
+
+ var VALID_FULL_NAME_REGEXP = /^[^:]+.+:[^:]+$/;
+ function validateFullName(fullName) {
+ if (!VALID_FULL_NAME_REGEXP.test(fullName)) {
+ throw new TypeError('Invalid Fullname, expected: `type:name` got: ' + fullName);
+ }
+ }
+
+ function addInjection(rules, factoryName, property, injectionName) {
+ var injections = rules[factoryName] = rules[factoryName] || [];
+ injections.push({ property: property, fullName: injectionName });
+ }
+
+ __exports__["default"] = Container;
+ });define("ember-runtime/ext/rsvp",
+ ["ember-metal/core","ember-metal/logger","exports"],
+ function(__dependency1__, __dependency2__, __exports__) {
+ "use strict";
+ var Ember = __dependency1__["default"];
+ var Logger = __dependency2__["default"];
+
+ var RSVP = requireModule("rsvp");
+ var Test, testModuleName = 'ember-testing/test';
+
+ RSVP.onerrorDefault = function(error) {
+ if (error instanceof Error) {
+ if (Ember.testing) {
+ // ES6TODO: remove when possible
+ if (!Test && Ember.__loader.registry[testModuleName]) {
+ Test = requireModule(testModuleName)['default'];
+ }
+
+ if (Test && Test.adapter) {
+ Test.adapter.exception(error);
+ } else {
+ throw error;
+ }
+ } else {
+ Logger.error(error.stack);
+ Ember.assert(error, false);
+ }
+ }
+ };
+
+ RSVP.on('error', RSVP.onerrorDefault);
+
+ __exports__["default"] = RSVP;
+ });define("ember-runtime/system/container",
+ ["ember-metal/property_set","exports"],
+ function(__dependency1__, __exports__) {
+ "use strict";
+ var set = __dependency1__["default"];
+
+ var Container = requireModule('container')["default"];
+ Container.set = set;
+
+ __exports__["default"] = Container;
+ });(function() {
+/**
+ @module ember
+ @submodule ember-testing
+ */
+var slice = [].slice,
+ helpers = {},
+ injectHelpersCallbacks = [];
+
+/**
+ This is a container for an assortment of testing related functionality:
+
+ * Choose your default test adapter (for your framework of choice).
+ * Register/Unregister additional test helpers.
+ * Setup callbacks to be fired when the test helpers are injected into
+ your application.
+
+ @class Test
+ @namespace Ember
+*/
+Ember.Test = {
+
+ /**
+ `registerHelper` is used to register a test helper that will be injected
+ when `App.injectTestHelpers` is called.
+
+ The helper method will always be called with the current Application as
+ the first parameter.
+
+ For example:
+
+ ```javascript
+ Ember.Test.registerHelper('boot', function(app) {
+ Ember.run(app, app.advanceReadiness);
+ });
+ ```
+
+ This helper can later be called without arguments because it will be
+ called with `app` as the first parameter.
+
+ ```javascript
+ App = Ember.Application.create();
+ App.injectTestHelpers();
+ boot();
+ ```
+
+ @public
+ @method registerHelper
+ @param {String} name The name of the helper method to add.
+ @param {Function} helperMethod
+ @param options {Object}
+ */
+ registerHelper: function(name, helperMethod) {
+ helpers[name] = {
+ method: helperMethod,
+ meta: { wait: false }
+ };
+ },
+
+ /**
+ `registerAsyncHelper` is used to register an async test helper that will be injected
+ when `App.injectTestHelpers` is called.
+
+ The helper method will always be called with the current Application as
+ the first parameter.
+
+ For example:
+
+ ```javascript
+ Ember.Test.registerAsyncHelper('boot', function(app) {
+ Ember.run(app, app.advanceReadiness);
+ });
+ ```
+
+ The advantage of an async helper is that it will not run
+ until the last async helper has completed. All async helpers
+ after it will wait for it complete before running.
+
+
+ For example:
+
+ ```javascript
+ Ember.Test.registerAsyncHelper('deletePost', function(app, postId) {
+ click('.delete-' + postId);
+ });
+
+ // ... in your test
+ visit('/post/2');
+ deletePost(2);
+ visit('/post/3');
+ deletePost(3);
+ ```
+
+ @public
+ @method registerAsyncHelper
+ @param {String} name The name of the helper method to add.
+ @param {Function} helperMethod
+ */
+ registerAsyncHelper: function(name, helperMethod) {
+ helpers[name] = {
+ method: helperMethod,
+ meta: { wait: true }
+ };
+ },
+
+ /**
+ Remove a previously added helper method.
+
+ Example:
+
+ ```javascript
+ Ember.Test.unregisterHelper('wait');
+ ```
+
+ @public
+ @method unregisterHelper
+ @param {String} name The helper to remove.
+ */
+ unregisterHelper: function(name) {
+ delete helpers[name];
+ delete Ember.Test.Promise.prototype[name];
+ },
+
+ /**
+ Used to register callbacks to be fired whenever `App.injectTestHelpers`
+ is called.
+
+ The callback will receive the current application as an argument.
+
+ Example:
+
+ ```javascript
+ Ember.Test.onInjectHelpers(function() {
+ Ember.$(document).ajaxSend(function() {
+ Test.pendingAjaxRequests++;
+ });
+
+ Ember.$(document).ajaxComplete(function() {
+ Test.pendingAjaxRequests--;
+ });
+ });
+ ```
+
+ @public
+ @method onInjectHelpers
+ @param {Function} callback The function to be called.
+ */
+ onInjectHelpers: function(callback) {
+ injectHelpersCallbacks.push(callback);
+ },
+
+ /**
+ This returns a thenable tailored for testing. It catches failed
+ `onSuccess` callbacks and invokes the `Ember.Test.adapter.exception`
+ callback in the last chained then.
+
+ This method should be returned by async helpers such as `wait`.
+
+ @public
+ @method promise
+ @param {Function} resolver The function used to resolve the promise.
+ */
+ promise: function(resolver) {
+ return new Ember.Test.Promise(resolver);
+ },
+
+ /**
+ Used to allow ember-testing to communicate with a specific testing
+ framework.
+
+ You can manually set it before calling `App.setupForTesting()`.
+
+ Example:
+
+ ```javascript
+ Ember.Test.adapter = MyCustomAdapter.create()
+ ```
+
+ If you do not set it, ember-testing will default to `Ember.Test.QUnitAdapter`.
+
+ @public
+ @property adapter
+ @type {Class} The adapter to be used.
+ @default Ember.Test.QUnitAdapter
+ */
+ adapter: null,
+
+ /**
+ Replacement for `Ember.RSVP.resolve`
+ The only difference is this uses
+ and instance of `Ember.Test.Promise`
+
+ @public
+ @method resolve
+ @param {Mixed} The value to resolve
+ */
+ resolve: function(val) {
+ return Ember.Test.promise(function(resolve) {
+ return resolve(val);
+ });
+ },
+
+ /**
+ This allows ember-testing to play nicely with other asynchronous
+ events, such as an application that is waiting for a CSS3
+ transition or an IndexDB transaction.
+
+ For example:
+
+ ```javascript
+ Ember.Test.registerWaiter(function() {
+ return myPendingTransactions() == 0;
+ });
+ ```
+ The `context` argument allows you to optionally specify the `this`
+ with which your callback will be invoked.
+
+ For example:
+
+ ```javascript
+ Ember.Test.registerWaiter(MyDB, MyDB.hasPendingTransactions);
+ ```
+
+ @public
+ @method registerWaiter
+ @param {Object} context (optional)
+ @param {Function} callback
+ */
+ registerWaiter: function(context, callback) {
+ if (arguments.length === 1) {
+ callback = context;
+ context = null;
+ }
+ if (!this.waiters) {
+ this.waiters = Ember.A();
+ }
+ this.waiters.push([context, callback]);
+ },
+ /**
+ `unregisterWaiter` is used to unregister a callback that was
+ registered with `registerWaiter`.
+
+ @public
+ @method unregisterWaiter
+ @param {Object} context (optional)
+ @param {Function} callback
+ */
+ unregisterWaiter: function(context, callback) {
+ var pair;
+ if (!this.waiters) { return; }
+ if (arguments.length === 1) {
+ callback = context;
+ context = null;
+ }
+ pair = [context, callback];
+ this.waiters = Ember.A(this.waiters.filter(function(elt) {
+ return Ember.compare(elt, pair)!==0;
+ }));
+ }
+};
+
+function helper(app, name) {
+ var fn = helpers[name].method,
+ meta = helpers[name].meta;
+
+ return function() {
+ var args = slice.call(arguments),
+ lastPromise = Ember.Test.lastPromise;
+
+ args.unshift(app);
+
+ // some helpers are not async and
+ // need to return a value immediately.
+ // example: `find`
+ if (!meta.wait) {
+ return fn.apply(app, args);
+ }
+
+ if (!lastPromise) {
+ // It's the first async helper in current context
+ lastPromise = fn.apply(app, args);
+ } else {
+ // wait for last helper's promise to resolve
+ // and then execute
+ run(function() {
+ lastPromise = Ember.Test.resolve(lastPromise).then(function() {
+ return fn.apply(app, args);
+ });
+ });
+ }
+
+ return lastPromise;
+ };
+}
+
+function run(fn) {
+ if (!Ember.run.currentRunLoop) {
+ Ember.run(fn);
+ } else {
+ fn();
+ }
+}
+
+Ember.Application.reopen({
+ /**
+ This property contains the testing helpers for the current application. These
+ are created once you call `injectTestHelpers` on your `Ember.Application`
+ instance. The included helpers are also available on the `window` object by
+ default, but can be used from this object on the individual application also.
+
+ @property testHelpers
+ @type {Object}
+ @default {}
+ */
+ testHelpers: {},
+
+ /**
+ This property will contain the original methods that were registered
+ on the `helperContainer` before `injectTestHelpers` is called.
+
+ When `removeTestHelpers` is called, these methods are restored to the
+ `helperContainer`.
+
+ @property originalMethods
+ @type {Object}
+ @default {}
+ @private
+ */
+ originalMethods: {},
+
+
+ /**
+ This property indicates whether or not this application is currently in
+ testing mode. This is set when `setupForTesting` is called on the current
+ application.
+
+ @property testing
+ @type {Boolean}
+ @default false
+ */
+ testing: false,
+
+ /**
+ This hook defers the readiness of the application, so that you can start
+ the app when your tests are ready to run. It also sets the router's
+ location to 'none', so that the window's location will not be modified
+ (preventing both accidental leaking of state between tests and interference
+ with your testing framework).
+
+ Example:
+
+ ```
+ App.setupForTesting();
+ ```
+
+ @method setupForTesting
+ */
+ setupForTesting: function() {
+ Ember.setupForTesting();
+
+ this.testing = true;
+
+ this.Router.reopen({
+ location: 'none'
+ });
+ },
+
+ /**
+ This will be used as the container to inject the test helpers into. By
+ default the helpers are injected into `window`.
+
+ @property helperContainer
+ @type {Object} The object to be used for test helpers.
+ @default window
+ */
+ helperContainer: window,
+
+ /**
+ This injects the test helpers into the `helperContainer` object. If an object is provided
+ it will be used as the helperContainer. If `helperContainer` is not set it will default
+ to `window`. If a function of the same name has already been defined it will be cached
+ (so that it can be reset if the helper is removed with `unregisterHelper` or
+ `removeTestHelpers`).
+
+ Any callbacks registered with `onInjectHelpers` will be called once the
+ helpers have been injected.
+
+ Example:
+ ```
+ App.injectTestHelpers();
+ ```
+
+ @method injectTestHelpers
+ */
+ injectTestHelpers: function(helperContainer) {
+ if (helperContainer) { this.helperContainer = helperContainer; }
+
+ this.testHelpers = {};
+ for (var name in helpers) {
+ this.originalMethods[name] = this.helperContainer[name];
+ this.testHelpers[name] = this.helperContainer[name] = helper(this, name);
+ protoWrap(Ember.Test.Promise.prototype, name, helper(this, name), helpers[name].meta.wait);
+ }
+
+ for(var i = 0, l = injectHelpersCallbacks.length; i < l; i++) {
+ injectHelpersCallbacks[i](this);
+ }
+ },
+
+ /**
+ This removes all helpers that have been registered, and resets and functions
+ that were overridden by the helpers.
+
+ Example:
+
+ ```javascript
+ App.removeTestHelpers();
+ ```
+
+ @public
+ @method removeTestHelpers
+ */
+ removeTestHelpers: function() {
+ for (var name in helpers) {
+ this.helperContainer[name] = this.originalMethods[name];
+ delete this.testHelpers[name];
+ delete this.originalMethods[name];
+ }
+ }
+});
+
+// This method is no longer needed
+// But still here for backwards compatibility
+// of helper chaining
+function protoWrap(proto, name, callback, isAsync) {
+ proto[name] = function() {
+ var args = arguments;
+ if (isAsync) {
+ return callback.apply(this, args);
+ } else {
+ return this.then(function() {
+ return callback.apply(this, args);
+ });
+ }
+ };
+}
+
+Ember.Test.Promise = function() {
+ Ember.RSVP.Promise.apply(this, arguments);
+ Ember.Test.lastPromise = this;
+};
+
+Ember.Test.Promise.prototype = Ember.create(Ember.RSVP.Promise.prototype);
+Ember.Test.Promise.prototype.constructor = Ember.Test.Promise;
+
+// Patch `then` to isolate async methods
+// specifically `Ember.Test.lastPromise`
+var originalThen = Ember.RSVP.Promise.prototype.then;
+Ember.Test.Promise.prototype.then = function(onSuccess, onFailure) {
+ return originalThen.call(this, function(val) {
+ return isolate(onSuccess, val);
+ }, onFailure);
+};
+
+// This method isolates nested async methods
+// so that they don't conflict with other last promises.
+//
+// 1. Set `Ember.Test.lastPromise` to null
+// 2. Invoke method
+// 3. Return the last promise created during method
+// 4. Restore `Ember.Test.lastPromise` to original value
+function isolate(fn, val) {
+ var value, lastPromise;
+
+ // Reset lastPromise for nested helpers
+ Ember.Test.lastPromise = null;
+
+ value = fn(val);
+
+ lastPromise = Ember.Test.lastPromise;
+
+ // If the method returned a promise
+ // return that promise. If not,
+ // return the last async helper's promise
+ if ((value && (value instanceof Ember.Test.Promise)) || !lastPromise) {
+ return value;
+ } else {
+ run(function() {
+ lastPromise = Ember.Test.resolve(lastPromise).then(function() {
+ return value;
+ });
+ });
+ return lastPromise;
+ }
+}
+
+})();
+
+
+
+(function() {
+var Test = Ember.Test;
+
+function incrementAjaxPendingRequests(){
+ Test.pendingAjaxRequests++;
+}
+
+function decrementAjaxPendingRequests(){
+ Ember.assert("An ajaxComplete event which would cause the number of pending AJAX " +
+ "requests to be negative has been triggered. This is most likely " +
+ "caused by AJAX events that were started before calling " +
+ "`injectTestHelpers()`.", Test.pendingAjaxRequests !== 0);
+ Test.pendingAjaxRequests--;
+}
+
+/**
+ Sets Ember up for testing. This is useful to perform
+ basic setup steps in order to unit test.
+
+ Use `App.setupForTesting` to perform integration tests (full
+ application testing).
+
+ @method setupForTesting
+ @namespace Ember
+*/
+Ember.setupForTesting = function() {
+ Ember.testing = true;
+
+ // if adapter is not manually set default to QUnit
+ if (!Ember.Test.adapter) {
+ Ember.Test.adapter = Ember.Test.QUnitAdapter.create();
+ }
+
+ if (!Test.pendingAjaxRequests) {
+ Test.pendingAjaxRequests = 0;
+ }
+
+ Ember.$(document).off('ajaxSend', incrementAjaxPendingRequests);
+ Ember.$(document).off('ajaxComplete', decrementAjaxPendingRequests);
+ Ember.$(document).on('ajaxSend', incrementAjaxPendingRequests);
+ Ember.$(document).on('ajaxComplete', decrementAjaxPendingRequests);
+};
+
+})();
+
+
+
+(function() {
+Ember.onLoad('Ember.Application', function(Application) {
+ Application.initializer({
+ name: 'deferReadiness in `testing` mode',
+
+ initialize: function(container, application){
+ if (application.testing) {
+ application.deferReadiness();
+ }
+ }
+ });
+});
+
+})();
+
+
+
+(function() {
+/**
+ @module ember
+ @submodule ember-testing
+ */
+
+var $ = Ember.$;
+
+/**
+ This method creates a checkbox and triggers the click event to fire the
+ passed in handler. It is used to correct for a bug in older versions
+ of jQuery (e.g 1.8.3).
+
+ @private
+ @method testCheckboxClick
+*/
+function testCheckboxClick(handler) {
+ $('<input type="checkbox">')
+ .css({ position: 'absolute', left: '-1000px', top: '-1000px' })
+ .appendTo('body')
+ .on('click', handler)
+ .trigger('click')
+ .remove();
+}
+
+$(function() {
+ /*
+ Determine whether a checkbox checked using jQuery's "click" method will have
+ the correct value for its checked property.
+
+ If we determine that the current jQuery version exhibits this behavior,
+ patch it to work correctly as in the commit for the actual fix:
+ https://github.com/jquery/jquery/commit/1fb2f92.
+ */
+ testCheckboxClick(function() {
+ if (!this.checked && !$.event.special.click) {
+ $.event.special.click = {
+ // For checkbox, fire native event so checked state will be right
+ trigger: function() {
+ if ($.nodeName( this, "input" ) && this.type === "checkbox" && this.click) {
+ this.click();
+ return false;
+ }
+ }
+ };
+ }
+ });
+
+ // Try again to verify that the patch took effect or blow up.
+ testCheckboxClick(function() {
+ Ember.warn("clicked checkboxes should be checked! the jQuery patch didn't work", this.checked);
+ });
+});
+
+})();
+
+
+
+(function() {
+/**
+ @module ember
+ @submodule ember-testing
+*/
+
+var Test = Ember.Test;
+
+/**
+ The primary purpose of this class is to create hooks that can be implemented
+ by an adapter for various test frameworks.
+
+ @class Adapter
+ @namespace Ember.Test
+*/
+Test.Adapter = Ember.Object.extend({
+ /**
+ This callback will be called whenever an async operation is about to start.
+
+ Override this to call your framework's methods that handle async
+ operations.
+
+ @public
+ @method asyncStart
+ */
+ asyncStart: Ember.K,
+
+ /**
+ This callback will be called whenever an async operation has completed.
+
+ @public
+ @method asyncEnd
+ */
+ asyncEnd: Ember.K,
+
+ /**
+ Override this method with your testing framework's false assertion.
+ This function is called whenever an exception occurs causing the testing
+ promise to fail.
+
+ QUnit example:
+
+ ```javascript
+ exception: function(error) {
+ ok(false, error);
+ };
+ ```
+
+ @public
+ @method exception
+ @param {String} error The exception to be raised.
+ */
+ exception: function(error) {
+ throw error;
+ }
+});
+
+/**
+ This class implements the methods defined by Ember.Test.Adapter for the
+ QUnit testing framework.
+
+ @class QUnitAdapter
+ @namespace Ember.Test
+ @extends Ember.Test.Adapter
+*/
+Test.QUnitAdapter = Test.Adapter.extend({
+ asyncStart: function() {
+ stop();
+ },
+ asyncEnd: function() {
+ start();
+ },
+ exception: function(error) {
+ ok(false, Ember.inspect(error));
+ }
+});
+
+})();
+
+
+
+(function() {
+/**
+* @module ember
+* @submodule ember-testing
+*/
+
+var get = Ember.get,
+ Test = Ember.Test,
+ helper = Test.registerHelper,
+ asyncHelper = Test.registerAsyncHelper,
+ countAsync = 0;
+
+function currentRouteName(app){
+ var appController = app.__container__.lookup('controller:application');
+
+ return get(appController, 'currentRouteName');
+}
+
+function currentPath(app){
+ var appController = app.__container__.lookup('controller:application');
+
+ return get(appController, 'currentPath');
+}
+
+function currentURL(app){
+ var router = app.__container__.lookup('router:main');
+
+ return get(router, 'location').getURL();
+}
+
+function visit(app, url) {
+ var router = app.__container__.lookup('router:main');
+ router.location.setURL(url);
+
+ if (app._readinessDeferrals > 0) {
+ router['initialURL'] = url;
+ Ember.run(app, 'advanceReadiness');
+ delete router['initialURL'];
+ } else {
+ Ember.run(app, app.handleURL, url);
+ }
+
+ return wait(app);
+}
+
+function click(app, selector, context) {
+ var $el = findWithAssert(app, selector, context);
+ Ember.run($el, 'mousedown');
+
+ if ($el.is(':input')) {
+ var type = $el.prop('type');
+ if (type !== 'checkbox' && type !== 'radio' && type !== 'hidden') {
+ Ember.run($el, function(){
+ // Firefox does not trigger the `focusin` event if the window
+ // does not have focus. If the document doesn't have focus just
+ // use trigger('focusin') instead.
+ if (!document.hasFocus || document.hasFocus()) {
+ this.focus();
+ } else {
+ this.trigger('focusin');
+ }
+ });
+ }
+ }
+
+ Ember.run($el, 'mouseup');
+ Ember.run($el, 'click');
+
+ return wait(app);
+}
+
+function triggerEvent(app, selector, context, type, options){
+ if (arguments.length === 3) {
+ type = context;
+ context = null;
+ }
+
+ if (typeof options === 'undefined') {
+ options = {};
+ }
+
+ var $el = findWithAssert(app, selector, context);
+
+ var event = Ember.$.Event(type, options);
+
+ Ember.run($el, 'trigger', event);
+
+ return wait(app);
+}
+
+function keyEvent(app, selector, context, type, keyCode) {
+ if (typeof keyCode === 'undefined') {
+ keyCode = type;
+ type = context;
+ context = null;
+ }
+
+ return triggerEvent(app, selector, context, type, { keyCode: keyCode, which: keyCode });
+}
+
+function fillIn(app, selector, context, text) {
+ var $el;
+ if (typeof text === 'undefined') {
+ text = context;
+ context = null;
+ }
+ $el = findWithAssert(app, selector, context);
+ Ember.run(function() {
+ $el.val(text).change();
+ });
+ return wait(app);
+}
+
+function findWithAssert(app, selector, context) {
+ var $el = find(app, selector, context);
+ if ($el.length === 0) {
+ throw new Ember.Error("Element " + selector + " not found.");
+ }
+ return $el;
+}
+
+function find(app, selector, context) {
+ var $el;
+ context = context || get(app, 'rootElement');
+ $el = app.$(selector, context);
+
+ return $el;
+}
+
+function andThen(app, callback) {
+ return wait(app, callback(app));
+}
+
+function wait(app, value) {
+ return Test.promise(function(resolve) {
+ // If this is the first async promise, kick off the async test
+ if (++countAsync === 1) {
+ Test.adapter.asyncStart();
+ }
+
+ // Every 10ms, poll for the async thing to have finished
+ var watcher = setInterval(function() {
+ // 1. If the router is loading, keep polling
+ var routerIsLoading = !!app.__container__.lookup('router:main').router.activeTransition;
+ if (routerIsLoading) { return; }
+
+ // 2. If there are pending Ajax requests, keep polling
+ if (Test.pendingAjaxRequests) { return; }
+
+ // 3. If there are scheduled timers or we are inside of a run loop, keep polling
+ if (Ember.run.hasScheduledTimers() || Ember.run.currentRunLoop) { return; }
+ if (Test.waiters && Test.waiters.any(function(waiter) {
+ var context = waiter[0];
+ var callback = waiter[1];
+ return !callback.call(context);
+ })) { return; }
+ // Stop polling
+ clearInterval(watcher);
+
+ // If this is the last async promise, end the async test
+ if (--countAsync === 0) {
+ Test.adapter.asyncEnd();
+ }
+
+ // Synchronously resolve the promise
+ Ember.run(null, resolve, value);
+ }, 10);
+ });
+
+}
+
+
+/**
+* Loads a route, sets up any controllers, and renders any templates associated
+* with the route as though a real user had triggered the route change while
+* using your app.
+*
+* Example:
+*
+* ```javascript
+* visit('posts/index').then(function() {
+* // assert something
+* });
+* ```
+*
+* @method visit
+* @param {String} url the name of the route
+* @return {RSVP.Promise}
+*/
+asyncHelper('visit', visit);
+
+/**
+* Clicks an element and triggers any actions triggered by the element's `click`
+* event.
+*
+* Example:
+*
+* ```javascript
+* click('.some-jQuery-selector').then(function() {
+* // assert something
+* });
+* ```
+*
+* @method click
+* @param {String} selector jQuery selector for finding element on the DOM
+* @return {RSVP.Promise}
+*/
+asyncHelper('click', click);
+
+/**
+* Simulates a key event, e.g. `keypress`, `keydown`, `keyup` with the desired keyCode
+*
+* Example:
+*
+* ```javascript
+* keyEvent('.some-jQuery-selector', 'keypress', 13).then(function() {
+* // assert something
+* });
+* ```
+*
+* @method keyEvent
+* @param {String} selector jQuery selector for finding element on the DOM
+* @param {String} type the type of key event, e.g. `keypress`, `keydown`, `keyup`
+* @param {Number} keyCode the keyCode of the simulated key event
+* @return {RSVP.Promise}
+*/
+asyncHelper('keyEvent', keyEvent);
+
+/**
+* Fills in an input element with some text.
+*
+* Example:
+*
+* ```javascript
+* fillIn('#email', 'you(a)example.com').then(function() {
+* // assert something
+* });
+* ```
+*
+* @method fillIn
+* @param {String} selector jQuery selector finding an input element on the DOM
+* to fill text with
+* @param {String} text text to place inside the input element
+* @return {RSVP.Promise}
+*/
+asyncHelper('fillIn', fillIn);
+
+/**
+* Finds an element in the context of the app's container element. A simple alias
+* for `app.$(selector)`.
+*
+* Example:
+*
+* ```javascript
+* var $el = find('.my-selector');
+* ```
+*
+* @method find
+* @param {String} selector jQuery string selector for element lookup
+* @return {Object} jQuery object representing the results of the query
+*/
+helper('find', find);
+
+/**
+* Like `find`, but throws an error if the element selector returns no results.
+*
+* Example:
+*
+* ```javascript
+* var $el = findWithAssert('.doesnt-exist'); // throws error
+* ```
+*
+* @method findWithAssert
+* @param {String} selector jQuery selector string for finding an element within
+* the DOM
+* @return {Object} jQuery object representing the results of the query
+* @throws {Error} throws error if jQuery object returned has a length of 0
+*/
+helper('findWithAssert', findWithAssert);
+
+/**
+ Causes the run loop to process any pending events. This is used to ensure that
+ any async operations from other helpers (or your assertions) have been processed.
+
+ This is most often used as the return value for the helper functions (see 'click',
+ 'fillIn','visit',etc).
+
+ Example:
+
+ ```javascript
+ Ember.Test.registerAsyncHelper('loginUser', function(app, username, password) {
+ visit('secured/path/here')
+ .fillIn('#username', username)
+ .fillIn('#password', username)
+ .click('.submit')
+
+ return wait();
+ });
+
+ @method wait
+ @param {Object} value The value to be returned.
+ @return {RSVP.Promise}
+*/
+asyncHelper('wait', wait);
+asyncHelper('andThen', andThen);
+
+
+
+ /**
+ Returns the currently active route name.
+
+ Example:
+
+ ```javascript
+ function validateRouteName(){
+ equal(currentRouteName(), 'some.path', "correct route was transitioned into.");
+ }
+
+ visit('/some/path').then(validateRouteName)
+ ```
+
+ @method currentRouteName
+ @return {Object} The name of the currently active route.
+ */
+ helper('currentRouteName', currentRouteName);
+
+ /**
+ Returns the current path.
+
+ Example:
+
+ ```javascript
+ function validateURL(){
+ equal(currentPath(), 'some.path.index', "correct path was transitioned into.");
+ }
+
+ click('#some-link-id').then(validateURL);
+ ```
+
+ @method currentPath
+ @return {Object} The currently active path.
+ */
+ helper('currentPath', currentPath);
+
+ /**
+ Returns the current URL.
+
+ Example:
+
+ ```javascript
+ function validateURL(){
+ equal(currentURL(), '/some/path', "correct URL was transitioned into.");
+ }
+
+ click('#some-link-id').then(validateURL);
+ ```
+
+ @method currentURL
+ @return {Object} The currently active URL.
+ */
+ helper('currentURL', currentURL);
+
+
+
+ /**
+ Triggers the given event on the element identified by the provided selector.
+
+ Example:
+
+ ```javascript
+ triggerEvent('#some-elem-id', 'blur');
+ ```
+
+ This is actually used internally by the `keyEvent` helper like so:
+
+ ```javascript
+ triggerEvent('#some-elem-id', 'keypress', { keyCode: 13 });
+ ```
+
+ @method triggerEvent
+ @param {String} selector jQuery selector for finding element on the DOM
+ @param {String} type The event type to be triggered.
+ @param {String} options The options to be passed to jQuery.Event.
+ @return {RSVP.Promise}
+ */
+ asyncHelper('triggerEvent', triggerEvent);
+
+
+})();
+
+
+
+(function() {
+/**
+ Ember Testing
+
+ @module ember
+ @submodule ember-testing
+ @requires ember-application
+*/
+
+})();
+
+(function() {
+/**
+Ember
+
+@module ember
+*/
+
+function throwWithMessage(msg) {
+ return function() {
+ throw new Ember.Error(msg);
+ };
+}
+
+function generateRemovedClass(className) {
+ var msg = " has been moved into a plugin: https://github.com/emberjs/ember-states";
+
+ return {
+ extend: throwWithMessage(className + msg),
+ create: throwWithMessage(className + msg)
+ };
+}
+
+Ember.StateManager = generateRemovedClass("Ember.StateManager");
+
+/**
+ This was exported to ember-states plugin for v 1.0.0 release. See: https://github.com/emberjs/ember-states
+
+ @class StateManager
+ @namespace Ember
+*/
+
+Ember.State = generateRemovedClass("Ember.State");
+
+/**
+ This was exported to ember-states plugin for v 1.0.0 release. See: https://github.com/emberjs/ember-states
+
+ @class State
+ @namespace Ember
+*/
+
+})();
+
+
+})();
Added: test-js-frameworks/emberJS/js/libs/handlebars-1.1.2.js
===================================================================
--- test-js-frameworks/emberJS/js/libs/handlebars-1.1.2.js (rev 0)
+++ test-js-frameworks/emberJS/js/libs/handlebars-1.1.2.js 2014-04-17 09:35:38 UTC (rev 701)
@@ -0,0 +1,2595 @@
+/*!
+
+ handlebars v1.1.2
+
+Copyright (C) 2011 by Yehuda Katz
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
+
+@license
+*/
+var Handlebars = (function() {
+// handlebars/safe-string.js
+var __module4__ = (function() {
+ "use strict";
+ var __exports__;
+ // Build out our basic SafeString type
+ function SafeString(string) {
+ this.string = string;
+ }
+
+ SafeString.prototype.toString = function() {
+ return "" + this.string;
+ };
+
+ __exports__ = SafeString;
+ return __exports__;
+})();
+
+// handlebars/utils.js
+var __module3__ = (function(__dependency1__) {
+ "use strict";
+ var __exports__ = {};
+ var SafeString = __dependency1__;
+
+ var escape = {
+ "&": "&",
+ "<": "<",
+ ">": ">",
+ '"': """,
+ "'": "'",
+ "`": "`"
+ };
+
+ var badChars = /[&<>"'`]/g;
+ var possible = /[&<>"'`]/;
+
+ function escapeChar(chr) {
+ return escape[chr] || "&";
+ }
+
+ function extend(obj, value) {
+ for(var key in value) {
+ if(value.hasOwnProperty(key)) {
+ obj[key] = value[key];
+ }
+ }
+ }
+
+ __exports__.extend = extend;var toString = Object.prototype.toString;
+ __exports__.toString = toString;
+ // Sourced from lodash
+ // https://github.com/bestiejs/lodash/blob/master/LICENSE.txt
+ var isFunction = function(value) {
+ return typeof value === 'function';
+ };
+ // fallback for older versions of Chrome and Safari
+ if (isFunction(/x/)) {
+ isFunction = function(value) {
+ return typeof value === 'function' && toString.call(value) === '[object Function]';
+ };
+ }
+ var isFunction;
+ __exports__.isFunction = isFunction;
+ var isArray = Array.isArray || function(value) {
+ return (value && typeof value === 'object') ? toString.call(value) === '[object Array]' : false;
+ };
+ __exports__.isArray = isArray;
+
+ function escapeExpression(string) {
+ // don't escape SafeStrings, since they're already safe
+ if (string instanceof SafeString) {
+ return string.toString();
+ } else if (!string && string !== 0) {
+ return "";
+ }
+
+ // Force a string conversion as this will be done by the append regardless and
+ // the regex test will do this transparently behind the scenes, causing issues if
+ // an object's to string has escaped characters in it.
+ string = "" + string;
+
+ if(!possible.test(string)) { return string; }
+ return string.replace(badChars, escapeChar);
+ }
+
+ __exports__.escapeExpression = escapeExpression;function isEmpty(value) {
+ if (!value && value !== 0) {
+ return true;
+ } else if (isArray(value) && value.length === 0) {
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+ __exports__.isEmpty = isEmpty;
+ return __exports__;
+})(__module4__);
+
+// handlebars/exception.js
+var __module5__ = (function() {
+ "use strict";
+ var __exports__;
+
+ var errorProps = ['description', 'fileName', 'lineNumber', 'message', 'name', 'number', 'stack'];
+
+ function Exception(/* message */) {
+ 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]];
+ }
+ }
+
+ Exception.prototype = new Error();
+
+ __exports__ = Exception;
+ return __exports__;
+})();
+
+// handlebars/base.js
+var __module2__ = (function(__dependency1__, __dependency2__) {
+ "use strict";
+ var __exports__ = {};
+ /*globals Exception, Utils */
+ var Utils = __dependency1__;
+ var Exception = __dependency2__;
+
+ var VERSION = "1.1.2";
+ __exports__.VERSION = VERSION;var COMPILER_REVISION = 4;
+ __exports__.COMPILER_REVISION = COMPILER_REVISION;
+ var REVISION_CHANGES = {
+ 1: '<= 1.0.rc.2', // 1.0.rc.2 is actually rev2 but doesn't report it
+ 2: '== 1.0.0-rc.3',
+ 3: '== 1.0.0-rc.4',
+ 4: '>= 1.0.0'
+ };
+ __exports__.REVISION_CHANGES = REVISION_CHANGES;
+ var isArray = Utils.isArray,
+ isFunction = Utils.isFunction,
+ toString = Utils.toString,
+ objectType = '[object Object]';
+
+ function HandlebarsEnvironment(helpers, partials) {
+ this.helpers = helpers || {};
+ this.partials = partials || {};
+
+ registerDefaultHelpers(this);
+ }
+
+ __exports__.HandlebarsEnvironment = HandlebarsEnvironment;HandlebarsEnvironment.prototype = {
+ constructor: HandlebarsEnvironment,
+
+ logger: logger,
+ log: log,
+
+ registerHelper: function(name, fn, inverse) {
+ if (toString.call(name) === objectType) {
+ if (inverse || fn) { throw new Exception('Arg not supported with multiple helpers'); }
+ Utils.extend(this.helpers, name);
+ } else {
+ if (inverse) { fn.not = inverse; }
+ this.helpers[name] = fn;
+ }
+ },
+
+ registerPartial: function(name, str) {
+ if (toString.call(name) === objectType) {
+ Utils.extend(this.partials, name);
+ } else {
+ this.partials[name] = str;
+ }
+ }
+ };
+
+ function registerDefaultHelpers(instance) {
+ instance.registerHelper('helperMissing', function(arg) {
+ if(arguments.length === 2) {
+ return undefined;
+ } else {
+ throw new Error("Missing helper: '" + arg + "'");
+ }
+ });
+
+ instance.registerHelper('blockHelperMissing', function(context, options) {
+ var inverse = options.inverse || function() {}, fn = options.fn;
+
+ if (isFunction(context)) { context = context.call(this); }
+
+ if(context === true) {
+ return fn(this);
+ } else if(context === false || context == null) {
+ return inverse(this);
+ } else if (isArray(context)) {
+ if(context.length > 0) {
+ return instance.helpers.each(context, options);
+ } else {
+ return inverse(this);
+ }
+ } else {
+ return fn(context);
+ }
+ });
+
+ instance.registerHelper('each', function(context, options) {
+ var fn = options.fn, inverse = options.inverse;
+ var i = 0, ret = "", data;
+
+ if (isFunction(context)) { context = context.call(this); }
+
+ if (options.data) {
+ data = createFrame(options.data);
+ }
+
+ if(context && typeof context === 'object') {
+ if (isArray(context)) {
+ for(var j = context.length; i<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; }
+ ret = ret + fn(context[key], {data: data});
+ i++;
+ }
+ }
+ }
+ }
+
+ if(i === 0){
+ ret = inverse(this);
+ }
+
+ return ret;
+ });
+
+ instance.registerHelper('if', function(conditional, options) {
+ if (isFunction(conditional)) { conditional = conditional.call(this); }
+
+ // 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);
+ });
+ }
+
+ var logger = {
+ methodMap: { 0: 'debug', 1: 'info', 2: 'warn', 3: 'error' },
+
+ // State enum
+ DEBUG: 0,
+ INFO: 1,
+ WARN: 2,
+ ERROR: 3,
+ level: 3,
+
+ // 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); }
+
+ __exports__.log = log;var createFrame = function(object) {
+ var obj = {};
+ Utils.extend(obj, object);
+ return obj;
+ };
+ __exports__.createFrame = createFrame;
+ return __exports__;
+})(__module3__, __module5__);
+
+// handlebars/runtime.js
+var __module6__ = (function(__dependency1__, __dependency2__, __dependency3__) {
+ "use strict";
+ var __exports__ = {};
+ /*global Utils */
+ var Utils = __dependency1__;
+ var Exception = __dependency2__;
+ var COMPILER_REVISION = __dependency3__.COMPILER_REVISION;
+ var REVISION_CHANGES = __dependency3__.REVISION_CHANGES;
+
+ 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 Error("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 Error("Template was precompiled with a newer version of Handlebars than the current runtime. "+
+ "Please update your runtime to a newer version ("+compilerInfo[1]+").");
+ }
+ }
+ }
+
+ // TODO: Remove this line and break up compilePartial
+
+ function template(templateSpec, env) {
+ if (!env) {
+ throw new Error("No environment passed to template");
+ }
+
+ var invokePartialWrapper;
+ if (env.compile) {
+ invokePartialWrapper = function(partial, name, context, helpers, partials, data) {
+ // TODO : Check this for all inputs and the options handling (partial flag, etc). This feels
+ // like there should be a common exec path
+ var result = invokePartial.apply(this, arguments);
+ if (result) { return result; }
+
+ var options = { helpers: helpers, partials: partials, data: data };
+ partials[name] = env.compile(partial, { data: data !== undefined }, env);
+ return partials[name](context, options);
+ };
+ } else {
+ invokePartialWrapper = function(partial, name /* , context, helpers, partials, data */) {
+ var result = invokePartial.apply(this, arguments);
+ if (result) { return result; }
+ 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);
+ }
+ return programWrapper;
+ },
+ merge: function(param, common) {
+ var ret = param || common;
+
+ if (param && common && (param !== common)) {
+ ret = {};
+ Utils.extend(ret, common);
+ Utils.extend(ret, param);
+ }
+ return ret;
+ },
+ programWithDepth: programWithDepth,
+ noop: 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) {
+ 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);
+ }
+ }
+
+ __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__;
+ 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 ProgramNode(statements, inverseStrip, inverse) {
+ this.type = "program";
+ this.statements = statements;
+ this.strip = {};
+
+ if(inverse) {
+ this.inverse = new ProgramNode(inverse, inverseStrip);
+ this.strip.right = inverseStrip.left;
+ } else if (inverseStrip) {
+ this.strip.left = inverseStrip.right;
+ }
+ }
+
+ __exports__.ProgramNode = ProgramNode;function MustacheNode(rawParams, hash, open, strip) {
+ this.type = "mustache";
+ this.hash = hash;
+ this.strip = strip;
+
+ var escapeFlag = open[3] || open[2];
+ this.escaped = escapeFlag !== '{' && escapeFlag !== '&';
+
+ var id = this.id = rawParams[0];
+ var params = this.params = rawParams.slice(1);
+
+ // 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.
+ }
+
+ __exports__.MustacheNode = MustacheNode;function PartialNode(partialName, context, strip) {
+ this.type = "partial";
+ this.partialName = partialName;
+ this.context = context;
+ this.strip = strip;
+ }
+
+ __exports__.PartialNode = PartialNode;function BlockNode(mustache, program, inverse, close) {
+ if(mustache.id.original !== close.path.original) {
+ throw new Exception(mustache.id.original + " doesn't match " + close.path.original);
+ }
+
+ this.type = "block";
+ this.mustache = mustache;
+ this.program = program;
+ this.inverse = inverse;
+
+ this.strip = {
+ left: mustache.strip.left,
+ right: close.strip.right
+ };
+
+ (program || inverse).strip.left = mustache.strip.right;
+ (inverse || program).strip.right = close.strip.left;
+
+ if (inverse && !program) {
+ this.isInverse = true;
+ }
+ }
+
+ __exports__.BlockNode = BlockNode;function ContentNode(string) {
+ this.type = "content";
+ this.string = string;
+ }
+
+ __exports__.ContentNode = ContentNode;function HashNode(pairs) {
+ this.type = "hash";
+ this.pairs = pairs;
+ }
+
+ __exports__.HashNode = HashNode;function IdNode(parts) {
+ this.type = "ID";
+
+ var original = "",
+ dig = [],
+ depth = 0;
+
+ for(var i=0,l=parts.length; i<l; i++) {
+ var part = parts[i].part;
+ original += (parts[i].separator || '') + part;
+
+ if (part === ".." || part === "." || part === "this") {
+ if (dig.length > 0) { throw new Exception("Invalid path: " + original); }
+ else if (part === "..") { depth++; }
+ else { this.isScoped = true; }
+ }
+ else { dig.push(part); }
+ }
+
+ this.original = original;
+ this.parts = dig;
+ this.string = dig.join('.');
+ this.depth = depth;
+
+ // an ID is simple if it only has one part, and that part is not
+ // `..` or `this`.
+ this.isSimple = parts.length === 1 && !this.isScoped && depth === 0;
+
+ this.stringModeValue = this.string;
+ }
+
+ __exports__.IdNode = IdNode;function PartialNameNode(name) {
+ this.type = "PARTIAL_NAME";
+ this.name = name.original;
+ }
+
+ __exports__.PartialNameNode = PartialNameNode;function DataNode(id) {
+ this.type = "DATA";
+ this.id = id;
+ }
+
+ __exports__.DataNode = DataNode;function StringNode(string) {
+ this.type = "STRING";
+ this.original =
+ this.string =
+ this.stringModeValue = string;
+ }
+
+ __exports__.StringNode = StringNode;function IntegerNode(integer) {
+ this.type = "INTEGER";
+ this.original =
+ this.integer = integer;
+ this.stringModeValue = Number(integer);
+ }
+
+ __exports__.IntegerNode = IntegerNode;function BooleanNode(bool) {
+ this.type = "BOOLEAN";
+ this.bool = bool;
+ this.stringModeValue = bool === "true";
+ }
+
+ __exports__.BooleanNode = BooleanNode;function CommentNode(comment) {
+ this.type = "comment";
+ this.comment = comment;
+ }
+
+ __exports__.CommentNode = CommentNode;
+ return __exports__;
+})(__module5__);
+
+// handlebars/compiler/parser.js
+var __module9__ = (function() {
+ "use strict";
+ var __exports__;
+ /* 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,"inMustache":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,"inMustache_repetition0":28,"inMustache_option0":29,"dataName":30,"param":31,"STRING":32,"INTEGER":33,"BOOLEAN":34,"hash":35,"hash_repetition_plus0":36,"hashSegment":37,"ID":38,"EQUALS":39,"DATA":40,"pathSegments":41,"SEP":42,"$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",38:"ID",39:"EQUALS",40:"DATA",42:"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],[35,1],[37,3],[26,1],[26,1],[26,1],[30,2],[21,1],[41,3],[41,1],[27,0],[27,1],[28,0],[28,2],[29,0],[29,1],[36,1],[36,2]],
+ performAction: function anonymous(yytext,yyleng,yylineno,yy,yystate,$$,_$) {
+
+ var $0 = $$.length - 1;
+ switch (yystate) {
+ case 1: return new yy.ProgramNode($$[$0-1]);
+ break;
+ case 2: return new yy.ProgramNode([]);
+ break;
+ case 3:this.$ = new yy.ProgramNode([], $$[$0-1], $$[$0]);
+ break;
+ case 4:this.$ = new yy.ProgramNode($$[$0-2], $$[$0-1], $$[$0]);
+ break;
+ case 5:this.$ = new yy.ProgramNode($$[$0-1], $$[$0], []);
+ break;
+ case 6:this.$ = new yy.ProgramNode($$[$0]);
+ break;
+ case 7:this.$ = new yy.ProgramNode([]);
+ break;
+ case 8:this.$ = new yy.ProgramNode([]);
+ 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]);
+ break;
+ case 12:this.$ = new yy.BlockNode($$[$0-2], $$[$0-1], $$[$0-1].inverse, $$[$0]);
+ break;
+ case 13:this.$ = $$[$0];
+ break;
+ case 14:this.$ = $$[$0];
+ break;
+ case 15:this.$ = new yy.ContentNode($$[$0]);
+ break;
+ case 16:this.$ = new yy.CommentNode($$[$0]);
+ break;
+ case 17:this.$ = new yy.MustacheNode($$[$0-1][0], $$[$0-1][1], $$[$0-2], stripFlags($$[$0-2], $$[$0]));
+ break;
+ case 18:this.$ = new yy.MustacheNode($$[$0-1][0], $$[$0-1][1], $$[$0-2], stripFlags($$[$0-2], $$[$0]));
+ break;
+ case 19:this.$ = {path: $$[$0-1], strip: stripFlags($$[$0-2], $$[$0])};
+ break;
+ case 20:this.$ = new yy.MustacheNode($$[$0-1][0], $$[$0-1][1], $$[$0-2], stripFlags($$[$0-2], $$[$0]));
+ break;
+ case 21:this.$ = new yy.MustacheNode($$[$0-1][0], $$[$0-1][1], $$[$0-2], stripFlags($$[$0-2], $$[$0]));
+ break;
+ case 22:this.$ = new yy.PartialNode($$[$0-2], $$[$0-1], stripFlags($$[$0-3], $$[$0]));
+ break;
+ case 23:this.$ = stripFlags($$[$0-1], $$[$0]);
+ break;
+ case 24:this.$ = [[$$[$0-2]].concat($$[$0-1]), $$[$0]];
+ break;
+ case 25:this.$ = [[$$[$0]], null];
+ break;
+ case 26:this.$ = $$[$0];
+ break;
+ case 27:this.$ = new yy.StringNode($$[$0]);
+ break;
+ case 28:this.$ = new yy.IntegerNode($$[$0]);
+ break;
+ case 29:this.$ = new yy.BooleanNode($$[$0]);
+ break;
+ case 30:this.$ = $$[$0];
+ break;
+ case 31:this.$ = new yy.HashNode($$[$0]);
+ break;
+ case 32:this.$ = [$$[$0-2], $$[$0]];
+ break;
+ case 33:this.$ = new yy.PartialNameNode($$[$0]);
+ break;
+ case 34:this.$ = new yy.PartialNameNode(new yy.StringNode($$[$0]));
+ break;
+ case 35:this.$ = new yy.PartialNameNode(new yy.IntegerNode($$[$0]));
+ break;
+ case 36:this.$ = new yy.DataNode($$[$0]);
+ break;
+ case 37:this.$ = new yy.IdNode($$[$0]);
+ break;
+ case 38: $$[$0-2].push({part: $$[$0], separator: $$[$0-1]}); this.$ = $$[$0-2];
+ break;
+ case 39:this.$ = [{part: $$[$0]}];
+ break;
+ case 42:this.$ = [];
+ break;
+ case 43:$$[$0-1].push($$[$0]);
+ break;
+ case 46:this.$ = [$$[$0]];
+ break;
+ case 47:$$[$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,38:[1,28],40:[1,27],41:26},{17:29,21:24,30:25,38:[1,28],40:[1,27],41:26},{17:30,21:24,30:25,38:[1,28],40:[1,27],41:26},{17:31,21:24,30:25,38:[1,28],40:[1,27],41:26},{21:33,26:32,32:[1,34],33:[1,35],38:[1,28],41: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,38:[1,28],40:[1,27],41:26},{10:41,20:[1,37]},{18:[1,42]},{18:[2,42],24:[2,42],28:43,32:[2,42],33:[2,42],34:[2,42],38:[2,42],40:[2,42]},{18:[2,25],24:[2,25]},{18:[2,37],24:[2,37],32:[2,37],33:[2,37],34:[2,37],38:[2,37],40:[2,37],42:[1,44]},{21:45,38:[1,28],41:26},{18:[2,39],24:[2,39],32:[2,39],33:[2,39],34:[2,39],38:[2,39],40:[2,39],42:[2,39]},{18:[1,46]},{18:[1,47]},{24:[1,48]},{18:[2,40],21:50,27:49,38:[1,28],41:26},{18:[2,33],38:[2,33]},{18:[2,34],38:[2,34]},{18:[2,35],38:[2,35]},{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,38:[1,28],41: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,44],21:56,24:[2,44],29:53,30:60,31:54,32:[1,57],33:[1,58],34:[1,59],35:55,36:61,37:62,38:[1,63],40:[1,27],41:26},{38:[1,64]},{18:[2,36],24:[2,36],32:[2,36],33:[2,36],34:[2,36],38:[2,36],40:[2,36]},{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,65]},{18:[2,41]},{18:[1,66]},{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]},{18:[2,43],24:[2,43],32:[2,43],33:[2,43],34:[2,43],38:[2,43],40:[2,43]},{18:[2,45],24:[2,45]},{18:[2,26],24:[2,26],32:[2,26],33:[2,26],34:[2,26],38:[2,26],40:[2,26]},{18:[2,27],24:[2,27],32:[2,27],33:[2,27],34:[2,27],38:[2,27],40:[2,27]},{18:[2,28],24:[2,28],32:[2,28],33:[2,28],34:[2,28],38:[2,28],40:[2,28]},{18:[2,29],24:[2,29],32:[2,29],33:[2,29],34:[2,29],38:[2,29],40:[2,29]},{18:[2,30],24:[2,30],32:[2,30],33:[2,30],34:[2,30],38:[2,30],40:[2,30]},{18:[2,31],24:[2,31],37:67,38:[1,68]},{18:[2,46],24:[2,46],38:[2,46]},{18:[2,39],24:[2,39],32:[2,39],33:[2,39],34:[2,39],38:[2,39],39:[1,69],40:[2,39],42:[2,39]},{18:[2,38],24:[2,38],32:[2,38],33:[2,38],34:[2,38],38:[2,38],40:[2,38],42:[2,38]},{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]},{18:[2,47],24:[2,47],38:[2,47]},{39:[1,69]},{21:56,30:60,31:70,32:[1,57],33:[1,58],34:[1,59],38:[1,28],40:[1,27],41:26},{18:[2,32],24:[2,32],38:[2,32]}],
+ defaultActions: {3:[2,2],16:[2,1],50:[2,41]},
+ parseError: function parseError(str, hash) {
+ throw new Error(str);
+ },
+ parse: function parse(input) {
+ var self = this, stack = [0], vstack = [null], lstack = [], table = this.table, yytext = "", yylineno = 0, yyleng = 0, recovering = 0, TERROR = 2, EOF = 1;
+ this.lexer.setInput(input);
+ this.lexer.yy = this.yy;
+ this.yy.lexer = this.lexer;
+ this.yy.parser = this;
+ if (typeof this.lexer.yylloc == "undefined")
+ this.lexer.yylloc = {};
+ var yyloc = this.lexer.yylloc;
+ lstack.push(yyloc);
+ var ranges = this.lexer.options && this.lexer.options.ranges;
+ if (typeof this.yy.parseError === "function")
+ this.parseError = this.yy.parseError;
+ function popStack(n) {
+ stack.length = stack.length - 2 * n;
+ vstack.length = vstack.length - n;
+ lstack.length = lstack.length - n;
+ }
+ function lex() {
+ var token;
+ token = self.lexer.lex() || 1;
+ if (typeof token !== "number") {
+ token = self.symbols_[token] || token;
+ }
+ return token;
+ }
+ var symbol, preErrorSymbol, state, action, a, r, yyval = {}, p, len, newState, expected;
+ while (true) {
+ state = stack[stack.length - 1];
+ if (this.defaultActions[state]) {
+ action = this.defaultActions[state];
+ } else {
+ if (symbol === null || typeof symbol == "undefined") {
+ symbol = lex();
+ }
+ action = table[state] && table[state][symbol];
+ }
+ if (typeof action === "undefined" || !action.length || !action[0]) {
+ var errStr = "";
+ if (!recovering) {
+ expected = [];
+ for (p in table[state])
+ if (this.terminals_[p] && p > 2) {
+ expected.push("'" + this.terminals_[p] + "'");
+ }
+ if (this.lexer.showPosition) {
+ errStr = "Parse error on line " + (yylineno + 1) + ":\n" + this.lexer.showPosition() + "\nExpecting " + expected.join(", ") + ", got '" + (this.terminals_[symbol] || symbol) + "'";
+ } else {
+ errStr = "Parse error on line " + (yylineno + 1) + ": Unexpected " + (symbol == 1?"end of input":"'" + (this.terminals_[symbol] || symbol) + "'");
+ }
+ this.parseError(errStr, {text: this.lexer.match, token: this.terminals_[symbol] || symbol, line: this.lexer.yylineno, loc: yyloc, expected: expected});
+ }
+ }
+ if (action[0] instanceof Array && action.length > 1) {
+ throw new Error("Parse Error: multiple actions possible at state: " + state + ", token: " + symbol);
+ }
+ switch (action[0]) {
+ case 1:
+ stack.push(symbol);
+ vstack.push(this.lexer.yytext);
+ lstack.push(this.lexer.yylloc);
+ stack.push(action[1]);
+ symbol = null;
+ if (!preErrorSymbol) {
+ yyleng = this.lexer.yyleng;
+ yytext = this.lexer.yytext;
+ yylineno = this.lexer.yylineno;
+ yyloc = this.lexer.yylloc;
+ if (recovering > 0)
+ recovering--;
+ } else {
+ symbol = preErrorSymbol;
+ preErrorSymbol = null;
+ }
+ break;
+ case 2:
+ len = this.productions_[action[1]][1];
+ yyval.$ = vstack[vstack.length - len];
+ yyval._$ = {first_line: lstack[lstack.length - (len || 1)].first_line, last_line: lstack[lstack.length - 1].last_line, first_column: lstack[lstack.length - (len || 1)].first_column, last_column: lstack[lstack.length - 1].last_column};
+ if (ranges) {
+ yyval._$.range = [lstack[lstack.length - (len || 1)].range[0], lstack[lstack.length - 1].range[1]];
+ }
+ r = this.performAction.call(yyval, yytext, yyleng, yylineno, this.yy, action[1], vstack, lstack);
+ if (typeof r !== "undefined") {
+ return r;
+ }
+ if (len) {
+ stack = stack.slice(0, -1 * len * 2);
+ vstack = vstack.slice(0, -1 * len);
+ lstack = lstack.slice(0, -1 * len);
+ }
+ stack.push(this.productions_[action[1]][0]);
+ vstack.push(yyval.$);
+ lstack.push(yyval._$);
+ newState = table[stack[stack.length - 2]][stack[stack.length - 1]];
+ stack.push(newState);
+ break;
+ case 3:
+ return true;
+ }
+ }
+ return true;
+ }
+ };
+
+
+ function stripFlags(open, close) {
+ return {
+ left: open[2] === '~',
+ right: close[0] === '~' || close[1] === '~'
+ };
+ }
+
+ /* Jison generated lexer */
+ var lexer = (function(){
+ var lexer = ({EOF:1,
+ parseError:function parseError(str, hash) {
+ if (this.yy.parser) {
+ this.yy.parser.parseError(str, hash);
+ } else {
+ throw new Error(str);
+ }
+ },
+ setInput:function (input) {
+ this._input = input;
+ this._more = this._less = this.done = false;
+ this.yylineno = this.yyleng = 0;
+ this.yytext = this.matched = this.match = '';
+ this.conditionStack = ['INITIAL'];
+ this.yylloc = {first_line:1,first_column:0,last_line:1,last_column:0};
+ if (this.options.ranges) this.yylloc.range = [0,0];
+ this.offset = 0;
+ return this;
+ },
+ input:function () {
+ var ch = this._input[0];
+ this.yytext += ch;
+ this.yyleng++;
+ this.offset++;
+ this.match += ch;
+ this.matched += ch;
+ var lines = ch.match(/(?:\r\n?|\n).*/g);
+ if (lines) {
+ this.yylineno++;
+ this.yylloc.last_line++;
+ } else {
+ this.yylloc.last_column++;
+ }
+ if (this.options.ranges) this.yylloc.range[1]++;
+
+ this._input = this._input.slice(1);
+ return ch;
+ },
+ unput:function (ch) {
+ var len = ch.length;
+ var lines = ch.split(/(?:\r\n?|\n)/g);
+
+ this._input = ch + this._input;
+ this.yytext = this.yytext.substr(0, this.yytext.length-len-1);
+ //this.yyleng -= len;
+ this.offset -= len;
+ var oldLines = this.match.split(/(?:\r\n?|\n)/g);
+ this.match = this.match.substr(0, this.match.length-1);
+ this.matched = this.matched.substr(0, this.matched.length-1);
+
+ if (lines.length-1) this.yylineno -= lines.length-1;
+ var r = this.yylloc.range;
+
+ this.yylloc = {first_line: this.yylloc.first_line,
+ last_line: this.yylineno+1,
+ first_column: this.yylloc.first_column,
+ last_column: lines ?
+ (lines.length === oldLines.length ? this.yylloc.first_column : 0) + oldLines[oldLines.length - lines.length].length - lines[0].length:
+ this.yylloc.first_column - len
+ };
+
+ if (this.options.ranges) {
+ this.yylloc.range = [r[0], r[0] + this.yyleng - len];
+ }
+ return this;
+ },
+ more:function () {
+ this._more = true;
+ return this;
+ },
+ less:function (n) {
+ this.unput(this.match.slice(n));
+ },
+ pastInput:function () {
+ var past = this.matched.substr(0, this.matched.length - this.match.length);
+ return (past.length > 20 ? '...':'') + past.substr(-20).replace(/\n/g, "");
+ },
+ upcomingInput:function () {
+ var next = this.match;
+ if (next.length < 20) {
+ next += this._input.substr(0, 20-next.length);
+ }
+ return (next.substr(0,20)+(next.length > 20 ? '...':'')).replace(/\n/g, "");
+ },
+ showPosition:function () {
+ var pre = this.pastInput();
+ var c = new Array(pre.length + 1).join("-");
+ return pre + this.upcomingInput() + "\n" + c+"^";
+ },
+ next:function () {
+ if (this.done) {
+ return this.EOF;
+ }
+ if (!this._input) this.done = true;
+
+ var token,
+ match,
+ tempMatch,
+ index,
+ col,
+ lines;
+ if (!this._more) {
+ this.yytext = '';
+ this.match = '';
+ }
+ var rules = this._currentRules();
+ for (var i=0;i < rules.length; i++) {
+ tempMatch = this._input.match(this.rules[rules[i]]);
+ if (tempMatch && (!match || tempMatch[0].length > match[0].length)) {
+ match = tempMatch;
+ index = i;
+ if (!this.options.flex) break;
+ }
+ }
+ if (match) {
+ lines = match[0].match(/(?:\r\n?|\n).*/g);
+ if (lines) this.yylineno += lines.length;
+ this.yylloc = {first_line: this.yylloc.last_line,
+ last_line: this.yylineno+1,
+ first_column: this.yylloc.last_column,
+ last_column: lines ? lines[lines.length-1].length-lines[lines.length-1].match(/\r?\n?/)[0].length : this.yylloc.last_column + match[0].length};
+ this.yytext += match[0];
+ this.match += match[0];
+ this.matches = match;
+ this.yyleng = this.yytext.length;
+ if (this.options.ranges) {
+ this.yylloc.range = [this.offset, this.offset += this.yyleng];
+ }
+ this._more = false;
+ this._input = this._input.slice(match[0].length);
+ this.matched += match[0];
+ token = this.performAction.call(this, this.yy, this, rules[index],this.conditionStack[this.conditionStack.length-1]);
+ if (this.done && this._input) this.done = false;
+ if (token) return token;
+ else return;
+ }
+ if (this._input === "") {
+ return this.EOF;
+ } else {
+ return this.parseError('Lexical error on line '+(this.yylineno+1)+'. Unrecognized text.\n'+this.showPosition(),
+ {text: "", token: null, line: this.yylineno});
+ }
+ },
+ lex:function lex() {
+ var r = this.next();
+ if (typeof r !== 'undefined') {
+ return r;
+ } else {
+ return this.lex();
+ }
+ },
+ begin:function begin(condition) {
+ this.conditionStack.push(condition);
+ },
+ popState:function popState() {
+ return this.conditionStack.pop();
+ },
+ _currentRules:function _currentRules() {
+ return this.conditions[this.conditionStack[this.conditionStack.length-1]].rules;
+ },
+ topState:function () {
+ return this.conditionStack[this.conditionStack.length-2];
+ },
+ pushState:function begin(condition) {
+ this.begin(condition);
+ }});
+ lexer.options = {};
+ lexer.performAction = function anonymous(yy,yy_,$avoiding_name_collisions,YY_START) {
+
+
+ function strip(start, end) {
+ return yy_.yytext = yy_.yytext.substr(start, yy_.yyleng-end);
+ }
+
+
+ var YYSTATE=YY_START
+ switch($avoiding_name_collisions) {
+ case 0:
+ if(yy_.yytext.slice(-2) === "\\\\") {
+ strip(0,1);
+ this.begin("mu");
+ } else if(yy_.yytext.slice(-1) === "\\") {
+ strip(0,1);
+ this.begin("emu");
+ } else {
+ this.begin("mu");
+ }
+ if(yy_.yytext) return 14;
+
+ break;
+ case 1:return 14;
+ break;
+ case 2:
+ if(yy_.yytext.slice(-1) !== "\\") this.popState();
+ if(yy_.yytext.slice(-1) === "\\") strip(0,1);
+ return 14;
+
+ break;
+ case 3:strip(0,4); this.popState(); return 15;
+ break;
+ case 4:return 25;
+ 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 22;
+ break;
+ case 11:this.popState(); this.begin('com');
+ break;
+ case 12:strip(3,5); this.popState(); return 15;
+ break;
+ case 13:return 22;
+ break;
+ case 14:return 39;
+ break;
+ case 15:return 38;
+ break;
+ case 16:return 38;
+ break;
+ case 17:return 42;
+ break;
+ case 18:/*ignore whitespace*/
+ break;
+ case 19:this.popState(); return 24;
+ break;
+ case 20:this.popState(); return 18;
+ break;
+ case 21:yy_.yytext = strip(1,2).replace(/\\"/g,'"'); return 32;
+ break;
+ case 22:yy_.yytext = strip(1,2).replace(/\\'/g,"'"); return 32;
+ break;
+ case 23:return 40;
+ break;
+ case 24:return 34;
+ break;
+ case 25:return 34;
+ break;
+ case 26:return 33;
+ break;
+ case 27:return 38;
+ break;
+ case 28:yy_.yytext = strip(1,2); return 38;
+ break;
+ case 29:return 'INVALID';
+ break;
+ case 30:return 5;
+ break;
+ }
+ };
+ lexer.rules = [/^(?:[^\x00]*?(?=(\{\{)))/,/^(?:[^\x00]+)/,/^(?:[^\x00]{2,}?(?=(\{\{|$)))/,/^(?:[\s\S]*?--\}\})/,/^(?:\{\{(~)?>)/,/^(?:\{\{(~)?#)/,/^(?:\{\{(~)?\/)/,/^(?:\{\{(~)?\^)/,/^(?:\{\{(~)?\s*else\b)/,/^(?:\{\{(~)?\{)/,/^(?:\{\{(~)?&)/,/^(?:\{\{!--)/,/^(?:\{\{![\s\S]*?\}\})/,/^(?:\{\{(~)?)/,/^(?:=)/,/^(?:\.\.)/,/^(?:\.(?=([=~}\s\/.])))/,/^(?:[\/.])/,/^(?:\s+)/,/^(?:\}(~)?\}\})/,/^(?:(~)?\}\})/,/^(?:"(\\["]|[^"])*")/,/^(?:'(\\[']|[^'])*')/,/^(?:@)/,/^(?:true(?=([~}\s])))/,/^(?:false(?=([~}\s])))/,/^(?:-?[0-9]+(?=([~}\s])))/,/^(?:([^\s!"#%-,\.\/;->@\[-\^`\{-~]+(?=([=~}\s\/.]))))/,/^(?:\[[^\]]*\])/,/^(?:.)/,/^(?:$)/];
+ lexer.conditions = {"mu":{"rules":[4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30],"inclusive":false},"emu":{"rules":[2],"inclusive":false},"com":{"rules":[3],"inclusive":false},"INITIAL":{"rules":[0,1,30],"inclusive":true}};
+ return lexer;})()
+ parser.lexer = lexer;
+ function Parser () { this.yy = {}; }Parser.prototype = parser;parser.Parser = Parser;
+ return new Parser;
+ })();__exports__ = handlebars;
+ return __exports__;
+})();
+
+// handlebars/compiler/base.js
+var __module8__ = (function(__dependency1__, __dependency2__) {
+ "use strict";
+ var __exports__ = {};
+ var parser = __dependency1__;
+ var AST = __dependency2__;
+
+ __exports__.parser = parser;
+
+ function parse(input) {
+ // Just return if an already-compile AST was passed in.
+ if(input.constructor === AST.ProgramNode) { return input; }
+
+ parser.yy = AST;
+ return parser.parse(input);
+ }
+
+ __exports__.parse = parse;
+ return __exports__;
+})(__module9__, __module7__);
+
+// handlebars/compiler/javascript-compiler.js
+var __module11__ = (function(__dependency1__) {
+ "use strict";
+ var __exports__;
+ var COMPILER_REVISION = __dependency1__.COMPILER_REVISION;
+ var REVISION_CHANGES = __dependency1__.REVISION_CHANGES;
+ var log = __dependency1__.log;
+
+ function Literal(value) {
+ this.value = value;
+ }
+
+ function JavaScriptCompiler() {}
+
+ JavaScriptCompiler.prototype = {
+ // PUBLIC API: You can override these methods in a subclass to provide
+ // alternative compiled forms for name lookup and buffering semantics
+ nameLookup: function(parent, name /* , type*/) {
+ var wrap,
+ ret;
+ if (parent.indexOf('depth') === 0) {
+ wrap = true;
+ }
+
+ if (/^[0-9]+$/.test(name)) {
+ ret = parent + "[" + name + "]";
+ } else if (JavaScriptCompiler.isValidJavaScriptVariableName(name)) {
+ ret = parent + "." + name;
+ }
+ else {
+ ret = parent + "['" + name + "']";
+ }
+
+ if (wrap) {
+ return '(' + parent + ' && ' + ret + ')';
+ } else {
+ return ret;
+ }
+ },
+
+ appendToBuffer: function(string) {
+ if (this.environment.isSimple) {
+ return "return " + string + ";";
+ } else {
+ return {
+ appendToBuffer: true,
+ content: string,
+ toString: function() { return "buffer += " + string + ";"; }
+ };
+ }
+ },
+
+ initializeBuffer: function() {
+ return this.quotedString("");
+ },
+
+ namespace: "Handlebars",
+ // END PUBLIC API
+
+ compile: function(environment, options, context, asObject) {
+ this.environment = environment;
+ this.options = options || {};
+
+ log('debug', this.environment.disassemble() + "\n\n");
+
+ this.name = this.environment.name;
+ this.isChild = !!context;
+ this.context = context || {
+ programs: [],
+ environments: [],
+ aliases: { }
+ };
+
+ this.preamble();
+
+ this.stackSlot = 0;
+ this.stackVars = [];
+ this.registers = { list: [] };
+ this.compileStack = [];
+ this.inlineStack = [];
+
+ this.compileChildren(environment, options);
+
+ var opcodes = environment.opcodes, opcode;
+
+ this.i = 0;
+
+ for(var l=opcodes.length; this.i<l; this.i++) {
+ opcode = opcodes[this.i];
+
+ if(opcode.opcode === 'DECLARE') {
+ this[opcode.name] = opcode.value;
+ } else {
+ this[opcode.opcode].apply(this, opcode.args);
+ }
+
+ // Reset the stripNext flag if it was not set by this operation.
+ if (opcode.opcode !== this.stripNext) {
+ this.stripNext = false;
+ }
+ }
+
+ // Flush any trailing content that might be pending.
+ this.pushSource('');
+
+ return this.createFunctionContext(asObject);
+ },
+
+ preamble: function() {
+ var out = [];
+
+ if (!this.isChild) {
+ var namespace = this.namespace;
+
+ var copies = "helpers = this.merge(helpers, " + namespace + ".helpers);";
+ if (this.environment.usePartial) { copies = copies + " partials = this.merge(partials, " + namespace + ".partials);"; }
+ if (this.options.data) { copies = copies + " data = data || {};"; }
+ out.push(copies);
+ } else {
+ out.push('');
+ }
+
+ if (!this.environment.isSimple) {
+ out.push(", buffer = " + this.initializeBuffer());
+ } else {
+ out.push("");
+ }
+
+ // track the last context pushed into place to allow skipping the
+ // getContext opcode when it would be a noop
+ this.lastContext = 0;
+ this.source = out;
+ },
+
+ createFunctionContext: function(asObject) {
+ var locals = this.stackVars.concat(this.registers.list);
+
+ if(locals.length > 0) {
+ this.source[1] = this.source[1] + ", " + locals.join(", ");
+ }
+
+ // Generate minimizer alias mappings
+ if (!this.isChild) {
+ for (var alias in this.context.aliases) {
+ if (this.context.aliases.hasOwnProperty(alias)) {
+ this.source[1] = this.source[1] + ', ' + alias + '=' + this.context.aliases[alias];
+ }
+ }
+ }
+
+ if (this.source[1]) {
+ this.source[1] = "var " + this.source[1].substring(2) + ";";
+ }
+
+ // Merge children
+ if (!this.isChild) {
+ this.source[1] += '\n' + this.context.programs.join('\n') + '\n';
+ }
+
+ if (!this.environment.isSimple) {
+ this.pushSource("return buffer;");
+ }
+
+ var params = this.isChild ? ["depth0", "data"] : ["Handlebars", "depth0", "helpers", "partials", "data"];
+
+ for(var i=0, l=this.environment.depths.list.length; i<l; i++) {
+ params.push("depth" + this.environment.depths.list[i]);
+ }
+
+ // Perform a second pass over the output to merge content when possible
+ var source = this.mergeSource();
+
+ if (!this.isChild) {
+ var revision = COMPILER_REVISION,
+ versions = REVISION_CHANGES[revision];
+ source = "this.compilerInfo = ["+revision+",'"+versions+"'];\n"+source;
+ }
+
+ if (asObject) {
+ params.push(source);
+
+ return Function.apply(this, params);
+ } else {
+ var functionSource = 'function ' + (this.name || '') + '(' + params.join(',') + ') {\n ' + source + '}';
+ log('debug', functionSource + "\n\n");
+ return functionSource;
+ }
+ },
+ mergeSource: function() {
+ // WARN: We are not handling the case where buffer is still populated as the source should
+ // not have buffer append operations as their final action.
+ var source = '',
+ buffer;
+ for (var i = 0, len = this.source.length; i < len; i++) {
+ var line = this.source[i];
+ if (line.appendToBuffer) {
+ if (buffer) {
+ buffer = buffer + '\n + ' + line.content;
+ } else {
+ buffer = line.content;
+ }
+ } else {
+ if (buffer) {
+ source += 'buffer += ' + buffer + ';\n ';
+ buffer = undefined;
+ }
+ source += line + '\n ';
+ }
+ }
+ return source;
+ },
+
+ // [blockValue]
+ //
+ // On stack, before: hash, inverse, program, value
+ // On stack, after: return value of blockHelperMissing
+ //
+ // The purpose of this opcode is to take a block of the form
+ // `{{#foo}}...{{/foo}}`, resolve the value of `foo`, and
+ // replace it on the stack with the result of properly
+ // invoking blockHelperMissing.
+ blockValue: function() {
+ this.context.aliases.blockHelperMissing = 'helpers.blockHelperMissing';
+
+ var params = ["depth0"];
+ this.setupParams(0, params);
+
+ this.replaceStack(function(current) {
+ params.splice(1, 0, current);
+ return "blockHelperMissing.call(" + params.join(", ") + ")";
+ });
+ },
+
+ // [ambiguousBlockValue]
+ //
+ // On stack, before: hash, inverse, program, value
+ // Compiler value, before: lastHelper=value of last found helper, if any
+ // On stack, after, if no lastHelper: same as [blockValue]
+ // On stack, after, if lastHelper: value
+ ambiguousBlockValue: function() {
+ this.context.aliases.blockHelperMissing = 'helpers.blockHelperMissing';
+
+ var params = ["depth0"];
+ this.setupParams(0, params);
+
+ var current = this.topStack();
+ params.splice(1, 0, current);
+
+ // Use the options value generated from the invocation
+ params[params.length-1] = 'options';
+
+ this.pushSource("if (!" + this.lastHelper + ") { " + current + " = blockHelperMissing.call(" + params.join(", ") + "); }");
+ },
+
+ // [appendContent]
+ //
+ // On stack, before: ...
+ // On stack, after: ...
+ //
+ // Appends the string value of `content` to the current buffer
+ appendContent: function(content) {
+ if (this.pendingContent) {
+ content = this.pendingContent + content;
+ }
+ if (this.stripNext) {
+ content = content.replace(/^\s+/, '');
+ }
+
+ this.pendingContent = content;
+ },
+
+ // [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';
+ },
+
+ // [append]
+ //
+ // On stack, before: value, ...
+ // On stack, after: ...
+ //
+ // Coerces `value` to a String and appends it to the current buffer.
+ //
+ // If `value` is truthy, or 0, it is coerced into a string and appended
+ // Otherwise, the empty string is appended
+ append: function() {
+ // Force anything that is inlined onto the stack so we don't have duplication
+ // when we examine local
+ this.flushInline();
+ var local = this.popStack();
+ this.pushSource("if(" + local + " || " + local + " === 0) { " + this.appendToBuffer(local) + " }");
+ if (this.environment.isSimple) {
+ this.pushSource("else { " + this.appendToBuffer("''") + " }");
+ }
+ },
+
+ // [appendEscaped]
+ //
+ // On stack, before: value, ...
+ // On stack, after: ...
+ //
+ // Escape `value` and append it to the buffer
+ appendEscaped: function() {
+ this.context.aliases.escapeExpression = 'this.escapeExpression';
+
+ this.pushSource(this.appendToBuffer("escapeExpression(" + this.popStack() + ")"));
+ },
+
+ // [getContext]
+ //
+ // On stack, before: ...
+ // On stack, after: ...
+ // Compiler value, after: lastContext=depth
+ //
+ // Set the value of the `lastContext` compiler value to the depth
+ getContext: function(depth) {
+ if(this.lastContext !== depth) {
+ this.lastContext = depth;
+ }
+ },
+
+ // [lookupOnContext]
+ //
+ // On stack, before: ...
+ // On stack, after: currentContext[name], ...
+ //
+ // Looks up the value of `name` on the current context and pushes
+ // it onto the stack.
+ lookupOnContext: function(name) {
+ this.push(this.nameLookup('depth' + this.lastContext, name, 'context'));
+ },
+
+ // [pushContext]
+ //
+ // On stack, before: ...
+ // On stack, after: currentContext, ...
+ //
+ // Pushes the value of the current context onto the stack.
+ pushContext: function() {
+ this.pushStackLiteral('depth' + this.lastContext);
+ },
+
+ // [resolvePossibleLambda]
+ //
+ // On stack, before: value, ...
+ // On stack, after: resolved value, ...
+ //
+ // If the `value` is a lambda, replace it on the stack by
+ // the return value of the lambda
+ resolvePossibleLambda: function() {
+ this.context.aliases.functionType = '"function"';
+
+ this.replaceStack(function(current) {
+ return "typeof " + current + " === functionType ? " + current + ".apply(depth0) : " + current;
+ });
+ },
+
+ // [lookup]
+ //
+ // On stack, before: value, ...
+ // On stack, after: value[name], ...
+ //
+ // Replace the value on the stack with the result of looking
+ // up `name` on `value`
+ lookup: function(name) {
+ this.replaceStack(function(current) {
+ return current + " == null || " + current + " === false ? " + current + " : " + this.nameLookup(current, name, 'context');
+ });
+ },
+
+ // [lookupData]
+ //
+ // On stack, before: ...
+ // On stack, after: data, ...
+ //
+ // Push the data lookup operator
+ lookupData: function() {
+ this.push('data');
+ },
+
+ // [pushStringParam]
+ //
+ // On stack, before: ...
+ // On stack, after: string, currentContext, ...
+ //
+ // This opcode is designed for use in string mode, which
+ // provides the string value of a parameter along with its
+ // depth rather than resolving it immediately.
+ pushStringParam: function(string, type) {
+ this.pushStackLiteral('depth' + this.lastContext);
+
+ this.pushString(type);
+
+ if (typeof string === 'string') {
+ this.pushString(string);
+ } else {
+ this.pushStackLiteral(string);
+ }
+ },
+
+ emptyHash: function() {
+ this.pushStackLiteral('{}');
+
+ if (this.options.stringParams) {
+ this.register('hashTypes', '{}');
+ this.register('hashContexts', '{}');
+ }
+ },
+ pushHash: function() {
+ this.hash = {values: [], types: [], contexts: []};
+ },
+ popHash: function() {
+ var hash = this.hash;
+ this.hash = undefined;
+
+ if (this.options.stringParams) {
+ this.register('hashContexts', '{' + hash.contexts.join(',') + '}');
+ this.register('hashTypes', '{' + hash.types.join(',') + '}');
+ }
+ this.push('{\n ' + hash.values.join(',\n ') + '\n }');
+ },
+
+ // [pushString]
+ //
+ // On stack, before: ...
+ // On stack, after: quotedString(string), ...
+ //
+ // Push a quoted version of `string` onto the stack
+ pushString: function(string) {
+ this.pushStackLiteral(this.quotedString(string));
+ },
+
+ // [push]
+ //
+ // On stack, before: ...
+ // On stack, after: expr, ...
+ //
+ // Push an expression onto the stack
+ push: function(expr) {
+ this.inlineStack.push(expr);
+ return expr;
+ },
+
+ // [pushLiteral]
+ //
+ // On stack, before: ...
+ // On stack, after: value, ...
+ //
+ // Pushes a value onto the stack. This operation prevents
+ // the compiler from creating a temporary variable to hold
+ // it.
+ pushLiteral: function(value) {
+ this.pushStackLiteral(value);
+ },
+
+ // [pushProgram]
+ //
+ // On stack, before: ...
+ // On stack, after: program(guid), ...
+ //
+ // Push a program expression onto the stack. This takes
+ // a compile-time guid and converts it into a runtime-accessible
+ // expression.
+ pushProgram: function(guid) {
+ if (guid != null) {
+ this.pushStackLiteral(this.programExpression(guid));
+ } else {
+ this.pushStackLiteral(null);
+ }
+ },
+
+ // [invokeHelper]
+ //
+ // On stack, before: hash, inverse, program, params..., ...
+ // On stack, after: result of helper invocation
+ //
+ // Pops off the helper's parameters, invokes the helper,
+ // and pushes the helper's return value onto the stack.
+ //
+ // If the helper is not found, `helperMissing` is called.
+ invokeHelper: function(paramSize, name) {
+ this.context.aliases.helperMissing = 'helpers.helperMissing';
+
+ var helper = this.lastHelper = this.setupHelper(paramSize, name, true);
+ var nonHelper = this.nameLookup('depth' + this.lastContext, name, 'context');
+
+ this.push(helper.name + ' || ' + nonHelper);
+ this.replaceStack(function(name) {
+ return name + ' ? ' + name + '.call(' +
+ helper.callParams + ") " + ": helperMissing.call(" +
+ helper.helperMissingParams + ")";
+ });
+ },
+
+ // [invokeKnownHelper]
+ //
+ // On stack, before: hash, inverse, program, params..., ...
+ // On stack, after: result of helper invocation
+ //
+ // This operation is used when the helper is known to exist,
+ // so a `helperMissing` fallback is not required.
+ invokeKnownHelper: function(paramSize, name) {
+ var helper = this.setupHelper(paramSize, name);
+ this.push(helper.name + ".call(" + helper.callParams + ")");
+ },
+
+ // [invokeAmbiguous]
+ //
+ // On stack, before: hash, inverse, program, params..., ...
+ // On stack, after: result of disambiguation
+ //
+ // This operation is used when an expression like `{{foo}}`
+ // is provided, but we don't know at compile-time whether it
+ // is a helper or a path.
+ //
+ // This operation emits more code than the other options,
+ // and can be avoided by passing the `knownHelpers` and
+ // `knownHelpersOnly` flags at compile-time.
+ invokeAmbiguous: function(name, helperCall) {
+ this.context.aliases.functionType = '"function"';
+
+ this.pushStackLiteral('{}'); // Hash value
+ var helper = this.setupHelper(0, name, helperCall);
+
+ var helperName = this.lastHelper = this.nameLookup('helpers', name, 'helper');
+
+ var nonHelper = this.nameLookup('depth' + this.lastContext, name, 'context');
+ var nextStack = this.nextStack();
+
+ this.pushSource('if (' + nextStack + ' = ' + helperName + ') { ' + nextStack + ' = ' + nextStack + '.call(' + helper.callParams + '); }');
+ this.pushSource('else { ' + nextStack + ' = ' + nonHelper + '; ' + nextStack + ' = typeof ' + nextStack + ' === functionType ? ' + nextStack + '.call(' + helper.callParams + ') : ' + nextStack + '; }');
+ },
+
+ // [invokePartial]
+ //
+ // On stack, before: context, ...
+ // On stack after: result of partial invocation
+ //
+ // This operation pops off a context, invokes a partial with that context,
+ // and pushes the result of the invocation back.
+ invokePartial: function(name) {
+ var params = [this.nameLookup('partials', name, 'partial'), "'" + name + "'", this.popStack(), "helpers", "partials"];
+
+ if (this.options.data) {
+ params.push("data");
+ }
+
+ this.context.aliases.self = "this";
+ this.push("self.invokePartial(" + params.join(", ") + ")");
+ },
+
+ // [assignToHash]
+ //
+ // On stack, before: value, hash, ...
+ // On stack, after: hash, ...
+ //
+ // Pops a value and hash off the stack, assigns `hash[key] = value`
+ // and pushes the hash back onto the stack.
+ assignToHash: function(key) {
+ var value = this.popStack(),
+ context,
+ type;
+
+ if (this.options.stringParams) {
+ type = this.popStack();
+ context = this.popStack();
+ }
+
+ var hash = this.hash;
+ if (context) {
+ hash.contexts.push("'" + key + "': " + context);
+ }
+ if (type) {
+ hash.types.push("'" + key + "': " + type);
+ }
+ hash.values.push("'" + key + "': (" + value + ")");
+ },
+
+ // HELPERS
+
+ compiler: JavaScriptCompiler,
+
+ compileChildren: function(environment, options) {
+ var children = environment.children, child, compiler;
+
+ for(var i=0, l=children.length; i<l; i++) {
+ child = children[i];
+ compiler = new this.compiler();
+
+ var index = this.matchExistingProgram(child);
+
+ if (index == null) {
+ this.context.programs.push(''); // Placeholder to prevent name conflicts for nested children
+ index = this.context.programs.length;
+ child.index = index;
+ child.name = 'program' + index;
+ this.context.programs[index] = compiler.compile(child, options, this.context);
+ this.context.environments[index] = child;
+ } else {
+ child.index = index;
+ child.name = 'program' + index;
+ }
+ }
+ },
+ matchExistingProgram: function(child) {
+ for (var i = 0, len = this.context.environments.length; i < len; i++) {
+ var environment = this.context.environments[i];
+ if (environment && environment.equals(child)) {
+ return i;
+ }
+ }
+ },
+
+ programExpression: function(guid) {
+ this.context.aliases.self = "this";
+
+ if(guid == null) {
+ return "self.noop";
+ }
+
+ var child = this.environment.children[guid],
+ depths = child.depths.list, depth;
+
+ var programParams = [child.index, child.name, "data"];
+
+ for(var i=0, l = depths.length; i<l; i++) {
+ depth = depths[i];
+
+ if(depth === 1) { programParams.push("depth0"); }
+ else { programParams.push("depth" + (depth - 1)); }
+ }
+
+ return (depths.length === 0 ? "self.program(" : "self.programWithDepth(") + programParams.join(", ") + ")";
+ },
+
+ register: function(name, val) {
+ this.useRegister(name);
+ this.pushSource(name + " = " + val + ";");
+ },
+
+ useRegister: function(name) {
+ if(!this.registers[name]) {
+ this.registers[name] = true;
+ this.registers.list.push(name);
+ }
+ },
+
+ pushStackLiteral: function(item) {
+ return this.push(new Literal(item));
+ },
+
+ pushSource: function(source) {
+ if (this.pendingContent) {
+ this.source.push(this.appendToBuffer(this.quotedString(this.pendingContent)));
+ this.pendingContent = undefined;
+ }
+
+ if (source) {
+ this.source.push(source);
+ }
+ },
+
+ pushStack: function(item) {
+ this.flushInline();
+
+ var stack = this.incrStack();
+ if (item) {
+ this.pushSource(stack + " = " + item + ";");
+ }
+ this.compileStack.push(stack);
+ return stack;
+ },
+
+ replaceStack: function(callback) {
+ var prefix = '',
+ inline = this.isInline(),
+ stack;
+
+ // If we are currently inline then we want to merge the inline statement into the
+ // replacement statement via ','
+ if (inline) {
+ var top = this.popStack(true);
+
+ if (top instanceof Literal) {
+ // Literals do not need to be inlined
+ stack = top.value;
+ } else {
+ // Get or create the current stack name for use by the inline
+ var name = this.stackSlot ? this.topStackName() : this.incrStack();
+
+ prefix = '(' + this.push(name) + ' = ' + top + '),';
+ stack = this.topStack();
+ }
+ } else {
+ stack = this.topStack();
+ }
+
+ var item = callback.call(this, stack);
+
+ if (inline) {
+ if (this.inlineStack.length || this.compileStack.length) {
+ this.popStack();
+ }
+ this.push('(' + prefix + item + ')');
+ } else {
+ // Prevent modification of the context depth variable. Through replaceStack
+ if (!/^stack/.test(stack)) {
+ stack = this.nextStack();
+ }
+
+ this.pushSource(stack + " = (" + prefix + item + ");");
+ }
+ return stack;
+ },
+
+ nextStack: function() {
+ return this.pushStack();
+ },
+
+ incrStack: function() {
+ this.stackSlot++;
+ if(this.stackSlot > this.stackVars.length) { this.stackVars.push("stack" + this.stackSlot); }
+ return this.topStackName();
+ },
+ topStackName: function() {
+ return "stack" + this.stackSlot;
+ },
+ flushInline: function() {
+ var inlineStack = this.inlineStack;
+ if (inlineStack.length) {
+ this.inlineStack = [];
+ for (var i = 0, len = inlineStack.length; i < len; i++) {
+ var entry = inlineStack[i];
+ if (entry instanceof Literal) {
+ this.compileStack.push(entry);
+ } else {
+ this.pushStack(entry);
+ }
+ }
+ }
+ },
+ isInline: function() {
+ return this.inlineStack.length;
+ },
+
+ popStack: function(wrapped) {
+ var inline = this.isInline(),
+ item = (inline ? this.inlineStack : this.compileStack).pop();
+
+ if (!wrapped && (item instanceof Literal)) {
+ return item.value;
+ } else {
+ if (!inline) {
+ this.stackSlot--;
+ }
+ return item;
+ }
+ },
+
+ topStack: function(wrapped) {
+ var stack = (this.isInline() ? this.inlineStack : this.compileStack),
+ item = stack[stack.length - 1];
+
+ if (!wrapped && (item instanceof Literal)) {
+ return item.value;
+ } else {
+ return item;
+ }
+ },
+
+ quotedString: function(str) {
+ return '"' + str
+ .replace(/\\/g, '\\\\')
+ .replace(/"/g, '\\"')
+ .replace(/\n/g, '\\n')
+ .replace(/\r/g, '\\r')
+ .replace(/\u2028/g, '\\u2028') // Per Ecma-262 7.3 + 7.8.4
+ .replace(/\u2029/g, '\\u2029') + '"';
+ },
+
+ setupHelper: function(paramSize, name, missingParams) {
+ var params = [];
+ this.setupParams(paramSize, params, missingParams);
+ var foundHelper = this.nameLookup('helpers', name, 'helper');
+
+ return {
+ params: params,
+ name: foundHelper,
+ callParams: ["depth0"].concat(params).join(", "),
+ helperMissingParams: missingParams && ["depth0", this.quotedString(name)].concat(params).join(", ")
+ };
+ },
+
+ // the params and contexts arguments are passed in arrays
+ // to fill in
+ setupParams: function(paramSize, params, useRegister) {
+ var options = [], contexts = [], types = [], param, inverse, program;
+
+ options.push("hash:" + this.popStack());
+
+ inverse = this.popStack();
+ program = this.popStack();
+
+ // Avoid setting fn and inverse if neither are set. This allows
+ // helpers to do a check for `if (options.fn)`
+ if (program || inverse) {
+ if (!program) {
+ this.context.aliases.self = "this";
+ program = "self.noop";
+ }
+
+ if (!inverse) {
+ this.context.aliases.self = "this";
+ inverse = "self.noop";
+ }
+
+ options.push("inverse:" + inverse);
+ options.push("fn:" + program);
+ }
+
+ for(var i=0; i<paramSize; i++) {
+ param = this.popStack();
+ params.push(param);
+
+ if(this.options.stringParams) {
+ types.push(this.popStack());
+ contexts.push(this.popStack());
+ }
+ }
+
+ if (this.options.stringParams) {
+ options.push("contexts:[" + contexts.join(",") + "]");
+ options.push("types:[" + types.join(",") + "]");
+ options.push("hashContexts:hashContexts");
+ options.push("hashTypes:hashTypes");
+ }
+
+ if(this.options.data) {
+ options.push("data:data");
+ }
+
+ options = "{" + options.join(",") + "}";
+ if (useRegister) {
+ this.register('options', options);
+ params.push('options');
+ } else {
+ params.push(options);
+ }
+ return params.join(", ");
+ }
+ };
+
+ var reservedWords = (
+ "break else new var" +
+ " case finally return void" +
+ " catch for switch while" +
+ " continue function this with" +
+ " default if throw" +
+ " delete in try" +
+ " do instanceof typeof" +
+ " abstract enum int short" +
+ " boolean export interface static" +
+ " byte extends long super" +
+ " char final native synchronized" +
+ " class float package throws" +
+ " const goto private transient" +
+ " debugger implements protected volatile" +
+ " double import public let yield"
+ ).split(" ");
+
+ var compilerWords = JavaScriptCompiler.RESERVED_WORDS = {};
+
+ for(var i=0, l=reservedWords.length; i<l; i++) {
+ compilerWords[reservedWords[i]] = true;
+ }
+
+ JavaScriptCompiler.isValidJavaScriptVariableName = function(name) {
+ if(!JavaScriptCompiler.RESERVED_WORDS[name] && /^[a-zA-Z_$][0-9a-zA-Z_$]+$/.test(name)) {
+ return true;
+ }
+ return false;
+ };
+
+ __exports__ = JavaScriptCompiler;
+ return __exports__;
+})(__module2__);
+
+// handlebars/compiler/compiler.js
+var __module10__ = (function(__dependency1__, __dependency2__, __dependency3__, __dependency4__) {
+ "use strict";
+ var __exports__ = {};
+ var Exception = __dependency1__;
+ var parse = __dependency2__.parse;
+ var JavaScriptCompiler = __dependency3__;
+ var AST = __dependency4__;
+
+ function Compiler() {}
+
+ __exports__.Compiler = Compiler;// the foundHelper register will disambiguate helper lookup from finding a
+ // function in a context. This is necessary for mustache compatibility, which
+ // requires that context functions in blocks are evaluated by blockHelperMissing,
+ // and then proceed as if the resulting value was provided to blockHelperMissing.
+
+ Compiler.prototype = {
+ compiler: Compiler,
+
+ disassemble: function() {
+ var opcodes = this.opcodes, opcode, out = [], params, param;
+
+ for (var i=0, l=opcodes.length; i<l; i++) {
+ opcode = opcodes[i];
+
+ if (opcode.opcode === 'DECLARE') {
+ out.push("DECLARE " + opcode.name + "=" + opcode.value);
+ } else {
+ params = [];
+ for (var j=0; j<opcode.args.length; j++) {
+ param = opcode.args[j];
+ if (typeof param === "string") {
+ param = "\"" + param.replace("\n", "\\n") + "\"";
+ }
+ params.push(param);
+ }
+ out.push(opcode.opcode + " " + params.join(" "));
+ }
+ }
+
+ return out.join("\n");
+ },
+
+ equals: function(other) {
+ var len = this.opcodes.length;
+ if (other.opcodes.length !== len) {
+ return false;
+ }
+
+ for (var i = 0; i < len; i++) {
+ var opcode = this.opcodes[i],
+ otherOpcode = other.opcodes[i];
+ if (opcode.opcode !== otherOpcode.opcode || opcode.args.length !== otherOpcode.args.length) {
+ return false;
+ }
+ for (var j = 0; j < opcode.args.length; j++) {
+ if (opcode.args[j] !== otherOpcode.args[j]) {
+ return false;
+ }
+ }
+ }
+
+ 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;
+ }
+ }
+
+ return true;
+ },
+
+ guid: 0,
+
+ compile: function(program, options) {
+ this.opcodes = [];
+ this.children = [];
+ this.depths = {list: []};
+ this.options = options;
+
+ // These changes will propagate to the other compiler components
+ var knownHelpers = this.options.knownHelpers;
+ this.options.knownHelpers = {
+ 'helperMissing': true,
+ 'blockHelperMissing': true,
+ 'each': true,
+ 'if': true,
+ 'unless': true,
+ 'with': true,
+ 'log': true
+ };
+ if (knownHelpers) {
+ for (var name in knownHelpers) {
+ this.options.knownHelpers[name] = knownHelpers[name];
+ }
+ }
+
+ return this.accept(program);
+ },
+
+ accept: function(node) {
+ var strip = node.strip || {},
+ ret;
+ if (strip.left) {
+ this.opcode('strip');
+ }
+
+ ret = this[node.type](node);
+
+ if (strip.right) {
+ this.opcode('strip');
+ }
+
+ return ret;
+ },
+
+ program: function(program) {
+ var statements = program.statements;
+
+ for(var i=0, l=statements.length; i<l; i++) {
+ this.accept(statements[i]);
+ }
+ this.isSimple = l === 1;
+
+ this.depths.list = this.depths.list.sort(function(a, b) {
+ return a - b;
+ });
+
+ return this;
+ },
+
+ compileProgram: function(program) {
+ var result = new this.compiler().compile(program, this.options);
+ var guid = this.guid++, depth;
+
+ this.usePartial = this.usePartial || result.usePartial;
+
+ this.children[guid] = result;
+
+ for(var i=0, l=result.depths.list.length; i<l; i++) {
+ depth = result.depths.list[i];
+
+ if(depth < 2) { continue; }
+ else { this.addDepth(depth - 1); }
+ }
+
+ return guid;
+ },
+
+ block: function(block) {
+ var mustache = block.mustache,
+ program = block.program,
+ inverse = block.inverse;
+
+ if (program) {
+ program = this.compileProgram(program);
+ }
+
+ if (inverse) {
+ inverse = this.compileProgram(inverse);
+ }
+
+ var type = this.classifyMustache(mustache);
+
+ if (type === "helper") {
+ this.helperMustache(mustache, program, inverse);
+ } else if (type === "simple") {
+ this.simpleMustache(mustache);
+
+ // now that the simple mustache is resolved, we need to
+ // evaluate it by executing `blockHelperMissing`
+ this.opcode('pushProgram', program);
+ this.opcode('pushProgram', inverse);
+ this.opcode('emptyHash');
+ this.opcode('blockValue');
+ } else {
+ this.ambiguousMustache(mustache, program, inverse);
+
+ // now that the simple mustache is resolved, we need to
+ // evaluate it by executing `blockHelperMissing`
+ this.opcode('pushProgram', program);
+ this.opcode('pushProgram', inverse);
+ this.opcode('emptyHash');
+ this.opcode('ambiguousBlockValue');
+ }
+
+ this.opcode('append');
+ },
+
+ hash: function(hash) {
+ var pairs = hash.pairs, pair, val;
+
+ this.opcode('pushHash');
+
+ for(var i=0, l=pairs.length; i<l; i++) {
+ pair = pairs[i];
+ val = pair[1];
+
+ if (this.options.stringParams) {
+ if(val.depth) {
+ this.addDepth(val.depth);
+ }
+ this.opcode('getContext', val.depth || 0);
+ this.opcode('pushStringParam', val.stringModeValue, val.type);
+ } else {
+ this.accept(val);
+ }
+
+ this.opcode('assignToHash', pair[0]);
+ }
+ this.opcode('popHash');
+ },
+
+ partial: function(partial) {
+ var partialName = partial.partialName;
+ this.usePartial = true;
+
+ if(partial.context) {
+ this.ID(partial.context);
+ } else {
+ this.opcode('push', 'depth0');
+ }
+
+ this.opcode('invokePartial', partialName.name);
+ this.opcode('append');
+ },
+
+ content: function(content) {
+ this.opcode('appendContent', content.string);
+ },
+
+ mustache: function(mustache) {
+ var options = this.options;
+ var type = this.classifyMustache(mustache);
+
+ if (type === "simple") {
+ this.simpleMustache(mustache);
+ } else if (type === "helper") {
+ this.helperMustache(mustache);
+ } else {
+ this.ambiguousMustache(mustache);
+ }
+
+ if(mustache.escaped && !options.noEscape) {
+ this.opcode('appendEscaped');
+ } else {
+ this.opcode('append');
+ }
+ },
+
+ ambiguousMustache: function(mustache, program, inverse) {
+ var id = mustache.id,
+ name = id.parts[0],
+ isBlock = program != null || inverse != null;
+
+ this.opcode('getContext', id.depth);
+
+ this.opcode('pushProgram', program);
+ this.opcode('pushProgram', inverse);
+
+ this.opcode('invokeAmbiguous', name, isBlock);
+ },
+
+ simpleMustache: function(mustache) {
+ var id = mustache.id;
+
+ if (id.type === 'DATA') {
+ this.DATA(id);
+ } else if (id.parts.length) {
+ this.ID(id);
+ } else {
+ // Simplified ID for `this`
+ this.addDepth(id.depth);
+ this.opcode('getContext', id.depth);
+ this.opcode('pushContext');
+ }
+
+ this.opcode('resolvePossibleLambda');
+ },
+
+ helperMustache: function(mustache, program, inverse) {
+ var params = this.setupFullMustacheParams(mustache, program, inverse),
+ name = mustache.id.parts[0];
+
+ if (this.options.knownHelpers[name]) {
+ this.opcode('invokeKnownHelper', params.length, name);
+ } else if (this.options.knownHelpersOnly) {
+ throw new Error("You specified knownHelpersOnly, but used the unknown helper " + name);
+ } else {
+ this.opcode('invokeHelper', params.length, name);
+ }
+ },
+
+ ID: function(id) {
+ this.addDepth(id.depth);
+ this.opcode('getContext', id.depth);
+
+ var name = id.parts[0];
+ if (!name) {
+ this.opcode('pushContext');
+ } else {
+ this.opcode('lookupOnContext', id.parts[0]);
+ }
+
+ for(var i=1, l=id.parts.length; i<l; i++) {
+ this.opcode('lookup', id.parts[i]);
+ }
+ },
+
+ DATA: function(data) {
+ this.options.data = true;
+ if (data.id.isScoped || data.id.depth) {
+ throw new Exception('Scoped data references are not supported: ' + data.original);
+ }
+
+ this.opcode('lookupData');
+ var parts = data.id.parts;
+ for(var i=0, l=parts.length; i<l; i++) {
+ this.opcode('lookup', parts[i]);
+ }
+ },
+
+ STRING: function(string) {
+ this.opcode('pushString', string.string);
+ },
+
+ INTEGER: function(integer) {
+ this.opcode('pushLiteral', integer.integer);
+ },
+
+ BOOLEAN: function(bool) {
+ this.opcode('pushLiteral', bool.bool);
+ },
+
+ comment: function() {},
+
+ // HELPERS
+ opcode: function(name) {
+ this.opcodes.push({ opcode: name, args: [].slice.call(arguments, 1) });
+ },
+
+ declare: function(name, value) {
+ this.opcodes.push({ opcode: 'DECLARE', name: name, value: value });
+ },
+
+ addDepth: function(depth) {
+ if(isNaN(depth)) { throw new Error("EWOT"); }
+ if(depth === 0) { return; }
+
+ if(!this.depths[depth]) {
+ this.depths[depth] = true;
+ this.depths.list.push(depth);
+ }
+ },
+
+ classifyMustache: function(mustache) {
+ var isHelper = mustache.isHelper;
+ var isEligible = mustache.eligibleHelper;
+ var options = this.options;
+
+ // if ambiguous, we can possibly resolve the ambiguity now
+ if (isEligible && !isHelper) {
+ var name = mustache.id.parts[0];
+
+ if (options.knownHelpers[name]) {
+ isHelper = true;
+ } else if (options.knownHelpersOnly) {
+ isEligible = false;
+ }
+ }
+
+ if (isHelper) { return "helper"; }
+ else if (isEligible) { return "ambiguous"; }
+ else { return "simple"; }
+ },
+
+ pushParams: function(params) {
+ var i = params.length, param;
+
+ while(i--) {
+ param = params[i];
+
+ if(this.options.stringParams) {
+ if(param.depth) {
+ this.addDepth(param.depth);
+ }
+
+ this.opcode('getContext', param.depth || 0);
+ this.opcode('pushStringParam', param.stringModeValue, param.type);
+ } else {
+ this[param.type](param);
+ }
+ }
+ },
+
+ setupMustacheParams: function(mustache) {
+ var params = mustache.params;
+ this.pushParams(params);
+
+ 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);
+
+ this.opcode('pushProgram', program);
+ this.opcode('pushProgram', inverse);
+
+ if(mustache.hash) {
+ this.hash(mustache.hash);
+ } else {
+ this.opcode('emptyHash');
+ }
+
+ return params;
+ }
+ };
+
+ function precompile(input, options) {
+ if (input == null || (typeof input !== 'string' && input.constructor !== 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 = parse(input);
+ var environment = new Compiler().compile(ast, options);
+ return new JavaScriptCompiler().compile(environment, options);
+ }
+
+ __exports__.precompile = precompile;function compile(input, options, env) {
+ if (input == null || (typeof input !== 'string' && input.constructor !== 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 = parse(input);
+ var environment = new Compiler().compile(ast, options);
+ var templateSpec = new 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__, __module8__, __module11__, __module7__);
+
+// handlebars.js
+var __module0__ = (function(__dependency1__, __dependency2__, __dependency3__, __dependency4__, __dependency5__) {
+ "use strict";
+ var __exports__;
+ var Handlebars = __dependency1__;
+
+ // 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__;
+
+ var _create = Handlebars.create;
+ var create = function() {
+ var hb = _create();
+
+ hb.compile = function(input, options) {
+ return compile(input, options, hb);
+ };
+ hb.precompile = precompile;
+
+ hb.AST = AST;
+ hb.Compiler = Compiler;
+ hb.JavaScriptCompiler = JavaScriptCompiler;
+ hb.Parser = Parser;
+ hb.parse = parse;
+
+ return hb;
+ };
+
+ Handlebars = create();
+ Handlebars.create = create;
+
+ __exports__ = Handlebars;
+ return __exports__;
+})(__module1__, __module7__, __module8__, __module10__, __module11__);
+
+ return __module0__;
+})();
Added: test-js-frameworks/emberJS/js/libs/jquery-1.10.2.js
===================================================================
--- test-js-frameworks/emberJS/js/libs/jquery-1.10.2.js (rev 0)
+++ test-js-frameworks/emberJS/js/libs/jquery-1.10.2.js 2014-04-17 09:35:38 UTC (rev 701)
@@ -0,0 +1,9789 @@
+/*!
+ * jQuery JavaScript Library v1.10.2
+ * http://jquery.com/
+ *
+ * Includes Sizzle.js
+ * http://sizzlejs.com/
+ *
+ * Copyright 2005, 2013 jQuery Foundation, Inc. and other contributors
+ * Released under the MIT license
+ * http://jquery.org/license
+ *
+ * Date: 2013-07-03T13:48Z
+ */
+(function( window, undefined ) {
+
+// Can't do this because several apps including ASP.NET trace
+// the stack via arguments.caller.callee and Firefox dies if
+// you try to trace through "use strict" call chains. (#13335)
+// Support: Firefox 18+
+//"use strict";
+var
+ // The deferred used on DOM ready
+ readyList,
+
+ // A central reference to the root jQuery(document)
+ rootjQuery,
+
+ // Support: IE<10
+ // For `typeof xmlNode.method` instead of `xmlNode.method !== undefined`
+ core_strundefined = typeof undefined,
+
+ // Use the correct document accordingly with window argument (sandbox)
+ location = window.location,
+ document = window.document,
+ docElem = document.documentElement,
+
+ // Map over jQuery in case of overwrite
+ _jQuery = window.jQuery,
+
+ // Map over the $ in case of overwrite
+ _$ = window.$,
+
+ // [[Class]] -> type pairs
+ class2type = {},
+
+ // List of deleted data cache ids, so we can reuse them
+ core_deletedIds = [],
+
+ core_version = "1.10.2",
+
+ // 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,
+
+ // 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 );
+ },
+
+ // Used for matching numbers
+ core_pnum = /[+-]?(?:\d*\.|)\d+(?:[eE][+-]?\d+|)/.source,
+
+ // Used for splitting on whitespace
+ core_rnotwhite = /\S+/g,
+
+ // 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,
+
+ // 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-]*))$/,
+
+ // Match a standalone tag
+ rsingleTag = /^<(\w+)\s*\/?>(?:<\/\1>|)$/,
+
+ // 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,
+
+ // Matches dashed string for camelizing
+ rmsPrefix = /^-ms-/,
+ rdashAlpha = /-([\da-z])/gi,
+
+ // Used by jQuery.camelCase as callback to replace()
+ fcamelCase = function( all, letter ) {
+ 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 );
+ }
+ };
+
+jQuery.fn = jQuery.prototype = {
+ // The current version of jQuery being used
+ jquery: core_version,
+
+ constructor: jQuery,
+ init: function( selector, context, rootjQuery ) {
+ var match, elem;
+
+ // 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 );
+ },
+
+ // Start with an empty selector
+ selector: "",
+
+ // The default length of a jQuery object is 0
+ length: 0,
+
+ toArray: function() {
+ return core_slice.call( this );
+ },
+
+ // Get the Nth element in the matched element set OR
+ // Get the whole matched element set as a clean array
+ get: function( num ) {
+ return num == null ?
+
+ // Return a 'clean' array
+ this.toArray() :
+
+ // Return just the object
+ ( num < 0 ? this[ this.length + num ] : this[ num ] );
+ },
+
+ // Take an array of elements and push it onto the stack
+ // (returning the new matched element set)
+ pushStack: function( elems ) {
+
+ // Build a new jQuery matched element set
+ var ret = jQuery.merge( this.constructor(), elems );
+
+ // Add the old object onto the stack (as a reference)
+ ret.prevObject = this;
+ ret.context = this.context;
+
+ // Return the newly-formed element set
+ return ret;
+ },
+
+ // Execute a callback for every element in the matched set.
+ // (You can seed the arguments with an array of args, but this is
+ // only used internally.)
+ each: function( callback, args ) {
+ return jQuery.each( this, callback, args );
+ },
+
+ ready: function( fn ) {
+ // Add the callback
+ jQuery.ready.promise().done( fn );
+
+ return this;
+ },
+
+ slice: function() {
+ return this.pushStack( core_slice.apply( this, arguments ) );
+ },
+
+ first: function() {
+ return this.eq( 0 );
+ },
+
+ last: function() {
+ return this.eq( -1 );
+ },
+
+ eq: function( i ) {
+ var len = this.length,
+ j = +i + ( i < 0 ? len : 0 );
+ return this.pushStack( j >= 0 && j < len ? [ this[j] ] : [] );
+ },
+
+ map: function( callback ) {
+ return this.pushStack( jQuery.map(this, function( elem, i ) {
+ return callback.call( elem, i, elem );
+ }));
+ },
+
+ end: function() {
+ return this.prevObject || this.constructor(null);
+ },
+
+ // For internal use only.
+ // Behaves like an Array's method, not like a jQuery method.
+ push: core_push,
+ sort: [].sort,
+ splice: [].splice
+};
+
+// Give the init function the jQuery prototype for later instantiation
+jQuery.fn.init.prototype = jQuery.fn;
+
+jQuery.extend = jQuery.fn.extend = function() {
+ var src, copyIsArray, copy, name, options, clone,
+ target = arguments[0] || {},
+ i = 1,
+ length = arguments.length,
+ deep = false;
+
+ // Handle a deep copy situation
+ if ( typeof target === "boolean" ) {
+ deep = target;
+ target = arguments[1] || {};
+ // skip the boolean and the target
+ i = 2;
+ }
+
+ // Handle case when target is a string or something (possible in deep copy)
+ if ( typeof target !== "object" && !jQuery.isFunction(target) ) {
+ target = {};
+ }
+
+ // extend jQuery itself if only one argument is passed
+ if ( length === i ) {
+ target = this;
+ --i;
+ }
+
+ for ( ; i < length; i++ ) {
+ // 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 ];
+
+ // Prevent never-ending loop
+ if ( target === copy ) {
+ continue;
+ }
+
+ // 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 : [];
+
+ } else {
+ clone = src && jQuery.isPlainObject(src) ? src : {};
+ }
+
+ // Never move original objects, clone them
+ target[ name ] = jQuery.extend( deep, clone, copy );
+
+ // Don't bring in undefined values
+ } else if ( copy !== undefined ) {
+ target[ name ] = copy;
+ }
+ }
+ }
+ }
+
+ // Return the modified object
+ return target;
+};
+
+jQuery.extend({
+ // Unique for each copy of jQuery on the page
+ // Non-digits removed to match rinlinejQuery
+ expando: "jQuery" + ( core_version + Math.random() ).replace( /\D/g, "" ),
+
+ noConflict: function( deep ) {
+ if ( window.$ === jQuery ) {
+ window.$ = _$;
+ }
+
+ if ( deep && window.jQuery === jQuery ) {
+ window.jQuery = _jQuery;
+ }
+
+ return jQuery;
+ },
+
+ // Is the DOM ready to be used? Set to true once it occurs.
+ isReady: false,
+
+ // 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");
+ }
+ },
+
+ // See test/unit/core.js for details concerning isFunction.
+ // Since version 1.3, DOM methods and functions like alert
+ // aren't supported. They return false on IE (#2968).
+ isFunction: function( obj ) {
+ return jQuery.type(obj) === "function";
+ },
+
+ isArray: Array.isArray || function( obj ) {
+ return jQuery.type(obj) === "array";
+ },
+
+ isWindow: function( obj ) {
+ /* jshint eqeqeq: false */
+ return obj != null && obj == obj.window;
+ },
+
+ isNumeric: function( obj ) {
+ return !isNaN( parseFloat(obj) ) && isFinite( obj );
+ },
+
+ type: function( obj ) {
+ if ( obj == null ) {
+ return String( obj );
+ }
+ return typeof obj === "object" || typeof obj === "function" ?
+ class2type[ core_toString.call(obj) ] || "object" :
+ typeof obj;
+ },
+
+ isPlainObject: function( obj ) {
+ var key;
+
+ // 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;
+ }
+
+ 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;
+ }
+
+ // Support: IE<9
+ // Handle iteration over inherited properties before own properties.
+ if ( jQuery.support.ownLast ) {
+ for ( key in obj ) {
+ return core_hasOwn.call( obj, key );
+ }
+ }
+
+ // Own properties are enumerated firstly, so to speed up,
+ // if last one is own, then all properties are own.
+ for ( key in obj ) {}
+
+ return key === undefined || core_hasOwn.call( obj, key );
+ },
+
+ isEmptyObject: function( obj ) {
+ var name;
+ for ( name in obj ) {
+ return false;
+ }
+ return true;
+ },
+
+ error: function( msg ) {
+ throw new Error( msg );
+ },
+
+ // 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;
+
+ var parsed = rsingleTag.exec( data ),
+ scripts = !keepScripts && [];
+
+ // 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 );
+ },
+
+ 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-gl…
+ 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 );
+ }
+ },
+
+ // Convert dashed to camelCase; used by the css and data modules
+ // Microsoft forgot to hump their vendor prefix (#9572)
+ camelCase: function( string ) {
+ return string.replace( rmsPrefix, "ms-" ).replace( rdashAlpha, fcamelCase );
+ },
+
+ nodeName: function( elem, name ) {
+ return elem.nodeName && elem.nodeName.toLowerCase() === name.toLowerCase();
+ },
+
+ // args is for internal usage only
+ each: function( obj, callback, args ) {
+ var value,
+ i = 0,
+ length = obj.length,
+ isArray = isArraylike( obj );
+
+ if ( args ) {
+ if ( isArray ) {
+ for ( ; i < length; i++ ) {
+ value = callback.apply( obj[ i ], args );
+
+ if ( value === false ) {
+ break;
+ }
+ }
+ } else {
+ for ( i in obj ) {
+ value = callback.apply( obj[ i ], args );
+
+ if ( value === false ) {
+ break;
+ }
+ }
+ }
+
+ // 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 ] );
+
+ if ( value === false ) {
+ break;
+ }
+ }
+ } else {
+ for ( i in obj ) {
+ value = callback.call( obj[ i ], i, obj[ i ] );
+
+ if ( value === false ) {
+ break;
+ }
+ }
+ }
+ }
+
+ return obj;
+ },
+
+ // 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, "" );
+ },
+
+ // results is for internal usage only
+ makeArray: function( arr, results ) {
+ var ret = results || [];
+
+ if ( arr != null ) {
+ if ( isArraylike( Object(arr) ) ) {
+ jQuery.merge( ret,
+ typeof arr === "string" ?
+ [ arr ] : arr
+ );
+ } else {
+ core_push.call( ret, arr );
+ }
+ }
+
+ return ret;
+ },
+
+ inArray: function( elem, arr, i ) {
+ 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;
+ },
+
+ merge: function( first, second ) {
+ var l = second.length,
+ i = first.length,
+ j = 0;
+
+ if ( typeof l === "number" ) {
+ for ( ; j < l; j++ ) {
+ first[ i++ ] = second[ j ];
+ }
+ } else {
+ while ( second[j] !== undefined ) {
+ first[ i++ ] = second[ j++ ];
+ }
+ }
+
+ first.length = i;
+
+ return first;
+ },
+
+ grep: function( elems, callback, inv ) {
+ var retVal,
+ ret = [],
+ i = 0,
+ length = elems.length;
+ inv = !!inv;
+
+ // 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 ] );
+ }
+ }
+
+ return ret;
+ },
+
+ // arg is for internal usage only
+ map: function( elems, callback, arg ) {
+ var value,
+ i = 0,
+ length = elems.length,
+ isArray = isArraylike( elems ),
+ ret = [];
+
+ // Go through the array, translating each of the items to their
+ if ( isArray ) {
+ for ( ; i < length; i++ ) {
+ value = callback( elems[ i ], i, arg );
+
+ if ( value != null ) {
+ ret[ ret.length ] = value;
+ }
+ }
+
+ // Go through every key on the object,
+ } else {
+ for ( i in elems ) {
+ value = callback( elems[ i ], i, arg );
+
+ if ( value != null ) {
+ ret[ ret.length ] = value;
+ }
+ }
+ }
+
+ // Flatten any nested arrays
+ return core_concat.apply( [], ret );
+ },
+
+ // A global GUID counter for objects
+ guid: 1,
+
+ // Bind a function to a context, optionally partially applying any
+ // arguments.
+ proxy: function( fn, context ) {
+ var args, proxy, tmp;
+
+ if ( typeof context === "string" ) {
+ tmp = fn[ context ];
+ context = fn;
+ fn = tmp;
+ }
+
+ // 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;
+ }
+
+ // Simulated bind
+ args = core_slice.call( arguments, 2 );
+ proxy = function() {
+ return fn.apply( context || this, args.concat( core_slice.call( arguments ) ) );
+ };
+
+ // 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++;
+
+ return proxy;
+ },
+
+ // 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;
+
+ // Sets many values
+ if ( jQuery.type( key ) === "object" ) {
+ chainable = true;
+ for ( i in key ) {
+ jQuery.access( elems, fn, i, key[i], true, emptyGet, raw );
+ }
+
+ // Sets one value
+ } else if ( value !== undefined ) {
+ chainable = true;
+
+ if ( !jQuery.isFunction( value ) ) {
+ raw = true;
+ }
+
+ 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 );
+ };
+ }
+ }
+
+ if ( fn ) {
+ for ( ; i < length; i++ ) {
+ fn( elems[i], key, raw ? value : value.call( elems[i], i, fn( elems[i], key ) ) );
+ }
+ }
+ }
+
+ return chainable ?
+ elems :
+
+ // Gets
+ bulk ?
+ fn.call( elems ) :
+ length ? fn( elems[0], key ) : emptyGet;
+ },
+
+ now: function() {
+ return ( new Date() ).getTime();
+ },
+
+ // A method for quickly swapping in/out CSS properties to get correct calculations.
+ // Note: this method belongs to the css module but it's needed here for the support module.
+ // If support gets modularized, this method should be moved back to the css module.
+ 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 ];
+ }
+
+ ret = callback.apply( elem, args || [] );
+
+ // Revert the old values
+ for ( name in options ) {
+ elem.style[ name ] = old[ name ];
+ }
+
+ return ret;
+ }
+});
+
+jQuery.ready.promise = function( obj ) {
+ if ( !readyList ) {
+
+ readyList = jQuery.Deferred();
+
+ // 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 );
+
+ // Standards-based browsers support DOMContentLoaded
+ } else if ( document.addEventListener ) {
+ // Use the handy event callback
+ document.addEventListener( "DOMContentLoaded", completed, false );
+
+ // A fallback to window.onload, that will always work
+ window.addEventListener( "load", completed, false );
+
+ // If IE event model is used
+ } else {
+ // Ensure firing before onload, maybe late but safe also for iframes
+ document.attachEvent( "onreadystatechange", completed );
+
+ // A fallback to window.onload, that will always work
+ window.attachEvent( "onload", completed );
+
+ // If IE and not a frame
+ // continually check to see if the document is ready
+ var top = false;
+
+ try {
+ top = window.frameElement == null && document.documentElement;
+ } catch(e) {}
+
+ if ( top && top.doScroll ) {
+ (function doScrollCheck() {
+ if ( !jQuery.isReady ) {
+
+ try {
+ // Use the trick by Diego Perini
+ // http://javascript.nwbox.com/IEContentLoaded/
+ top.doScroll("left");
+ } catch(e) {
+ return setTimeout( doScrollCheck, 50 );
+ }
+
+ // detach all dom ready events
+ detach();
+
+ // and execute any waiting functions
+ jQuery.ready();
+ }
+ })();
+ }
+ }
+ }
+ return readyList.promise( obj );
+};
+
+// Populate the class2type map
+jQuery.each("Boolean Number String Function Array Date RegExp Object Error".split(" "), function(i, name) {
+ class2type[ "[object " + name + "]" ] = name.toLowerCase();
+});
+
+function isArraylike( obj ) {
+ var length = obj.length,
+ type = jQuery.type( obj );
+
+ if ( jQuery.isWindow( obj ) ) {
+ return false;
+ }
+
+ if ( obj.nodeType === 1 && length ) {
+ return true;
+ }
+
+ return type === "array" || type !== "function" &&
+ ( length === 0 ||
+ typeof length === "number" && length > 0 && ( length - 1 ) in obj );
+}
+
+// All jQuery objects should point back to these
+rootjQuery = jQuery(document);
+/*!
+ * Sizzle CSS Selector Engine v1.10.2
+ * http://sizzlejs.com/
+ *
+ * Copyright 2013 jQuery Foundation, Inc. and other contributors
+ * Released under the MIT license
+ * http://jquery.org/license
+ *
+ * Date: 2013-07-03
+ */
+(function( window, undefined ) {
+
+var i,
+ support,
+ cachedruns,
+ Expr,
+ getText,
+ isXML,
+ compile,
+ outermostContext,
+ sortInput,
+
+ // 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(),
+ hasDuplicate = false,
+ sortOrder = function( a, b ) {
+ if ( a === b ) {
+ hasDuplicate = true;
+ return 0;
+ }
+ return 0;
+ },
+
+ // General-purpose constants
+ strundefined = typeof undefined,
+ MAX_NEGATIVE = 1 << 31,
+
+ // 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;
+ },
+
+ booleans = "checked|selected|async|autofocus|autoplay|controls|defer|disabled|hidden|ismap|loop|multiple|open|readonly|required|scoped",
+
+ // Regular expressions
+
+ // 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])+",
+
+ // 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#" ),
+
+ // Acceptable operators http://www.w3.org/TR/selectors/#attribute-selectors
+ attributes = "\\[" + whitespace + "*(" + characterEncoding + ")" + whitespace +
+ "*(?:([*^$|!~]?=)" + whitespace + "*(?:(['\"])((?:\\\\.|[^\\\\])*?)\\3|(" + identifier + ")|)|)" + whitespace + "*\\]",
+
+ // 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 ) + ")*)|.*)\\)|)",
+
+ // Leading and non-escaped trailing whitespace, capturing some non-whitespace characters preceding the latter
+ rtrim = new RegExp( "^" + whitespace + "+|((?:^|[^\\\\])(?:\\\\.)*)" + whitespace + "+$", "g" ),
+
+ rcomma = new RegExp( "^" + whitespace + "*," + whitespace + "*" ),
+ rcombinators = new RegExp( "^" + whitespace + "*([>+~]|" + whitespace + ")" + whitespace + "*" ),
+
+ rsibling = new RegExp( whitespace + "*[+~]" ),
+ rattributeQuotes = new RegExp( "=" + whitespace + "*([^\\]'\"]*)" + whitespace + "*\\]", "g" ),
+
+ rpseudo = new RegExp( pseudos ),
+ ridentifier = new RegExp( "^" + identifier + "$" ),
+
+ 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" )
+ },
+
+ rnative = /^[^{]+\{\s*\[native \w/,
+
+ // Easily-parseable/retrievable ID or TAG or CLASS selectors
+ rquickExpr = /^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/,
+
+ rinputs = /^(?:input|select|textarea|button)$/i,
+ rheader = /^h\d$/i,
+
+ rescape = /'|\\/g,
+
+ // 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 :
+ // BMP codepoint
+ high < 0 ?
+ String.fromCharCode( high + 0x10000 ) :
+ // Supplemental Plane codepoint (surrogate pair)
+ String.fromCharCode( high >> 10 | 0xD800, high & 0x3FF | 0xDC00 );
+ };
+
+// 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 ?
+
+ // Leverage slice if possible
+ function( target, els ) {
+ push_native.apply( target, slice.call(els) );
+ } :
+
+ // 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;
+ }
+ };
+}
+
+function Sizzle( selector, context, results, seed ) {
+ var match, elem, m, nodeType,
+ // QSA vars
+ i, groups, old, nid, newContext, newSelector;
+
+ if ( ( context ? context.ownerDocument || context : preferredDoc ) !== document ) {
+ setDocument( context );
+ }
+
+ context = context || document;
+ results = results || [];
+
+ if ( !selector || typeof selector !== "string" ) {
+ return results;
+ }
+
+ if ( (nodeType = context.nodeType) !== 1 && nodeType !== 9 ) {
+ return [];
+ }
+
+ if ( documentIsHTML && !seed ) {
+
+ // 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;
+ }
+ } 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;
+ }
+ }
+
+ // Speed-up: Sizzle("TAG")
+ } else if ( match[2] ) {
+ push.apply( results, context.getElementsByTagName( selector ) );
+ return results;
+
+ // Speed-up: Sizzle(".CLASS")
+ } else if ( (m = match[3]) && support.getElementsByClassName && context.getElementsByClassName ) {
+ push.apply( results, context.getElementsByClassName( m ) );
+ return results;
+ }
+ }
+
+ // QSA path
+ if ( support.qsa && (!rbuggyQSA || !rbuggyQSA.test( selector )) ) {
+ nid = old = expando;
+ newContext = context;
+ newSelector = nodeType === 9 && selector;
+
+ // 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 );
+
+ if ( (old = context.getAttribute("id")) ) {
+ nid = old.replace( rescape, "\\$&" );
+ } else {
+ context.setAttribute( "id", nid );
+ }
+ nid = "[id='" + nid + "'] ";
+
+ i = groups.length;
+ while ( i-- ) {
+ groups[i] = nid + toSelector( groups[i] );
+ }
+ newContext = rsibling.test( selector ) && 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");
+ }
+ }
+ }
+ }
+ }
+
+ // All others
+ return select( selector.replace( rtrim, "$1" ), context, results, seed );
+}
+
+/**
+ * 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 = [];
+
+ 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;
+}
+
+/**
+ * Mark a function for special use by Sizzle
+ * @param {Function} fn The function to mark
+ */
+function markFunction( fn ) {
+ fn[ expando ] = true;
+ return fn;
+}
+
+/**
+ * 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;
+ }
+}
+
+/**
+ * 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;
+
+ while ( i-- ) {
+ Expr.attrHandle[ arr[i] ] = handler;
+ }
+}
+
+/**
+ * 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;
+ }
+
+ // Check if b follows a
+ if ( cur ) {
+ while ( (cur = cur.nextSibling) ) {
+ if ( cur === b ) {
+ return -1;
+ }
+ }
+ }
+
+ return a ? 1 : -1;
+}
+
+/**
+ * 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;
+ };
+}
+
+/**
+ * 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;
+ };
+}
+
+/**
+ * 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;
+
+ // Match elements found at the specified indexes
+ while ( i-- ) {
+ if ( seed[ (j = matchIndexes[i]) ] ) {
+ seed[j] = !(matches[j] = seed[j]);
+ }
+ }
+ });
+ });
+}
+
+/**
+ * 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;
+};
+
+// Expose support vars for convenience
+support = Sizzle.support = {};
+
+/**
+ * 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,
+ parent = doc.defaultView;
+
+ // If no document and documentElement is available, return
+ if ( doc === document || doc.nodeType !== 9 || !doc.documentElement ) {
+ return document;
+ }
+
+ // Set our document
+ document = doc;
+ docElem = doc.documentElement;
+
+ // Support tests
+ documentIsHTML = !isXML( doc );
+
+ // 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.attachEvent && parent !== parent.top ) {
+ parent.attachEvent( "onbeforeunload", function() {
+ setDocument();
+ });
+ }
+
+ /* Attributes
+ ---------------------------------------------------------------------- */
+
+ // 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");
+ });
+
+ /* getElement(s)By*
+ ---------------------------------------------------------------------- */
+
+ // Check if getElementsByTagName("*") returns only elements
+ support.getElementsByTagName = assert(function( div ) {
+ div.appendChild( doc.createComment("") );
+ return !div.getElementsByTagName("*").length;
+ });
+
+ // Check if getElementsByClassName can be trusted
+ support.getElementsByClassName = assert(function( div ) {
+ div.innerHTML = "<div class='a'></div><div class='a i'></div>";
+
+ // 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;
+ });
+
+ // 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;
+ });
+
+ // 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;
+ };
+ };
+ }
+
+ // 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 );
+
+ // Filter out possible comments
+ if ( tag === "*" ) {
+ while ( (elem = results[i++]) ) {
+ if ( elem.nodeType === 1 ) {
+ tmp.push( elem );
+ }
+ }
+
+ return tmp;
+ }
+ return results;
+ };
+
+ // Class
+ Expr.find["CLASS"] = support.getElementsByClassName && function( className, context ) {
+ if ( typeof context.getElementsByClassName !== strundefined && documentIsHTML ) {
+ return context.getElementsByClassName( className );
+ }
+ };
+
+ /* QSA/matchesSelector
+ ---------------------------------------------------------------------- */
+
+ // QSA and matchesSelector support
+
+ // matchesSelector(:active) reports false when true (IE9/Opera 11.5)
+ rbuggyMatches = [];
+
+ // 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 = [];
+
+ 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><option selected=''></option></select>";
+
+ // Support: IE8
+ // Boolean attributes and "value" are not treated correctly
+ if ( !div.querySelectorAll("[selected]").length ) {
+ rbuggyQSA.push( "\\[" + whitespace + "*(?:value|" + booleans + ")" );
+ }
+
+ // 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");
+ }
+ });
+
+ assert(function( div ) {
+
+ // Support: Opera 10-12/IE8
+ // ^= $= *= and empty values
+ // Should not select anything
+ // Support: Windows 8 Native Apps
+ // The type attribute is restricted during .innerHTML assignment
+ var input = doc.createElement("input");
+ input.setAttribute( "type", "hidden" );
+ div.appendChild( input ).setAttribute( "t", "" );
+
+ if ( div.querySelectorAll("[t^='']").length ) {
+ rbuggyQSA.push( "[*^$]=" + whitespace + "*(?:''|\"\")" );
+ }
+
+ // 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" );
+ }
+
+ // Opera 10-11 does not throw on post-comma invalid pseudos
+ div.querySelectorAll("*,:x");
+ rbuggyQSA.push(",.*:");
+ });
+ }
+
+ if ( (support.matchesSelector = rnative.test( (matches = docElem.webkitMatchesSelector ||
+ docElem.mozMatchesSelector ||
+ docElem.oMatchesSelector ||
+ docElem.msMatchesSelector) )) ) {
+
+ 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" );
+
+ // This should fail with an exception
+ // Gecko does not error, returns false instead
+ matches.call( div, "[s!='']:x" );
+ rbuggyMatches.push( "!=", pseudos );
+ });
+ }
+
+ rbuggyQSA = rbuggyQSA.length && new RegExp( rbuggyQSA.join("|") );
+ rbuggyMatches = rbuggyMatches.length && new RegExp( rbuggyMatches.join("|") );
+
+ /* Contains
+ ---------------------------------------------------------------------- */
+
+ // Element contains another
+ // Purposefully does not implement inclusive descendent
+ // As in, an element does not contain itself
+ contains = rnative.test( 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;
+ };
+
+ /* Sorting
+ ---------------------------------------------------------------------- */
+
+ // Document order sorting
+ sortOrder = docElem.compareDocumentPosition ?
+ function( a, b ) {
+
+ // Flag for duplicate removal
+ if ( a === b ) {
+ hasDuplicate = true;
+ return 0;
+ }
+
+ var compare = b.compareDocumentPosition && a.compareDocumentPosition && a.compareDocumentPosition( b );
+
+ if ( compare ) {
+ // Disconnected nodes
+ if ( compare & 1 ||
+ (!support.sortDetached && b.compareDocumentPosition( a ) === compare) ) {
+
+ // Choose the first element that is related to our preferred document
+ if ( a === doc || contains(preferredDoc, a) ) {
+ return -1;
+ }
+ if ( b === doc || contains(preferredDoc, b) ) {
+ return 1;
+ }
+
+ // Maintain original order
+ return sortInput ?
+ ( indexOf.call( sortInput, a ) - indexOf.call( sortInput, b ) ) :
+ 0;
+ }
+
+ return compare & 4 ? -1 : 1;
+ }
+
+ // Not directly comparable, sort on existence of method
+ return a.compareDocumentPosition ? -1 : 1;
+ } :
+ function( a, b ) {
+ var cur,
+ i = 0,
+ aup = a.parentNode,
+ bup = b.parentNode,
+ ap = [ a ],
+ bp = [ b ];
+
+ // Exit early if the nodes are identical
+ if ( a === b ) {
+ hasDuplicate = true;
+ return 0;
+
+ // Parentless nodes are either documents or disconnected
+ } else if ( !aup || !bup ) {
+ return a === doc ? -1 :
+ b === doc ? 1 :
+ aup ? -1 :
+ bup ? 1 :
+ sortInput ?
+ ( indexOf.call( sortInput, a ) - indexOf.call( sortInput, b ) ) :
+ 0;
+
+ // If the nodes are siblings, we can do a quick check
+ } else if ( aup === bup ) {
+ return siblingCheck( a, b );
+ }
+
+ // 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 );
+ }
+
+ // Walk down the tree looking for a discrepancy
+ while ( ap[i] === bp[i] ) {
+ i++;
+ }
+
+ return i ?
+ // Do a sibling check if the nodes have a common ancestor
+ siblingCheck( ap[i], bp[i] ) :
+
+ // Otherwise nodes in our document sort first
+ ap[i] === preferredDoc ? -1 :
+ bp[i] === preferredDoc ? 1 :
+ 0;
+ };
+
+ return doc;
+};
+
+Sizzle.matches = function( expr, elements ) {
+ return Sizzle( expr, null, null, elements );
+};
+
+Sizzle.matchesSelector = function( elem, expr ) {
+ // Set document vars if needed
+ if ( ( elem.ownerDocument || elem ) !== document ) {
+ setDocument( elem );
+ }
+
+ // Make sure that attribute selectors are quoted
+ expr = expr.replace( rattributeQuotes, "='$1']" );
+
+ if ( support.matchesSelector && documentIsHTML &&
+ ( !rbuggyMatches || !rbuggyMatches.test( expr ) ) &&
+ ( !rbuggyQSA || !rbuggyQSA.test( expr ) ) ) {
+
+ try {
+ var ret = matches.call( elem, expr );
+
+ // 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) {}
+ }
+
+ return Sizzle( expr, document, null, [elem] ).length > 0;
+};
+
+Sizzle.contains = function( context, elem ) {
+ // Set document vars if needed
+ if ( ( context.ownerDocument || context ) !== document ) {
+ setDocument( context );
+ }
+ return contains( context, elem );
+};
+
+Sizzle.attr = function( elem, name ) {
+ // Set document vars if needed
+ if ( ( elem.ownerDocument || elem ) !== document ) {
+ setDocument( elem );
+ }
+
+ 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;
+
+ return val === undefined ?
+ support.attributes || !documentIsHTML ?
+ elem.getAttribute( name ) :
+ (val = elem.getAttributeNode(name)) && val.specified ?
+ val.value :
+ null :
+ val;
+};
+
+Sizzle.error = function( msg ) {
+ throw new Error( "Syntax error, unrecognized expression: " + msg );
+};
+
+/**
+ * Document sorting and removing duplicates
+ * @param {ArrayLike} results
+ */
+Sizzle.uniqueSort = function( results ) {
+ var elem,
+ duplicates = [],
+ j = 0,
+ i = 0;
+
+ // Unless we *know* we can detect duplicates, assume their presence
+ hasDuplicate = !support.detectDuplicates;
+ sortInput = !support.sortStable && results.slice( 0 );
+ results.sort( sortOrder );
+
+ if ( hasDuplicate ) {
+ while ( (elem = results[i++]) ) {
+ if ( elem === results[ i ] ) {
+ j = duplicates.push( i );
+ }
+ }
+ while ( j-- ) {
+ results.splice( duplicates[ j ], 1 );
+ }
+ }
+
+ return results;
+};
+
+/**
+ * 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;
+
+ 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
+
+ return ret;
+};
+
+Expr = Sizzle.selectors = {
+
+ // Can be adjusted by the user
+ cacheLength: 50,
+
+ createPseudo: markFunction,
+
+ match: matchExpr,
+
+ attrHandle: {},
+
+ find: {},
+
+ relative: {
+ ">": { dir: "parentNode", first: true },
+ " ": { dir: "parentNode" },
+ "+": { dir: "previousSibling", first: true },
+ "~": { dir: "previousSibling" }
+ },
+
+ preFilter: {
+ "ATTR": function( match ) {
+ match[1] = match[1].replace( runescape, funescape );
+
+ // Move the given value to match[3] whether quoted or unquoted
+ match[3] = ( match[4] || match[5] || "" ).replace( runescape, funescape );
+
+ if ( match[2] === "~=" ) {
+ match[3] = " " + match[3] + " ";
+ }
+
+ return match.slice( 0, 4 );
+ },
+
+ "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();
+
+ if ( match[1].slice( 0, 3 ) === "nth" ) {
+ // nth-* requires argument
+ if ( !match[3] ) {
+ Sizzle.error( match[0] );
+ }
+
+ // 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" );
+
+ // other types prohibit arguments
+ } else if ( match[3] ) {
+ Sizzle.error( match[0] );
+ }
+
+ return match;
+ },
+
+ "PSEUDO": function( match ) {
+ var excess,
+ unquoted = !match[5] && match[2];
+
+ if ( matchExpr["CHILD"].test( match[0] ) ) {
+ return null;
+ }
+
+ // Accept quoted arguments as-is
+ if ( match[3] && match[4] !== undefined ) {
+ match[2] = match[4];
+
+ // 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) ) {
+
+ // 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 );
+ }
+ },
+
+ filter: {
+
+ "TAG": function( nodeNameSelector ) {
+ var nodeName = nodeNameSelector.replace( runescape, funescape ).toLowerCase();
+ return nodeNameSelector === "*" ?
+ function() { return true; } :
+ function( elem ) {
+ return elem.nodeName && elem.nodeName.toLowerCase() === nodeName;
+ };
+ },
+
+ "CLASS": function( className ) {
+ var pattern = classCache[ className + " " ];
+
+ 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") || "" );
+ });
+ },
+
+ "ATTR": function( name, operator, check ) {
+ return function( elem ) {
+ var result = Sizzle.attr( elem, name );
+
+ if ( result == null ) {
+ return operator === "!=";
+ }
+ if ( !operator ) {
+ return true;
+ }
+
+ result += "";
+
+ 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;
+ };
+ },
+
+ "CHILD": function( type, what, argument, first, last ) {
+ var simple = type.slice( 0, 3 ) !== "nth",
+ forward = type.slice( -4 ) !== "last",
+ ofType = what === "of-type";
+
+ return first === 1 && last === 0 ?
+
+ // Shortcut for :nth-*(n)
+ function( elem ) {
+ return !!elem.parentNode;
+ } :
+
+ 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;
+
+ if ( parent ) {
+
+ // :(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;
+ }
+
+ start = [ forward ? parent.firstChild : parent.lastChild ];
+
+ // 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 ];
+
+ while ( (node = ++nodeIndex && node && node[ dir ] ||
+
+ // Fallback to seeking `elem` from the start
+ (diff = nodeIndex = 0) || start.pop()) ) {
+
+ // When found, cache indexes on `parent` and break
+ if ( node.nodeType === 1 && ++diff && node === elem ) {
+ outerCache[ type ] = [ dirruns, nodeIndex, diff ];
+ break;
+ }
+ }
+
+ // Use previously-cached element index if available
+ } else if ( useCache && (cache = (elem[ expando ] || (elem[ expando ] = {}))[ type ]) && cache[0] === dirruns ) {
+ diff = cache[1];
+
+ // 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()) ) {
+
+ 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 ];
+ }
+
+ if ( node === elem ) {
+ break;
+ }
+ }
+ }
+ }
+
+ // Incorporate the offset, then check against cycle size
+ diff -= last;
+ return diff === first || ( diff % first === 0 && diff / first >= 0 );
+ }
+ };
+ },
+
+ "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 );
+
+ // 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 );
+ }
+
+ // 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 );
+ };
+ }
+
+ return fn;
+ }
+ },
+
+ 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" ) );
+
+ 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);
+ }
+ }
+ }) :
+ function( elem, context, xml ) {
+ input[0] = elem;
+ matcher( input, null, xml, results );
+ return !results.pop();
+ };
+ }),
+
+ "has": markFunction(function( selector ) {
+ return function( elem ) {
+ return Sizzle( selector, elem ).length > 0;
+ };
+ }),
+
+ "contains": markFunction(function( text ) {
+ return function( elem ) {
+ return ( elem.textContent || elem.innerText || getText( elem ) ).indexOf( text ) > -1;
+ };
+ }),
+
+ // "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")) ) {
+
+ elemLang = elemLang.toLowerCase();
+ return elemLang === lang || elemLang.indexOf( lang + "-" ) === 0;
+ }
+ } while ( (elem = elem.parentNode) && elem.nodeType === 1 );
+ return false;
+ };
+ }),
+
+ // Miscellaneous
+ "target": function( elem ) {
+ var hash = window.location && window.location.hash;
+ return hash && hash.slice( 1 ) === elem.id;
+ },
+
+ "root": function( elem ) {
+ return elem === docElem;
+ },
+
+ "focus": function( elem ) {
+ return elem === document.activeElement && (!document.hasFocus || document.hasFocus()) && !!(elem.type || elem.href || ~elem.tabIndex);
+ },
+
+ // Boolean properties
+ "enabled": function( elem ) {
+ return elem.disabled === false;
+ },
+
+ "disabled": function( elem ) {
+ return elem.disabled === true;
+ },
+
+ "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);
+ },
+
+ "selected": function( elem ) {
+ // Accessing this property makes selected-by-default
+ // options in Safari work properly
+ if ( elem.parentNode ) {
+ elem.parentNode.selectedIndex;
+ }
+
+ return elem.selected === true;
+ },
+
+ // 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;
+ },
+
+ "parent": function( elem ) {
+ return !Expr.pseudos["empty"]( elem );
+ },
+
+ // Element/input types
+ "header": function( elem ) {
+ return rheader.test( elem.nodeName );
+ },
+
+ "input": function( elem ) {
+ return rinputs.test( elem.nodeName );
+ },
+
+ "button": function( elem ) {
+ var name = elem.nodeName.toLowerCase();
+ return name === "input" && elem.type === "button" || name === "button";
+ },
+
+ "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 );
+ },
+
+ // Position-in-collection
+ "first": createPositionalPseudo(function() {
+ return [ 0 ];
+ }),
+
+ "last": createPositionalPseudo(function( matchIndexes, length ) {
+ return [ length - 1 ];
+ }),
+
+ "eq": createPositionalPseudo(function( matchIndexes, length, argument ) {
+ return [ argument < 0 ? argument + length : argument ];
+ }),
+
+ "even": createPositionalPseudo(function( matchIndexes, length ) {
+ var i = 0;
+ for ( ; i < length; i += 2 ) {
+ matchIndexes.push( i );
+ }
+ return matchIndexes;
+ }),
+
+ "odd": createPositionalPseudo(function( matchIndexes, length ) {
+ var i = 1;
+ for ( ; i < length; i += 2 ) {
+ matchIndexes.push( i );
+ }
+ return matchIndexes;
+ }),
+
+ "lt": createPositionalPseudo(function( matchIndexes, length, argument ) {
+ var i = argument < 0 ? argument + length : argument;
+ for ( ; --i >= 0; ) {
+ matchIndexes.push( i );
+ }
+ return matchIndexes;
+ }),
+
+ "gt": createPositionalPseudo(function( matchIndexes, length, argument ) {
+ var i = argument < 0 ? argument + length : argument;
+ for ( ; ++i < length; ) {
+ matchIndexes.push( i );
+ }
+ return matchIndexes;
+ })
+ }
+};
+
+Expr.pseudos["nth"] = Expr.pseudos["eq"];
+
+// 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 );
+}
+
+// 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 );
+ }
+
+ soFar = selector;
+ groups = [];
+ preFilters = Expr.preFilter;
+
+ while ( soFar ) {
+
+ // 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 = [] );
+ }
+
+ matched = false;
+
+ // 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 );
+ }
+
+ // 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 );
+ }
+ }
+
+ if ( !matched ) {
+ break;
+ }
+ }
+
+ // 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;
+ }
+ return selector;
+}
+
+function addCombinator( matcher, combinator, base ) {
+ var dir = combinator.dir,
+ checkNonElements = base && dir === "parentNode",
+ doneName = done++;
+
+ 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 );
+ }
+ }
+ } :
+
+ // Check against all ancestor/preceding elements
+ function( elem, context, xml ) {
+ var data, cache, outerCache,
+ dirkey = dirruns + " " + doneName;
+
+ // 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;
+ }
+ }
+ }
+ }
+ }
+ };
+}
+
+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];
+}
+
+function condense( unmatched, map, filter, context, xml ) {
+ var elem,
+ newUnmatched = [],
+ i = 0,
+ len = unmatched.length,
+ mapped = map != null;
+
+ for ( ; i < len; i++ ) {
+ if ( (elem = unmatched[i]) ) {
+ if ( !filter || filter( elem, context, xml ) ) {
+ newUnmatched.push( elem );
+ if ( mapped ) {
+ map.push( i );
+ }
+ }
+ }
+ }
+
+ return newUnmatched;
+}
+
+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,
+
+ // Get initial elements from seed or context
+ elems = seed || multipleContexts( selector || "*", context.nodeType ? [ context ] : context, [] ),
+
+ // Prefilter to get matcher input, preserving a map for seed-results synchronization
+ matcherIn = preFilter && ( seed || !selector ) ?
+ condense( elems, preMap, preFilter, context, xml ) :
+ elems,
+
+ matcherOut = matcher ?
+ // If we have a postFinder, or filtered seed, or non-seed postFilter or preexisting results,
+ postFinder || ( seed ? preFilter : preexisting || postFilter ) ?
+
+ // ...intermediate processing is necessary
+ [] :
+
+ // ...otherwise use results directly
+ results :
+ matcherIn;
+
+ // Find primary matches
+ if ( matcher ) {
+ matcher( matcherIn, matcherOut, context, xml );
+ }
+
+ // Apply postFilter
+ if ( postFilter ) {
+ temp = condense( matcherOut, postMap );
+ postFilter( temp, [], context, xml );
+
+ // 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);
+ }
+ }
+ }
+
+ 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 );
+ }
+
+ // 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 ) {
+
+ seed[temp] = !(results[temp] = elem);
+ }
+ }
+ }
+
+ // 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 );
+ }
+ }
+ });
+}
+
+function matcherFromTokens( tokens ) {
+ var checkContext, matcher, j,
+ len = tokens.length,
+ leadingRelative = Expr.relative[ tokens[0].type ],
+ implicitRelative = leadingRelative || Expr.relative[" "],
+ i = leadingRelative ? 1 : 0,
+
+ // 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 ) );
+ } ];
+
+ 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 );
+
+ // 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 );
+ }
+ }
+
+ return elementMatcher( matchers );
+}
+
+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);
+
+ if ( outermost ) {
+ outermostContext = context !== document && context;
+ cachedruns = matcherCachedRuns;
+ }
+
+ // 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;
+ }
+ }
+
+ // Track unmatched elements for set filters
+ if ( bySet ) {
+ // They will have gone through all possible matchers
+ if ( (elem = !matcher && elem) ) {
+ matchedCount--;
+ }
+
+ // Lengthen the array for every element, matched or not
+ if ( seed ) {
+ unmatched.push( elem );
+ }
+ }
+ }
+
+ // Apply set filters to unmatched elements
+ matchedCount += i;
+ if ( bySet && i !== matchedCount ) {
+ j = 0;
+ while ( (matcher = setMatchers[j++]) ) {
+ matcher( unmatched, setMatched, context, xml );
+ }
+
+ 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 );
+ }
+ }
+ }
+
+ // Discard index placeholder values to get only actual matches
+ setMatched = condense( setMatched );
+ }
+
+ // Add matches to results
+ push.apply( results, setMatched );
+
+ // Seedless set matches succeeding multiple successful matchers stipulate sorting
+ if ( outermost && !seed && setMatched.length > 0 &&
+ ( matchedCount + setMatchers.length ) > 1 ) {
+
+ Sizzle.uniqueSort( results );
+ }
+ }
+
+ // Override manipulation of globals by nested matchers
+ if ( outermost ) {
+ dirruns = dirrunsUnique;
+ outermostContext = contextBackup;
+ }
+
+ return unmatched;
+ };
+
+ return bySet ?
+ markFunction( superMatcher ) :
+ superMatcher;
+}
+
+compile = Sizzle.compile = function( selector, group /* Internal Use Only */ ) {
+ var i,
+ setMatchers = [],
+ elementMatchers = [],
+ cached = compilerCache[ selector + " " ];
+
+ 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 );
+ }
+ }
+
+ // Cache the compiled function
+ cached = compilerCache( selector, matcherFromGroupMatchers( elementMatchers, setMatchers ) );
+ }
+ return cached;
+};
+
+function multipleContexts( selector, contexts, results ) {
+ var i = 0,
+ len = contexts.length;
+ for ( ; i < len; i++ ) {
+ Sizzle( selector, contexts[i], results );
+ }
+ return results;
+}
+
+function select( selector, context, results, seed ) {
+ var i, tokens, token, type, find,
+ match = tokenize( selector );
+
+ if ( !seed ) {
+ // Try to minimize operations if there is only one group
+ if ( match.length === 1 ) {
+
+ // 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 ] ) {
+
+ context = ( Expr.find["ID"]( token.matches[0].replace(runescape, funescape), context ) || [] )[0];
+ if ( !context ) {
+ return results;
+ }
+ selector = selector.slice( tokens.shift().value.length );
+ }
+
+ // Fetch a seed set for right-to-left matching
+ i = matchExpr["needsContext"].test( selector ) ? 0 : tokens.length;
+ while ( i-- ) {
+ token = tokens[i];
+
+ // 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
+ )) ) {
+
+ // 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;
+ }
+ }
+ }
+ }
+ }
+
+ // 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 )
+ );
+ return results;
+}
+
+// One-time assignments
+
+// 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"
+// http://msdn.microsoft.com/en-us/library/ms536429%28VS.85%29.aspx
+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 );
+ }
+ });
+}
+
+// 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;
+ }
+ });
+}
+
+// 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 (val = elem.getAttributeNode( name )) && val.specified ?
+ val.value :
+ elem[ name ] === true ? name.toLowerCase() : null;
+ }
+ });
+}
+
+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;
+
+
+})( window );
+// String to Object options format cache
+var optionsCache = {};
+
+// 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;
+}
+
+/*
+ * 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 ) {
+
+ // 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;
+ }
+ }
+ 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;
+ }
+ };
+
+ return self;
+};
+jQuery.extend({
+
+ 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 );
+ }
+ });
+ });
+ 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 = {};
+
+ // Keep pipe for back-compat
+ promise.pipe = promise.then;
+
+ // Add list-specific methods
+ jQuery.each( tuples, function( i, tuple ) {
+ var list = tuple[ 2 ],
+ stateString = tuple[ 3 ];
+
+ // promise[ done | fail | progress ] = list.add
+ promise[ tuple[1] ] = list.add;
+
+ // Handle state
+ if ( stateString ) {
+ list.add(function() {
+ // state = [ resolved | rejected ]
+ state = stateString;
+
+ // [ reject_list | resolve_list ].disable; progress_list.lock
+ }, tuples[ i ^ 1 ][ 2 ].disable, tuples[ 2 ][ 2 ].lock );
+ }
+
+ // 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;
+ });
+
+ // Make the deferred a promise
+ promise.promise( deferred );
+
+ // Call given func if any
+ if ( func ) {
+ func.call( deferred, deferred );
+ }
+
+ // All done!
+ return deferred;
+ },
+
+ // Deferred helper
+ when: function( subordinate /* , ..., subordinateN */ ) {
+ var i = 0,
+ resolveValues = core_slice.call( arguments ),
+ length = resolveValues.length,
+
+ // the count of uncompleted subordinates
+ remaining = length !== 1 || ( subordinate && jQuery.isFunction( subordinate.promise ) ) ? length : 0,
+
+ // the master Deferred. If resolveValues consist of only a single Deferred, just use that.
+ deferred = remaining === 1 ? subordinate : jQuery.Deferred(),
+
+ // 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 );
+ }
+ };
+ },
+
+ progressValues, progressContexts, resolveContexts;
+
+ // 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;
+ }
+ }
+ }
+
+ // if we're not waiting on anything, resolve the master
+ if ( !remaining ) {
+ deferred.resolveWith( resolveContexts, resolveValues );
+ }
+
+ return deferred.promise();
+ }
+});
+jQuery.support = (function( support ) {
+
+ var all, a, input, select, fragment, opt, eventName, isSupported, i,
+ div = document.createElement("div");
+
+ // Setup
+ div.setAttribute( "className", "t" );
+ div.innerHTML = " <link/><table></table><a href='/a'>a</a><input type='checkbox'/>";
+
+ // Finish early in limited (non-browser) environments
+ all = div.getElementsByTagName("*") || [];
+ a = div.getElementsByTagName("a")[ 0 ];
+ if ( !a || !a.style || !all.length ) {
+ return support;
+ }
+
+ // First batch of tests
+ select = document.createElement("select");
+ opt = select.appendChild( document.createElement("option") );
+ input = div.getElementsByTagName("input")[ 0 ];
+
+ a.style.cssText = "top:1px;float:left;opacity:.5";
+
+ // Test setAttribute on camelCase class. If it works, we need attrFixes when doing get/setAttribute (ie6/7)
+ support.getSetAttribute = div.className !== "t";
+
+ // IE strips leading whitespace when .innerHTML is used
+ support.leadingWhitespace = div.firstChild.nodeType === 3;
+
+ // Make sure that tbody elements aren't automatically inserted
+ // IE will insert them into empty tables
+ support.tbody = !div.getElementsByTagName("tbody").length;
+
+ // Make sure that link elements get serialized correctly by innerHTML
+ // This requires a wrapper element in IE
+ support.htmlSerialize = !!div.getElementsByTagName("link").length;
+
+ // Get the style information from getAttribute
+ // (IE uses .cssText instead)
+ support.style = /top/.test( a.getAttribute("style") );
+
+ // Make sure that URLs aren't manipulated
+ // (IE normalizes it by default)
+ support.hrefNormalized = a.getAttribute("href") === "/a";
+
+ // Make sure that element opacity exists
+ // (IE uses filter instead)
+ // Use a regex to work around a WebKit issue. See #5145
+ support.opacity = /^0.5/.test( a.style.opacity );
+
+ // Verify style float existence
+ // (IE uses styleFloat instead of cssFloat)
+ support.cssFloat = !!a.style.cssFloat;
+
+ // Check the default checkbox/radio value ("" on WebKit; "on" elsewhere)
+ support.checkOn = !!input.value;
+
+ // 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)
+ support.optSelected = opt.selected;
+
+ // Tests for enctype support on a form (#6743)
+ support.enctype = !!document.createElement("form").enctype;
+
+ // Makes sure cloning an html5 element does not cause problems
+ // Where outerHTML is undefined, this still works
+ support.html5Clone = document.createElement("nav").cloneNode( true ).outerHTML !== "<:nav></:nav>";
+
+ // Will be defined later
+ support.inlineBlockNeedsLayout = false;
+ support.shrinkWrapBlocks = false;
+ support.pixelPosition = false;
+ support.deleteExpando = true;
+ support.noCloneEvent = true;
+ support.reliableMarginRight = true;
+ support.boxSizingReliable = true;
+
+ // Make sure checked status is properly cloned
+ input.checked = true;
+ support.noCloneChecked = input.cloneNode( true ).checked;
+
+ // 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;
+
+ // Support: IE<9
+ try {
+ delete div.test;
+ } catch( e ) {
+ support.deleteExpando = false;
+ }
+
+ // Check if we can trust getAttribute("value")
+ input = document.createElement("input");
+ input.setAttribute( "value", "" );
+ support.input = input.getAttribute( "value" ) === "";
+
+ // Check if an input maintains its value after becoming a radio
+ input.value = "t";
+ input.setAttribute( "type", "radio" );
+ support.radioValue = input.value === "t";
+
+ // #11217 - WebKit loses check when the name is after the checked attribute
+ input.setAttribute( "checked", "t" );
+ input.setAttribute( "name", "t" );
+
+ fragment = document.createDocumentFragment();
+ fragment.appendChild( input );
+
+ // Check if a disconnected checkbox will retain its checked
+ // value of true after appended to the DOM (IE6/7)
+ support.appendChecked = input.checked;
+
+ // WebKit doesn't clone checked state correctly in fragments
+ support.checkClone = fragment.cloneNode( true ).cloneNode( true ).lastChild.checked;
+
+ // 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;
+ });
+
+ div.cloneNode( true ).click();
+ }
+
+ // Support: IE<9 (lack submit/change bubble), Firefox 17+ (lack focusin event)
+ // Beware of CSP restrictions (https://developer.mozilla.org/en/Security/CSP)
+ for ( i in { submit: true, change: true, focusin: true }) {
+ div.setAttribute( eventName = "on" + i, "t" );
+
+ support[ i + "Bubbles" ] = eventName in window || div.attributes[ eventName ].expando === false;
+ }
+
+ div.style.backgroundClip = "content-box";
+ div.cloneNode( true ).style.backgroundClip = "";
+ support.clearCloneStyle = div.style.backgroundClip === "content-box";
+
+ // Support: IE<9
+ // Iteration over object's inherited properties before its own.
+ for ( i in jQuery( support ) ) {
+ break;
+ }
+ support.ownLast = i !== "0";
+
+ // 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];
+
+ if ( !body ) {
+ // Return for frameset docs that don't have a body
+ return;
+ }
+
+ container = document.createElement("div");
+ container.style.cssText = "border:0;width:0;height:0;position:absolute;top:0;left:-9999px;margin-top:1px";
+
+ body.appendChild( container ).appendChild( div );
+
+ // 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 );
+
+ tds[ 0 ].style.display = "";
+ tds[ 1 ].style.display = "none";
+
+ // Support: IE8
+ // Check if empty table cells still have offsetWidth/Height
+ support.reliableHiddenOffsets = isSupported && ( tds[ 0 ].offsetHeight === 0 );
+
+ // 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%;";
+
+ // Workaround failing boxSizing test due to offsetWidth returning wrong value
+ // with some non-1 values of body zoom, ticket #13543
+ jQuery.swap( body, body.style.zoom != null ? { zoom: 1 } : {}, function() {
+ support.boxSizing = div.offsetWidth === 4;
+ });
+
+ // 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";
+
+ // 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";
+
+ support.reliableMarginRight =
+ !parseFloat( ( window.getComputedStyle( marginDiv, null ) || {} ).marginRight );
+ }
+
+ 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 );
+
+ // 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 );
+
+ 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;
+ }
+ }
+
+ body.removeChild( container );
+
+ // Null elements to avoid leaks in IE
+ container = div = tds = marginDiv = null;
+ });
+
+ // Null elements to avoid leaks in IE
+ all = select = fragment = opt = a = input = null;
+
+ return support;
+})({});
+
+var rbrace = /(?:\{[\s\S]*\}|\[[\s\S]*\])$/,
+ rmultiDash = /([A-Z])/g;
+
+function internalData( elem, name, data, pvt /* Internal Use Only */ ){
+ if ( !jQuery.acceptData( elem ) ) {
+ return;
+ }
+
+ var ret, thisCache,
+ internalKey = jQuery.expando,
+
+ // 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,
+
+ // 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,
+
+ // 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;
+
+ // 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)) && data === undefined && typeof name === "string" ) {
+ return;
+ }
+
+ if ( !id ) {
+ // Only DOM nodes need a new unique ID for each element since their data
+ // ends up in the global cache
+ if ( isNode ) {
+ id = elem[ internalKey ] = core_deletedIds.pop() || jQuery.guid++;
+ } else {
+ id = internalKey;
+ }
+ }
+
+ if ( !cache[ id ] ) {
+ // Avoid exposing jQuery metadata on plain JS objects when the object
+ // is serialized using JSON.stringify
+ cache[ id ] = isNode ? {} : { toJSON: jQuery.noop };
+ }
+
+ // 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 );
+ }
+ }
+
+ thisCache = cache[ id ];
+
+ // 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 = {};
+ }
+
+ thisCache = thisCache.data;
+ }
+
+ if ( data !== undefined ) {
+ thisCache[ jQuery.camelCase( name ) ] = data;
+ }
+
+ // Check for both converted-to-camel and non-converted data property names
+ // If a data property was specified
+ if ( typeof name === "string" ) {
+
+ // 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 ) ];
+ }
+ } else {
+ ret = thisCache;
+ }
+
+ return ret;
+}
+
+function internalRemoveData( elem, name, pvt ) {
+ if ( !jQuery.acceptData( elem ) ) {
+ return;
+ }
+
+ var thisCache, i,
+ 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;
+ }
+
+ if ( name ) {
+
+ thisCache = pvt ? cache[ id ] : cache[ id ].data;
+
+ if ( thisCache ) {
+
+ // Support array or space separated string names for data keys
+ if ( !jQuery.isArray( name ) ) {
+
+ // try the string as a key before any manipulation
+ if ( name in thisCache ) {
+ name = [ name ];
+ } else {
+
+ // 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(" ");
+ }
+ }
+ } 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 ) );
+ }
+
+ i = name.length;
+ while ( i-- ) {
+ delete thisCache[ name[i] ];
+ }
+
+ // If there is no data left in the cache, we want to continue
+ // and let the cache object itself get destroyed
+ if ( pvt ? !isEmptyDataObject(thisCache) : !jQuery.isEmptyObject(thisCache) ) {
+ return;
+ }
+ }
+ }
+
+ // See jQuery.data for more information
+ if ( !pvt ) {
+ delete cache[ id ].data;
+
+ // Don't destroy the parent cache unless the internal data object
+ // had been the only thing left in it
+ if ( !isEmptyDataObject( cache[ id ] ) ) {
+ return;
+ }
+ }
+
+ // Destroy the cache
+ if ( isNode ) {
+ jQuery.cleanData( [ elem ], true );
+
+ // Use delete when supported for expandos or `cache` is not a window per isWindow (#10080)
+ /* jshint eqeqeq: false */
+ } else if ( jQuery.support.deleteExpando || cache != cache.window ) {
+ /* jshint eqeqeq: true */
+ delete cache[ id ];
+
+ // When all else fails, null
+ } else {
+ cache[ id ] = null;
+ }
+}
+
+jQuery.extend({
+ cache: {},
+
+ // The following elements throw uncatchable exceptions if you
+ // attempt to add expando properties to them.
+ noData: {
+ "applet": true,
+ "embed": true,
+ // Ban all objects except for Flash (which handle expandos)
+ "object": "clsid:D27CDB6E-AE6D-11cf-96B8-444553540000"
+ },
+
+ hasData: function( elem ) {
+ elem = elem.nodeType ? jQuery.cache[ elem[jQuery.expando] ] : elem[ jQuery.expando ];
+ return !!elem && !isEmptyDataObject( elem );
+ },
+
+ data: function( elem, name, data ) {
+ return internalData( elem, name, data );
+ },
+
+ removeData: function( elem, name ) {
+ return internalRemoveData( elem, name );
+ },
+
+ // For internal use only.
+ _data: function( elem, name, data ) {
+ return internalData( elem, name, data, true );
+ },
+
+ _removeData: function( elem, name ) {
+ return internalRemoveData( elem, name, true );
+ },
+
+ // 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;
+ }
+
+ var noData = elem.nodeName && jQuery.noData[ elem.nodeName.toLowerCase() ];
+
+ // nodes accept data unless otherwise specified; rejection can be conditional
+ return !noData || noData !== true && elem.getAttribute("classid") === noData;
+ }
+});
+
+jQuery.fn.extend({
+ data: function( key, value ) {
+ var attrs, name,
+ data = null,
+ i = 0,
+ elem = this[0];
+
+ // Special expections of .data basically thwart jQuery.access,
+ // so implement the relevant behavior ourselves
+
+ // Gets all values
+ if ( key === undefined ) {
+ if ( this.length ) {
+ data = jQuery.data( elem );
+
+ if ( elem.nodeType === 1 && !jQuery._data( elem, "parsedAttrs" ) ) {
+ attrs = elem.attributes;
+ for ( ; i < attrs.length; i++ ) {
+ name = attrs[i].name;
+
+ if ( name.indexOf("data-") === 0 ) {
+ name = jQuery.camelCase( name.slice(5) );
+
+ dataAttr( elem, name, data[ name ] );
+ }
+ }
+ jQuery._data( elem, "parsedAttrs", true );
+ }
+ }
+
+ return data;
+ }
+
+ // Sets multiple values
+ if ( typeof key === "object" ) {
+ return this.each(function() {
+ jQuery.data( this, key );
+ });
+ }
+
+ return arguments.length > 1 ?
+
+ // Sets one value
+ this.each(function() {
+ jQuery.data( this, key, value );
+ }) :
+
+ // Gets one value
+ // Try to fetch any internally stored data first
+ elem ? dataAttr( elem, key, jQuery.data( elem, key ) ) : null;
+ },
+
+ removeData: function( key ) {
+ return this.each(function() {
+ jQuery.removeData( this, key );
+ });
+ }
+});
+
+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 ) {
+
+ var name = "data-" + key.replace( rmultiDash, "-$1" ).toLowerCase();
+
+ data = elem.getAttribute( name );
+
+ 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 ) {}
+
+ // Make sure we set the data so it isn't changed later
+ jQuery.data( elem, key, data );
+
+ } else {
+ data = undefined;
+ }
+ }
+
+ return data;
+}
+
+// checks a cache object for emptiness
+function isEmptyDataObject( obj ) {
+ var name;
+ for ( name in obj ) {
+
+ // 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;
+ }
+ }
+
+ return true;
+}
+jQuery.extend({
+ queue: function( elem, type, data ) {
+ var queue;
+
+ if ( elem ) {
+ type = ( type || "fx" ) + "queue";
+ queue = jQuery._data( elem, type );
+
+ // 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 || [];
+ }
+ },
+
+ dequeue: function( elem, type ) {
+ type = type || "fx";
+
+ var queue = jQuery.queue( elem, type ),
+ startLength = queue.length,
+ fn = queue.shift(),
+ hooks = jQuery._queueHooks( elem, type ),
+ next = function() {
+ jQuery.dequeue( elem, type );
+ };
+
+ // If the fx queue is dequeued, always remove the progress sentinel
+ if ( fn === "inprogress" ) {
+ fn = queue.shift();
+ startLength--;
+ }
+
+ if ( fn ) {
+
+ // Add a progress sentinel to prevent the fx queue from being
+ // automatically dequeued
+ if ( type === "fx" ) {
+ queue.unshift( "inprogress" );
+ }
+
+ // clear up the last queue stop function
+ delete hooks.stop;
+ fn.call( elem, next, hooks );
+ }
+
+ if ( !startLength && hooks ) {
+ hooks.empty.fire();
+ }
+ },
+
+ // 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 );
+ })
+ });
+ }
+});
+
+jQuery.fn.extend({
+ queue: function( type, data ) {
+ var setter = 2;
+
+ if ( typeof type !== "string" ) {
+ data = type;
+ type = "fx";
+ setter--;
+ }
+
+ if ( arguments.length < setter ) {
+ return jQuery.queue( this[0], type );
+ }
+
+ return data === undefined ?
+ this :
+ this.each(function() {
+ var queue = jQuery.queue( this, type, data );
+
+ // ensure a hooks for this queue
+ jQuery._queueHooks( this, type );
+
+ 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";
+
+ return this.queue( type, function( next, hooks ) {
+ var timeout = setTimeout( next, time );
+ hooks.stop = function() {
+ clearTimeout( timeout );
+ };
+ });
+ },
+ 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 ] );
+ }
+ };
+
+ if ( typeof type !== "string" ) {
+ obj = type;
+ type = undefined;
+ }
+ type = type || "fx";
+
+ 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\f]/g,
+ rreturn = /\r/g,
+ rfocusable = /^(?:input|select|textarea|button|object)$/i,
+ rclickable = /^(?:a|area)$/i,
+ ruseDefault = /^(?:checked|selected)$/i,
+ getSetAttribute = jQuery.support.getSetAttribute,
+ getSetInput = jQuery.support.input;
+
+jQuery.fn.extend({
+ attr: function( name, value ) {
+ return jQuery.access( this, jQuery.attr, name, value, arguments.length > 1 );
+ },
+
+ removeAttr: function( name ) {
+ return this.each(function() {
+ jQuery.removeAttr( this, name );
+ });
+ },
+
+ prop: function( name, value ) {
+ return jQuery.access( this, jQuery.prop, name, value, arguments.length > 1 );
+ },
+
+ 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 ) {}
+ });
+ },
+
+ addClass: function( value ) {
+ var classes, elem, cur, clazz, j,
+ i = 0,
+ len = this.length,
+ proceed = typeof value === "string" && value;
+
+ if ( jQuery.isFunction( value ) ) {
+ return this.each(function( j ) {
+ jQuery( this ).addClass( value.call( this, j, this.className ) );
+ });
+ }
+
+ if ( proceed ) {
+ // The disjunction here is for better compressibility (see removeClass)
+ classes = ( value || "" ).match( core_rnotwhite ) || [];
+
+ for ( ; i < len; i++ ) {
+ elem = this[ i ];
+ cur = elem.nodeType === 1 && ( elem.className ?
+ ( " " + elem.className + " " ).replace( rclass, " " ) :
+ " "
+ );
+
+ if ( cur ) {
+ j = 0;
+ while ( (clazz = classes[j++]) ) {
+ if ( cur.indexOf( " " + clazz + " " ) < 0 ) {
+ cur += clazz + " ";
+ }
+ }
+ elem.className = jQuery.trim( cur );
+
+ }
+ }
+ }
+
+ return this;
+ },
+
+ removeClass: function( value ) {
+ var classes, elem, cur, clazz, j,
+ i = 0,
+ len = this.length,
+ proceed = arguments.length === 0 || typeof value === "string" && value;
+
+ 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 ) || [];
+
+ 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, " " ) :
+ ""
+ );
+
+ 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 ) : "";
+ }
+ }
+ }
+
+ return this;
+ },
+
+ toggleClass: function( value, stateVal ) {
+ var type = typeof value;
+
+ if ( typeof stateVal === "boolean" && type === "string" ) {
+ return stateVal ? this.addClass( value ) : this.removeClass( value );
+ }
+
+ if ( jQuery.isFunction( value ) ) {
+ return this.each(function( i ) {
+ jQuery( this ).toggleClass( value.call(this, i, this.className, stateVal), stateVal );
+ });
+ }
+
+ return this.each(function() {
+ if ( type === "string" ) {
+ // toggle individual class names
+ var className,
+ i = 0,
+ self = jQuery( this ),
+ classNames = value.match( core_rnotwhite ) || [];
+
+ while ( (className = classNames[ i++ ]) ) {
+ // check each className given, space separated list
+ if ( self.hasClass( className ) ) {
+ self.removeClass( className );
+ } else {
+ self.addClass( className );
+ }
+ }
+
+ // Toggle whole class name
+ } else if ( type === core_strundefined || type === "boolean" ) {
+ if ( this.className ) {
+ // store className if set
+ jQuery._data( 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 ? "" : jQuery._data( 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;
+ },
+
+ val: function( value ) {
+ var ret, hooks, isFunction,
+ elem = this[0];
+
+ if ( !arguments.length ) {
+ if ( elem ) {
+ hooks = jQuery.valHooks[ elem.type ] || jQuery.valHooks[ elem.nodeName.toLowerCase() ];
+
+ if ( hooks && "get" in hooks && (ret = hooks.get( elem, "value" )) !== undefined ) {
+ return ret;
+ }
+
+ ret = elem.value;
+
+ return typeof ret === "string" ?
+ // handle most common string cases
+ ret.replace(rreturn, "") :
+ // handle cases where value is null/undef or number
+ ret == null ? "" : ret;
+ }
+
+ return;
+ }
+
+ isFunction = jQuery.isFunction( value );
+
+ return this.each(function( i ) {
+ var val;
+
+ if ( this.nodeType !== 1 ) {
+ return;
+ }
+
+ if ( isFunction ) {
+ val = value.call( this, i, jQuery( this ).val() );
+ } else {
+ val = value;
+ }
+
+ // 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 + "";
+ });
+ }
+
+ 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;
+ }
+ });
+ }
+});
+
+jQuery.extend({
+ valHooks: {
+ option: {
+ get: function( elem ) {
+ // Use proper attribute retrieval(#6932, #12072)
+ var val = jQuery.find.attr( elem, "value" );
+ return val != null ?
+ val :
+ elem.text;
+ }
+ },
+ 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;
+
+ // Loop through all the selected options
+ for ( ; i < max; i++ ) {
+ option = options[ i ];
+
+ // 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" ) ) ) {
+
+ // Get the specific value for the option
+ value = jQuery( option ).val();
+
+ // We don't need an array for one selects
+ if ( one ) {
+ return value;
+ }
+
+ // Multi-Selects return an array
+ values.push( value );
+ }
+ }
+
+ return values;
+ },
+
+ set: function( elem, value ) {
+ var optionSet, option,
+ options = elem.options,
+ values = jQuery.makeArray( value ),
+ i = options.length;
+
+ 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;
+ }
+ }
+ },
+
+ attr: function( elem, name, value ) {
+ var hooks, ret,
+ nType = elem.nodeType;
+
+ // don't get/set attributes on text, comment and attribute nodes
+ if ( !elem || nType === 3 || nType === 8 || nType === 2 ) {
+ return;
+ }
+
+ // Fallback to prop when attributes are not supported
+ if ( typeof elem.getAttribute === core_strundefined ) {
+ return jQuery.prop( elem, name, value );
+ }
+
+ // 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 );
+ }
+
+ if ( value !== undefined ) {
+
+ if ( value === null ) {
+ jQuery.removeAttr( elem, name );
+
+ } else if ( hooks && "set" in hooks && (ret = hooks.set( elem, value, name )) !== undefined ) {
+ return ret;
+
+ } else {
+ elem.setAttribute( name, value + "" );
+ return value;
+ }
+
+ } else if ( hooks && "get" in hooks && (ret = hooks.get( elem, name )) !== null ) {
+ return ret;
+
+ } else {
+ ret = jQuery.find.attr( elem, name );
+
+ // Non-existent attributes return null, we normalize to undefined
+ return ret == null ?
+ undefined :
+ ret;
+ }
+ },
+
+ removeAttr: function( elem, value ) {
+ var name, propName,
+ i = 0,
+ attrNames = value && value.match( core_rnotwhite );
+
+ if ( attrNames && elem.nodeType === 1 ) {
+ while ( (name = attrNames[i++]) ) {
+ propName = jQuery.propFix[ name ] || name;
+
+ // Boolean attributes get special treatment (#10870)
+ if ( jQuery.expr.match.bool.test( name ) ) {
+ // Set corresponding property to false
+ if ( getSetInput && getSetAttribute || !ruseDefault.test( name ) ) {
+ elem[ propName ] = false;
+ // Support: IE<9
+ // Also clear defaultChecked/defaultSelected (if appropriate)
+ } else {
+ elem[ jQuery.camelCase( "default-" + name ) ] =
+ elem[ propName ] = false;
+ }
+
+ // See #9699 for explanation of this approach (setting first, then removal)
+ } else {
+ jQuery.attr( elem, name, "" );
+ }
+
+ elem.removeAttribute( getSetAttribute ? name : propName );
+ }
+ }
+ },
+
+ 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;
+ }
+ return value;
+ }
+ }
+ }
+ },
+
+ propFix: {
+ "for": "htmlFor",
+ "class": "className"
+ },
+
+ prop: function( elem, name, value ) {
+ var ret, hooks, notxml,
+ nType = elem.nodeType;
+
+ // don't get/set properties on text, comment and attribute nodes
+ if ( !elem || nType === 3 || nType === 8 || nType === 2 ) {
+ return;
+ }
+
+ notxml = nType !== 1 || !jQuery.isXMLDoc( elem );
+
+ if ( notxml ) {
+ // Fix name and attach hooks
+ name = jQuery.propFix[ name ] || name;
+ hooks = jQuery.propHooks[ name ];
+ }
+
+ 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 ) {
+ // 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-tabind…
+ // Use proper attribute retrieval(#12072)
+ var tabindex = jQuery.find.attr( elem, "tabindex" );
+
+ return tabindex ?
+ parseInt( tabindex, 10 ) :
+ rfocusable.test( elem.nodeName ) || rclickable.test( elem.nodeName ) && elem.href ?
+ 0 :
+ -1;
+ }
+ }
+ }
+});
+
+// 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 if ( getSetInput && getSetAttribute || !ruseDefault.test( name ) ) {
+ // IE<8 needs the *property* name
+ elem.setAttribute( !getSetAttribute && jQuery.propFix[ name ] || name, name );
+
+ // Use defaultChecked and defaultSelected for oldIE
+ } else {
+ elem[ jQuery.camelCase( "default-" + name ) ] = elem[ name ] = true;
+ }
+
+ return name;
+ }
+};
+jQuery.each( jQuery.expr.match.bool.source.match( /\w+/g ), function( i, name ) {
+ var getter = jQuery.expr.attrHandle[ name ] || jQuery.find.attr;
+
+ jQuery.expr.attrHandle[ name ] = getSetInput && getSetAttribute || !ruseDefault.test( name ) ?
+ function( elem, name, isXML ) {
+ var fn = jQuery.expr.attrHandle[ name ],
+ ret = isXML ?
+ undefined :
+ /* jshint eqeqeq: false */
+ (jQuery.expr.attrHandle[ name ] = undefined) !=
+ getter( elem, name, isXML ) ?
+
+ name.toLowerCase() :
+ null;
+ jQuery.expr.attrHandle[ name ] = fn;
+ return ret;
+ } :
+ function( elem, name, isXML ) {
+ return isXML ?
+ undefined :
+ elem[ jQuery.camelCase( "default-" + name ) ] ?
+ name.toLowerCase() :
+ null;
+ };
+});
+
+// fix oldIE attroperties
+if ( !getSetInput || !getSetAttribute ) {
+ jQuery.attrHooks.value = {
+ 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 );
+ }
+ }
+ };
+}
+
+// IE6/7 do not support getting/setting some attributes with get/setAttribute
+if ( !getSetAttribute ) {
+
+ // Use this for any attribute in IE6/7
+ // This fixes almost every IE6/7 issue
+ nodeHook = {
+ 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 ))
+ );
+ }
+
+ ret.value = value += "";
+
+ // Break association with cloned elements by also using setAttribute (#9646)
+ return name === "value" || value === elem.getAttribute( name ) ?
+ value :
+ undefined;
+ }
+ };
+ jQuery.expr.attrHandle.id = jQuery.expr.attrHandle.name = jQuery.expr.attrHandle.coords =
+ // Some attributes are constructed with empty-string values when not defined
+ function( elem, name, isXML ) {
+ var ret;
+ return isXML ?
+ undefined :
+ (ret = elem.getAttributeNode( name )) && ret.value !== "" ?
+ ret.value :
+ null;
+ };
+ jQuery.valHooks.button = {
+ get: function( elem, name ) {
+ var ret = elem.getAttributeNode( name );
+ return ret && ret.specified ?
+ ret.value :
+ undefined;
+ },
+ set: nodeHook.set
+ };
+
+ // Set contenteditable to false on removals(#10429)
+ // Setting to empty string throws an error as an invalid value
+ jQuery.attrHooks.contenteditable = {
+ set: function( elem, value, name ) {
+ nodeHook.set( elem, value === "" ? false : value, name );
+ }
+ };
+
+ // 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 ] = {
+ set: function( elem, value ) {
+ if ( value === "" ) {
+ elem.setAttribute( name, "auto" );
+ return value;
+ }
+ }
+ };
+ });
+}
+
+
+// Some attributes require a special call on IE
+// http://msdn.microsoft.com/en-us/library/ms536429%28VS.85%29.aspx
+if ( !jQuery.support.hrefNormalized ) {
+ // 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 );
+ }
+ };
+ });
+}
+
+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 + "" );
+ }
+ };
+}
+
+// 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 = {
+ get: function( elem ) {
+ var parent = elem.parentNode;
+
+ if ( parent ) {
+ parent.selectedIndex;
+
+ // Make sure that it also works with optgroups, see #5701
+ if ( parent.parentNode ) {
+ parent.parentNode.selectedIndex;
+ }
+ }
+ return null;
+ }
+ };
+}
+
+jQuery.each([
+ "tabIndex",
+ "readOnly",
+ "maxLength",
+ "cellSpacing",
+ "cellPadding",
+ "rowSpan",
+ "colSpan",
+ "useMap",
+ "frameBorder",
+ "contentEditable"
+], function() {
+ jQuery.propFix[ this.toLowerCase() ] = this;
+});
+
+// IE6/7 call enctype encoding
+if ( !jQuery.support.enctype ) {
+ jQuery.propFix.enctype = "encoding";
+}
+
+// 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 );
+ }
+ }
+ };
+ if ( !jQuery.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;
+ };
+ }
+});
+var rformElems = /^(?:input|select|textarea)$/i,
+ rkeyEvent = /^key/,
+ rmouseEvent = /^(?:mouse|contextmenu)|click/,
+ rfocusMorph = /^(?:focusinfocus|focusoutblur)$/,
+ rtypenamespace = /^([^.]*)(?:\.(.+)|)$/;
+
+function returnTrue() {
+ return true;
+}
+
+function returnFalse() {
+ return false;
+}
+
+function safeActiveElement() {
+ try {
+ return document.activeElement;
+ } catch ( err ) { }
+}
+
+/*
+ * Helper functions for managing events -- not part of the public interface.
+ * Props to Dean Edwards' addEvent library for many of the ideas.
+ */
+jQuery.event = {
+
+ global: {},
+
+ add: function( elem, types, handler, data, selector ) {
+ var tmp, events, t, handleObjIn,
+ special, eventHandle, handleObj,
+ handlers, type, namespaces, origType,
+ elemData = jQuery._data( elem );
+
+ // Don't attach events to noData or text/comment nodes (but allow plain objects)
+ if ( !elemData ) {
+ return;
+ }
+
+ // 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;
+ }
+
+ // Make sure that the handler has a unique ID, used to find/remove it later
+ if ( !handler.guid ) {
+ handler.guid = jQuery.guid++;
+ }
+
+ // 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;
+ }
+
+ // Handle multiple events separated by a space
+ types = ( types || "" ).match( core_rnotwhite ) || [""];
+ t = types.length;
+ while ( t-- ) {
+ tmp = rtypenamespace.exec( types[t] ) || [];
+ type = origType = tmp[1];
+ namespaces = ( tmp[2] || "" ).split( "." ).sort();
+
+ // There *must* be a type, no attaching namespace-only handlers
+ if ( !type ) {
+ continue;
+ }
+
+ // If event changes its type, use the special event handlers for the changed type
+ special = jQuery.event.special[ type ] || {};
+
+ // If selector defined, determine special event api type, otherwise given type
+ type = ( selector ? special.delegateType : special.bindType ) || type;
+
+ // Update special based on newly reset type
+ special = jQuery.event.special[ type ] || {};
+
+ // 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 );
+
+ // Init the event handler queue if we're the first
+ if ( !(handlers = events[ type ]) ) {
+ handlers = events[ type ] = [];
+ handlers.delegateCount = 0;
+
+ // 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 );
+
+ } else if ( elem.attachEvent ) {
+ elem.attachEvent( "on" + type, eventHandle );
+ }
+ }
+ }
+
+ if ( special.add ) {
+ special.add.call( elem, handleObj );
+
+ if ( !handleObj.handler.guid ) {
+ handleObj.handler.guid = handler.guid;
+ }
+ }
+
+ // Add to the element's handler list, delegates in front
+ if ( selector ) {
+ handlers.splice( handlers.delegateCount++, 0, handleObj );
+ } else {
+ handlers.push( handleObj );
+ }
+
+ // Keep track of which events have ever been used, for event optimization
+ jQuery.event.global[ type ] = true;
+ }
+
+ // Nullify elem to prevent memory leaks in IE
+ elem = null;
+ },
+
+ // 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 );
+
+ if ( !elemData || !(events = elemData.events) ) {
+ return;
+ }
+
+ // 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();
+
+ // 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;
+ }
+
+ special = jQuery.event.special[ type ] || {};
+ type = ( selector ? special.delegateType : special.bindType ) || type;
+ handlers = events[ type ] || [];
+ tmp = tmp[2] && new RegExp( "(^|\\.)" + namespaces.join("\\.(?:.*\\.|)") + "(\\.|$)" );
+
+ // Remove matching events
+ origCount = j = handlers.length;
+ while ( j-- ) {
+ handleObj = handlers[ j ];
+
+ 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 );
+
+ if ( handleObj.selector ) {
+ handlers.delegateCount--;
+ }
+ if ( special.remove ) {
+ special.remove.call( elem, handleObj );
+ }
+ }
+ }
+
+ // 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 );
+ }
+
+ delete events[ type ];
+ }
+ }
+
+ // Remove the expando if it's no longer used
+ if ( jQuery.isEmptyObject( events ) ) {
+ delete elemData.handle;
+
+ // removeData also checks for emptiness and clears the expando if empty
+ // so use it instead of delete
+ jQuery._removeData( elem, "events" );
+ }
+ },
+
+ 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(".") : [];
+
+ cur = tmp = elem = elem || document;
+
+ // Don't do events on text and comment nodes
+ if ( elem.nodeType === 3 || elem.nodeType === 8 ) {
+ return;
+ }
+
+ // focus/blur morphs to focusin/out; ensure we're not firing them right now
+ if ( rfocusMorph.test( type + jQuery.event.triggered ) ) {
+ return;
+ }
+
+ 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;
+
+ // 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 );
+
+ // 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;
+
+ // Clean up the event in case it is being reused
+ event.result = undefined;
+ if ( !event.target ) {
+ event.target = elem;
+ }
+
+ // Clone any incoming data and prepend the event, creating the handler arg list
+ data = data == null ?
+ [ event ] :
+ jQuery.makeArray( data, [ event ] );
+
+ // Allow special events to draw outside the lines
+ special = jQuery.event.special[ type ] || {};
+ if ( !onlyHandlers && special.trigger && special.trigger.apply( elem, data ) === false ) {
+ return;
+ }
+
+ // 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 ) ) {
+
+ bubbleType = special.delegateType || type;
+ if ( !rfocusMorph.test( bubbleType + type ) ) {
+ cur = cur.parentNode;
+ }
+ for ( ; cur; cur = cur.parentNode ) {
+ eventPath.push( cur );
+ tmp = cur;
+ }
+
+ // 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 );
+ }
+ }
+
+ // Fire handlers on the event path
+ i = 0;
+ while ( (cur = eventPath[i++]) && !event.isPropagationStopped() ) {
+
+ event.type = i > 1 ?
+ bubbleType :
+ special.bindType || type;
+
+ // jQuery handler
+ handle = ( jQuery._data( cur, "events" ) || {} )[ event.type ] && jQuery._data( cur, "handle" );
+ if ( handle ) {
+ handle.apply( cur, data );
+ }
+
+ // Native handler
+ handle = ontype && cur[ ontype ];
+ if ( handle && jQuery.acceptData( cur ) && handle.apply && handle.apply( cur, data ) === false ) {
+ event.preventDefault();
+ }
+ }
+ event.type = type;
+
+ // If nobody prevented the default action, do it now
+ if ( !onlyHandlers && !event.isDefaultPrevented() ) {
+
+ if ( (!special._default || special._default.apply( eventPath.pop(), data ) === false) &&
+ jQuery.acceptData( elem ) ) {
+
+ // 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 ) ) {
+
+ // Don't re-trigger an onFOO event when we call its FOO() method
+ tmp = elem[ ontype ];
+
+ if ( tmp ) {
+ elem[ ontype ] = null;
+ }
+
+ // 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;
+
+ if ( tmp ) {
+ elem[ ontype ] = tmp;
+ }
+ }
+ }
+ }
+
+ return event.result;
+ },
+
+ dispatch: function( event ) {
+
+ // Make a writable jQuery.Event from the native event object
+ event = jQuery.event.fix( event );
+
+ 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 ] || {};
+
+ // Use the fix-ed jQuery.Event rather than the (read-only) native event
+ args[0] = event;
+ event.delegateTarget = this;
+
+ // Call the preDispatch hook for the mapped type, and let it bail if desired
+ if ( special.preDispatch && special.preDispatch.call( this, event ) === false ) {
+ return;
+ }
+
+ // Determine handlers
+ handlerQueue = jQuery.event.handlers.call( this, event, handlers );
+
+ // Run delegates first; they may want to stop propagation beneath us
+ i = 0;
+ while ( (matched = handlerQueue[ i++ ]) && !event.isPropagationStopped() ) {
+ event.currentTarget = matched.elem;
+
+ j = 0;
+ while ( (handleObj = matched.handlers[ j++ ]) && !event.isImmediatePropagationStopped() ) {
+
+ // 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 ) ) {
+
+ event.handleObj = handleObj;
+ event.data = handleObj.data;
+
+ ret = ( (jQuery.event.special[ handleObj.origType ] || {}).handle || handleObj.handler )
+ .apply( matched.elem, args );
+
+ if ( ret !== undefined ) {
+ if ( (event.result = ret) === false ) {
+ event.preventDefault();
+ event.stopPropagation();
+ }
+ }
+ }
+ }
+ }
+
+ // Call the postDispatch hook for the mapped type
+ if ( special.postDispatch ) {
+ special.postDispatch.call( this, event );
+ }
+
+ return event.result;
+ },
+
+ handlers: function( event, handlers ) {
+ var sel, handleObj, matches, i,
+ handlerQueue = [],
+ delegateCount = handlers.delegateCount,
+ cur = event.target;
+
+ // 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") ) {
+
+ /* jshint eqeqeq: false */
+ for ( ; cur != this; cur = cur.parentNode || this ) {
+ /* jshint eqeqeq: true */
+
+ // 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 ];
+
+ // Don't conflict with Object.prototype properties (#13203)
+ sel = handleObj.selector + " ";
+
+ 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 });
+ }
+ }
+ }
+ }
+
+ // Add the remaining (directly-bound) handlers
+ if ( delegateCount < handlers.length ) {
+ handlerQueue.push({ elem: this, handlers: handlers.slice( delegateCount ) });
+ }
+
+ return handlerQueue;
+ },
+
+ fix: function( event ) {
+ if ( event[ jQuery.expando ] ) {
+ return event;
+ }
+
+ // 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 ];
+
+ 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;
+
+ event = new jQuery.Event( originalEvent );
+
+ i = copy.length;
+ while ( i-- ) {
+ prop = copy[ i ];
+ event[ prop ] = originalEvent[ prop ];
+ }
+
+ // Support: IE<9
+ // Fix target property (#1925)
+ if ( !event.target ) {
+ event.target = originalEvent.srcElement || document;
+ }
+
+ // Support: Chrome 23+, Safari?
+ // Target should not be a text node (#504, #13143)
+ if ( event.target.nodeType === 3 ) {
+ event.target = event.target.parentNode;
+ }
+
+ // Support: IE<9
+ // For mouse/key events, metaKey==false if it's undefined (#3368, #11328)
+ event.metaKey = !!event.metaKey;
+
+ return fixHook.filter ? fixHook.filter( event, originalEvent ) : event;
+ },
+
+ // Includes some event props shared by KeyEvent and MouseEvent
+ props: "altKey bubbles cancelable ctrlKey currentTarget eventPhase metaKey relatedTarget shiftKey target timeStamp view which".split(" "),
+
+ fixHooks: {},
+
+ keyHooks: {
+ props: "char charCode key keyCode".split(" "),
+ filter: function( event, original ) {
+
+ // Add which for key events
+ if ( event.which == null ) {
+ event.which = original.charCode != null ? original.charCode : original.keyCode;
+ }
+
+ return event;
+ }
+ },
+
+ 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;
+
+ // 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;
+
+ 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 );
+ }
+
+ // Add relatedTarget, if necessary
+ if ( !event.relatedTarget && fromElement ) {
+ event.relatedTarget = fromElement === event.target ? original.toElement : fromElement;
+ }
+
+ // 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 ) ) );
+ }
+
+ return event;
+ }
+ },
+
+ 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 ) {
+ 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 === safeActiveElement() && this.blur ) {
+ this.blur();
+ return false;
+ }
+ },
+ delegateType: "focusout"
+ },
+ 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;
+ }
+ },
+
+ // For cross-browser consistency, don't fire native .click() on links
+ _default: function( event ) {
+ return jQuery.nodeName( event.target, "a" );
+ }
+ },
+
+ beforeunload: {
+ postDispatch: function( event ) {
+
+ // Even when returnValue equals to undefined Firefox will still show alert
+ if ( event.result !== undefined ) {
+ event.originalEvent.returnValue = event.result;
+ }
+ }
+ }
+ },
+
+ 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();
+ }
+ }
+};
+
+jQuery.removeEvent = document.removeEventListener ?
+ function( elem, type, handle ) {
+ if ( elem.removeEventListener ) {
+ elem.removeEventListener( type, handle, false );
+ }
+ } :
+ function( elem, type, handle ) {
+ var name = "on" + type;
+
+ if ( elem.detachEvent ) {
+
+ // #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;
+ }
+
+ elem.detachEvent( name, handle );
+ }
+ };
+
+jQuery.Event = function( src, props ) {
+ // Allow instantiation without the 'new' keyword
+ if ( !(this instanceof jQuery.Event) ) {
+ return new jQuery.Event( src, props );
+ }
+
+ // Event object
+ if ( src && src.type ) {
+ this.originalEvent = src;
+ this.type = src.type;
+
+ // 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;
+
+ // Event type
+ } else {
+ this.type = src;
+ }
+
+ // Put explicitly provided properties onto the event object
+ if ( props ) {
+ jQuery.extend( this, props );
+ }
+
+ // Create a timestamp if incoming event doesn't have one
+ this.timeStamp = src && src.timeStamp || jQuery.now();
+
+ // Mark it as fixed
+ this[ jQuery.expando ] = true;
+};
+
+// 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-bindin…
+jQuery.Event.prototype = {
+ isDefaultPrevented: returnFalse,
+ isPropagationStopped: returnFalse,
+ isImmediatePropagationStopped: returnFalse,
+
+ preventDefault: function() {
+ var e = this.originalEvent;
+
+ this.isDefaultPrevented = returnTrue;
+ if ( !e ) {
+ return;
+ }
+
+ // If preventDefault exists, run it on the original event
+ if ( e.preventDefault ) {
+ e.preventDefault();
+
+ // Support: IE
+ // Otherwise set the returnValue property of the original event to false
+ } else {
+ e.returnValue = false;
+ }
+ },
+ stopPropagation: function() {
+ var e = this.originalEvent;
+
+ this.isPropagationStopped = returnTrue;
+ if ( !e ) {
+ return;
+ }
+ // If stopPropagation exists, run it on the original event
+ if ( e.stopPropagation ) {
+ e.stopPropagation();
+ }
+
+ // Support: IE
+ // Set the cancelBubble property of the original event to true
+ e.cancelBubble = true;
+ },
+ stopImmediatePropagation: function() {
+ this.isImmediatePropagationStopped = returnTrue;
+ this.stopPropagation();
+ }
+};
+
+// 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,
+
+ 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;
+ }
+ };
+});
+
+// IE submit delegation
+if ( !jQuery.support.submitBubbles ) {
+
+ jQuery.event.special.submit = {
+ setup: function() {
+ // Only need this for delegated form submit events
+ if ( jQuery.nodeName( this, "form" ) ) {
+ return false;
+ }
+
+ // 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
+ },
+
+ 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 );
+ }
+ }
+ },
+
+ teardown: function() {
+ // Only need this for delegated form submit events
+ if ( jQuery.nodeName( this, "form" ) ) {
+ return false;
+ }
+
+ // Remove delegated handlers; cleanData eventually reaps submit handlers attached above
+ jQuery.event.remove( this, "._submit" );
+ }
+ };
+}
+
+// IE change delegation and checkbox/radio fix
+if ( !jQuery.support.changeBubbles ) {
+
+ jQuery.event.special.change = {
+
+ setup: function() {
+
+ 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;
+
+ 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 );
+ }
+ });
+ },
+
+ 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 );
+ }
+ },
+
+ teardown: function() {
+ jQuery.event.remove( this, "._change" );
+
+ return !rformElems.test( this.nodeName );
+ }
+ };
+}
+
+// Create "bubbling" focus and blur events
+if ( !jQuery.support.focusinBubbles ) {
+ jQuery.each({ focus: "focusin", blur: "focusout" }, function( orig, fix ) {
+
+ // 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 );
+ };
+
+ jQuery.event.special[ fix ] = {
+ setup: function() {
+ if ( attaches++ === 0 ) {
+ document.addEventListener( orig, handler, true );
+ }
+ },
+ teardown: function() {
+ if ( --attaches === 0 ) {
+ document.removeEventListener( orig, handler, true );
+ }
+ }
+ };
+ });
+}
+
+jQuery.fn.extend({
+
+ on: function( types, selector, data, fn, /*INTERNAL*/ one ) {
+ var type, origFn;
+
+ // 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;
+ }
+
+ 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;
+ }
+
+ 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 );
+ });
+ },
+ 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 );
+ });
+ },
+
+ trigger: function( type, data ) {
+ return this.each(function() {
+ jQuery.event.trigger( type, data, this );
+ });
+ },
+ triggerHandler: function( type, data ) {
+ var elem = this[0];
+ if ( elem ) {
+ return jQuery.event.trigger( type, data, elem, true );
+ }
+ }
+});
+var isSimple = /^.[^:#\[\.,]*$/,
+ rparentsprev = /^(?:parents|prev(?:Until|All))/,
+ 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
+ };
+
+jQuery.fn.extend({
+ find: function( selector ) {
+ var i,
+ ret = [],
+ self = this,
+ len = self.length;
+
+ 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;
+ }
+ }
+ }) );
+ }
+
+ for ( i = 0; i < len; i++ ) {
+ jQuery.find( selector, self[ i ], ret );
+ }
+
+ // 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;
+ },
+
+ has: function( target ) {
+ var i,
+ targets = jQuery( target, this ),
+ len = targets.length;
+
+ return this.filter(function() {
+ for ( i = 0; i < len; i++ ) {
+ if ( jQuery.contains( this, targets[i] ) ) {
+ return true;
+ }
+ }
+ });
+ },
+
+ not: function( selector ) {
+ return this.pushStack( winnow(this, selector || [], true) );
+ },
+
+ filter: function( selector ) {
+ return this.pushStack( winnow(this, selector || [], false) );
+ },
+
+ is: function( selector ) {
+ return !!winnow(
+ this,
+
+ // 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;
+ },
+
+ 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;
+
+ 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 :
+
+ // Don't pass non-elements to Sizzle
+ cur.nodeType === 1 &&
+ jQuery.find.matchesSelector(cur, selectors)) ) {
+
+ cur = ret.push( cur );
+ break;
+ }
+ }
+ }
+
+ return this.pushStack( ret.length > 1 ? jQuery.unique( ret ) : ret );
+ },
+
+ // Determine the position of an element within
+ // the matched set of elements
+ index: function( elem ) {
+
+ // No argument, return index in parent
+ if ( !elem ) {
+ return ( this[0] && this[0].parentNode ) ? this.first().prevAll().length : -1;
+ }
+
+ // index in selector
+ if ( typeof elem === "string" ) {
+ return jQuery.inArray( this[0], jQuery( elem ) );
+ }
+
+ // 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 );
+ },
+
+ 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 );
+
+ return this.pushStack( jQuery.unique(all) );
+ },
+
+ addBack: function( selector ) {
+ return this.add( selector == null ?
+ this.prevObject : this.prevObject.filter(selector)
+ );
+ }
+});
+
+function sibling( cur, dir ) {
+ do {
+ cur = cur[ dir ];
+ } while ( cur && 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 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 );
+
+ if ( name.slice( -5 ) !== "Until" ) {
+ selector = until;
+ }
+
+ if ( selector && typeof selector === "string" ) {
+ ret = jQuery.filter( selector, ret );
+ }
+
+ if ( this.length > 1 ) {
+ // Remove duplicates
+ if ( !guaranteedUnique[ name ] ) {
+ ret = jQuery.unique( ret );
+ }
+
+ // Reverse order for parents* and prev-derivatives
+ if ( rparentsprev.test( name ) ) {
+ ret = ret.reverse();
+ }
+ }
+
+ return this.pushStack( ret );
+ };
+});
+
+jQuery.extend({
+ filter: function( expr, elems, not ) {
+ var elem = elems[ 0 ];
+
+ if ( not ) {
+ expr = ":not(" + expr + ")";
+ }
+
+ 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;
+ }));
+ },
+
+ dir: function( elem, dir, until ) {
+ var matched = [],
+ cur = elem[ dir ];
+
+ 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;
+ },
+
+ sibling: function( n, elem ) {
+ var r = [];
+
+ for ( ; n; n = n.nextSibling ) {
+ if ( n.nodeType === 1 && n !== elem ) {
+ r.push( n );
+ }
+ }
+
+ return r;
+ }
+});
+
+// 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;
+ });
+
+ }
+
+ if ( qualifier.nodeType ) {
+ return jQuery.grep( elements, function( elem ) {
+ return ( elem === qualifier ) !== not;
+ });
+
+ }
+
+ if ( typeof qualifier === "string" ) {
+ if ( isSimple.test( qualifier ) ) {
+ return jQuery.filter( qualifier, elements, not );
+ }
+
+ qualifier = jQuery.filter( qualifier, elements );
+ }
+
+ return jQuery.grep( elements, function( elem ) {
+ return ( jQuery.inArray( elem, qualifier ) >= 0 ) !== not;
+ });
+}
+function createSafeFragment( document ) {
+ var list = nodeNames.split( "|" ),
+ safeFrag = document.createDocumentFragment();
+
+ if ( safeFrag.createElement ) {
+ while ( list.length ) {
+ safeFrag.createElement(
+ list.pop()
+ );
+ }
+ }
+ return safeFrag;
+}
+
+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,
+
+ // 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>" ],
+
+ // 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") );
+
+wrapMap.optgroup = wrapMap.option;
+wrapMap.tbody = wrapMap.tfoot = wrapMap.colgroup = wrapMap.caption = wrapMap.thead;
+wrapMap.th = wrapMap.td;
+
+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 );
+ },
+
+ 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 );
+ }
+ });
+ },
+
+ 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 );
+ }
+ });
+ },
+
+ before: function() {
+ return this.domManip( arguments, function( elem ) {
+ if ( this.parentNode ) {
+ this.parentNode.insertBefore( elem, this );
+ }
+ });
+ },
+
+ after: function() {
+ return this.domManip( arguments, function( elem ) {
+ if ( this.parentNode ) {
+ this.parentNode.insertBefore( elem, this.nextSibling );
+ }
+ });
+ },
+
+ // keepData is for internal use only--do not document
+ remove: function( selector, keepData ) {
+ var elem,
+ elems = selector ? jQuery.filter( selector, this ) : this,
+ i = 0;
+
+ for ( ; (elem = elems[i]) != null; i++ ) {
+
+ if ( !keepData && elem.nodeType === 1 ) {
+ jQuery.cleanData( getAll( elem ) );
+ }
+
+ if ( elem.parentNode ) {
+ if ( keepData && jQuery.contains( elem.ownerDocument, elem ) ) {
+ setGlobalEval( getAll( elem, "script" ) );
+ }
+ elem.parentNode.removeChild( elem );
+ }
+ }
+
+ return this;
+ },
+
+ empty: function() {
+ var elem,
+ i = 0;
+
+ for ( ; (elem = this[i]) != null; i++ ) {
+ // Remove element nodes and prevent memory leaks
+ if ( elem.nodeType === 1 ) {
+ jQuery.cleanData( getAll( elem, false ) );
+ }
+
+ // Remove any remaining nodes
+ while ( elem.firstChild ) {
+ elem.removeChild( elem.firstChild );
+ }
+
+ // 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;
+ }
+ }
+
+ return this;
+ },
+
+ clone: function( dataAndEvents, deepDataAndEvents ) {
+ dataAndEvents = dataAndEvents == null ? false : dataAndEvents;
+ deepDataAndEvents = deepDataAndEvents == null ? dataAndEvents : deepDataAndEvents;
+
+ return this.map( function () {
+ return jQuery.clone( this, dataAndEvents, deepDataAndEvents );
+ });
+ },
+
+ html: function( value ) {
+ return jQuery.access( this, function( value ) {
+ var elem = this[0] || {},
+ i = 0,
+ l = this.length;
+
+ if ( value === undefined ) {
+ return elem.nodeType === 1 ?
+ elem.innerHTML.replace( rinlinejQuery, "" ) :
+ undefined;
+ }
+
+ // 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() ] ) {
+
+ value = value.replace( rxhtmlTag, "<$1></$2>" );
+
+ 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;
+ }
+ }
+
+ elem = 0;
+
+ // If using innerHTML throws an exception, use the fallback method
+ } catch(e) {}
+ }
+
+ if ( elem ) {
+ this.empty().append( value );
+ }
+ }, null, value, arguments.length );
+ },
+
+ replaceWith: function() {
+ var
+ // Snapshot the DOM in case .domManip sweeps something relevant into its fragment
+ args = jQuery.map( this, function( elem ) {
+ return [ elem.nextSibling, elem.parentNode ];
+ }),
+ i = 0;
+
+ // Make the changes, replacing each context element with the new content
+ this.domManip( arguments, function( elem ) {
+ var next = args[ i++ ],
+ parent = args[ i++ ];
+
+ if ( parent ) {
+ // Don't use the snapshot next if it has moved (#13810)
+ if ( next && next.parentNode !== parent ) {
+ next = this.nextSibling;
+ }
+ jQuery( this ).remove();
+ parent.insertBefore( elem, next );
+ }
+ // Allow new content to include elements from the context set
+ }, true );
+
+ // Force removal if there was no new content (e.g., from empty arguments)
+ return i ? this : this.remove();
+ },
+
+ detach: function( selector ) {
+ return this.remove( selector, true );
+ },
+
+ domManip: function( args, callback, allowIntersection ) {
+
+ // Flatten any nested arrays
+ args = core_concat.apply( [], args );
+
+ var first, node, hasScripts,
+ scripts, doc, fragment,
+ i = 0,
+ l = this.length,
+ set = this,
+ iNoClone = l - 1,
+ value = args[0],
+ isFunction = jQuery.isFunction( value );
+
+ // 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, self.html() );
+ }
+ self.domManip( args, callback, allowIntersection );
+ });
+ }
+
+ if ( l ) {
+ fragment = jQuery.buildFragment( args, this[ 0 ].ownerDocument, false, !allowIntersection && this );
+ first = fragment.firstChild;
+
+ if ( fragment.childNodes.length === 1 ) {
+ fragment = first;
+ }
+
+ if ( first ) {
+ scripts = jQuery.map( getAll( fragment, "script" ), disableScript );
+ hasScripts = scripts.length;
+
+ // 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;
+
+ if ( i !== iNoClone ) {
+ node = jQuery.clone( node, true, true );
+
+ // Keep references to cloned scripts for later restoration
+ if ( hasScripts ) {
+ jQuery.merge( scripts, getAll( node, "script" ) );
+ }
+ }
+
+ callback.call( this[i], node, i );
+ }
+
+ 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 || "" ) &&
+ !jQuery._data( node, "globalEval" ) && jQuery.contains( doc, node ) ) {
+
+ if ( node.src ) {
+ // Hope ajax is available...
+ jQuery._evalUrl( node.src );
+ } else {
+ jQuery.globalEval( ( node.text || node.textContent || node.innerHTML || "" ).replace( rcleanScript, "" ) );
+ }
+ }
+ }
+ }
+
+ // Fix #11809: Avoid leaking memory
+ fragment = first = null;
+ }
+ }
+
+ return this;
+ }
+});
+
+// Support: IE<8
+// Manipulating tables requires a tbody
+function manipulationTarget( elem, content ) {
+ return jQuery.nodeName( elem, "table" ) &&
+ jQuery.nodeName( content.nodeType === 1 ? content : content.firstChild, "tr" ) ?
+
+ elem.getElementsByTagName("tbody")[0] ||
+ elem.appendChild( elem.ownerDocument.createElement("tbody") ) :
+ elem;
+}
+
+// Replace/restore the type attribute of script elements for safe DOM manipulation
+function disableScript( elem ) {
+ elem.type = (jQuery.find.attr( elem, "type" ) !== null) + "/" + elem.type;
+ return elem;
+}
+function restoreScript( elem ) {
+ var match = rscriptTypeMasked.exec( elem.type );
+ if ( match ) {
+ elem.type = match[1];
+ } else {
+ elem.removeAttribute("type");
+ }
+ return elem;
+}
+
+// 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" ) );
+ }
+}
+
+function cloneCopyEvent( src, dest ) {
+
+ if ( dest.nodeType !== 1 || !jQuery.hasData( src ) ) {
+ return;
+ }
+
+ var type, i, l,
+ oldData = jQuery._data( src ),
+ curData = jQuery._data( dest, oldData ),
+ events = oldData.events;
+
+ if ( events ) {
+ delete curData.handle;
+ curData.events = {};
+
+ for ( type in events ) {
+ for ( i = 0, l = events[ type ].length; i < l; i++ ) {
+ jQuery.event.add( dest, type, events[ type ][ i ] );
+ }
+ }
+ }
+
+ // make the cloned public data object a copy from the original
+ if ( curData.data ) {
+ curData.data = jQuery.extend( {}, curData.data );
+ }
+}
+
+function fixCloneNodeIssues( src, dest ) {
+ var nodeName, e, data;
+
+ // We do not need to do anything for non-Elements
+ if ( dest.nodeType !== 1 ) {
+ return;
+ }
+
+ nodeName = dest.nodeName.toLowerCase();
+
+ // IE6-8 copies events bound via attachEvent when using cloneNode.
+ if ( !jQuery.support.noCloneEvent && dest[ jQuery.expando ] ) {
+ data = jQuery._data( dest );
+
+ for ( e in data.events ) {
+ jQuery.removeEvent( dest, e, data.handle );
+ }
+
+ // Event data gets referenced instead of copied if the expando gets copied too
+ dest.removeAttribute( jQuery.expando );
+ }
+
+ // 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 );
+
+ // 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;
+ }
+
+ // 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;
+ }
+
+ } 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
+
+ dest.defaultChecked = dest.checked = src.checked;
+
+ // 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;
+ }
+
+ // 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;
+
+ // 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;
+ }
+}
+
+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;
+
+ for ( ; i <= last; i++ ) {
+ elems = i === last ? this : this.clone(true);
+ jQuery( insert[i] )[ original ]( elems );
+
+ // Modern browsers can apply jQuery collections as arrays, but oldIE needs a .get()
+ core_push.apply( ret, elems.get() );
+ }
+
+ return this.pushStack( ret );
+ };
+});
+
+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;
+
+ 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 ) );
+ }
+ }
+ }
+
+ return tag === undefined || tag && jQuery.nodeName( context, tag ) ?
+ jQuery.merge( [ context ], found ) :
+ found;
+}
+
+// Used in buildFragment, fixes the defaultChecked property
+function fixDefaultChecked( elem ) {
+ if ( manipulation_rcheckableType.test( elem.type ) ) {
+ elem.defaultChecked = elem.checked;
+ }
+}
+
+jQuery.extend({
+ clone: function( elem, dataAndEvents, deepDataAndEvents ) {
+ var destElements, node, clone, i, srcElements,
+ inPage = jQuery.contains( elem.ownerDocument, elem );
+
+ if ( jQuery.support.html5Clone || jQuery.isXMLDoc(elem) || !rnoshimcache.test( "<" + elem.nodeName + ">" ) ) {
+ clone = elem.cloneNode( true );
+
+ // IE<=8 does not properly clone detached, unknown element nodes
+ } else {
+ fragmentDiv.innerHTML = elem.outerHTML;
+ fragmentDiv.removeChild( clone = fragmentDiv.firstChild );
+ }
+
+ if ( (!jQuery.support.noCloneEvent || !jQuery.support.noCloneChecked) &&
+ (elem.nodeType === 1 || elem.nodeType === 11) && !jQuery.isXMLDoc(elem) ) {
+
+ // We eschew Sizzle here for performance reasons: http://jsperf.com/getall-vs-sizzle/2
+ destElements = getAll( clone );
+ srcElements = getAll( elem );
+
+ // 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] );
+ }
+ }
+ }
+
+ // Copy the events from the original to the clone
+ if ( dataAndEvents ) {
+ if ( deepDataAndEvents ) {
+ srcElements = srcElements || getAll( elem );
+ destElements = destElements || getAll( clone );
+
+ for ( i = 0; (node = srcElements[i]) != null; i++ ) {
+ cloneCopyEvent( node, destElements[i] );
+ }
+ } else {
+ cloneCopyEvent( elem, clone );
+ }
+ }
+
+ // Preserve script evaluation history
+ destElements = getAll( clone, "script" );
+ if ( destElements.length > 0 ) {
+ setGlobalEval( destElements, !inPage && getAll( elem, "script" ) );
+ }
+
+ destElements = srcElements = node = null;
+
+ // Return the cloned set
+ return clone;
+ },
+
+ buildFragment: function( elems, context, scripts, selection ) {
+ var j, elem, contains,
+ tmp, tag, tbody, wrap,
+ l = elems.length,
+
+ // Ensure a safe fragment
+ safe = createSafeFragment( context ),
+
+ nodes = [],
+ i = 0;
+
+ for ( ; i < l; i++ ) {
+ elem = elems[ i ];
+
+ if ( elem || elem === 0 ) {
+
+ // Add nodes directly
+ if ( jQuery.type( elem ) === "object" ) {
+ jQuery.merge( nodes, elem.nodeType ? [ elem ] : elem );
+
+ // Convert non-html into a text node
+ } else if ( !rhtml.test( elem ) ) {
+ nodes.push( context.createTextNode( elem ) );
+
+ // Convert html into DOM nodes
+ } else {
+ tmp = tmp || safe.appendChild( context.createElement("div") );
+
+ // 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];
+
+ // Descend through wrappers to the right content
+ j = wrap[0];
+ while ( j-- ) {
+ tmp = tmp.lastChild;
+ }
+
+ // Manually add leading whitespace removed by IE
+ if ( !jQuery.support.leadingWhitespace && rleadingWhitespace.test( elem ) ) {
+ nodes.push( context.createTextNode( rleadingWhitespace.exec( elem )[0] ) );
+ }
+
+ // Remove IE's autoinserted <tbody> from table fragments
+ if ( !jQuery.support.tbody ) {
+
+ // String was a <table>, *may* have spurious <tbody>
+ elem = tag === "table" && !rtbody.test( elem ) ?
+ tmp.firstChild :
+
+ // String was a bare <thead> or <tfoot>
+ wrap[1] === "<table>" && !rtbody.test( elem ) ?
+ tmp :
+ 0;
+
+ j = elem && elem.childNodes.length;
+ while ( j-- ) {
+ if ( jQuery.nodeName( (tbody = elem.childNodes[j]), "tbody" ) && !tbody.childNodes.length ) {
+ elem.removeChild( tbody );
+ }
+ }
+ }
+
+ jQuery.merge( nodes, tmp.childNodes );
+
+ // Fix #12392 for WebKit and IE > 9
+ tmp.textContent = "";
+
+ // Fix #12392 for oldIE
+ while ( tmp.firstChild ) {
+ tmp.removeChild( tmp.firstChild );
+ }
+
+ // Remember the top-level container for proper cleanup
+ tmp = safe.lastChild;
+ }
+ }
+ }
+
+ // Fix #11356: Clear elements from fragment
+ if ( tmp ) {
+ safe.removeChild( tmp );
+ }
+
+ // 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 );
+ }
+
+ i = 0;
+ while ( (elem = nodes[ i++ ]) ) {
+
+ // #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;
+ }
+
+ contains = jQuery.contains( elem.ownerDocument, elem );
+
+ // Append to fragment
+ tmp = getAll( safe.appendChild( elem ), "script" );
+
+ // Preserve script evaluation history
+ if ( contains ) {
+ setGlobalEval( tmp );
+ }
+
+ // Capture executables
+ if ( scripts ) {
+ j = 0;
+ while ( (elem = tmp[ j++ ]) ) {
+ if ( rscriptType.test( elem.type || "" ) ) {
+ scripts.push( elem );
+ }
+ }
+ }
+ }
+
+ tmp = null;
+
+ return safe;
+ },
+
+ 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;
+
+ for ( ; (elem = elems[i]) != null; i++ ) {
+
+ if ( acceptData || jQuery.acceptData( elem ) ) {
+
+ id = elem[ internalKey ];
+ data = id && cache[ id ];
+
+ if ( data ) {
+ if ( data.events ) {
+ for ( type in data.events ) {
+ if ( special[ type ] ) {
+ jQuery.event.remove( elem, type );
+
+ // This is a shortcut to avoid jQuery.event.remove's overhead
+ } else {
+ jQuery.removeEvent( elem, type, data.handle );
+ }
+ }
+ }
+
+ // Remove cache only if it was not already removed by jQuery.event.remove
+ if ( cache[ id ] ) {
+
+ delete cache[ id ];
+
+ // 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 ];
+
+ } else if ( typeof elem.removeAttribute !== core_strundefined ) {
+ elem.removeAttribute( internalKey );
+
+ } else {
+ elem[ internalKey ] = null;
+ }
+
+ core_deletedIds.push( id );
+ }
+ }
+ }
+ }
+ },
+
+ _evalUrl: function( url ) {
+ return jQuery.ajax({
+ url: url,
+ type: "GET",
+ dataType: "script",
+ async: false,
+ global: false,
+ "throws": true
+ });
+ }
+});
+jQuery.fn.extend({
+ wrapAll: function( html ) {
+ if ( jQuery.isFunction( html ) ) {
+ return this.each(function(i) {
+ jQuery(this).wrapAll( html.call(this, i) );
+ });
+ }
+
+ if ( this[0] ) {
+ // The elements to wrap the target around
+ var wrap = jQuery( html, this[0].ownerDocument ).eq(0).clone(true);
+
+ if ( this[0].parentNode ) {
+ wrap.insertBefore( this[0] );
+ }
+
+ wrap.map(function() {
+ var elem = this;
+
+ while ( elem.firstChild && elem.firstChild.nodeType === 1 ) {
+ elem = elem.firstChild;
+ }
+
+ return elem;
+ }).append( this );
+ }
+
+ return this;
+ },
+
+ wrapInner: function( html ) {
+ if ( jQuery.isFunction( html ) ) {
+ return this.each(function(i) {
+ jQuery(this).wrapInner( html.call(this, i) );
+ });
+ }
+
+ return this.each(function() {
+ var self = jQuery( this ),
+ contents = self.contents();
+
+ if ( contents.length ) {
+ contents.wrapAll( html );
+
+ } else {
+ self.append( html );
+ }
+ });
+ },
+
+ wrap: function( html ) {
+ var isFunction = jQuery.isFunction( html );
+
+ return this.each(function(i) {
+ jQuery( this ).wrapAll( isFunction ? html.call(this, i) : html );
+ });
+ },
+
+ unwrap: function() {
+ return this.parent().each(function() {
+ if ( !jQuery.nodeName( this, "body" ) ) {
+ jQuery( this ).replaceWith( this.childNodes );
+ }
+ }).end();
+ }
+});
+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" },
+
+ cssShow = { position: "absolute", visibility: "hidden", display: "block" },
+ cssNormalTransform = {
+ letterSpacing: 0,
+ fontWeight: 400
+ },
+
+ cssExpand = [ "Top", "Right", "Bottom", "Left" ],
+ cssPrefixes = [ "Webkit", "O", "Moz", "ms" ];
+
+// return a css property mapped to a potentially vendor prefixed property
+function vendorPropName( style, name ) {
+
+ // shortcut for names that are not vendor prefixed
+ if ( name in style ) {
+ return name;
+ }
+
+ // check for vendor prefixed names
+ var capName = name.charAt(0).toUpperCase() + name.slice(1),
+ origName = name,
+ i = cssPrefixes.length;
+
+ while ( i-- ) {
+ name = cssPrefixes[ i ] + capName;
+ if ( name in style ) {
+ return name;
+ }
+ }
+
+ return origName;
+}
+
+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 );
+}
+
+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 ] = 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 = "";
+ }
+
+ // 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 {
+
+ if ( !values[ index ] ) {
+ hidden = isHidden( elem );
+
+ if ( display && display !== "none" || !hidden ) {
+ jQuery._data( elem, "olddisplay", hidden ? display : jQuery.css( elem, "display" ) );
+ }
+ }
+ }
+ }
+
+ // 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";
+ }
+ }
+
+ return elements;
+}
+
+jQuery.fn.extend({
+ css: function( name, value ) {
+ return jQuery.access( this, function( elem, name, value ) {
+ var len, styles,
+ map = {},
+ i = 0;
+
+ if ( jQuery.isArray( name ) ) {
+ styles = getStyles( elem );
+ len = name.length;
+
+ for ( ; i < len; i++ ) {
+ map[ name[ i ] ] = jQuery.css( elem, name[ i ], false, styles );
+ }
+
+ return map;
+ }
+
+ 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();
+ }
+
+ return this.each(function() {
+ if ( isHidden( this ) ) {
+ jQuery( this ).show();
+ } else {
+ jQuery( this ).hide();
+ }
+ });
+ }
+});
+
+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;
+ }
+ }
+ }
+ },
+
+ // 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
+ },
+
+ // 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"
+ },
+
+ // 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;
+ }
+
+ // Make sure that we're working with the right name
+ var ret, type, hooks,
+ origName = jQuery.camelCase( name ),
+ style = elem.style;
+
+ name = jQuery.cssProps[ origName ] || ( jQuery.cssProps[ origName ] = vendorPropName( style, origName ) );
+
+ // gets hook for the prefixed version
+ // followed by the unprefixed version
+ hooks = jQuery.cssHooks[ name ] || jQuery.cssHooks[ origName ];
+
+ // Check if we're setting a value
+ if ( value !== undefined ) {
+ type = typeof value;
+
+ // 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";
+ }
+
+ // Make sure that NaN and null values aren't set. See: #7116
+ if ( value == null || type === "number" && isNaN( value ) ) {
+ return;
+ }
+
+ // If a number was passed in, add 'px' to the (except for certain CSS properties)
+ if ( type === "number" && !jQuery.cssNumber[ origName ] ) {
+ value += "px";
+ }
+
+ // 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";
+ }
+
+ // 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 ) {
+
+ // Wrapped to prevent IE from throwing errors when 'invalid' values are provided
+ // Fixes bug #5509
+ try {
+ style[ name ] = value;
+ } catch(e) {}
+ }
+
+ } 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;
+ }
+
+ // Otherwise just get the value from the style object
+ return style[ name ];
+ }
+ },
+
+ css: function( elem, name, extra, styles ) {
+ var num, val, hooks,
+ origName = jQuery.camelCase( name );
+
+ // Make sure that we're working with the right name
+ name = jQuery.cssProps[ origName ] || ( jQuery.cssProps[ origName ] = vendorPropName( elem.style, origName ) );
+
+ // gets hook for the prefixed version
+ // followed by the unprefixed version
+ hooks = jQuery.cssHooks[ name ] || jQuery.cssHooks[ origName ];
+
+ // If a hook was provided get the computed value from there
+ if ( hooks && "get" in hooks ) {
+ val = hooks.get( elem, true, extra );
+ }
+
+ // 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;
+ }
+});
+
+// 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 );
+ };
+
+ curCSS = function( elem, name, _computed ) {
+ var width, minWidth, maxWidth,
+ computed = _computed || getStyles( elem ),
+
+ // getPropertyValue is only needed for .css('filter') in IE9, see #12537
+ ret = computed ? computed.getPropertyValue( name ) || computed[ name ] : undefined,
+ style = elem.style;
+
+ if ( computed ) {
+
+ if ( ret === "" && !jQuery.contains( elem.ownerDocument, elem ) ) {
+ ret = jQuery.style( elem, name );
+ }
+
+ // 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 ) ) {
+
+ // Remember the original values
+ width = style.width;
+ minWidth = style.minWidth;
+ maxWidth = style.maxWidth;
+
+ // Put in the new values to get a computed value out
+ style.minWidth = style.maxWidth = style.width = ret;
+ ret = computed.width;
+
+ // Revert the changed values
+ style.width = width;
+ style.minWidth = minWidth;
+ style.maxWidth = maxWidth;
+ }
+ }
+
+ return ret;
+ };
+} else if ( document.documentElement.currentStyle ) {
+ getStyles = function( elem ) {
+ return elem.currentStyle;
+ };
+
+ curCSS = function( elem, name, _computed ) {
+ var left, rs, rsLeft,
+ computed = _computed || getStyles( elem ),
+ ret = computed ? computed[ name ] : undefined,
+ style = elem.style;
+
+ // Avoid setting ret to empty string here
+ // so we don't default to auto
+ if ( ret == null && style && style[ name ] ) {
+ ret = style[ name ];
+ }
+
+ // From the awesome hack by Dean Edwards
+ // http://erik.eae.net/archives/2007/07/27/18.54.15/#comment-102291
+
+ // 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 ) ) {
+
+ // Remember the original values
+ left = style.left;
+ rs = elem.runtimeStyle;
+ rsLeft = rs && rs.left;
+
+ // 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";
+
+ // Revert the changed values
+ style.left = left;
+ if ( rsLeft ) {
+ rs.left = rsLeft;
+ }
+ }
+
+ return ret === "" ? "auto" : ret;
+ };
+}
+
+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;
+}
+
+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,
+
+ val = 0;
+
+ 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 );
+ }
+ }
+ }
+
+ return val;
+}
+
+function getWidthOrHeight( elem, name, extra ) {
+
+ // 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";
+
+ // 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 && ( jQuery.support.boxSizingReliable || val === elem.style[ name ] );
+
+ // Normalize "", auto, and prepare for extra
+ val = parseFloat( val ) || 0;
+ }
+
+ // use the active box-sizing model to add/subtract irrelevant styles
+ return ( val +
+ augmentWidthOrHeight(
+ elem,
+ name,
+ extra || ( isBorderBox ? "border" : "content" ),
+ valueIsBorderBox,
+ styles
+ )
+ ) + "px";
+}
+
+// Try to determine the default display value of an element
+function css_defaultDisplay( nodeName ) {
+ var doc = document,
+ display = elemdisplay[ nodeName ];
+
+ if ( !display ) {
+ display = actualDisplay( nodeName, doc );
+
+ // 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 );
+
+ // 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();
+
+ display = actualDisplay( nodeName, doc );
+ iframe.detach();
+ }
+
+ // Store the correct default display
+ elemdisplay[ nodeName ] = display;
+ }
+
+ return display;
+}
+
+// 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;
+}
+
+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 );
+ }
+ },
+
+ 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
+ );
+ }
+ };
+});
+
+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" : "";
+ },
+
+ 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 || "";
+
+ // IE has trouble with opacity if it does not have layout
+ // Force it by setting the zoom level
+ style.zoom = 1;
+
+ // 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 ) {
+
+ // 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" );
+
+ // if there is no filter style applied in a css rule or unset inline opacity, we are done
+ if ( value === "" || currentStyle && !currentStyle.filter ) {
+ return;
+ }
+ }
+
+ // otherwise, set new filter values
+ style.filter = ralpha.test( filter ) ?
+ filter.replace( ralpha, opacity ) :
+ filter + " " + opacity;
+ }
+ };
+}
+
+// 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" ] );
+ }
+ }
+ };
+ }
+
+ // 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;
+ }
+ }
+ };
+ });
+ }
+
+});
+
+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");
+ };
+
+ jQuery.expr.filters.visible = function( elem ) {
+ return !jQuery.expr.filters.hidden( elem );
+ };
+}
+
+// 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 = {},
+
+ // assumes a single number if not a string
+ parts = typeof value === "string" ? value.split(" ") : [ value ];
+
+ for ( ; i < 4; i++ ) {
+ expanded[ prefix + cssExpand[ i ] + suffix ] =
+ parts[ i ] || parts[ i - 2 ] || parts[ 0 ];
+ }
+
+ return expanded;
+ }
+ };
+
+ if ( !rmargin.test( prefix ) ) {
+ jQuery.cssHooks[ prefix + suffix ].set = setPositiveNumber;
+ }
+});
+var r20 = /%20/g,
+ rbracket = /\[\]$/,
+ rCRLF = /\r?\n/g,
+ rsubmitterTypes = /^(?:submit|button|image|reset|file)$/i,
+ rsubmittable = /^(?:input|select|textarea|keygen)/i;
+
+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;
+ // 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();
+
+ 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();
+ }
+});
+
+//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 );
+ };
+
+ // Set traditional to true for jQuery <= 1.3.2 behavior.
+ if ( traditional === undefined ) {
+ traditional = jQuery.ajaxSettings && jQuery.ajaxSettings.traditional;
+ }
+
+ // 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 );
+ });
+
+ } 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 );
+ }
+ }
+
+ // Return the resulting serialization
+ return s.join( "&" ).replace( r20, "+" );
+};
+
+function buildParams( prefix, obj, traditional, add ) {
+ var name;
+
+ 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 );
+ }
+ });
+
+ } else if ( !traditional && jQuery.type( obj ) === "object" ) {
+ // Serialize object item.
+ for ( name in obj ) {
+ buildParams( prefix + "[" + name + "]", obj[ name ], traditional, add );
+ }
+
+ } else {
+ // Serialize scalar item.
+ add( prefix, obj );
+ }
+}
+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 ) {
+
+ // Handle event binding
+ jQuery.fn[ name ] = function( data, fn ) {
+ return arguments.length > 0 ?
+ this.on( name, null, data, fn ) :
+ this.trigger( name );
+ };
+});
+
+jQuery.fn.extend({
+ hover: function( fnOver, fnOut ) {
+ return this.mouseenter( fnOver ).mouseleave( fnOut || fnOver );
+ },
+
+ 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 );
+ }
+});
+var
+ // Document location
+ ajaxLocParts,
+ ajaxLocation,
+ ajax_nonce = jQuery.now(),
+
+ ajax_rquery = /\?/,
+ rhash = /#.*$/,
+ rts = /([?&])_=[^&]*/,
+ rheaders = /^(.*?):[ \t]*([^\r\n]*)\r?$/mg, // IE leaves an \r character at EOL
+ // #7653, #8125, #8152: local protocol detection
+ rlocalProtocol = /^(?:about|app|app-storage|.+-extension|file|res|widget):$/,
+ rnoContent = /^(?:GET|HEAD)$/,
+ rprotocol = /^\/\//,
+ rurl = /^([\w.+-]+:)(?:\/\/([^\/?#:]*)(?::(\d+)|)|)/,
+
+ // Keep a copy of the old load method
+ _load = jQuery.fn.load,
+
+ /* Prefilters
+ * 1) They are useful to introduce custom dataTypes (see ajax/jsonp.js for an example)
+ * 2) These are called:
+ * - BEFORE asking for a transport
+ * - AFTER param serialization (s.data is a string if s.processData is true)
+ * 3) key is the dataType
+ * 4) the catchall symbol "*" can be used
+ * 5) execution will start with transport dataType and THEN continue down to "*" if needed
+ */
+ prefilters = {},
+
+ /* Transports bindings
+ * 1) key is the dataType
+ * 2) the catchall symbol "*" can be used
+ * 3) selection will start with transport dataType and THEN go to "*" if needed
+ */
+ transports = {},
+
+ // Avoid comment-prolog char sequence (#10098); must appease lint and evade compression
+ allTypes = "*/".concat("*");
+
+// #8138, IE may throw an exception when accessing
+// a field from window.location if document.domain has been set
+try {
+ ajaxLocation = location.href;
+} catch( e ) {
+ // Use the href attribute of an A element
+ // since IE will modify it given document.location
+ ajaxLocation = document.createElement( "a" );
+ ajaxLocation.href = "";
+ ajaxLocation = ajaxLocation.href;
+}
+
+// Segment location into parts
+ajaxLocParts = rurl.exec( ajaxLocation.toLowerCase() ) || [];
+
+// Base "constructor" for jQuery.ajaxPrefilter and jQuery.ajaxTransport
+function addToPrefiltersOrTransports( structure ) {
+
+ // dataTypeExpression is optional and defaults to "*"
+ return function( dataTypeExpression, func ) {
+
+ if ( typeof dataTypeExpression !== "string" ) {
+ func = dataTypeExpression;
+ dataTypeExpression = "*";
+ }
+
+ var dataType,
+ i = 0,
+ dataTypes = dataTypeExpression.toLowerCase().match( core_rnotwhite ) || [];
+
+ 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 );
+
+ // Otherwise append
+ } else {
+ (structure[ dataType ] = structure[ dataType ] || []).push( func );
+ }
+ }
+ }
+ };
+}
+
+// Base inspection function for prefilters and transports
+function inspectPrefiltersOrTransports( structure, options, originalOptions, jqXHR ) {
+
+ var inspected = {},
+ seekingTransport = ( structure === transports );
+
+ function inspect( dataType ) {
+ 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;
+ }
+
+ return inspect( options.dataTypes[ 0 ] ) || !inspected[ "*" ] && inspect( "*" );
+}
+
+// A special extend for ajax options
+// that takes "flat" options (not to be deep extended)
+// Fixes #9887
+function ajaxExtend( target, src ) {
+ var deep, key,
+ flatOptions = jQuery.ajaxSettings.flatOptions || {};
+
+ for ( key in src ) {
+ if ( src[ key ] !== undefined ) {
+ ( flatOptions[ key ] ? target : ( deep || (deep = {}) ) )[ key ] = src[ key ];
+ }
+ }
+ if ( deep ) {
+ jQuery.extend( true, target, deep );
+ }
+
+ return target;
+}
+
+jQuery.fn.load = function( url, params, callback ) {
+ if ( typeof url !== "string" && _load ) {
+ return _load.apply( this, arguments );
+ }
+
+ var selector, response, type,
+ self = this,
+ off = url.indexOf(" ");
+
+ if ( off >= 0 ) {
+ selector = url.slice( off, url.length );
+ url = url.slice( 0, off );
+ }
+
+ // If it's a function
+ if ( jQuery.isFunction( params ) ) {
+
+ // We assume that it's the callback
+ callback = params;
+ params = undefined;
+
+ // Otherwise, build a param string
+ } else if ( params && typeof params === "object" ) {
+ type = "POST";
+ }
+
+ // If we have elements to modify, make the request
+ if ( self.length > 0 ) {
+ jQuery.ajax({
+ url: url,
+
+ // if "type" variable is undefined, then "GET" method will be used
+ type: type,
+ dataType: "html",
+ data: params
+ }).done(function( responseText ) {
+
+ // Save response for use in complete callback
+ response = arguments;
+
+ self.html( selector ?
+
+ // 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 ) :
+
+ // Otherwise use the full result
+ responseText );
+
+ }).complete( callback && function( jqXHR, status ) {
+ self.each( callback, response || [ jqXHR.responseText, status, jqXHR ] );
+ });
+ }
+
+ return this;
+};
+
+// 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 );
+ };
+});
+
+jQuery.extend({
+
+ // Counter for holding the number of active queries
+ active: 0,
+
+ // Last-Modified header cache for next request
+ lastModified: {},
+ etag: {},
+
+ ajaxSettings: {
+ 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: {},
+ */
+
+ accepts: {
+ "*": allTypes,
+ text: "text/plain",
+ html: "text/html",
+ xml: "application/xml, text/xml",
+ json: "application/json, text/javascript"
+ },
+
+ contents: {
+ xml: /xml/,
+ html: /html/,
+ json: /json/
+ },
+
+ responseFields: {
+ xml: "responseXML",
+ text: "responseText",
+ json: "responseJSON"
+ },
+
+ // Data converters
+ // Keys separate source (or catchall "*") and destination types with a single space
+ converters: {
+
+ // Convert anything to text
+ "* text": String,
+
+ // Text to html (true = no transformation)
+ "text html": true,
+
+ // Evaluate text as a json expression
+ "text json": jQuery.parseJSON,
+
+ // Parse text as xml
+ "text xml": jQuery.parseXML
+ },
+
+ // 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
+ }
+ },
+
+ // Creates a full fledged settings object into target
+ // with both ajaxSettings and settings fields.
+ // If target is omitted, writes into ajaxSettings.
+ ajaxSetup: function( target, settings ) {
+ return settings ?
+
+ // Building a settings object
+ ajaxExtend( ajaxExtend( target, jQuery.ajaxSettings ), settings ) :
+
+ // Extending ajaxSettings
+ ajaxExtend( jQuery.ajaxSettings, target );
+ },
+
+ ajaxPrefilter: addToPrefiltersOrTransports( prefilters ),
+ ajaxTransport: addToPrefiltersOrTransports( transports ),
+
+ // Main method
+ ajax: function( url, options ) {
+
+ // If url is an object, simulate pre-1.5 signature
+ if ( typeof url === "object" ) {
+ options = url;
+ url = undefined;
+ }
+
+ // Force options to be an object
+ options = options || {};
+
+ var // Cross-domain detection vars
+ parts,
+ // Loop variable
+ i,
+ // URL without anti-cache param
+ cacheURL,
+ // Response headers as string
+ responseHeadersString,
+ // timeout handle
+ timeoutTimer,
+
+ // To know if global events are to be dispatched
+ fireGlobals,
+
+ 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,
+
+ // 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;
+ },
+
+ // Raw string
+ getAllResponseHeaders: function() {
+ return state === 2 ? responseHeadersString : null;
+ },
+
+ // Caches the header
+ setRequestHeader: function( name, value ) {
+ var lname = name.toLowerCase();
+ if ( !state ) {
+ name = requestHeadersNames[ lname ] = requestHeadersNames[ lname ] || name;
+ requestHeaders[ name ] = value;
+ }
+ return this;
+ },
+
+ // Overrides response content-type header
+ overrideMimeType: function( type ) {
+ if ( !state ) {
+ s.mimeType = type;
+ }
+ return this;
+ },
+
+ // 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;
+ },
+
+ // Cancel the request
+ abort: function( statusText ) {
+ var finalText = statusText || strAbort;
+ if ( transport ) {
+ transport.abort( finalText );
+ }
+ done( 0, finalText );
+ return this;
+ }
+ };
+
+ // Attach deferreds
+ deferred.promise( jqXHR ).complete = completeDeferred.add;
+ jqXHR.success = jqXHR.done;
+ jqXHR.error = jqXHR.fail;
+
+ // 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 ] + "//" );
+
+ // Alias method option to type as per ticket #12004
+ s.type = options.method || options.type || s.method || s.type;
+
+ // Extract dataTypes list
+ s.dataTypes = jQuery.trim( s.dataType || "*" ).toLowerCase().match( core_rnotwhite ) || [""];
+
+ // 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" ) ) )
+ );
+ }
+
+ // Convert data if not already a string
+ if ( s.data && s.processData && typeof s.data !== "string" ) {
+ s.data = jQuery.param( s.data, s.traditional );
+ }
+
+ // Apply prefilters
+ inspectPrefiltersOrTransports( prefilters, s, options, jqXHR );
+
+ // If request was aborted inside a prefilter, stop there
+ if ( state === 2 ) {
+ return jqXHR;
+ }
+
+ // We can fire global events as of now if asked to
+ fireGlobals = s.global;
+
+ // Watch for a new set of requests
+ if ( fireGlobals && jQuery.active++ === 0 ) {
+ jQuery.event.trigger("ajaxStart");
+ }
+
+ // Uppercase the type
+ s.type = s.type.toUpperCase();
+
+ // Determine if request has content
+ s.hasContent = !rnoContent.test( s.type );
+
+ // Save the URL in case we're toying with the If-Modified-Since
+ // and/or If-None-Match header later on
+ cacheURL = s.url;
+
+ // More options handling for requests with no content
+ if ( !s.hasContent ) {
+
+ // 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;
+ }
+
+ // Add anti-cache in url if needed
+ if ( s.cache === false ) {
+ s.url = rts.test( cacheURL ) ?
+
+ // If there is already a '_' parameter, set its value
+ cacheURL.replace( rts, "$1_=" + ajax_nonce++ ) :
+
+ // Otherwise add one to the end
+ cacheURL + ( ajax_rquery.test( cacheURL ) ? "&" : "?" ) + "_=" + ajax_nonce++;
+ }
+ }
+
+ // 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 ] );
+ }
+ }
+
+ // 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 );
+ }
+
+ // 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[ "*" ]
+ );
+
+ // Check for headers option
+ for ( i in s.headers ) {
+ jqXHR.setRequestHeader( i, s.headers[ i ] );
+ }
+
+ // 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();
+ }
+
+ // aborting is no longer a cancellation
+ strAbort = "abort";
+
+ // Install callbacks on deferreds
+ for ( i in { success: 1, error: 1, complete: 1 } ) {
+ jqXHR[ i ]( s[ i ] );
+ }
+
+ // Get transport
+ transport = inspectPrefiltersOrTransports( transports, s, options, jqXHR );
+
+ // If no transport, we auto-abort
+ if ( !transport ) {
+ done( -1, "No Transport" );
+ } else {
+ jqXHR.readyState = 1;
+
+ // 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 );
+ }
+
+ 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;
+ }
+ }
+ }
+
+ // Callback for when everything is done
+ function done( status, nativeStatusText, responses, headers ) {
+ var isSuccess, success, error, response, modified,
+ statusText = nativeStatusText;
+
+ // Called once
+ if ( state === 2 ) {
+ return;
+ }
+
+ // State is "done" now
+ state = 2;
+
+ // Clear timeout if it exists
+ if ( timeoutTimer ) {
+ clearTimeout( timeoutTimer );
+ }
+
+ // Dereference transport for early garbage collection
+ // (no matter how long the jqXHR object will be used)
+ transport = undefined;
+
+ // Cache response headers
+ responseHeadersString = headers || "";
+
+ // Set readyState
+ jqXHR.readyState = status > 0 ? 4 : 0;
+
+ // Determine if successful
+ isSuccess = status >= 200 && status < 300 || status === 304;
+
+ // Get response data
+ if ( responses ) {
+ response = ajaxHandleResponses( s, jqXHR, responses );
+ }
+
+ // Convert no matter what (that way responseXXX fields are always set)
+ response = ajaxConvert( s, response, jqXHR, isSuccess );
+
+ // If successful, handle type chaining
+ if ( isSuccess ) {
+
+ // 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;
+ }
+ }
+
+ // if no content
+ if ( status === 204 || s.type === "HEAD" ) {
+ statusText = "nocontent";
+
+ // if not modified
+ } else if ( status === 304 ) {
+ statusText = "notmodified";
+
+ // 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;
+ }
+ }
+ }
+
+ // Set data for the fake xhr object
+ jqXHR.status = status;
+ jqXHR.statusText = ( nativeStatusText || statusText ) + "";
+
+ // Success/Error
+ if ( isSuccess ) {
+ deferred.resolveWith( callbackContext, [ success, statusText, jqXHR ] );
+ } else {
+ deferred.rejectWith( callbackContext, [ jqXHR, statusText, error ] );
+ }
+
+ // Status-dependent callbacks
+ jqXHR.statusCode( statusCode );
+ statusCode = undefined;
+
+ if ( fireGlobals ) {
+ globalEventContext.trigger( isSuccess ? "ajaxSuccess" : "ajaxError",
+ [ jqXHR, s, isSuccess ? success : error ] );
+ }
+
+ // Complete
+ completeDeferred.fireWith( callbackContext, [ jqXHR, statusText ] );
+
+ if ( fireGlobals ) {
+ globalEventContext.trigger( "ajaxComplete", [ jqXHR, s ] );
+ // Handle the global AJAX counter
+ if ( !( --jQuery.active ) ) {
+ jQuery.event.trigger("ajaxStop");
+ }
+ }
+ }
+
+ return jqXHR;
+ },
+
+ getJSON: function( url, data, callback ) {
+ return jQuery.get( url, data, callback, "json" );
+ },
+
+ getScript: function( url, callback ) {
+ return jQuery.get( url, undefined, callback, "script" );
+ }
+});
+
+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;
+ }
+
+ return jQuery.ajax({
+ url: url,
+ type: method,
+ dataType: type,
+ data: data,
+ success: callback
+ });
+ };
+});
+
+/* 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 firstDataType, ct, finalDataType, type,
+ 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");
+ }
+ }
+
+ // 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;
+ }
+ }
+ }
+
+ // 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;
+ }
+
+ // 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 ];
+ }
+}
+
+/* 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();
+
+ // Create converters map with lowercased keys
+ if ( dataTypes[ 1 ] ) {
+ for ( conv in s.converters ) {
+ converters[ conv.toLowerCase() ] = s.converters[ conv ];
+ }
+ }
+
+ current = dataTypes.shift();
+
+ // Convert to each sequential dataType
+ while ( current ) {
+
+ if ( s.responseFields[ current ] ) {
+ jqXHR[ s.responseFields[ current ] ] = response;
+ }
+
+ // Apply the dataFilter if provided
+ if ( !prev && isSuccess && s.dataFilter ) {
+ response = s.dataFilter( response, s.dataType );
+ }
+
+ prev = current;
+ current = dataTypes.shift();
+
+ if ( current ) {
+
+ // There's only work to do if current dataType is non-auto
+ if ( current === "*" ) {
+
+ current = prev;
+
+ // Convert response if prev dataType is non-auto and differs from current
+ } else if ( prev !== "*" && prev !== current ) {
+
+ // 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 };
+ }
+ }
+ }
+ }
+ }
+ }
+
+ return { state: "success", data: response };
+}
+// 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;
+ }
+ }
+});
+
+// Handle cache's special case and global
+jQuery.ajaxPrefilter( "script", function( s ) {
+ if ( s.cache === undefined ) {
+ s.cache = false;
+ }
+ if ( s.crossDomain ) {
+ s.type = "GET";
+ s.global = false;
+ }
+});
+
+// Bind script tag hack transport
+jQuery.ajaxTransport( "script", function(s) {
+
+ // This transport only deals with cross domain requests
+ if ( s.crossDomain ) {
+
+ var script,
+ head = document.head || jQuery("head")[0] || document.documentElement;
+
+ return {
+
+ send: function( _, callback ) {
+
+ script = document.createElement("script");
+
+ script.async = true;
+
+ if ( s.scriptCharset ) {
+ script.charset = s.scriptCharset;
+ }
+
+ script.src = s.url;
+
+ // Attach handlers for all browsers
+ script.onload = script.onreadystatechange = function( _, isAbort ) {
+
+ 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 );
+ }
+
+ // Dereference the script
+ script = null;
+
+ // Callback if not abort
+ if ( !isAbort ) {
+ callback( 200, "success" );
+ }
+ }
+ };
+
+ // 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 );
+ },
+
+ abort: function() {
+ if ( script ) {
+ script.onload( undefined, true );
+ }
+ }
+ };
+ }
+});
+var oldCallbacks = [],
+ rjsonp = /(=)\?(?=&|$)|\?\?/;
+
+// Default jsonp settings
+jQuery.ajaxSetup({
+ jsonp: "callback",
+ jsonpCallback: function() {
+ var callback = oldCallbacks.pop() || ( jQuery.expando + "_" + ( ajax_nonce++ ) );
+ this[ callback ] = true;
+ return callback;
+ }
+});
+
+// Detect, normalize options and install callbacks for jsonp requests
+jQuery.ajaxPrefilter( "json jsonp", function( s, originalSettings, jqXHR ) {
+
+ 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"
+ );
+
+ // 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;
+ }
+
+ // 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";
+ }
+});
+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 );
+ }
+ };
+
+// Functions to create xhrs
+function createStandardXHR() {
+ try {
+ return new window.XMLHttpRequest();
+ } catch( e ) {}
+}
+
+function createActiveXHR() {
+ try {
+ return new window.ActiveXObject("Microsoft.XMLHTTP");
+ } catch( e ) {}
+}
+
+// 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;
+
+// Determine support properties
+xhrSupported = jQuery.ajaxSettings.xhr();
+jQuery.support.cors = !!xhrSupported && ( "withCredentials" in xhrSupported );
+xhrSupported = jQuery.support.ajax = !!xhrSupported;
+
+// Create transport if the browser can provide an xhr
+if ( xhrSupported ) {
+
+ jQuery.ajaxTransport(function( s ) {
+ // Cross domain only allowed if supported through XMLHttpRequest
+ if ( !s.crossDomain || jQuery.support.cors ) {
+
+ var callback;
+
+ return {
+ send: function( headers, complete ) {
+
+ // Get a new xhr
+ var handle, i,
+ xhr = s.xhr();
+
+ // 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 );
+ }
+
+ // Apply custom fields if provided
+ if ( s.xhrFields ) {
+ for ( i in s.xhrFields ) {
+ xhr[ i ] = s.xhrFields[ i ];
+ }
+ }
+
+ // Override mime type if needed
+ if ( s.mimeType && xhr.overrideMimeType ) {
+ xhr.overrideMimeType( s.mimeType );
+ }
+
+ // 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";
+ }
+
+ // Need an extra try/catch for cross domain requests in Firefox 3
+ try {
+ for ( i in headers ) {
+ xhr.setRequestHeader( i, headers[ i ] );
+ }
+ } catch( err ) {}
+
+ // 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 );
+
+ // Listener
+ callback = function( _, isAbort ) {
+ var status, responseHeaders, statusText, responses;
+
+ // 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:_0…
+ try {
+
+ // Was never called and is aborted or complete
+ if ( callback && ( isAbort || xhr.readyState === 4 ) ) {
+
+ // Only called once
+ callback = undefined;
+
+ // Do not keep as active anymore
+ if ( handle ) {
+ xhr.onreadystatechange = jQuery.noop;
+ if ( xhrOnUnloadAbort ) {
+ delete xhrCallbacks[ handle ];
+ }
+ }
+
+ // 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();
+
+ // 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;
+ }
+
+ // 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 = "";
+ }
+
+ // 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;
+ }
+ }
+ }
+ } catch( firefoxAccessException ) {
+ if ( !isAbort ) {
+ complete( -1, firefoxAccessException );
+ }
+ }
+
+ // Call complete if needed
+ if ( responses ) {
+ complete( status, statusText, responses, responseHeaders );
+ }
+ };
+
+ 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 );
+ }
+ // Add to list of active xhrs callbacks
+ xhrCallbacks[ handle ] = callback;
+ }
+ xhr.onreadystatechange = callback;
+ }
+ },
+
+ abort: function() {
+ if ( callback ) {
+ callback( undefined, true );
+ }
+ }
+ };
+ }
+ });
+}
+var fxNow, timerId,
+ rfxtypes = /^(?:toggle|show|hide)$/,
+ rfxnum = new RegExp( "^(?:([+-])=|)(" + core_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" ),
+
+ // 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;
+
+ if ( start && start[ 3 ] !== unit ) {
+ // Trust units reported by jQuery.css
+ unit = unit || start[ 3 ];
+
+ // Make sure we update the tween properties later on
+ parts = parts || [];
+
+ // Iteratively approximate from a nonzero starting point
+ start = +target || 1;
+
+ 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";
+
+ // Adjust and apply
+ start = start / scale;
+ jQuery.style( tween.elem, prop, start + unit );
+
+ // 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 );
+ }
+
+ // 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 ];
+ }
+
+ return tween;
+ }]
+ };
+
+// Animations created synchronously will run synchronously
+function createFxNow() {
+ setTimeout(function() {
+ fxNow = undefined;
+ });
+ return ( fxNow = jQuery.now() );
+}
+
+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;
+ }
+ }
+}
+
+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;
+
+ for ( ; index < length ; index++ ) {
+ animation.tweens[ index ].run( percent );
+ }
+
+ deferred.notifyWith( elem, [ animation, percent, remaining ]);
+
+ 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 );
+ }
+
+ // 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;
+ }
+ }
+
+ jQuery.map( props, createTween, animation );
+
+ if ( jQuery.isFunction( animation.opts.start ) ) {
+ animation.opts.start.call( elem, animation );
+ }
+
+ jQuery.fx.timer(
+ jQuery.extend( tick, {
+ elem: elem,
+ anim: animation,
+ queue: animation.opts.queue
+ })
+ );
+
+ // 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 );
+}
+
+function propFilter( props, specialEasing ) {
+ var index, name, easing, value, hooks;
+
+ // 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 ];
+ }
+
+ if ( index !== name ) {
+ props[ name ] = value;
+ delete props[ index ];
+ }
+
+ hooks = jQuery.cssHooks[ name ];
+ if ( hooks && "expand" in hooks ) {
+ value = hooks.expand( value );
+ delete props[ name ];
+
+ // 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;
+ }
+ }
+}
+
+jQuery.Animation = jQuery.extend( Animation, {
+
+ tweener: function( props, callback ) {
+ if ( jQuery.isFunction( props ) ) {
+ callback = props;
+ props = [ "*" ];
+ } else {
+ props = props.split(" ");
+ }
+
+ var prop,
+ index = 0,
+ length = props.length;
+
+ for ( ; index < length ; index++ ) {
+ prop = props[ index ];
+ tweeners[ prop ] = tweeners[ prop ] || [];
+ tweeners[ prop ].unshift( callback );
+ }
+ },
+
+ prefilter: function( callback, prepend ) {
+ if ( prepend ) {
+ animationPrefilters.unshift( callback );
+ } else {
+ animationPrefilters.push( callback );
+ }
+ }
+});
+
+function defaultPrefilter( elem, props, opts ) {
+ /* jshint validthis: true */
+ var prop, value, toggle, tween, hooks, oldfire,
+ anim = this,
+ orig = {},
+ style = elem.style,
+ hidden = elem.nodeType && isHidden( elem ),
+ dataShow = jQuery._data( elem, "fxshow" );
+
+ // 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++;
+
+ 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();
+ }
+ });
+ });
+ }
+
+ // 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;
+ }
+ }
+ }
+
+ 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 ];
+ });
+ }
+ }
+
+
+ // 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" ) ) {
+ continue;
+ }
+ orig[ prop ] = dataShow && dataShow[ prop ] || jQuery.style( elem, prop );
+ }
+ }
+
+ if ( !jQuery.isEmptyObject( orig ) ) {
+ if ( dataShow ) {
+ if ( "hidden" in dataShow ) {
+ hidden = dataShow.hidden;
+ }
+ } else {
+ dataShow = jQuery._data( elem, "fxshow", {} );
+ }
+
+ // 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 ( prop in orig ) {
+ tween = createTween( hidden ? dataShow[ prop ] : 0, prop, anim );
+
+ if ( !( prop in dataShow ) ) {
+ dataShow[ prop ] = tween.start;
+ if ( hidden ) {
+ tween.end = tween.start;
+ tween.start = prop === "width" || prop === "height" ? 1 : 0;
+ }
+ }
+ }
+ }
+}
+
+function Tween( elem, options, prop, end, easing ) {
+ return new Tween.prototype.init( elem, options, prop, end, easing );
+}
+jQuery.Tween = Tween;
+
+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 ];
+
+ return hooks && hooks.get ?
+ hooks.get( this ) :
+ Tween.propHooks._default.get( this );
+ },
+ run: function( percent ) {
+ var eased,
+ hooks = Tween.propHooks[ this.prop ];
+
+ 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;
+ }
+};
+
+Tween.prototype.init.prototype = Tween.prototype;
+
+Tween.propHooks = {
+ _default: {
+ get: function( tween ) {
+ var result;
+
+ if ( tween.elem[ tween.prop ] != null &&
+ (!tween.elem.style || tween.elem.style[ tween.prop ] == null) ) {
+ return tween.elem[ tween.prop ];
+ }
+
+ // 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;
+ }
+ }
+ }
+};
+
+// Support: IE <=9
+// Panic based approach to setting things on disconnected nodes
+
+Tween.propHooks.scrollTop = Tween.propHooks.scrollLeft = {
+ set: function( tween ) {
+ if ( tween.elem.nodeType && tween.elem.parentNode ) {
+ tween.elem[ tween.prop ] = tween.now;
+ }
+ }
+};
+
+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 );
+ };
+});
+
+jQuery.fn.extend({
+ fadeTo: function( speed, to, easing, callback ) {
+
+ // show any hidden elements after setting opacity to 0
+ return this.filter( isHidden ).css( "opacity", 0 ).show()
+
+ // 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 );
+
+ // Empty animations, or finishing resolves immediately
+ if ( empty || jQuery._data( this, "finish" ) ) {
+ anim.stop( true );
+ }
+ };
+ doAnimation.finish = doAnimation;
+
+ 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 );
+ };
+
+ if ( typeof type !== "string" ) {
+ gotoEnd = clearQueue;
+ clearQueue = type;
+ type = undefined;
+ }
+ if ( clearQueue && type !== false ) {
+ this.queue( type || "fx", [] );
+ }
+
+ return this.each(function() {
+ var dequeue = true,
+ index = type != null && type + "queueHooks",
+ timers = jQuery.timers,
+ data = jQuery._data( this );
+
+ 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 ] );
+ }
+ }
+ }
+
+ 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 );
+ }
+ }
+
+ // 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;
+
+ // enable finishing flag on private data
+ data.finish = true;
+
+ // empty the queue first
+ jQuery.queue( this, type, [] );
+
+ if ( hooks && hooks.stop ) {
+ hooks.stop.call( this, true );
+ }
+
+ // 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 );
+ }
+ }
+
+ // 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 );
+ }
+ }
+
+ // turn off finishing flag
+ delete data.finish;
+ });
+ }
+});
+
+// Generate parameters to create a standard animation
+function genFx( type, includeWidth ) {
+ var which,
+ attrs = { height: type },
+ i = 0;
+
+ // 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;
+ }
+
+ if ( includeWidth ) {
+ attrs.opacity = attrs.width = type;
+ }
+
+ return attrs;
+}
+
+// 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 );
+ };
+});
+
+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
+ };
+
+ 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;
+
+ // normalize opt.queue - true/undefined/null -> "fx"
+ if ( opt.queue == null || opt.queue === true ) {
+ opt.queue = "fx";
+ }
+
+ // Queueing
+ opt.old = opt.complete;
+
+ 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;
+ },
+ swing: function( p ) {
+ return 0.5 - Math.cos( p*Math.PI ) / 2;
+ }
+};
+
+jQuery.timers = [];
+jQuery.fx = Tween.prototype.init;
+jQuery.fx.tick = function() {
+ var timer,
+ timers = jQuery.timers,
+ i = 0;
+
+ fxNow = jQuery.now();
+
+ 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 );
+ }
+ }
+
+ if ( !timers.length ) {
+ jQuery.fx.stop();
+ }
+ fxNow = undefined;
+};
+
+jQuery.fx.timer = function( timer ) {
+ if ( timer() && jQuery.timers.push( timer ) ) {
+ jQuery.fx.start();
+ }
+};
+
+jQuery.fx.interval = 13;
+
+jQuery.fx.start = function() {
+ if ( !timerId ) {
+ timerId = setInterval( jQuery.fx.tick, jQuery.fx.interval );
+ }
+};
+
+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;
+ };
+}
+jQuery.fn.offset = function( options ) {
+ if ( arguments.length ) {
+ return options === undefined ?
+ this :
+ this.each(function( i ) {
+ jQuery.offset.setOffset( this, options, i );
+ });
+ }
+
+ var docElem, win,
+ box = { top: 0, left: 0 },
+ elem = this[ 0 ],
+ doc = elem && elem.ownerDocument;
+
+ if ( !doc ) {
+ return;
+ }
+
+ docElem = doc.documentElement;
+
+ // Make sure it's not a disconnected DOM node
+ if ( !jQuery.contains( docElem, elem ) ) {
+ return box;
+ }
+
+ // 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 )
+ };
+};
+
+jQuery.offset = {
+
+ setOffset: function( elem, options, i ) {
+ var position = jQuery.css( elem, "position" );
+
+ // set position first, in-case top/left are set even on static elem
+ if ( position === "static" ) {
+ elem.style.position = "relative";
+ }
+
+ 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;
+
+ // 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;
+ }
+
+ 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 );
+ }
+ }
+};
+
+
+jQuery.fn.extend({
+
+ position: function() {
+ if ( !this[ 0 ] ) {
+ return;
+ }
+
+ var offsetParent, offset,
+ parentOffset = { top: 0, left: 0 },
+ elem = this[ 0 ];
+
+ // 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 || docElem;
+ while ( offsetParent && ( !jQuery.nodeName( offsetParent, "html" ) && jQuery.css( offsetParent, "position") === "static" ) ) {
+ offsetParent = offsetParent.offsetParent;
+ }
+ return offsetParent || docElem;
+ });
+ }
+});
+
+
+// Create scrollLeft and scrollTop methods
+jQuery.each( {scrollLeft: "pageXOffset", scrollTop: "pageYOffset"}, function( method, prop ) {
+ var top = /Y/.test( prop );
+
+ jQuery.fn[ method ] = function( val ) {
+ return jQuery.access( this, function( elem, method, val ) {
+ var win = getWindow( elem );
+
+ if ( val === undefined ) {
+ return win ? (prop in win) ? win[ prop ] :
+ win.document.documentElement[ method ] :
+ elem[ method ];
+ }
+
+ if ( win ) {
+ win.scrollTo(
+ !top ? val : jQuery( win ).scrollLeft(),
+ top ? val : jQuery( win ).scrollTop()
+ );
+
+ } else {
+ elem[ method ] = val;
+ }
+ }, method, val, arguments.length, null );
+ };
+});
+
+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 ];
+ }
+
+ // Get document width or height
+ if ( elem.nodeType === 9 ) {
+ doc = elem.documentElement;
+
+ // 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 ]
+ );
+ }
+
+ return value === undefined ?
+ // Get width or height on the element, requesting but not forcing parseFloat
+ jQuery.css( elem, type, extra ) :
+
+ // 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() {
+
+// The number of elements contained in the matched element set
+jQuery.fn.size = function() {
+ return this.length;
+};
+
+jQuery.fn.andSelf = jQuery.fn.addBack;
+
+// })();
+if ( typeof module === "object" && module && typeof module.exports === "object" ) {
+ // Expose jQuery as module.exports in loaders that implement the Node
+ // module pattern (including browserify). Do not create the global, since
+ // the user will be storing it themselves locally, and globals are frowned
+ // upon in the Node module world.
+ module.exports = jQuery;
+} else {
+ // Otherwise expose jQuery to the global object as usual
+ window.jQuery = window.$ = jQuery;
+
+ // 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; } );
+ }
+}
+
+})( window );
1
0
r700 - in test-js-frameworks: . angularjs angularjs/js angularjs/partials
by dralagen@users.nuiton.org 17 Apr '14
by dralagen@users.nuiton.org 17 Apr '14
17 Apr '14
Author: dralagen
Date: 2014-04-17 10:34:32 +0200 (Thu, 17 Apr 2014)
New Revision: 700
Url: http://forge.nuiton.org/projects/sandbox/repository/revisions/700
Log:
Add test AngulrJS
Added:
test-js-frameworks/angularjs/
test-js-frameworks/angularjs/index.html
test-js-frameworks/angularjs/js/
test-js-frameworks/angularjs/js/angular.js
test-js-frameworks/angularjs/js/app.js
test-js-frameworks/angularjs/js/controllers.js
test-js-frameworks/angularjs/partials/
test-js-frameworks/angularjs/partials/create.html
test-js-frameworks/angularjs/partials/home.html
test-js-frameworks/angularjs/partials/view.html
Added: test-js-frameworks/angularjs/index.html
===================================================================
--- test-js-frameworks/angularjs/index.html (rev 0)
+++ test-js-frameworks/angularjs/index.html 2014-04-17 08:34:32 UTC (rev 700)
@@ -0,0 +1,16 @@
+<!DOCTYPE html>
+
+<html lang="fr" ng-app="poll">
+ <head>
+ <meta charset="utf-8"/>
+ <title> Test AngularJS</title>
+ <script src="./js/angular.js"></script>
+ <script src="./js/app.js"></script>
+ <script src="./js/controllers.js"></script>
+ </head>
+ <body>
+ <h1> Test AngularJS </h1>
+ <div ng-view class="container" id="content"></div>
+ </body>
+</html>
+
Added: test-js-frameworks/angularjs/js/angular.js
===================================================================
--- test-js-frameworks/angularjs/js/angular.js (rev 0)
+++ test-js-frameworks/angularjs/js/angular.js 2014-04-17 08:34:32 UTC (rev 700)
@@ -0,0 +1,15158 @@
+/**
+ * @license AngularJS v1.0.8
+ * (c) 2010-2012 Google, Inc. http://angularjs.org
+ * License: MIT
+ */
+(function(window, document, undefined) {
+'use strict';
+
+////////////////////////////////////
+
+/**
+ * @ngdoc function
+ * @name angular.lowercase
+ * @function
+ *
+ * @description Converts the specified string to lowercase.
+ * @param {string} string String to be converted to lowercase.
+ * @returns {string} Lowercased string.
+ */
+var lowercase = function(string){return isString(string) ? string.toLowerCase() : string;};
+
+
+/**
+ * @ngdoc function
+ * @name angular.uppercase
+ * @function
+ *
+ * @description Converts the specified string to uppercase.
+ * @param {string} string String to be converted to uppercase.
+ * @returns {string} Uppercased string.
+ */
+var uppercase = function(string){return isString(string) ? string.toUpperCase() : string;};
+
+
+var manualLowercase = function(s) {
+ return isString(s)
+ ? s.replace(/[A-Z]/g, function(ch) {return String.fromCharCode(ch.charCodeAt(0) | 32);})
+ : s;
+};
+var manualUppercase = function(s) {
+ return isString(s)
+ ? s.replace(/[a-z]/g, function(ch) {return String.fromCharCode(ch.charCodeAt(0) & ~32);})
+ : s;
+};
+
+
+// String#toLowerCase and String#toUpperCase don't produce correct results in browsers with Turkish
+// locale, for this reason we need to detect this case and redefine lowercase/uppercase methods
+// with correct but slower alternatives.
+if ('i' !== 'I'.toLowerCase()) {
+ lowercase = manualLowercase;
+ uppercase = manualUppercase;
+}
+
+
+var /** holds major version number for IE or NaN for real browsers */
+ msie = int((/msie (\d+)/.exec(lowercase(navigator.userAgent)) || [])[1]),
+ jqLite, // delay binding since jQuery could be loaded after us.
+ jQuery, // delay binding
+ slice = [].slice,
+ push = [].push,
+ toString = Object.prototype.toString,
+
+ /** @name angular */
+ angular = window.angular || (window.angular = {}),
+ angularModule,
+ nodeName_,
+ uid = ['0', '0', '0'];
+
+
+/**
+ * @private
+ * @param {*} obj
+ * @return {boolean} Returns true if `obj` is an array or array-like object (NodeList, Arguments, ...)
+ */
+function isArrayLike(obj) {
+ if (!obj || (typeof obj.length !== 'number')) return false;
+
+ // We have on object which has length property. Should we treat it as array?
+ if (typeof obj.hasOwnProperty != 'function' &&
+ typeof obj.constructor != 'function') {
+ // This is here for IE8: it is a bogus object treat it as array;
+ return true;
+ } else {
+ return obj instanceof JQLite || // JQLite
+ (jQuery && obj instanceof jQuery) || // jQuery
+ toString.call(obj) !== '[object Object]' || // some browser native object
+ typeof obj.callee === 'function'; // arguments (on IE8 looks like regular obj)
+ }
+}
+
+
+/**
+ * @ngdoc function
+ * @name angular.forEach
+ * @function
+ *
+ * @description
+ * Invokes the `iterator` function once for each item in `obj` collection, which can be either an
+ * object or an array. The `iterator` function is invoked with `iterator(value, key)`, where `value`
+ * is the value of an object property or an array element and `key` is the object property key or
+ * array element index. Specifying a `context` for the function is optional.
+ *
+ * Note: this function was previously known as `angular.foreach`.
+ *
+ <pre>
+ var values = {name: 'misko', gender: 'male'};
+ var log = [];
+ angular.forEach(values, function(value, key){
+ this.push(key + ': ' + value);
+ }, log);
+ expect(log).toEqual(['name: misko', 'gender:male']);
+ </pre>
+ *
+ * @param {Object|Array} obj Object to iterate over.
+ * @param {Function} iterator Iterator function.
+ * @param {Object=} context Object to become context (`this`) for the iterator function.
+ * @returns {Object|Array} Reference to `obj`.
+ */
+function forEach(obj, iterator, context) {
+ var key;
+ if (obj) {
+ if (isFunction(obj)){
+ for (key in obj) {
+ if (key != 'prototype' && key != 'length' && key != 'name' && obj.hasOwnProperty(key)) {
+ iterator.call(context, obj[key], key);
+ }
+ }
+ } else if (obj.forEach && obj.forEach !== forEach) {
+ obj.forEach(iterator, context);
+ } else if (isArrayLike(obj)) {
+ for (key = 0; key < obj.length; key++)
+ iterator.call(context, obj[key], key);
+ } else {
+ for (key in obj) {
+ if (obj.hasOwnProperty(key)) {
+ iterator.call(context, obj[key], key);
+ }
+ }
+ }
+ }
+ return obj;
+}
+
+function sortedKeys(obj) {
+ var keys = [];
+ for (var key in obj) {
+ if (obj.hasOwnProperty(key)) {
+ keys.push(key);
+ }
+ }
+ return keys.sort();
+}
+
+function forEachSorted(obj, iterator, context) {
+ var keys = sortedKeys(obj);
+ for ( var i = 0; i < keys.length; i++) {
+ iterator.call(context, obj[keys[i]], keys[i]);
+ }
+ return keys;
+}
+
+
+/**
+ * when using forEach the params are value, key, but it is often useful to have key, value.
+ * @param {function(string, *)} iteratorFn
+ * @returns {function(*, string)}
+ */
+function reverseParams(iteratorFn) {
+ return function(value, key) { iteratorFn(key, value) };
+}
+
+/**
+ * A consistent way of creating unique IDs in angular. The ID is a sequence of alpha numeric
+ * characters such as '012ABC'. The reason why we are not using simply a number counter is that
+ * the number string gets longer over time, and it can also overflow, where as the nextId
+ * will grow much slower, it is a string, and it will never overflow.
+ *
+ * @returns an unique alpha-numeric string
+ */
+function nextUid() {
+ var index = uid.length;
+ var digit;
+
+ while(index) {
+ index--;
+ digit = uid[index].charCodeAt(0);
+ if (digit == 57 /*'9'*/) {
+ uid[index] = 'A';
+ return uid.join('');
+ }
+ if (digit == 90 /*'Z'*/) {
+ uid[index] = '0';
+ } else {
+ uid[index] = String.fromCharCode(digit + 1);
+ return uid.join('');
+ }
+ }
+ uid.unshift('0');
+ return uid.join('');
+}
+
+
+/**
+ * Set or clear the hashkey for an object.
+ * @param obj object
+ * @param h the hashkey (!truthy to delete the hashkey)
+ */
+function setHashKey(obj, h) {
+ if (h) {
+ obj.$$hashKey = h;
+ }
+ else {
+ delete obj.$$hashKey;
+ }
+}
+
+/**
+ * @ngdoc function
+ * @name angular.extend
+ * @function
+ *
+ * @description
+ * Extends the destination object `dst` by copying all of the properties from the `src` object(s)
+ * to `dst`. You can specify multiple `src` objects.
+ *
+ * @param {Object} dst Destination object.
+ * @param {...Object} src Source object(s).
+ * @returns {Object} Reference to `dst`.
+ */
+function extend(dst) {
+ var h = dst.$$hashKey;
+ forEach(arguments, function(obj){
+ if (obj !== dst) {
+ forEach(obj, function(value, key){
+ dst[key] = value;
+ });
+ }
+ });
+
+ setHashKey(dst,h);
+ return dst;
+}
+
+function int(str) {
+ return parseInt(str, 10);
+}
+
+
+function inherit(parent, extra) {
+ return extend(new (extend(function() {}, {prototype:parent}))(), extra);
+}
+
+
+/**
+ * @ngdoc function
+ * @name angular.noop
+ * @function
+ *
+ * @description
+ * A function that performs no operations. This function can be useful when writing code in the
+ * functional style.
+ <pre>
+ function foo(callback) {
+ var result = calculateResult();
+ (callback || angular.noop)(result);
+ }
+ </pre>
+ */
+function noop() {}
+noop.$inject = [];
+
+
+/**
+ * @ngdoc function
+ * @name angular.identity
+ * @function
+ *
+ * @description
+ * A function that returns its first argument. This function is useful when writing code in the
+ * functional style.
+ *
+ <pre>
+ function transformer(transformationFn, value) {
+ return (transformationFn || angular.identity)(value);
+ };
+ </pre>
+ */
+function identity($) {return $;}
+identity.$inject = [];
+
+
+function valueFn(value) {return function() {return value;};}
+
+/**
+ * @ngdoc function
+ * @name angular.isUndefined
+ * @function
+ *
+ * @description
+ * Determines if a reference is undefined.
+ *
+ * @param {*} value Reference to check.
+ * @returns {boolean} True if `value` is undefined.
+ */
+function isUndefined(value){return typeof value == 'undefined';}
+
+
+/**
+ * @ngdoc function
+ * @name angular.isDefined
+ * @function
+ *
+ * @description
+ * Determines if a reference is defined.
+ *
+ * @param {*} value Reference to check.
+ * @returns {boolean} True if `value` is defined.
+ */
+function isDefined(value){return typeof value != 'undefined';}
+
+
+/**
+ * @ngdoc function
+ * @name angular.isObject
+ * @function
+ *
+ * @description
+ * Determines if a reference is an `Object`. Unlike `typeof` in JavaScript, `null`s are not
+ * considered to be objects.
+ *
+ * @param {*} value Reference to check.
+ * @returns {boolean} True if `value` is an `Object` but not `null`.
+ */
+function isObject(value){return value != null && typeof value == 'object';}
+
+
+/**
+ * @ngdoc function
+ * @name angular.isString
+ * @function
+ *
+ * @description
+ * Determines if a reference is a `String`.
+ *
+ * @param {*} value Reference to check.
+ * @returns {boolean} True if `value` is a `String`.
+ */
+function isString(value){return typeof value == 'string';}
+
+
+/**
+ * @ngdoc function
+ * @name angular.isNumber
+ * @function
+ *
+ * @description
+ * Determines if a reference is a `Number`.
+ *
+ * @param {*} value Reference to check.
+ * @returns {boolean} True if `value` is a `Number`.
+ */
+function isNumber(value){return typeof value == 'number';}
+
+
+/**
+ * @ngdoc function
+ * @name angular.isDate
+ * @function
+ *
+ * @description
+ * Determines if a value is a date.
+ *
+ * @param {*} value Reference to check.
+ * @returns {boolean} True if `value` is a `Date`.
+ */
+function isDate(value){
+ return toString.apply(value) == '[object Date]';
+}
+
+
+/**
+ * @ngdoc function
+ * @name angular.isArray
+ * @function
+ *
+ * @description
+ * Determines if a reference is an `Array`.
+ *
+ * @param {*} value Reference to check.
+ * @returns {boolean} True if `value` is an `Array`.
+ */
+function isArray(value) {
+ return toString.apply(value) == '[object Array]';
+}
+
+
+/**
+ * @ngdoc function
+ * @name angular.isFunction
+ * @function
+ *
+ * @description
+ * Determines if a reference is a `Function`.
+ *
+ * @param {*} value Reference to check.
+ * @returns {boolean} True if `value` is a `Function`.
+ */
+function isFunction(value){return typeof value == 'function';}
+
+
+/**
+ * Determines if a value is a regular expression object.
+ *
+ * @private
+ * @param {*} value Reference to check.
+ * @returns {boolean} True if `value` is a `RegExp`.
+ */
+function isRegExp(value) {
+ return toString.apply(value) == '[object RegExp]';
+}
+
+
+/**
+ * Checks if `obj` is a window object.
+ *
+ * @private
+ * @param {*} obj Object to check
+ * @returns {boolean} True if `obj` is a window obj.
+ */
+function isWindow(obj) {
+ return obj && obj.document && obj.location && obj.alert && obj.setInterval;
+}
+
+
+function isScope(obj) {
+ return obj && obj.$evalAsync && obj.$watch;
+}
+
+
+function isFile(obj) {
+ return toString.apply(obj) === '[object File]';
+}
+
+
+function isBoolean(value) {
+ return typeof value == 'boolean';
+}
+
+
+var trim = (function() {
+ // native trim is way faster: http://jsperf.com/angular-trim-test
+ // but IE doesn't have it... :-(
+ // TODO: we should move this into IE/ES5 polyfill
+ if (!String.prototype.trim) {
+ return function(value) {
+ return isString(value) ? value.replace(/^\s*/, '').replace(/\s*$/, '') : value;
+ };
+ }
+ return function(value) {
+ return isString(value) ? value.trim() : value;
+ };
+})();
+
+
+/**
+ * @ngdoc function
+ * @name angular.isElement
+ * @function
+ *
+ * @description
+ * Determines if a reference is a DOM element (or wrapped jQuery element).
+ *
+ * @param {*} value Reference to check.
+ * @returns {boolean} True if `value` is a DOM element (or wrapped jQuery element).
+ */
+function isElement(node) {
+ return node &&
+ (node.nodeName // we are a direct element
+ || (node.bind && node.find)); // we have a bind and find method part of jQuery API
+}
+
+/**
+ * @param str 'key1,key2,...'
+ * @returns {object} in the form of {key1:true, key2:true, ...}
+ */
+function makeMap(str){
+ var obj = {}, items = str.split(","), i;
+ for ( i = 0; i < items.length; i++ )
+ obj[ items[i] ] = true;
+ return obj;
+}
+
+
+if (msie < 9) {
+ nodeName_ = function(element) {
+ element = element.nodeName ? element : element[0];
+ return (element.scopeName && element.scopeName != 'HTML')
+ ? uppercase(element.scopeName + ':' + element.nodeName) : element.nodeName;
+ };
+} else {
+ nodeName_ = function(element) {
+ return element.nodeName ? element.nodeName : element[0].nodeName;
+ };
+}
+
+
+function map(obj, iterator, context) {
+ var results = [];
+ forEach(obj, function(value, index, list) {
+ results.push(iterator.call(context, value, index, list));
+ });
+ return results;
+}
+
+
+/**
+ * @description
+ * Determines the number of elements in an array, the number of properties an object has, or
+ * the length of a string.
+ *
+ * Note: This function is used to augment the Object type in Angular expressions. See
+ * {@link angular.Object} for more information about Angular arrays.
+ *
+ * @param {Object|Array|string} obj Object, array, or string to inspect.
+ * @param {boolean} [ownPropsOnly=false] Count only "own" properties in an object
+ * @returns {number} The size of `obj` or `0` if `obj` is neither an object nor an array.
+ */
+function size(obj, ownPropsOnly) {
+ var size = 0, key;
+
+ if (isArray(obj) || isString(obj)) {
+ return obj.length;
+ } else if (isObject(obj)){
+ for (key in obj)
+ if (!ownPropsOnly || obj.hasOwnProperty(key))
+ size++;
+ }
+
+ return size;
+}
+
+
+function includes(array, obj) {
+ return indexOf(array, obj) != -1;
+}
+
+function indexOf(array, obj) {
+ if (array.indexOf) return array.indexOf(obj);
+
+ for ( var i = 0; i < array.length; i++) {
+ if (obj === array[i]) return i;
+ }
+ return -1;
+}
+
+function arrayRemove(array, value) {
+ var index = indexOf(array, value);
+ if (index >=0)
+ array.splice(index, 1);
+ return value;
+}
+
+function isLeafNode (node) {
+ if (node) {
+ switch (node.nodeName) {
+ case "OPTION":
+ case "PRE":
+ case "TITLE":
+ return true;
+ }
+ }
+ return false;
+}
+
+/**
+ * @ngdoc function
+ * @name angular.copy
+ * @function
+ *
+ * @description
+ * Creates a deep copy of `source`, which should be an object or an array.
+ *
+ * * If no destination is supplied, a copy of the object or array is created.
+ * * If a destination is provided, all of its elements (for array) or properties (for objects)
+ * are deleted and then all elements/properties from the source are copied to it.
+ * * If `source` is not an object or array, `source` is returned.
+ *
+ * Note: this function is used to augment the Object type in Angular expressions. See
+ * {@link ng.$filter} for more information about Angular arrays.
+ *
+ * @param {*} source The source that will be used to make a copy.
+ * Can be any type, including primitives, `null`, and `undefined`.
+ * @param {(Object|Array)=} destination Destination into which the source is copied. If
+ * provided, must be of the same type as `source`.
+ * @returns {*} The copy or updated `destination`, if `destination` was specified.
+ */
+function copy(source, destination){
+ if (isWindow(source) || isScope(source)) throw Error("Can't copy Window or Scope");
+ if (!destination) {
+ destination = source;
+ if (source) {
+ if (isArray(source)) {
+ destination = copy(source, []);
+ } else if (isDate(source)) {
+ destination = new Date(source.getTime());
+ } else if (isRegExp(source)) {
+ destination = new RegExp(source.source);
+ } else if (isObject(source)) {
+ destination = copy(source, {});
+ }
+ }
+ } else {
+ if (source === destination) throw Error("Can't copy equivalent objects or arrays");
+ if (isArray(source)) {
+ destination.length = 0;
+ for ( var i = 0; i < source.length; i++) {
+ destination.push(copy(source[i]));
+ }
+ } else {
+ var h = destination.$$hashKey;
+ forEach(destination, function(value, key){
+ delete destination[key];
+ });
+ for ( var key in source) {
+ destination[key] = copy(source[key]);
+ }
+ setHashKey(destination,h);
+ }
+ }
+ return destination;
+}
+
+/**
+ * Create a shallow copy of an object
+ */
+function shallowCopy(src, dst) {
+ dst = dst || {};
+
+ for(var key in src) {
+ if (src.hasOwnProperty(key) && key.substr(0, 2) !== '$$') {
+ dst[key] = src[key];
+ }
+ }
+
+ return dst;
+}
+
+
+/**
+ * @ngdoc function
+ * @name angular.equals
+ * @function
+ *
+ * @description
+ * Determines if two objects or two values are equivalent. Supports value types, regular expressions, arrays and
+ * objects.
+ *
+ * Two objects or values are considered equivalent if at least one of the following is true:
+ *
+ * * Both objects or values pass `===` comparison.
+ * * Both objects or values are of the same type and all of their properties pass `===` comparison.
+ * * Both values are NaN. (In JavasScript, NaN == NaN => false. But we consider two NaN as equal)
+ * * Both values represent the same regular expression (In JavasScript,
+ * /abc/ == /abc/ => false. But we consider two regular expressions as equal when their textual
+ * representation matches).
+ *
+ * During a property comparision, properties of `function` type and properties with names
+ * that begin with `$` are ignored.
+ *
+ * Scope and DOMWindow objects are being compared only by identify (`===`).
+ *
+ * @param {*} o1 Object or value to compare.
+ * @param {*} o2 Object or value to compare.
+ * @returns {boolean} True if arguments are equal.
+ */
+function equals(o1, o2) {
+ if (o1 === o2) return true;
+ if (o1 === null || o2 === null) return false;
+ if (o1 !== o1 && o2 !== o2) return true; // NaN === NaN
+ var t1 = typeof o1, t2 = typeof o2, length, key, keySet;
+ if (t1 == t2) {
+ if (t1 == 'object') {
+ if (isArray(o1)) {
+ if (!isArray(o2)) return false;
+ if ((length = o1.length) == o2.length) {
+ for(key=0; key<length; key++) {
+ if (!equals(o1[key], o2[key])) return false;
+ }
+ return true;
+ }
+ } else if (isDate(o1)) {
+ return isDate(o2) && o1.getTime() == o2.getTime();
+ } else if (isRegExp(o1) && isRegExp(o2)) {
+ return o1.toString() == o2.toString();
+ } else {
+ if (isScope(o1) || isScope(o2) || isWindow(o1) || isWindow(o2) || isArray(o2)) return false;
+ keySet = {};
+ for(key in o1) {
+ if (key.charAt(0) === '$' || isFunction(o1[key])) continue;
+ if (!equals(o1[key], o2[key])) return false;
+ keySet[key] = true;
+ }
+ for(key in o2) {
+ if (!keySet[key] &&
+ key.charAt(0) !== '$' &&
+ o2[key] !== undefined &&
+ !isFunction(o2[key])) return false;
+ }
+ return true;
+ }
+ }
+ }
+ return false;
+}
+
+
+function concat(array1, array2, index) {
+ return array1.concat(slice.call(array2, index));
+}
+
+function sliceArgs(args, startIndex) {
+ return slice.call(args, startIndex || 0);
+}
+
+
+/**
+ * @ngdoc function
+ * @name angular.bind
+ * @function
+ *
+ * @description
+ * Returns a function which calls function `fn` bound to `self` (`self` becomes the `this` for
+ * `fn`). You can supply optional `args` that are prebound to the function. This feature is also
+ * known as [function currying](http://en.wikipedia.org/wiki/Currying).
+ *
+ * @param {Object} self Context which `fn` should be evaluated in.
+ * @param {function()} fn Function to be bound.
+ * @param {...*} args Optional arguments to be prebound to the `fn` function call.
+ * @returns {function()} Function that wraps the `fn` with all the specified bindings.
+ */
+function bind(self, fn) {
+ var curryArgs = arguments.length > 2 ? sliceArgs(arguments, 2) : [];
+ if (isFunction(fn) && !(fn instanceof RegExp)) {
+ return curryArgs.length
+ ? function() {
+ return arguments.length
+ ? fn.apply(self, curryArgs.concat(slice.call(arguments, 0)))
+ : fn.apply(self, curryArgs);
+ }
+ : function() {
+ return arguments.length
+ ? fn.apply(self, arguments)
+ : fn.call(self);
+ };
+ } else {
+ // in IE, native methods are not functions so they cannot be bound (note: they don't need to be)
+ return fn;
+ }
+}
+
+
+function toJsonReplacer(key, value) {
+ var val = value;
+
+ if (/^\$+/.test(key)) {
+ val = undefined;
+ } else if (isWindow(value)) {
+ val = '$WINDOW';
+ } else if (value && document === value) {
+ val = '$DOCUMENT';
+ } else if (isScope(value)) {
+ val = '$SCOPE';
+ }
+
+ return val;
+}
+
+
+/**
+ * @ngdoc function
+ * @name angular.toJson
+ * @function
+ *
+ * @description
+ * Serializes input into a JSON-formatted string. Properties with leading $ characters will be
+ * stripped since angular uses this notation internally.
+ *
+ * @param {Object|Array|Date|string|number} obj Input to be serialized into JSON.
+ * @param {boolean=} pretty If set to true, the JSON output will contain newlines and whitespace.
+ * @returns {string|undefined} Jsonified string representing `obj`.
+ */
+function toJson(obj, pretty) {
+ if (typeof obj === 'undefined') return undefined;
+ return JSON.stringify(obj, toJsonReplacer, pretty ? ' ' : null);
+}
+
+
+/**
+ * @ngdoc function
+ * @name angular.fromJson
+ * @function
+ *
+ * @description
+ * Deserializes a JSON string.
+ *
+ * @param {string} json JSON string to deserialize.
+ * @returns {Object|Array|Date|string|number} Deserialized thingy.
+ */
+function fromJson(json) {
+ return isString(json)
+ ? JSON.parse(json)
+ : json;
+}
+
+
+function toBoolean(value) {
+ if (value && value.length !== 0) {
+ var v = lowercase("" + value);
+ value = !(v == 'f' || v == '0' || v == 'false' || v == 'no' || v == 'n' || v == '[]');
+ } else {
+ value = false;
+ }
+ return value;
+}
+
+/**
+ * @returns {string} Returns the string representation of the element.
+ */
+function startingTag(element) {
+ element = jqLite(element).clone();
+ try {
+ // turns out IE does not let you set .html() on elements which
+ // are not allowed to have children. So we just ignore it.
+ element.html('');
+ } catch(e) {}
+ // As Per DOM Standards
+ var TEXT_NODE = 3;
+ var elemHtml = jqLite('<div>').append(element).html();
+ try {
+ return element[0].nodeType === TEXT_NODE ? lowercase(elemHtml) :
+ elemHtml.
+ match(/^(<[^>]+>)/)[1].
+ replace(/^<([\w\-]+)/, function(match, nodeName) { return '<' + lowercase(nodeName); });
+ } catch(e) {
+ return lowercase(elemHtml);
+ }
+
+}
+
+
+/////////////////////////////////////////////////
+
+/**
+ * Tries to decode the URI component without throwing an exception.
+ *
+ * @private
+ * @param str value potential URI component to check.
+ * @returns {boolean} True if `value` can be decoded
+ * with the decodeURIComponent function.
+ */
+function tryDecodeURIComponent(value) {
+ try {
+ return decodeURIComponent(value);
+ } catch(e) {
+ // Ignore any invalid uri component
+ }
+}
+
+
+/**
+ * Parses an escaped url query string into key-value pairs.
+ * @returns Object.<(string|boolean)>
+ */
+function parseKeyValue(/**string*/keyValue) {
+ var obj = {}, key_value, key;
+ forEach((keyValue || "").split('&'), function(keyValue){
+ if ( keyValue ) {
+ key_value = keyValue.split('=');
+ key = tryDecodeURIComponent(key_value[0]);
+ if ( isDefined(key) ) {
+ obj[key] = isDefined(key_value[1]) ? tryDecodeURIComponent(key_value[1]) : true;
+ }
+ }
+ });
+ return obj;
+}
+
+function toKeyValue(obj) {
+ var parts = [];
+ forEach(obj, function(value, key) {
+ parts.push(encodeUriQuery(key, true) + (value === true ? '' : '=' + encodeUriQuery(value, true)));
+ });
+ return parts.length ? parts.join('&') : '';
+}
+
+
+/**
+ * We need our custom method because encodeURIComponent is too agressive and doesn't follow
+ * http://www.ietf.org/rfc/rfc3986.txt with regards to the character set (pchar) allowed in path
+ * segments:
+ * segment = *pchar
+ * pchar = unreserved / pct-encoded / sub-delims / ":" / "@"
+ * pct-encoded = "%" HEXDIG HEXDIG
+ * unreserved = ALPHA / DIGIT / "-" / "." / "_" / "~"
+ * sub-delims = "!" / "$" / "&" / "'" / "(" / ")"
+ * / "*" / "+" / "," / ";" / "="
+ */
+function encodeUriSegment(val) {
+ return encodeUriQuery(val, true).
+ replace(/%26/gi, '&').
+ replace(/%3D/gi, '=').
+ replace(/%2B/gi, '+');
+}
+
+
+/**
+ * This method is intended for encoding *key* or *value* parts of query component. We need a custom
+ * method becuase encodeURIComponent is too agressive and encodes stuff that doesn't have to be
+ * encoded per http://tools.ietf.org/html/rfc3986:
+ * query = *( pchar / "/" / "?" )
+ * pchar = unreserved / pct-encoded / sub-delims / ":" / "@"
+ * unreserved = ALPHA / DIGIT / "-" / "." / "_" / "~"
+ * pct-encoded = "%" HEXDIG HEXDIG
+ * sub-delims = "!" / "$" / "&" / "'" / "(" / ")"
+ * / "*" / "+" / "," / ";" / "="
+ */
+function encodeUriQuery(val, pctEncodeSpaces) {
+ return encodeURIComponent(val).
+ replace(/%40/gi, '@').
+ replace(/%3A/gi, ':').
+ replace(/%24/g, '$').
+ replace(/%2C/gi, ',').
+ replace(/%20/g, (pctEncodeSpaces ? '%20' : '+'));
+}
+
+
+/**
+ * @ngdoc directive
+ * @name ng.directive:ngApp
+ *
+ * @element ANY
+ * @param {angular.Module} ngApp an optional application
+ * {@link angular.module module} name to load.
+ *
+ * @description
+ *
+ * Use this directive to auto-bootstrap an application. Only
+ * one ngApp directive can be used per HTML document. The directive
+ * designates the root of the application and is typically placed
+ * at the root of the page.
+ *
+ * The first ngApp found in the document will be auto-bootstrapped. To use multiple applications in an
+ * HTML document you must manually bootstrap them using {@link angular.bootstrap}.
+ * Applications cannot be nested.
+ *
+ * In the example below if the `ngApp` directive would not be placed
+ * on the `html` element then the document would not be compiled
+ * and the `{{ 1+2 }}` would not be resolved to `3`.
+ *
+ * `ngApp` is the easiest way to bootstrap an application.
+ *
+ <doc:example>
+ <doc:source>
+ I can add: 1 + 2 = {{ 1+2 }}
+ </doc:source>
+ </doc:example>
+ *
+ */
+function angularInit(element, bootstrap) {
+ var elements = [element],
+ appElement,
+ module,
+ names = ['ng:app', 'ng-app', 'x-ng-app', 'data-ng-app'],
+ NG_APP_CLASS_REGEXP = /\sng[:\-]app(:\s*([\w\d_]+);?)?\s/;
+
+ function append(element) {
+ element && elements.push(element);
+ }
+
+ forEach(names, function(name) {
+ names[name] = true;
+ append(document.getElementById(name));
+ name = name.replace(':', '\\:');
+ if (element.querySelectorAll) {
+ forEach(element.querySelectorAll('.' + name), append);
+ forEach(element.querySelectorAll('.' + name + '\\:'), append);
+ forEach(element.querySelectorAll('[' + name + ']'), append);
+ }
+ });
+
+ forEach(elements, function(element) {
+ if (!appElement) {
+ var className = ' ' + element.className + ' ';
+ var match = NG_APP_CLASS_REGEXP.exec(className);
+ if (match) {
+ appElement = element;
+ module = (match[2] || '').replace(/\s+/g, ',');
+ } else {
+ forEach(element.attributes, function(attr) {
+ if (!appElement && names[attr.name]) {
+ appElement = element;
+ module = attr.value;
+ }
+ });
+ }
+ }
+ });
+ if (appElement) {
+ bootstrap(appElement, module ? [module] : []);
+ }
+}
+
+/**
+ * @ngdoc function
+ * @name angular.bootstrap
+ * @description
+ * Use this function to manually start up angular application.
+ *
+ * See: {@link guide/bootstrap Bootstrap}
+ *
+ * Note that ngScenario-based end-to-end tests cannot use this function to bootstrap manually.
+ * They must use {@link api/ng.directive:ngApp ngApp}.
+ *
+ * @param {Element} element DOM element which is the root of angular application.
+ * @param {Array<String|Function>=} modules an array of module declarations. See: {@link angular.module modules}
+ * @returns {AUTO.$injector} Returns the newly created injector for this app.
+ */
+function bootstrap(element, modules) {
+ var doBootstrap = function() {
+ element = jqLite(element);
+ modules = modules || [];
+ modules.unshift(['$provide', function($provide) {
+ $provide.value('$rootElement', element);
+ }]);
+ modules.unshift('ng');
+ var injector = createInjector(modules);
+ injector.invoke(['$rootScope', '$rootElement', '$compile', '$injector',
+ function(scope, element, compile, injector) {
+ scope.$apply(function() {
+ element.data('$injector', injector);
+ compile(element)(scope);
+ });
+ }]
+ );
+ return injector;
+ };
+
+ var NG_DEFER_BOOTSTRAP = /^NG_DEFER_BOOTSTRAP!/;
+
+ if (window && !NG_DEFER_BOOTSTRAP.test(window.name)) {
+ return doBootstrap();
+ }
+
+ window.name = window.name.replace(NG_DEFER_BOOTSTRAP, '');
+ angular.resumeBootstrap = function(extraModules) {
+ forEach(extraModules, function(module) {
+ modules.push(module);
+ });
+ doBootstrap();
+ };
+}
+
+var SNAKE_CASE_REGEXP = /[A-Z]/g;
+function snake_case(name, separator){
+ separator = separator || '_';
+ return name.replace(SNAKE_CASE_REGEXP, function(letter, pos) {
+ return (pos ? separator : '') + letter.toLowerCase();
+ });
+}
+
+function bindJQuery() {
+ // bind to jQuery if present;
+ jQuery = window.jQuery;
+ // reset to jQuery or default to us.
+ if (jQuery) {
+ jqLite = jQuery;
+ extend(jQuery.fn, {
+ scope: JQLitePrototype.scope,
+ controller: JQLitePrototype.controller,
+ injector: JQLitePrototype.injector,
+ inheritedData: JQLitePrototype.inheritedData
+ });
+ // Method signature: JQLitePatchJQueryRemove(name, dispatchThis, filterElems, getterIfNoArguments)
+ JQLitePatchJQueryRemove('remove', true, true, false);
+ JQLitePatchJQueryRemove('empty', false, false, false);
+ JQLitePatchJQueryRemove('html', false, false, true);
+ } else {
+ jqLite = JQLite;
+ }
+ angular.element = jqLite;
+}
+
+/**
+ * throw error if the argument is falsy.
+ */
+function assertArg(arg, name, reason) {
+ if (!arg) {
+ throw new Error("Argument '" + (name || '?') + "' is " + (reason || "required"));
+ }
+ return arg;
+}
+
+function assertArgFn(arg, name, acceptArrayAnnotation) {
+ if (acceptArrayAnnotation && isArray(arg)) {
+ arg = arg[arg.length - 1];
+ }
+
+ assertArg(isFunction(arg), name, 'not a function, got ' +
+ (arg && typeof arg == 'object' ? arg.constructor.name || 'Object' : typeof arg));
+ return arg;
+}
+
+/**
+ * Return the value accessible from the object by path. Any undefined traversals are ignored
+ * @param {Object} obj starting object
+ * @param {string} path path to traverse
+ * @param {boolean=true} bindFnToScope
+ * @returns value as accessible by path
+ */
+//TODO(misko): this function needs to be removed
+function getter(obj, path, bindFnToScope) {
+ if (!path) return obj;
+ var keys = path.split('.');
+ var key;
+ var lastInstance = obj;
+ var len = keys.length;
+
+ for (var i = 0; i < len; i++) {
+ key = keys[i];
+ if (obj) {
+ obj = (lastInstance = obj)[key];
+ }
+ }
+ if (!bindFnToScope && isFunction(obj)) {
+ return bind(lastInstance, obj);
+ }
+ return obj;
+}
+
+/**
+ * @ngdoc interface
+ * @name angular.Module
+ * @description
+ *
+ * Interface for configuring angular {@link angular.module modules}.
+ */
+
+function setupModuleLoader(window) {
+
+ function ensure(obj, name, factory) {
+ return obj[name] || (obj[name] = factory());
+ }
+
+ return ensure(ensure(window, 'angular', Object), 'module', function() {
+ /** @type {Object.<string, angular.Module>} */
+ var modules = {};
+
+ /**
+ * @ngdoc function
+ * @name angular.module
+ * @description
+ *
+ * The `angular.module` is a global place for creating and registering Angular modules. All
+ * modules (angular core or 3rd party) that should be available to an application must be
+ * registered using this mechanism.
+ *
+ *
+ * # Module
+ *
+ * A module is a collection of services, directives, filters, and configuration information.
+ * `angular.module` is used to configure the {@link AUTO.$injector $injector}.
+ *
+ * <pre>
+ * // Create a new module
+ * var myModule = angular.module('myModule', []);
+ *
+ * // register a new service
+ * myModule.value('appName', 'MyCoolApp');
+ *
+ * // configure existing services inside initialization blocks.
+ * myModule.config(function($locationProvider) {
+ * // Configure existing providers
+ * $locationProvider.hashPrefix('!');
+ * });
+ * </pre>
+ *
+ * Then you can create an injector and load your modules like this:
+ *
+ * <pre>
+ * var injector = angular.injector(['ng', 'MyModule'])
+ * </pre>
+ *
+ * However it's more likely that you'll just use
+ * {@link ng.directive:ngApp ngApp} or
+ * {@link angular.bootstrap} to simplify this process for you.
+ *
+ * @param {!string} name The name of the module to create or retrieve.
+ * @param {Array.<string>=} requires If specified then new module is being created. If unspecified then the
+ * the module is being retrieved for further configuration.
+ * @param {Function} configFn Optional configuration function for the module. Same as
+ * {@link angular.Module#config Module#config()}.
+ * @returns {module} new module with the {@link angular.Module} api.
+ */
+ return function module(name, requires, configFn) {
+ if (requires && modules.hasOwnProperty(name)) {
+ modules[name] = null;
+ }
+ return ensure(modules, name, function() {
+ if (!requires) {
+ throw Error('No module: ' + name);
+ }
+
+ /** @type {!Array.<Array.<*>>} */
+ var invokeQueue = [];
+
+ /** @type {!Array.<Function>} */
+ var runBlocks = [];
+
+ var config = invokeLater('$injector', 'invoke');
+
+ /** @type {angular.Module} */
+ var moduleInstance = {
+ // Private state
+ _invokeQueue: invokeQueue,
+ _runBlocks: runBlocks,
+
+ /**
+ * @ngdoc property
+ * @name angular.Module#requires
+ * @propertyOf angular.Module
+ * @returns {Array.<string>} List of module names which must be loaded before this module.
+ * @description
+ * Holds the list of modules which the injector will load before the current module is loaded.
+ */
+ requires: requires,
+
+ /**
+ * @ngdoc property
+ * @name angular.Module#name
+ * @propertyOf angular.Module
+ * @returns {string} Name of the module.
+ * @description
+ */
+ name: name,
+
+
+ /**
+ * @ngdoc method
+ * @name angular.Module#provider
+ * @methodOf angular.Module
+ * @param {string} name service name
+ * @param {Function} providerType Construction function for creating new instance of the service.
+ * @description
+ * See {@link AUTO.$provide#provider $provide.provider()}.
+ */
+ provider: invokeLater('$provide', 'provider'),
+
+ /**
+ * @ngdoc method
+ * @name angular.Module#factory
+ * @methodOf angular.Module
+ * @param {string} name service name
+ * @param {Function} providerFunction Function for creating new instance of the service.
+ * @description
+ * See {@link AUTO.$provide#factory $provide.factory()}.
+ */
+ factory: invokeLater('$provide', 'factory'),
+
+ /**
+ * @ngdoc method
+ * @name angular.Module#service
+ * @methodOf angular.Module
+ * @param {string} name service name
+ * @param {Function} constructor A constructor function that will be instantiated.
+ * @description
+ * See {@link AUTO.$provide#service $provide.service()}.
+ */
+ service: invokeLater('$provide', 'service'),
+
+ /**
+ * @ngdoc method
+ * @name angular.Module#value
+ * @methodOf angular.Module
+ * @param {string} name service name
+ * @param {*} object Service instance object.
+ * @description
+ * See {@link AUTO.$provide#value $provide.value()}.
+ */
+ value: invokeLater('$provide', 'value'),
+
+ /**
+ * @ngdoc method
+ * @name angular.Module#constant
+ * @methodOf angular.Module
+ * @param {string} name constant name
+ * @param {*} object Constant value.
+ * @description
+ * Because the constant are fixed, they get applied before other provide methods.
+ * See {@link AUTO.$provide#constant $provide.constant()}.
+ */
+ constant: invokeLater('$provide', 'constant', 'unshift'),
+
+ /**
+ * @ngdoc method
+ * @name angular.Module#filter
+ * @methodOf angular.Module
+ * @param {string} name Filter name.
+ * @param {Function} filterFactory Factory function for creating new instance of filter.
+ * @description
+ * See {@link ng.$filterProvider#register $filterProvider.register()}.
+ */
+ filter: invokeLater('$filterProvider', 'register'),
+
+ /**
+ * @ngdoc method
+ * @name angular.Module#controller
+ * @methodOf angular.Module
+ * @param {string} name Controller name.
+ * @param {Function} constructor Controller constructor function.
+ * @description
+ * See {@link ng.$controllerProvider#register $controllerProvider.register()}.
+ */
+ controller: invokeLater('$controllerProvider', 'register'),
+
+ /**
+ * @ngdoc method
+ * @name angular.Module#directive
+ * @methodOf angular.Module
+ * @param {string} name directive name
+ * @param {Function} directiveFactory Factory function for creating new instance of
+ * directives.
+ * @description
+ * See {@link ng.$compileProvider#directive $compileProvider.directive()}.
+ */
+ directive: invokeLater('$compileProvider', 'directive'),
+
+ /**
+ * @ngdoc method
+ * @name angular.Module#config
+ * @methodOf angular.Module
+ * @param {Function} configFn Execute this function on module load. Useful for service
+ * configuration.
+ * @description
+ * Use this method to register work which needs to be performed on module loading.
+ */
+ config: config,
+
+ /**
+ * @ngdoc method
+ * @name angular.Module#run
+ * @methodOf angular.Module
+ * @param {Function} initializationFn Execute this function after injector creation.
+ * Useful for application initialization.
+ * @description
+ * Use this method to register work which should be performed when the injector is done
+ * loading all modules.
+ */
+ run: function(block) {
+ runBlocks.push(block);
+ return this;
+ }
+ };
+
+ if (configFn) {
+ config(configFn);
+ }
+
+ return moduleInstance;
+
+ /**
+ * @param {string} provider
+ * @param {string} method
+ * @param {String=} insertMethod
+ * @returns {angular.Module}
+ */
+ function invokeLater(provider, method, insertMethod) {
+ return function() {
+ invokeQueue[insertMethod || 'push']([provider, method, arguments]);
+ return moduleInstance;
+ }
+ }
+ });
+ };
+ });
+
+}
+
+/**
+ * @ngdoc property
+ * @name angular.version
+ * @description
+ * An object that contains information about the current AngularJS version. This object has the
+ * following properties:
+ *
+ * - `full` – `{string}` – Full version string, such as "0.9.18".
+ * - `major` – `{number}` – Major version number, such as "0".
+ * - `minor` – `{number}` – Minor version number, such as "9".
+ * - `dot` – `{number}` – Dot version number, such as "18".
+ * - `codeName` – `{string}` – Code name of the release, such as "jiggling-armfat".
+ */
+var version = {
+ full: '1.0.8', // all of these placeholder strings will be replaced by grunt's
+ major: 1, // package task
+ minor: 0,
+ dot: 8,
+ codeName: 'bubble-burst'
+};
+
+
+function publishExternalAPI(angular){
+ extend(angular, {
+ 'bootstrap': bootstrap,
+ 'copy': copy,
+ 'extend': extend,
+ 'equals': equals,
+ 'element': jqLite,
+ 'forEach': forEach,
+ 'injector': createInjector,
+ 'noop':noop,
+ 'bind':bind,
+ 'toJson': toJson,
+ 'fromJson': fromJson,
+ 'identity':identity,
+ 'isUndefined': isUndefined,
+ 'isDefined': isDefined,
+ 'isString': isString,
+ 'isFunction': isFunction,
+ 'isObject': isObject,
+ 'isNumber': isNumber,
+ 'isElement': isElement,
+ 'isArray': isArray,
+ 'version': version,
+ 'isDate': isDate,
+ 'lowercase': lowercase,
+ 'uppercase': uppercase,
+ 'callbacks': {counter: 0}
+ });
+
+ angularModule = setupModuleLoader(window);
+ try {
+ angularModule('ngLocale');
+ } catch (e) {
+ angularModule('ngLocale', []).provider('$locale', $LocaleProvider);
+ }
+
+ angularModule('ng', ['ngLocale'], ['$provide',
+ function ngModule($provide) {
+ $provide.provider('$compile', $CompileProvider).
+ directive({
+ a: htmlAnchorDirective,
+ input: inputDirective,
+ textarea: inputDirective,
+ form: formDirective,
+ script: scriptDirective,
+ select: selectDirective,
+ style: styleDirective,
+ option: optionDirective,
+ ngBind: ngBindDirective,
+ ngBindHtmlUnsafe: ngBindHtmlUnsafeDirective,
+ ngBindTemplate: ngBindTemplateDirective,
+ ngClass: ngClassDirective,
+ ngClassEven: ngClassEvenDirective,
+ ngClassOdd: ngClassOddDirective,
+ ngCsp: ngCspDirective,
+ ngCloak: ngCloakDirective,
+ ngController: ngControllerDirective,
+ ngForm: ngFormDirective,
+ ngHide: ngHideDirective,
+ ngInclude: ngIncludeDirective,
+ ngInit: ngInitDirective,
+ ngNonBindable: ngNonBindableDirective,
+ ngPluralize: ngPluralizeDirective,
+ ngRepeat: ngRepeatDirective,
+ ngShow: ngShowDirective,
+ ngStyle: ngStyleDirective,
+ ngSwitch: ngSwitchDirective,
+ ngSwitchWhen: ngSwitchWhenDirective,
+ ngSwitchDefault: ngSwitchDefaultDirective,
+ ngOptions: ngOptionsDirective,
+ ngView: ngViewDirective,
+ ngTransclude: ngTranscludeDirective,
+ ngModel: ngModelDirective,
+ ngList: ngListDirective,
+ ngChange: ngChangeDirective,
+ required: requiredDirective,
+ ngRequired: requiredDirective,
+ ngValue: ngValueDirective
+ }).
+ directive(ngAttributeAliasDirectives).
+ directive(ngEventDirectives);
+ $provide.provider({
+ $anchorScroll: $AnchorScrollProvider,
+ $browser: $BrowserProvider,
+ $cacheFactory: $CacheFactoryProvider,
+ $controller: $ControllerProvider,
+ $document: $DocumentProvider,
+ $exceptionHandler: $ExceptionHandlerProvider,
+ $filter: $FilterProvider,
+ $interpolate: $InterpolateProvider,
+ $http: $HttpProvider,
+ $httpBackend: $HttpBackendProvider,
+ $location: $LocationProvider,
+ $log: $LogProvider,
+ $parse: $ParseProvider,
+ $route: $RouteProvider,
+ $routeParams: $RouteParamsProvider,
+ $rootScope: $RootScopeProvider,
+ $q: $QProvider,
+ $sniffer: $SnifferProvider,
+ $templateCache: $TemplateCacheProvider,
+ $timeout: $TimeoutProvider,
+ $window: $WindowProvider
+ });
+ }
+ ]);
+}
+
+//////////////////////////////////
+//JQLite
+//////////////////////////////////
+
+/**
+ * @ngdoc function
+ * @name angular.element
+ * @function
+ *
+ * @description
+ * Wraps a raw DOM element or HTML string as a [jQuery](http://jquery.com) element.
+ * `angular.element` can be either an alias for [jQuery](http://api.jquery.com/jQuery/) function, if
+ * jQuery is available, or a function that wraps the element or string in Angular's jQuery lite
+ * implementation (commonly referred to as jqLite).
+ *
+ * Real jQuery always takes precedence over jqLite, provided it was loaded before `DOMContentLoaded`
+ * event fired.
+ *
+ * jqLite is a tiny, API-compatible subset of jQuery that allows
+ * Angular to manipulate the DOM. jqLite implements only the most commonly needed functionality
+ * within a very small footprint, so only a subset of the jQuery API - methods, arguments and
+ * invocation styles - are supported.
+ *
+ * Note: All element references in Angular are always wrapped with jQuery or jqLite; they are never
+ * raw DOM references.
+ *
+ * ## Angular's jqLite
+ * Angular's lite version of jQuery provides only the following jQuery methods:
+ *
+ * - [addClass()](http://api.jquery.com/addClass/)
+ * - [after()](http://api.jquery.com/after/)
+ * - [append()](http://api.jquery.com/append/)
+ * - [attr()](http://api.jquery.com/attr/)
+ * - [bind()](http://api.jquery.com/bind/) - Does not support namespaces
+ * - [children()](http://api.jquery.com/children/) - Does not support selectors
+ * - [clone()](http://api.jquery.com/clone/)
+ * - [contents()](http://api.jquery.com/contents/)
+ * - [css()](http://api.jquery.com/css/)
+ * - [data()](http://api.jquery.com/data/)
+ * - [eq()](http://api.jquery.com/eq/)
+ * - [find()](http://api.jquery.com/find/) - Limited to lookups by tag name
+ * - [hasClass()](http://api.jquery.com/hasClass/)
+ * - [html()](http://api.jquery.com/html/)
+ * - [next()](http://api.jquery.com/next/) - Does not support selectors
+ * - [parent()](http://api.jquery.com/parent/) - Does not support selectors
+ * - [prepend()](http://api.jquery.com/prepend/)
+ * - [prop()](http://api.jquery.com/prop/)
+ * - [ready()](http://api.jquery.com/ready/)
+ * - [remove()](http://api.jquery.com/remove/)
+ * - [removeAttr()](http://api.jquery.com/removeAttr/)
+ * - [removeClass()](http://api.jquery.com/removeClass/)
+ * - [removeData()](http://api.jquery.com/removeData/)
+ * - [replaceWith()](http://api.jquery.com/replaceWith/)
+ * - [text()](http://api.jquery.com/text/)
+ * - [toggleClass()](http://api.jquery.com/toggleClass/)
+ * - [triggerHandler()](http://api.jquery.com/triggerHandler/) - Doesn't pass native event objects to handlers.
+ * - [unbind()](http://api.jquery.com/unbind/) - Does not support namespaces
+ * - [val()](http://api.jquery.com/val/)
+ * - [wrap()](http://api.jquery.com/wrap/)
+ *
+ * ## jQuery/jqLite Extras
+ * Angular also provides the following additional methods and events to both jQuery and jqLite:
+ *
+ * ### Events
+ * - `$destroy` - AngularJS intercepts all jqLite/jQuery's DOM destruction apis and fires this event
+ * on all DOM nodes being removed. This can be used to clean up and 3rd party bindings to the DOM
+ * element before it is removed.
+ * ### Methods
+ * - `controller(name)` - retrieves the controller of the current element or its parent. By default
+ * retrieves controller associated with the `ngController` directive. If `name` is provided as
+ * camelCase directive name, then the controller for this directive will be retrieved (e.g.
+ * `'ngModel'`).
+ * - `injector()` - retrieves the injector of the current element or its parent.
+ * - `scope()` - retrieves the {@link api/ng.$rootScope.Scope scope} of the current
+ * element or its parent.
+ * - `inheritedData()` - same as `data()`, but walks up the DOM until a value is found or the top
+ * parent element is reached.
+ *
+ * @param {string|DOMElement} element HTML string or DOMElement to be wrapped into jQuery.
+ * @returns {Object} jQuery object.
+ */
+
+var jqCache = JQLite.cache = {},
+ jqName = JQLite.expando = 'ng-' + new Date().getTime(),
+ jqId = 1,
+ addEventListenerFn = (window.document.addEventListener
+ ? function(element, type, fn) {element.addEventListener(type, fn, false);}
+ : function(element, type, fn) {element.attachEvent('on' + type, fn);}),
+ removeEventListenerFn = (window.document.removeEventListener
+ ? function(element, type, fn) {element.removeEventListener(type, fn, false); }
+ : function(element, type, fn) {element.detachEvent('on' + type, fn); });
+
+function jqNextId() { return ++jqId; }
+
+
+var SPECIAL_CHARS_REGEXP = /([\:\-\_]+(.))/g;
+var MOZ_HACK_REGEXP = /^moz([A-Z])/;
+
+/**
+ * Converts snake_case to camelCase.
+ * Also there is special case for Moz prefix starting with upper case letter.
+ * @param name Name to normalize
+ */
+function camelCase(name) {
+ return name.
+ replace(SPECIAL_CHARS_REGEXP, function(_, separator, letter, offset) {
+ return offset ? letter.toUpperCase() : letter;
+ }).
+ replace(MOZ_HACK_REGEXP, 'Moz$1');
+}
+
+/////////////////////////////////////////////
+// jQuery mutation patch
+//
+// In conjunction with bindJQuery intercepts all jQuery's DOM destruction apis and fires a
+// $destroy event on all DOM nodes being removed.
+//
+/////////////////////////////////////////////
+
+function JQLitePatchJQueryRemove(name, dispatchThis, filterElems, getterIfNoArguments) {
+ var originalJqFn = jQuery.fn[name];
+ originalJqFn = originalJqFn.$original || originalJqFn;
+ removePatch.$original = originalJqFn;
+ jQuery.fn[name] = removePatch;
+
+ function removePatch(param) {
+ var list = filterElems && param ? [this.filter(param)] : [this],
+ fireEvent = dispatchThis,
+ set, setIndex, setLength,
+ element, childIndex, childLength, children;
+
+ if (!getterIfNoArguments || param != null) {
+ while(list.length) {
+ set = list.shift();
+ for(setIndex = 0, setLength = set.length; setIndex < setLength; setIndex++) {
+ element = jqLite(set[setIndex]);
+ if (fireEvent) {
+ element.triggerHandler('$destroy');
+ } else {
+ fireEvent = !fireEvent;
+ }
+ for(childIndex = 0, childLength = (children = element.children()).length;
+ childIndex < childLength;
+ childIndex++) {
+ list.push(jQuery(children[childIndex]));
+ }
+ }
+ }
+ }
+ return originalJqFn.apply(this, arguments);
+ }
+}
+
+/////////////////////////////////////////////
+function JQLite(element) {
+ if (element instanceof JQLite) {
+ return element;
+ }
+ if (!(this instanceof JQLite)) {
+ if (isString(element) && element.charAt(0) != '<') {
+ throw Error('selectors not implemented');
+ }
+ return new JQLite(element);
+ }
+
+ if (isString(element)) {
+ var div = document.createElement('div');
+ // Read about the NoScope elements here:
+ // http://msdn.microsoft.com/en-us/library/ms533897(VS.85).aspx
+ div.innerHTML = '<div> </div>' + element; // IE insanity to make NoScope elements work!
+ div.removeChild(div.firstChild); // remove the superfluous div
+ JQLiteAddNodes(this, div.childNodes);
+ this.remove(); // detach the elements from the temporary DOM div.
+ } else {
+ JQLiteAddNodes(this, element);
+ }
+}
+
+function JQLiteClone(element) {
+ return element.cloneNode(true);
+}
+
+function JQLiteDealoc(element){
+ JQLiteRemoveData(element);
+ for ( var i = 0, children = element.childNodes || []; i < children.length; i++) {
+ JQLiteDealoc(children[i]);
+ }
+}
+
+function JQLiteUnbind(element, type, fn) {
+ var events = JQLiteExpandoStore(element, 'events'),
+ handle = JQLiteExpandoStore(element, 'handle');
+
+ if (!handle) return; //no listeners registered
+
+ if (isUndefined(type)) {
+ forEach(events, function(eventHandler, type) {
+ removeEventListenerFn(element, type, eventHandler);
+ delete events[type];
+ });
+ } else {
+ if (isUndefined(fn)) {
+ removeEventListenerFn(element, type, events[type]);
+ delete events[type];
+ } else {
+ arrayRemove(events[type] || [], fn);
+ }
+ }
+}
+
+function JQLiteRemoveData(element) {
+ var expandoId = element[jqName],
+ expandoStore = jqCache[expandoId];
+
+ if (expandoStore) {
+ if (expandoStore.handle) {
+ expandoStore.events.$destroy && expandoStore.handle({}, '$destroy');
+ JQLiteUnbind(element);
+ }
+ delete jqCache[expandoId];
+ element[jqName] = undefined; // ie does not allow deletion of attributes on elements.
+ }
+}
+
+function JQLiteExpandoStore(element, key, value) {
+ var expandoId = element[jqName],
+ expandoStore = jqCache[expandoId || -1];
+
+ if (isDefined(value)) {
+ if (!expandoStore) {
+ element[jqName] = expandoId = jqNextId();
+ expandoStore = jqCache[expandoId] = {};
+ }
+ expandoStore[key] = value;
+ } else {
+ return expandoStore && expandoStore[key];
+ }
+}
+
+function JQLiteData(element, key, value) {
+ var data = JQLiteExpandoStore(element, 'data'),
+ isSetter = isDefined(value),
+ keyDefined = !isSetter && isDefined(key),
+ isSimpleGetter = keyDefined && !isObject(key);
+
+ if (!data && !isSimpleGetter) {
+ JQLiteExpandoStore(element, 'data', data = {});
+ }
+
+ if (isSetter) {
+ data[key] = value;
+ } else {
+ if (keyDefined) {
+ if (isSimpleGetter) {
+ // don't create data in this case.
+ return data && data[key];
+ } else {
+ extend(data, key);
+ }
+ } else {
+ return data;
+ }
+ }
+}
+
+function JQLiteHasClass(element, selector) {
+ return ((" " + element.className + " ").replace(/[\n\t]/g, " ").
+ indexOf( " " + selector + " " ) > -1);
+}
+
+function JQLiteRemoveClass(element, cssClasses) {
+ if (cssClasses) {
+ forEach(cssClasses.split(' '), function(cssClass) {
+ element.className = trim(
+ (" " + element.className + " ")
+ .replace(/[\n\t]/g, " ")
+ .replace(" " + trim(cssClass) + " ", " ")
+ );
+ });
+ }
+}
+
+function JQLiteAddClass(element, cssClasses) {
+ if (cssClasses) {
+ forEach(cssClasses.split(' '), function(cssClass) {
+ if (!JQLiteHasClass(element, cssClass)) {
+ element.className = trim(element.className + ' ' + trim(cssClass));
+ }
+ });
+ }
+}
+
+function JQLiteAddNodes(root, elements) {
+ if (elements) {
+ elements = (!elements.nodeName && isDefined(elements.length) && !isWindow(elements))
+ ? elements
+ : [ elements ];
+ for(var i=0; i < elements.length; i++) {
+ root.push(elements[i]);
+ }
+ }
+}
+
+function JQLiteController(element, name) {
+ return JQLiteInheritedData(element, '$' + (name || 'ngController' ) + 'Controller');
+}
+
+function JQLiteInheritedData(element, name, value) {
+ element = jqLite(element);
+
+ // if element is the document object work with the html element instead
+ // this makes $(document).scope() possible
+ if(element[0].nodeType == 9) {
+ element = element.find('html');
+ }
+
+ while (element.length) {
+ if (value = element.data(name)) return value;
+ element = element.parent();
+ }
+}
+
+//////////////////////////////////////////
+// Functions which are declared directly.
+//////////////////////////////////////////
+var JQLitePrototype = JQLite.prototype = {
+ ready: function(fn) {
+ var fired = false;
+
+ function trigger() {
+ if (fired) return;
+ fired = true;
+ fn();
+ }
+
+ this.bind('DOMContentLoaded', trigger); // works for modern browsers and IE9
+ // we can not use jqLite since we are not done loading and jQuery could be loaded later.
+ JQLite(window).bind('load', trigger); // fallback to window.onload for others
+ },
+ toString: function() {
+ var value = [];
+ forEach(this, function(e){ value.push('' + e);});
+ return '[' + value.join(', ') + ']';
+ },
+
+ eq: function(index) {
+ return (index >= 0) ? jqLite(this[index]) : jqLite(this[this.length + index]);
+ },
+
+ length: 0,
+ push: push,
+ sort: [].sort,
+ splice: [].splice
+};
+
+//////////////////////////////////////////
+// Functions iterating getter/setters.
+// these functions return self on setter and
+// value on get.
+//////////////////////////////////////////
+var BOOLEAN_ATTR = {};
+forEach('multiple,selected,checked,disabled,readOnly,required'.split(','), function(value) {
+ BOOLEAN_ATTR[lowercase(value)] = value;
+});
+var BOOLEAN_ELEMENTS = {};
+forEach('input,select,option,textarea,button,form'.split(','), function(value) {
+ BOOLEAN_ELEMENTS[uppercase(value)] = true;
+});
+
+function getBooleanAttrName(element, name) {
+ // check dom last since we will most likely fail on name
+ var booleanAttr = BOOLEAN_ATTR[name.toLowerCase()];
+
+ // booleanAttr is here twice to minimize DOM access
+ return booleanAttr && BOOLEAN_ELEMENTS[element.nodeName] && booleanAttr;
+}
+
+forEach({
+ data: JQLiteData,
+ inheritedData: JQLiteInheritedData,
+
+ scope: function(element) {
+ return JQLiteInheritedData(element, '$scope');
+ },
+
+ controller: JQLiteController ,
+
+ injector: function(element) {
+ return JQLiteInheritedData(element, '$injector');
+ },
+
+ removeAttr: function(element,name) {
+ element.removeAttribute(name);
+ },
+
+ hasClass: JQLiteHasClass,
+
+ css: function(element, name, value) {
+ name = camelCase(name);
+
+ if (isDefined(value)) {
+ element.style[name] = value;
+ } else {
+ var val;
+
+ if (msie <= 8) {
+ // this is some IE specific weirdness that jQuery 1.6.4 does not sure why
+ val = element.currentStyle && element.currentStyle[name];
+ if (val === '') val = 'auto';
+ }
+
+ val = val || element.style[name];
+
+ if (msie <= 8) {
+ // jquery weirdness :-/
+ val = (val === '') ? undefined : val;
+ }
+
+ return val;
+ }
+ },
+
+ attr: function(element, name, value){
+ var lowercasedName = lowercase(name);
+ if (BOOLEAN_ATTR[lowercasedName]) {
+ if (isDefined(value)) {
+ if (!!value) {
+ element[name] = true;
+ element.setAttribute(name, lowercasedName);
+ } else {
+ element[name] = false;
+ element.removeAttribute(lowercasedName);
+ }
+ } else {
+ return (element[name] ||
+ (element.attributes.getNamedItem(name)|| noop).specified)
+ ? lowercasedName
+ : undefined;
+ }
+ } else if (isDefined(value)) {
+ element.setAttribute(name, value);
+ } else if (element.getAttribute) {
+ // the extra argument "2" is to get the right thing for a.href in IE, see jQuery code
+ // some elements (e.g. Document) don't have get attribute, so return undefined
+ var ret = element.getAttribute(name, 2);
+ // normalize non-existing attributes to undefined (as jQuery)
+ return ret === null ? undefined : ret;
+ }
+ },
+
+ prop: function(element, name, value) {
+ if (isDefined(value)) {
+ element[name] = value;
+ } else {
+ return element[name];
+ }
+ },
+
+ text: extend((msie < 9)
+ ? function(element, value) {
+ if (element.nodeType == 1 /** Element */) {
+ if (isUndefined(value))
+ return element.innerText;
+ element.innerText = value;
+ } else {
+ if (isUndefined(value))
+ return element.nodeValue;
+ element.nodeValue = value;
+ }
+ }
+ : function(element, value) {
+ if (isUndefined(value)) {
+ return element.textContent;
+ }
+ element.textContent = value;
+ }, {$dv:''}),
+
+ val: function(element, value) {
+ if (isUndefined(value)) {
+ if (nodeName_(element) === 'SELECT' && element.multiple) {
+ var result = [];
+ forEach(element.options, function (option) {
+ if (option.selected) {
+ result.push(option.value || option.text);
+ }
+ });
+ return result.length === 0 ? null : result;
+ }
+ return element.value;
+ }
+ element.value = value;
+ },
+
+ html: function(element, value) {
+ if (isUndefined(value)) {
+ return element.innerHTML;
+ }
+ for (var i = 0, childNodes = element.childNodes; i < childNodes.length; i++) {
+ JQLiteDealoc(childNodes[i]);
+ }
+ element.innerHTML = value;
+ }
+}, function(fn, name){
+ /**
+ * Properties: writes return selection, reads return first value
+ */
+ JQLite.prototype[name] = function(arg1, arg2) {
+ var i, key;
+
+ // JQLiteHasClass has only two arguments, but is a getter-only fn, so we need to special-case it
+ // in a way that survives minification.
+ if (((fn.length == 2 && (fn !== JQLiteHasClass && fn !== JQLiteController)) ? arg1 : arg2) === undefined) {
+ if (isObject(arg1)) {
+
+ // we are a write, but the object properties are the key/values
+ for(i=0; i < this.length; i++) {
+ if (fn === JQLiteData) {
+ // data() takes the whole object in jQuery
+ fn(this[i], arg1);
+ } else {
+ for (key in arg1) {
+ fn(this[i], key, arg1[key]);
+ }
+ }
+ }
+ // return self for chaining
+ return this;
+ } else {
+ // we are a read, so read the first child.
+ if (this.length)
+ return fn(this[0], arg1, arg2);
+ }
+ } else {
+ // we are a write, so apply to all children
+ for(i=0; i < this.length; i++) {
+ fn(this[i], arg1, arg2);
+ }
+ // return self for chaining
+ return this;
+ }
+ return fn.$dv;
+ };
+});
+
+function createEventHandler(element, events) {
+ var eventHandler = function (event, type) {
+ if (!event.preventDefault) {
+ event.preventDefault = function() {
+ event.returnValue = false; //ie
+ };
+ }
+
+ if (!event.stopPropagation) {
+ event.stopPropagation = function() {
+ event.cancelBubble = true; //ie
+ };
+ }
+
+ if (!event.target) {
+ event.target = event.srcElement || document;
+ }
+
+ if (isUndefined(event.defaultPrevented)) {
+ var prevent = event.preventDefault;
+ event.preventDefault = function() {
+ event.defaultPrevented = true;
+ prevent.call(event);
+ };
+ event.defaultPrevented = false;
+ }
+
+ event.isDefaultPrevented = function() {
+ return event.defaultPrevented;
+ };
+
+ forEach(events[type || event.type], function(fn) {
+ fn.call(element, event);
+ });
+
+ // Remove monkey-patched methods (IE),
+ // as they would cause memory leaks in IE8.
+ if (msie <= 8) {
+ // IE7/8 does not allow to delete property on native object
+ event.preventDefault = null;
+ event.stopPropagation = null;
+ event.isDefaultPrevented = null;
+ } else {
+ // It shouldn't affect normal browsers (native methods are defined on prototype).
+ delete event.preventDefault;
+ delete event.stopPropagation;
+ delete event.isDefaultPrevented;
+ }
+ };
+ eventHandler.elem = element;
+ return eventHandler;
+}
+
+//////////////////////////////////////////
+// Functions iterating traversal.
+// These functions chain results into a single
+// selector.
+//////////////////////////////////////////
+forEach({
+ removeData: JQLiteRemoveData,
+
+ dealoc: JQLiteDealoc,
+
+ bind: function bindFn(element, type, fn){
+ var events = JQLiteExpandoStore(element, 'events'),
+ handle = JQLiteExpandoStore(element, 'handle');
+
+ if (!events) JQLiteExpandoStore(element, 'events', events = {});
+ if (!handle) JQLiteExpandoStore(element, 'handle', handle = createEventHandler(element, events));
+
+ forEach(type.split(' '), function(type){
+ var eventFns = events[type];
+
+ if (!eventFns) {
+ if (type == 'mouseenter' || type == 'mouseleave') {
+ var contains = document.body.contains || document.body.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;
+ };
+
+ events[type] = [];
+
+ // Refer to jQuery's implementation of mouseenter & mouseleave
+ // Read about mouseenter and mouseleave:
+ // http://www.quirksmode.org/js/events_mouse.html#link8
+ var eventmap = { mouseleave : "mouseout", mouseenter : "mouseover"}
+ bindFn(element, eventmap[type], function(event) {
+ var ret, target = this, related = event.relatedTarget;
+ // 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 && !contains(target, related)) ){
+ handle(event, type);
+ }
+
+ });
+
+ } else {
+ addEventListenerFn(element, type, handle);
+ events[type] = [];
+ }
+ eventFns = events[type]
+ }
+ eventFns.push(fn);
+ });
+ },
+
+ unbind: JQLiteUnbind,
+
+ replaceWith: function(element, replaceNode) {
+ var index, parent = element.parentNode;
+ JQLiteDealoc(element);
+ forEach(new JQLite(replaceNode), function(node){
+ if (index) {
+ parent.insertBefore(node, index.nextSibling);
+ } else {
+ parent.replaceChild(node, element);
+ }
+ index = node;
+ });
+ },
+
+ children: function(element) {
+ var children = [];
+ forEach(element.childNodes, function(element){
+ if (element.nodeType === 1)
+ children.push(element);
+ });
+ return children;
+ },
+
+ contents: function(element) {
+ return element.childNodes || [];
+ },
+
+ append: function(element, node) {
+ forEach(new JQLite(node), function(child){
+ if (element.nodeType === 1)
+ element.appendChild(child);
+ });
+ },
+
+ prepend: function(element, node) {
+ if (element.nodeType === 1) {
+ var index = element.firstChild;
+ forEach(new JQLite(node), function(child){
+ element.insertBefore(child, index);
+ });
+ }
+ },
+
+ wrap: function(element, wrapNode) {
+ wrapNode = jqLite(wrapNode)[0];
+ var parent = element.parentNode;
+ if (parent) {
+ parent.replaceChild(wrapNode, element);
+ }
+ wrapNode.appendChild(element);
+ },
+
+ remove: function(element) {
+ JQLiteDealoc(element);
+ var parent = element.parentNode;
+ if (parent) parent.removeChild(element);
+ },
+
+ after: function(element, newElement) {
+ var index = element, parent = element.parentNode;
+ forEach(new JQLite(newElement), function(node){
+ parent.insertBefore(node, index.nextSibling);
+ index = node;
+ });
+ },
+
+ addClass: JQLiteAddClass,
+ removeClass: JQLiteRemoveClass,
+
+ toggleClass: function(element, selector, condition) {
+ if (isUndefined(condition)) {
+ condition = !JQLiteHasClass(element, selector);
+ }
+ (condition ? JQLiteAddClass : JQLiteRemoveClass)(element, selector);
+ },
+
+ parent: function(element) {
+ var parent = element.parentNode;
+ return parent && parent.nodeType !== 11 ? parent : null;
+ },
+
+ next: function(element) {
+ if (element.nextElementSibling) {
+ return element.nextElementSibling;
+ }
+
+ // IE8 doesn't have nextElementSibling
+ var elm = element.nextSibling;
+ while (elm != null && elm.nodeType !== 1) {
+ elm = elm.nextSibling;
+ }
+ return elm;
+ },
+
+ find: function(element, selector) {
+ return element.getElementsByTagName(selector);
+ },
+
+ clone: JQLiteClone,
+
+ triggerHandler: function(element, eventName) {
+ var eventFns = (JQLiteExpandoStore(element, 'events') || {})[eventName];
+
+ forEach(eventFns, function(fn) {
+ fn.call(element, null);
+ });
+ }
+}, function(fn, name){
+ /**
+ * chaining functions
+ */
+ JQLite.prototype[name] = function(arg1, arg2) {
+ var value;
+ for(var i=0; i < this.length; i++) {
+ if (value == undefined) {
+ value = fn(this[i], arg1, arg2);
+ if (value !== undefined) {
+ // any function which returns a value needs to be wrapped
+ value = jqLite(value);
+ }
+ } else {
+ JQLiteAddNodes(value, fn(this[i], arg1, arg2));
+ }
+ }
+ return value == undefined ? this : value;
+ };
+});
+
+/**
+ * Computes a hash of an 'obj'.
+ * Hash of a:
+ * string is string
+ * number is number as string
+ * object is either result of calling $$hashKey function on the object or uniquely generated id,
+ * that is also assigned to the $$hashKey property of the object.
+ *
+ * @param obj
+ * @returns {string} hash string such that the same input will have the same hash string.
+ * The resulting string key is in 'type:hashKey' format.
+ */
+function hashKey(obj) {
+ var objType = typeof obj,
+ key;
+
+ if (objType == 'object' && obj !== null) {
+ if (typeof (key = obj.$$hashKey) == 'function') {
+ // must invoke on object to keep the right this
+ key = obj.$$hashKey();
+ } else if (key === undefined) {
+ key = obj.$$hashKey = nextUid();
+ }
+ } else {
+ key = obj;
+ }
+
+ return objType + ':' + key;
+}
+
+/**
+ * HashMap which can use objects as keys
+ */
+function HashMap(array){
+ forEach(array, this.put, this);
+}
+HashMap.prototype = {
+ /**
+ * Store key value pair
+ * @param key key to store can be any type
+ * @param value value to store can be any type
+ */
+ put: function(key, value) {
+ this[hashKey(key)] = value;
+ },
+
+ /**
+ * @param key
+ * @returns the value for the key
+ */
+ get: function(key) {
+ return this[hashKey(key)];
+ },
+
+ /**
+ * Remove the key/value pair
+ * @param key
+ */
+ remove: function(key) {
+ var value = this[key = hashKey(key)];
+ delete this[key];
+ return value;
+ }
+};
+
+/**
+ * A map where multiple values can be added to the same key such that they form a queue.
+ * @returns {HashQueueMap}
+ */
+function HashQueueMap() {}
+HashQueueMap.prototype = {
+ /**
+ * Same as array push, but using an array as the value for the hash
+ */
+ push: function(key, value) {
+ var array = this[key = hashKey(key)];
+ if (!array) {
+ this[key] = [value];
+ } else {
+ array.push(value);
+ }
+ },
+
+ /**
+ * Same as array shift, but using an array as the value for the hash
+ */
+ shift: function(key) {
+ var array = this[key = hashKey(key)];
+ if (array) {
+ if (array.length == 1) {
+ delete this[key];
+ return array[0];
+ } else {
+ return array.shift();
+ }
+ }
+ },
+
+ /**
+ * return the first item without deleting it
+ */
+ peek: function(key) {
+ var array = this[hashKey(key)];
+ if (array) {
+ return array[0];
+ }
+ }
+};
+
+/**
+ * @ngdoc function
+ * @name angular.injector
+ * @function
+ *
+ * @description
+ * Creates an injector function that can be used for retrieving services as well as for
+ * dependency injection (see {@link guide/di dependency injection}).
+ *
+
+ * @param {Array.<string|Function>} modules A list of module functions or their aliases. See
+ * {@link angular.module}. The `ng` module must be explicitly added.
+ * @returns {function()} Injector function. See {@link AUTO.$injector $injector}.
+ *
+ * @example
+ * Typical usage
+ * <pre>
+ * // create an injector
+ * var $injector = angular.injector(['ng']);
+ *
+ * // use the injector to kick off your application
+ * // use the type inference to auto inject arguments, or use implicit injection
+ * $injector.invoke(function($rootScope, $compile, $document){
+ * $compile($document)($rootScope);
+ * $rootScope.$digest();
+ * });
+ * </pre>
+ */
+
+
+/**
+ * @ngdoc overview
+ * @name AUTO
+ * @description
+ *
+ * Implicit module which gets automatically added to each {@link AUTO.$injector $injector}.
+ */
+
+var FN_ARGS = /^function\s*[^\(]*\(\s*([^\)]*)\)/m;
+var FN_ARG_SPLIT = /,/;
+var FN_ARG = /^\s*(_?)(\S+?)\1\s*$/;
+var STRIP_COMMENTS = /((\/\/.*$)|(\/\*[\s\S]*?\*\/))/mg;
+function annotate(fn) {
+ var $inject,
+ fnText,
+ argDecl,
+ last;
+
+ if (typeof fn == 'function') {
+ if (!($inject = fn.$inject)) {
+ $inject = [];
+ fnText = fn.toString().replace(STRIP_COMMENTS, '');
+ argDecl = fnText.match(FN_ARGS);
+ forEach(argDecl[1].split(FN_ARG_SPLIT), function(arg){
+ arg.replace(FN_ARG, function(all, underscore, name){
+ $inject.push(name);
+ });
+ });
+ fn.$inject = $inject;
+ }
+ } else if (isArray(fn)) {
+ last = fn.length - 1;
+ assertArgFn(fn[last], 'fn');
+ $inject = fn.slice(0, last);
+ } else {
+ assertArgFn(fn, 'fn', true);
+ }
+ return $inject;
+}
+
+///////////////////////////////////////
+
+/**
+ * @ngdoc object
+ * @name AUTO.$injector
+ * @function
+ *
+ * @description
+ *
+ * `$injector` is used to retrieve object instances as defined by
+ * {@link AUTO.$provide provider}, instantiate types, invoke methods,
+ * and load modules.
+ *
+ * The following always holds true:
+ *
+ * <pre>
+ * var $injector = angular.injector();
+ * expect($injector.get('$injector')).toBe($injector);
+ * expect($injector.invoke(function($injector){
+ * return $injector;
+ * }).toBe($injector);
+ * </pre>
+ *
+ * # Injection Function Annotation
+ *
+ * JavaScript does not have annotations, and annotations are needed for dependency injection. The
+ * following are all valid ways of annotating function with injection arguments and are equivalent.
+ *
+ * <pre>
+ * // inferred (only works if code not minified/obfuscated)
+ * $injector.invoke(function(serviceA){});
+ *
+ * // annotated
+ * function explicit(serviceA) {};
+ * explicit.$inject = ['serviceA'];
+ * $injector.invoke(explicit);
+ *
+ * // inline
+ * $injector.invoke(['serviceA', function(serviceA){}]);
+ * </pre>
+ *
+ * ## Inference
+ *
+ * In JavaScript calling `toString()` on a function returns the function definition. The definition can then be
+ * parsed and the function arguments can be extracted. *NOTE:* This does not work with minification, and obfuscation
+ * tools since these tools change the argument names.
+ *
+ * ## `$inject` Annotation
+ * By adding a `$inject` property onto a function the injection parameters can be specified.
+ *
+ * ## Inline
+ * As an array of injection names, where the last item in the array is the function to call.
+ */
+
+/**
+ * @ngdoc method
+ * @name AUTO.$injector#get
+ * @methodOf AUTO.$injector
+ *
+ * @description
+ * Return an instance of the service.
+ *
+ * @param {string} name The name of the instance to retrieve.
+ * @return {*} The instance.
+ */
+
+/**
+ * @ngdoc method
+ * @name AUTO.$injector#invoke
+ * @methodOf AUTO.$injector
+ *
+ * @description
+ * Invoke the method and supply the method arguments from the `$injector`.
+ *
+ * @param {!function} fn The function to invoke. The function arguments come form the function annotation.
+ * @param {Object=} self The `this` for the invoked method.
+ * @param {Object=} locals Optional object. If preset then any argument names are read from this object first, before
+ * the `$injector` is consulted.
+ * @returns {*} the value returned by the invoked `fn` function.
+ */
+
+/**
+ * @ngdoc method
+ * @name AUTO.$injector#instantiate
+ * @methodOf AUTO.$injector
+ * @description
+ * Create a new instance of JS type. The method takes a constructor function invokes the new operator and supplies
+ * all of the arguments to the constructor function as specified by the constructor annotation.
+ *
+ * @param {function} Type Annotated constructor function.
+ * @param {Object=} locals Optional object. If preset then any argument names are read from this object first, before
+ * the `$injector` is consulted.
+ * @returns {Object} new instance of `Type`.
+ */
+
+/**
+ * @ngdoc method
+ * @name AUTO.$injector#annotate
+ * @methodOf AUTO.$injector
+ *
+ * @description
+ * Returns an array of service names which the function is requesting for injection. This API is used by the injector
+ * to determine which services need to be injected into the function when the function is invoked. There are three
+ * ways in which the function can be annotated with the needed dependencies.
+ *
+ * # Argument names
+ *
+ * The simplest form is to extract the dependencies from the arguments of the function. This is done by converting
+ * the function into a string using `toString()` method and extracting the argument names.
+ * <pre>
+ * // Given
+ * function MyController($scope, $route) {
+ * // ...
+ * }
+ *
+ * // Then
+ * expect(injector.annotate(MyController)).toEqual(['$scope', '$route']);
+ * </pre>
+ *
+ * This method does not work with code minfication / obfuscation. For this reason the following annotation strategies
+ * are supported.
+ *
+ * # The `$inject` property
+ *
+ * If a function has an `$inject` property and its value is an array of strings, then the strings represent names of
+ * services to be injected into the function.
+ * <pre>
+ * // Given
+ * var MyController = function(obfuscatedScope, obfuscatedRoute) {
+ * // ...
+ * }
+ * // Define function dependencies
+ * MyController.$inject = ['$scope', '$route'];
+ *
+ * // Then
+ * expect(injector.annotate(MyController)).toEqual(['$scope', '$route']);
+ * </pre>
+ *
+ * # The array notation
+ *
+ * It is often desirable to inline Injected functions and that's when setting the `$inject` property is very
+ * inconvenient. In these situations using the array notation to specify the dependencies in a way that survives
+ * minification is a better choice:
+ *
+ * <pre>
+ * // We wish to write this (not minification / obfuscation safe)
+ * injector.invoke(function($compile, $rootScope) {
+ * // ...
+ * });
+ *
+ * // We are forced to write break inlining
+ * var tmpFn = function(obfuscatedCompile, obfuscatedRootScope) {
+ * // ...
+ * };
+ * tmpFn.$inject = ['$compile', '$rootScope'];
+ * injector.invoke(tmpFn);
+ *
+ * // To better support inline function the inline annotation is supported
+ * injector.invoke(['$compile', '$rootScope', function(obfCompile, obfRootScope) {
+ * // ...
+ * }]);
+ *
+ * // Therefore
+ * expect(injector.annotate(
+ * ['$compile', '$rootScope', function(obfus_$compile, obfus_$rootScope) {}])
+ * ).toEqual(['$compile', '$rootScope']);
+ * </pre>
+ *
+ * @param {function|Array.<string|Function>} fn Function for which dependent service names need to be retrieved as described
+ * above.
+ *
+ * @returns {Array.<string>} The names of the services which the function requires.
+ */
+
+
+
+
+/**
+ * @ngdoc object
+ * @name AUTO.$provide
+ *
+ * @description
+ *
+ * Use `$provide` to register new providers with the `$injector`. The providers are the factories for the instance.
+ * The providers share the same name as the instance they create with `Provider` suffixed to them.
+ *
+ * A provider is an object with a `$get()` method. The injector calls the `$get` method to create a new instance of
+ * a service. The Provider can have additional methods which would allow for configuration of the provider.
+ *
+ * <pre>
+ * function GreetProvider() {
+ * var salutation = 'Hello';
+ *
+ * this.salutation = function(text) {
+ * salutation = text;
+ * };
+ *
+ * this.$get = function() {
+ * return function (name) {
+ * return salutation + ' ' + name + '!';
+ * };
+ * };
+ * }
+ *
+ * describe('Greeter', function(){
+ *
+ * beforeEach(module(function($provide) {
+ * $provide.provider('greet', GreetProvider);
+ * }));
+ *
+ * it('should greet', inject(function(greet) {
+ * expect(greet('angular')).toEqual('Hello angular!');
+ * }));
+ *
+ * it('should allow configuration of salutation', function() {
+ * module(function(greetProvider) {
+ * greetProvider.salutation('Ahoj');
+ * });
+ * inject(function(greet) {
+ * expect(greet('angular')).toEqual('Ahoj angular!');
+ * });
+ * });
+ * </pre>
+ */
+
+/**
+ * @ngdoc method
+ * @name AUTO.$provide#provider
+ * @methodOf AUTO.$provide
+ * @description
+ *
+ * Register a provider for a service. The providers can be retrieved and can have additional configuration methods.
+ *
+ * @param {string} name The name of the instance. NOTE: the provider will be available under `name + 'Provider'` key.
+ * @param {(Object|function())} provider If the provider is:
+ *
+ * - `Object`: then it should have a `$get` method. The `$get` method will be invoked using
+ * {@link AUTO.$injector#invoke $injector.invoke()} when an instance needs to be created.
+ * - `Constructor`: a new instance of the provider will be created using
+ * {@link AUTO.$injector#instantiate $injector.instantiate()}, then treated as `object`.
+ *
+ * @returns {Object} registered provider instance
+ */
+
+/**
+ * @ngdoc method
+ * @name AUTO.$provide#factory
+ * @methodOf AUTO.$provide
+ * @description
+ *
+ * A short hand for configuring services if only `$get` method is required.
+ *
+ * @param {string} name The name of the instance.
+ * @param {function()} $getFn The $getFn for the instance creation. Internally this is a short hand for
+ * `$provide.provider(name, {$get: $getFn})`.
+ * @returns {Object} registered provider instance
+ */
+
+
+/**
+ * @ngdoc method
+ * @name AUTO.$provide#service
+ * @methodOf AUTO.$provide
+ * @description
+ *
+ * A short hand for registering service of given class.
+ *
+ * @param {string} name The name of the instance.
+ * @param {Function} constructor A class (constructor function) that will be instantiated.
+ * @returns {Object} registered provider instance
+ */
+
+
+/**
+ * @ngdoc method
+ * @name AUTO.$provide#value
+ * @methodOf AUTO.$provide
+ * @description
+ *
+ * A short hand for configuring services if the `$get` method is a constant.
+ *
+ * @param {string} name The name of the instance.
+ * @param {*} value The value.
+ * @returns {Object} registered provider instance
+ */
+
+
+/**
+ * @ngdoc method
+ * @name AUTO.$provide#constant
+ * @methodOf AUTO.$provide
+ * @description
+ *
+ * A constant value, but unlike {@link AUTO.$provide#value value} it can be injected
+ * into configuration function (other modules) and it is not interceptable by
+ * {@link AUTO.$provide#decorator decorator}.
+ *
+ * @param {string} name The name of the constant.
+ * @param {*} value The constant value.
+ * @returns {Object} registered instance
+ */
+
+
+/**
+ * @ngdoc method
+ * @name AUTO.$provide#decorator
+ * @methodOf AUTO.$provide
+ * @description
+ *
+ * Decoration of service, allows the decorator to intercept the service instance creation. The
+ * returned instance may be the original instance, or a new instance which delegates to the
+ * original instance.
+ *
+ * @param {string} name The name of the service to decorate.
+ * @param {function()} decorator This function will be invoked when the service needs to be
+ * instantiated. The function is called using the {@link AUTO.$injector#invoke
+ * injector.invoke} method and is therefore fully injectable. Local injection arguments:
+ *
+ * * `$delegate` - The original service instance, which can be monkey patched, configured,
+ * decorated or delegated to.
+ */
+
+
+function createInjector(modulesToLoad) {
+ var INSTANTIATING = {},
+ providerSuffix = 'Provider',
+ path = [],
+ loadedModules = new HashMap(),
+ providerCache = {
+ $provide: {
+ provider: supportObject(provider),
+ factory: supportObject(factory),
+ service: supportObject(service),
+ value: supportObject(value),
+ constant: supportObject(constant),
+ decorator: decorator
+ }
+ },
+ providerInjector = createInternalInjector(providerCache, function() {
+ throw Error("Unknown provider: " + path.join(' <- '));
+ }),
+ instanceCache = {},
+ instanceInjector = (instanceCache.$injector =
+ createInternalInjector(instanceCache, function(servicename) {
+ var provider = providerInjector.get(servicename + providerSuffix);
+ return instanceInjector.invoke(provider.$get, provider);
+ }));
+
+
+ forEach(loadModules(modulesToLoad), function(fn) { instanceInjector.invoke(fn || noop); });
+
+ return instanceInjector;
+
+ ////////////////////////////////////
+ // $provider
+ ////////////////////////////////////
+
+ function supportObject(delegate) {
+ return function(key, value) {
+ if (isObject(key)) {
+ forEach(key, reverseParams(delegate));
+ } else {
+ return delegate(key, value);
+ }
+ }
+ }
+
+ function provider(name, provider_) {
+ if (isFunction(provider_) || isArray(provider_)) {
+ provider_ = providerInjector.instantiate(provider_);
+ }
+ if (!provider_.$get) {
+ throw Error('Provider ' + name + ' must define $get factory method.');
+ }
+ return providerCache[name + providerSuffix] = provider_;
+ }
+
+ function factory(name, factoryFn) { return provider(name, { $get: factoryFn }); }
+
+ function service(name, constructor) {
+ return factory(name, ['$injector', function($injector) {
+ return $injector.instantiate(constructor);
+ }]);
+ }
+
+ function value(name, value) { return factory(name, valueFn(value)); }
+
+ function constant(name, value) {
+ providerCache[name] = value;
+ instanceCache[name] = value;
+ }
+
+ function decorator(serviceName, decorFn) {
+ var origProvider = providerInjector.get(serviceName + providerSuffix),
+ orig$get = origProvider.$get;
+
+ origProvider.$get = function() {
+ var origInstance = instanceInjector.invoke(orig$get, origProvider);
+ return instanceInjector.invoke(decorFn, null, {$delegate: origInstance});
+ };
+ }
+
+ ////////////////////////////////////
+ // Module Loading
+ ////////////////////////////////////
+ function loadModules(modulesToLoad){
+ var runBlocks = [];
+ forEach(modulesToLoad, function(module) {
+ if (loadedModules.get(module)) return;
+ loadedModules.put(module, true);
+ if (isString(module)) {
+ var moduleFn = angularModule(module);
+ runBlocks = runBlocks.concat(loadModules(moduleFn.requires)).concat(moduleFn._runBlocks);
+
+ try {
+ for(var invokeQueue = moduleFn._invokeQueue, i = 0, ii = invokeQueue.length; i < ii; i++) {
+ var invokeArgs = invokeQueue[i],
+ provider = invokeArgs[0] == '$injector'
+ ? providerInjector
+ : providerInjector.get(invokeArgs[0]);
+
+ provider[invokeArgs[1]].apply(provider, invokeArgs[2]);
+ }
+ } catch (e) {
+ if (e.message) e.message += ' from ' + module;
+ throw e;
+ }
+ } else if (isFunction(module)) {
+ try {
+ runBlocks.push(providerInjector.invoke(module));
+ } catch (e) {
+ if (e.message) e.message += ' from ' + module;
+ throw e;
+ }
+ } else if (isArray(module)) {
+ try {
+ runBlocks.push(providerInjector.invoke(module));
+ } catch (e) {
+ if (e.message) e.message += ' from ' + String(module[module.length - 1]);
+ throw e;
+ }
+ } else {
+ assertArgFn(module, 'module');
+ }
+ });
+ return runBlocks;
+ }
+
+ ////////////////////////////////////
+ // internal Injector
+ ////////////////////////////////////
+
+ function createInternalInjector(cache, factory) {
+
+ function getService(serviceName) {
+ if (typeof serviceName !== 'string') {
+ throw Error('Service name expected');
+ }
+ if (cache.hasOwnProperty(serviceName)) {
+ if (cache[serviceName] === INSTANTIATING) {
+ throw Error('Circular dependency: ' + path.join(' <- '));
+ }
+ return cache[serviceName];
+ } else {
+ try {
+ path.unshift(serviceName);
+ cache[serviceName] = INSTANTIATING;
+ return cache[serviceName] = factory(serviceName);
+ } finally {
+ path.shift();
+ }
+ }
+ }
+
+ function invoke(fn, self, locals){
+ var args = [],
+ $inject = annotate(fn),
+ length, i,
+ key;
+
+ for(i = 0, length = $inject.length; i < length; i++) {
+ key = $inject[i];
+ args.push(
+ locals && locals.hasOwnProperty(key)
+ ? locals[key]
+ : getService(key)
+ );
+ }
+ if (!fn.$inject) {
+ // this means that we must be an array.
+ fn = fn[length];
+ }
+
+
+ // Performance optimization: http://jsperf.com/apply-vs-call-vs-invoke
+ switch (self ? -1 : args.length) {
+ case 0: return fn();
+ case 1: return fn(args[0]);
+ case 2: return fn(args[0], args[1]);
+ case 3: return fn(args[0], args[1], args[2]);
+ case 4: return fn(args[0], args[1], args[2], args[3]);
+ case 5: return fn(args[0], args[1], args[2], args[3], args[4]);
+ case 6: return fn(args[0], args[1], args[2], args[3], args[4], args[5]);
+ case 7: return fn(args[0], args[1], args[2], args[3], args[4], args[5], args[6]);
+ case 8: return fn(args[0], args[1], args[2], args[3], args[4], args[5], args[6], args[7]);
+ case 9: return fn(args[0], args[1], args[2], args[3], args[4], args[5], args[6], args[7], args[8]);
+ case 10: return fn(args[0], args[1], args[2], args[3], args[4], args[5], args[6], args[7], args[8], args[9]);
+ default: return fn.apply(self, args);
+ }
+ }
+
+ function instantiate(Type, locals) {
+ var Constructor = function() {},
+ instance, returnedValue;
+
+ // Check if Type is annotated and use just the given function at n-1 as parameter
+ // e.g. someModule.factory('greeter', ['$window', function(renamed$window) {}]);
+ Constructor.prototype = (isArray(Type) ? Type[Type.length - 1] : Type).prototype;
+ instance = new Constructor();
+ returnedValue = invoke(Type, instance, locals);
+
+ return isObject(returnedValue) ? returnedValue : instance;
+ }
+
+ return {
+ invoke: invoke,
+ instantiate: instantiate,
+ get: getService,
+ annotate: annotate
+ };
+ }
+}
+
+/**
+ * @ngdoc function
+ * @name ng.$anchorScroll
+ * @requires $window
+ * @requires $location
+ * @requires $rootScope
+ *
+ * @description
+ * When called, it checks current value of `$location.hash()` and scroll to related element,
+ * according to rules specified in
+ * {@link http://dev.w3.org/html5/spec/Overview.html#the-indicated-part-of-the-docume… Html5 spec}.
+ *
+ * It also watches the `$location.hash()` and scroll whenever it changes to match any anchor.
+ * This can be disabled by calling `$anchorScrollProvider.disableAutoScrolling()`.
+ */
+function $AnchorScrollProvider() {
+
+ var autoScrollingEnabled = true;
+
+ this.disableAutoScrolling = function() {
+ autoScrollingEnabled = false;
+ };
+
+ this.$get = ['$window', '$location', '$rootScope', function($window, $location, $rootScope) {
+ var document = $window.document;
+
+ // helper function to get first anchor from a NodeList
+ // can't use filter.filter, as it accepts only instances of Array
+ // and IE can't convert NodeList to an array using [].slice
+ // TODO(vojta): use filter if we change it to accept lists as well
+ function getFirstAnchor(list) {
+ var result = null;
+ forEach(list, function(element) {
+ if (!result && lowercase(element.nodeName) === 'a') result = element;
+ });
+ return result;
+ }
+
+ function scroll() {
+ var hash = $location.hash(), elm;
+
+ // empty hash, scroll to the top of the page
+ if (!hash) $window.scrollTo(0, 0);
+
+ // element with given id
+ else if ((elm = document.getElementById(hash))) elm.scrollIntoView();
+
+ // first anchor with given name :-D
+ else if ((elm = getFirstAnchor(document.getElementsByName(hash)))) elm.scrollIntoView();
+
+ // no element and hash == 'top', scroll to the top of the page
+ else if (hash === 'top') $window.scrollTo(0, 0);
+ }
+
+ // does not scroll when user clicks on anchor link that is currently on
+ // (no url change, no $location.hash() change), browser native does scroll
+ if (autoScrollingEnabled) {
+ $rootScope.$watch(function autoScrollWatch() {return $location.hash();},
+ function autoScrollWatchAction() {
+ $rootScope.$evalAsync(scroll);
+ });
+ }
+
+ return scroll;
+ }];
+}
+
+/**
+ * ! This is a private undocumented service !
+ *
+ * @name ng.$browser
+ * @requires $log
+ * @description
+ * This object has two goals:
+ *
+ * - hide all the global state in the browser caused by the window object
+ * - abstract away all the browser specific features and inconsistencies
+ *
+ * For tests we provide {@link ngMock.$browser mock implementation} of the `$browser`
+ * service, which can be used for convenient testing of the application without the interaction with
+ * the real browser apis.
+ */
+/**
+ * @param {object} window The global window object.
+ * @param {object} document jQuery wrapped document.
+ * @param {function()} XHR XMLHttpRequest constructor.
+ * @param {object} $log console.log or an object with the same interface.
+ * @param {object} $sniffer $sniffer service
+ */
+function Browser(window, document, $log, $sniffer) {
+ var self = this,
+ rawDocument = document[0],
+ location = window.location,
+ history = window.history,
+ setTimeout = window.setTimeout,
+ clearTimeout = window.clearTimeout,
+ pendingDeferIds = {};
+
+ self.isMock = false;
+
+ var outstandingRequestCount = 0;
+ var outstandingRequestCallbacks = [];
+
+ // TODO(vojta): remove this temporary api
+ self.$$completeOutstandingRequest = completeOutstandingRequest;
+ self.$$incOutstandingRequestCount = function() { outstandingRequestCount++; };
+
+ /**
+ * Executes the `fn` function(supports currying) and decrements the `outstandingRequestCallbacks`
+ * counter. If the counter reaches 0, all the `outstandingRequestCallbacks` are executed.
+ */
+ function completeOutstandingRequest(fn) {
+ try {
+ fn.apply(null, sliceArgs(arguments, 1));
+ } finally {
+ outstandingRequestCount--;
+ if (outstandingRequestCount === 0) {
+ while(outstandingRequestCallbacks.length) {
+ try {
+ outstandingRequestCallbacks.pop()();
+ } catch (e) {
+ $log.error(e);
+ }
+ }
+ }
+ }
+ }
+
+ /**
+ * @private
+ * Note: this method is used only by scenario runner
+ * TODO(vojta): prefix this method with $$ ?
+ * @param {function()} callback Function that will be called when no outstanding request
+ */
+ self.notifyWhenNoOutstandingRequests = function(callback) {
+ // force browser to execute all pollFns - this is needed so that cookies and other pollers fire
+ // at some deterministic time in respect to the test runner's actions. Leaving things up to the
+ // regular poller would result in flaky tests.
+ forEach(pollFns, function(pollFn){ pollFn(); });
+
+ if (outstandingRequestCount === 0) {
+ callback();
+ } else {
+ outstandingRequestCallbacks.push(callback);
+ }
+ };
+
+ //////////////////////////////////////////////////////////////
+ // Poll Watcher API
+ //////////////////////////////////////////////////////////////
+ var pollFns = [],
+ pollTimeout;
+
+ /**
+ * @name ng.$browser#addPollFn
+ * @methodOf ng.$browser
+ *
+ * @param {function()} fn Poll function to add
+ *
+ * @description
+ * Adds a function to the list of functions that poller periodically executes,
+ * and starts polling if not started yet.
+ *
+ * @returns {function()} the added function
+ */
+ self.addPollFn = function(fn) {
+ if (isUndefined(pollTimeout)) startPoller(100, setTimeout);
+ pollFns.push(fn);
+ return fn;
+ };
+
+ /**
+ * @param {number} interval How often should browser call poll functions (ms)
+ * @param {function()} setTimeout Reference to a real or fake `setTimeout` function.
+ *
+ * @description
+ * Configures the poller to run in the specified intervals, using the specified
+ * setTimeout fn and kicks it off.
+ */
+ function startPoller(interval, setTimeout) {
+ (function check() {
+ forEach(pollFns, function(pollFn){ pollFn(); });
+ pollTimeout = setTimeout(check, interval);
+ })();
+ }
+
+ //////////////////////////////////////////////////////////////
+ // URL API
+ //////////////////////////////////////////////////////////////
+
+ var lastBrowserUrl = location.href,
+ baseElement = document.find('base'),
+ replacedUrl = null;
+
+ /**
+ * @name ng.$browser#url
+ * @methodOf ng.$browser
+ *
+ * @description
+ * GETTER:
+ * Without any argument, this method just returns current value of location.href.
+ *
+ * SETTER:
+ * With at least one argument, this method sets url to new value.
+ * If html5 history api supported, pushState/replaceState is used, otherwise
+ * location.href/location.replace is used.
+ * Returns its own instance to allow chaining
+ *
+ * NOTE: this api is intended for use only by the $location service. Please use the
+ * {@link ng.$location $location service} to change url.
+ *
+ * @param {string} url New url (when used as setter)
+ * @param {boolean=} replace Should new url replace current history record ?
+ */
+ self.url = function(url, replace) {
+ // setter
+ if (url) {
+ if (lastBrowserUrl == url) return;
+ lastBrowserUrl = url;
+ if ($sniffer.history) {
+ if (replace) history.replaceState(null, '', url);
+ else {
+ history.pushState(null, '', url);
+ // Crazy Opera Bug: http://my.opera.com/community/forums/topic.dml?id=1185462
+ baseElement.attr('href', baseElement.attr('href'));
+ }
+ } else {
+ if (replace) {
+ location.replace(url);
+ replacedUrl = url;
+ } else {
+ location.href = url;
+ replacedUrl = null;
+ }
+ }
+ return self;
+ // getter
+ } else {
+ // - the replacedUrl is a workaround for an IE8-9 issue with location.replace method that doesn't update
+ // location.href synchronously
+ // - the replacement is a workaround for https://bugzilla.mozilla.org/show_bug.cgi?id=407172
+ return replacedUrl || location.href.replace(/%27/g,"'");
+ }
+ };
+
+ var urlChangeListeners = [],
+ urlChangeInit = false;
+
+ function fireUrlChange() {
+ if (lastBrowserUrl == self.url()) return;
+
+ lastBrowserUrl = self.url();
+ forEach(urlChangeListeners, function(listener) {
+ listener(self.url());
+ });
+ }
+
+ /**
+ * @name ng.$browser#onUrlChange
+ * @methodOf ng.$browser
+ * @TODO(vojta): refactor to use node's syntax for events
+ *
+ * @description
+ * Register callback function that will be called, when url changes.
+ *
+ * It's only called when the url is changed by outside of angular:
+ * - user types different url into address bar
+ * - user clicks on history (forward/back) button
+ * - user clicks on a link
+ *
+ * It's not called when url is changed by $browser.url() method
+ *
+ * The listener gets called with new url as parameter.
+ *
+ * NOTE: this api is intended for use only by the $location service. Please use the
+ * {@link ng.$location $location service} to monitor url changes in angular apps.
+ *
+ * @param {function(string)} listener Listener function to be called when url changes.
+ * @return {function(string)} Returns the registered listener fn - handy if the fn is anonymous.
+ */
+ self.onUrlChange = function(callback) {
+ if (!urlChangeInit) {
+ // We listen on both (hashchange/popstate) when available, as some browsers (e.g. Opera)
+ // don't fire popstate when user change the address bar and don't fire hashchange when url
+ // changed by push/replaceState
+
+ // html5 history api - popstate event
+ if ($sniffer.history) jqLite(window).bind('popstate', fireUrlChange);
+ // hashchange event
+ if ($sniffer.hashchange) jqLite(window).bind('hashchange', fireUrlChange);
+ // polling
+ else self.addPollFn(fireUrlChange);
+
+ urlChangeInit = true;
+ }
+
+ urlChangeListeners.push(callback);
+ return callback;
+ };
+
+ //////////////////////////////////////////////////////////////
+ // Misc API
+ //////////////////////////////////////////////////////////////
+
+ /**
+ * Returns current <base href>
+ * (always relative - without domain)
+ *
+ * @returns {string=}
+ */
+ self.baseHref = function() {
+ var href = baseElement.attr('href');
+ return href ? href.replace(/^https?\:\/\/[^\/]*/, '') : '';
+ };
+
+ //////////////////////////////////////////////////////////////
+ // Cookies API
+ //////////////////////////////////////////////////////////////
+ var lastCookies = {};
+ var lastCookieString = '';
+ var cookiePath = self.baseHref();
+
+ /**
+ * @name ng.$browser#cookies
+ * @methodOf ng.$browser
+ *
+ * @param {string=} name Cookie name
+ * @param {string=} value Cokkie value
+ *
+ * @description
+ * The cookies method provides a 'private' low level access to browser cookies.
+ * It is not meant to be used directly, use the $cookie service instead.
+ *
+ * The return values vary depending on the arguments that the method was called with as follows:
+ * <ul>
+ * <li>cookies() -> hash of all cookies, this is NOT a copy of the internal state, so do not modify it</li>
+ * <li>cookies(name, value) -> set name to value, if value is undefined delete the cookie</li>
+ * <li>cookies(name) -> the same as (name, undefined) == DELETES (no one calls it right now that way)</li>
+ * </ul>
+ *
+ * @returns {Object} Hash of all cookies (if called without any parameter)
+ */
+ self.cookies = function(name, value) {
+ var cookieLength, cookieArray, cookie, i, index;
+
+ if (name) {
+ if (value === undefined) {
+ rawDocument.cookie = escape(name) + "=;path=" + cookiePath + ";expires=Thu, 01 Jan 1970 00:00:00 GMT";
+ } else {
+ if (isString(value)) {
+ cookieLength = (rawDocument.cookie = escape(name) + '=' + escape(value) + ';path=' + cookiePath).length + 1;
+
+ // per http://www.ietf.org/rfc/rfc2109.txt browser must allow at minimum:
+ // - 300 cookies
+ // - 20 cookies per unique domain
+ // - 4096 bytes per cookie
+ if (cookieLength > 4096) {
+ $log.warn("Cookie '"+ name +"' possibly not set or overflowed because it was too large ("+
+ cookieLength + " > 4096 bytes)!");
+ }
+ }
+ }
+ } else {
+ if (rawDocument.cookie !== lastCookieString) {
+ lastCookieString = rawDocument.cookie;
+ cookieArray = lastCookieString.split("; ");
+ lastCookies = {};
+
+ for (i = 0; i < cookieArray.length; i++) {
+ cookie = cookieArray[i];
+ index = cookie.indexOf('=');
+ if (index > 0) { //ignore nameless cookies
+ var name = unescape(cookie.substring(0, index));
+ // the first value that is seen for a cookie is the most
+ // specific one. values for the same cookie name that
+ // follow are for less specific paths.
+ if (lastCookies[name] === undefined) {
+ lastCookies[name] = unescape(cookie.substring(index + 1));
+ }
+ }
+ }
+ }
+ return lastCookies;
+ }
+ };
+
+
+ /**
+ * @name ng.$browser#defer
+ * @methodOf ng.$browser
+ * @param {function()} fn A function, who's execution should be defered.
+ * @param {number=} [delay=0] of milliseconds to defer the function execution.
+ * @returns {*} DeferId that can be used to cancel the task via `$browser.defer.cancel()`.
+ *
+ * @description
+ * Executes a fn asynchroniously via `setTimeout(fn, delay)`.
+ *
+ * Unlike when calling `setTimeout` directly, in test this function is mocked and instead of using
+ * `setTimeout` in tests, the fns are queued in an array, which can be programmatically flushed
+ * via `$browser.defer.flush()`.
+ *
+ */
+ self.defer = function(fn, delay) {
+ var timeoutId;
+ outstandingRequestCount++;
+ timeoutId = setTimeout(function() {
+ delete pendingDeferIds[timeoutId];
+ completeOutstandingRequest(fn);
+ }, delay || 0);
+ pendingDeferIds[timeoutId] = true;
+ return timeoutId;
+ };
+
+
+ /**
+ * @name ng.$browser#defer.cancel
+ * @methodOf ng.$browser.defer
+ *
+ * @description
+ * Cancels a defered task identified with `deferId`.
+ *
+ * @param {*} deferId Token returned by the `$browser.defer` function.
+ * @returns {boolean} Returns `true` if the task hasn't executed yet and was successfuly canceled.
+ */
+ self.defer.cancel = function(deferId) {
+ if (pendingDeferIds[deferId]) {
+ delete pendingDeferIds[deferId];
+ clearTimeout(deferId);
+ completeOutstandingRequest(noop);
+ return true;
+ }
+ return false;
+ };
+
+}
+
+function $BrowserProvider(){
+ this.$get = ['$window', '$log', '$sniffer', '$document',
+ function( $window, $log, $sniffer, $document){
+ return new Browser($window, $document, $log, $sniffer);
+ }];
+}
+
+/**
+ * @ngdoc object
+ * @name ng.$cacheFactory
+ *
+ * @description
+ * Factory that constructs cache objects and gives access to them.
+ *
+ * <pre>
+ *
+ * var cache = $cacheFactory('cacheId');
+ * expect($cacheFactory.get('cacheId')).toBe(cache);
+ * expect($cacheFactory.get('noSuchCacheId')).not.toBeDefined();
+ *
+ * cache.put("key", "value");
+ * cache.put("another key", "another value");
+ *
+ * expect(cache.info()).toEqual({id: 'cacheId', size: 2}); // Since we've specified no options on creation
+ *
+ * </pre>
+ *
+ *
+ * @param {string} cacheId Name or id of the newly created cache.
+ * @param {object=} options Options object that specifies the cache behavior. Properties:
+ *
+ * - `{number=}` `capacity` — turns the cache into LRU cache.
+ *
+ * @returns {object} Newly created cache object with the following set of methods:
+ *
+ * - `{object}` `info()` — Returns id, size, and options of cache.
+ * - `{void}` `put({string} key, {*} value)` — Puts a new key-value pair into the cache.
+ * - `{{*}}` `get({string} key)` — Returns cached value for `key` or undefined for cache miss.
+ * - `{void}` `remove({string} key)` — Removes a key-value pair from the cache.
+ * - `{void}` `removeAll()` — Removes all cached values.
+ * - `{void}` `destroy()` — Removes references to this cache from $cacheFactory.
+ *
+ */
+function $CacheFactoryProvider() {
+
+ this.$get = function() {
+ var caches = {};
+
+ function cacheFactory(cacheId, options) {
+ if (cacheId in caches) {
+ throw Error('cacheId ' + cacheId + ' taken');
+ }
+
+ var size = 0,
+ stats = extend({}, options, {id: cacheId}),
+ data = {},
+ capacity = (options && options.capacity) || Number.MAX_VALUE,
+ lruHash = {},
+ freshEnd = null,
+ staleEnd = null;
+
+ return caches[cacheId] = {
+
+ put: function(key, value) {
+ var lruEntry = lruHash[key] || (lruHash[key] = {key: key});
+
+ refresh(lruEntry);
+
+ if (isUndefined(value)) return;
+ if (!(key in data)) size++;
+ data[key] = value;
+
+ if (size > capacity) {
+ this.remove(staleEnd.key);
+ }
+ },
+
+
+ get: function(key) {
+ var lruEntry = lruHash[key];
+
+ if (!lruEntry) return;
+
+ refresh(lruEntry);
+
+ return data[key];
+ },
+
+
+ remove: function(key) {
+ var lruEntry = lruHash[key];
+
+ if (!lruEntry) return;
+
+ if (lruEntry == freshEnd) freshEnd = lruEntry.p;
+ if (lruEntry == staleEnd) staleEnd = lruEntry.n;
+ link(lruEntry.n,lruEntry.p);
+
+ delete lruHash[key];
+ delete data[key];
+ size--;
+ },
+
+
+ removeAll: function() {
+ data = {};
+ size = 0;
+ lruHash = {};
+ freshEnd = staleEnd = null;
+ },
+
+
+ destroy: function() {
+ data = null;
+ stats = null;
+ lruHash = null;
+ delete caches[cacheId];
+ },
+
+
+ info: function() {
+ return extend({}, stats, {size: size});
+ }
+ };
+
+
+ /**
+ * makes the `entry` the freshEnd of the LRU linked list
+ */
+ function refresh(entry) {
+ if (entry != freshEnd) {
+ if (!staleEnd) {
+ staleEnd = entry;
+ } else if (staleEnd == entry) {
+ staleEnd = entry.n;
+ }
+
+ link(entry.n, entry.p);
+ link(entry, freshEnd);
+ freshEnd = entry;
+ freshEnd.n = null;
+ }
+ }
+
+
+ /**
+ * bidirectionally links two entries of the LRU linked list
+ */
+ function link(nextEntry, prevEntry) {
+ if (nextEntry != prevEntry) {
+ if (nextEntry) nextEntry.p = prevEntry; //p stands for previous, 'prev' didn't minify
+ if (prevEntry) prevEntry.n = nextEntry; //n stands for next, 'next' didn't minify
+ }
+ }
+ }
+
+
+ /**
+ * @ngdoc method
+ * @name ng.$cacheFactory#info
+ * @methodOf ng.$cacheFactory
+ *
+ * @description
+ * Get information about all the of the caches that have been created
+ *
+ * @returns {Object} - key-value map of `cacheId` to the result of calling `cache#info`
+ */
+ cacheFactory.info = function() {
+ var info = {};
+ forEach(caches, function(cache, cacheId) {
+ info[cacheId] = cache.info();
+ });
+ return info;
+ };
+
+
+ /**
+ * @ngdoc method
+ * @name ng.$cacheFactory#get
+ * @methodOf ng.$cacheFactory
+ *
+ * @description
+ * Get access to a cache object by the `cacheId` used when it was created.
+ *
+ * @param {string} cacheId Name or id of a cache to access.
+ * @returns {object} Cache object identified by the cacheId or undefined if no such cache.
+ */
+ cacheFactory.get = function(cacheId) {
+ return caches[cacheId];
+ };
+
+
+ return cacheFactory;
+ };
+}
+
+/**
+ * @ngdoc object
+ * @name ng.$templateCache
+ *
+ * @description
+ * The first time a template is used, it is loaded in the template cache for quick retrieval. You can
+ * load templates directly into the cache in a `script` tag, or by consuming the `$templateCache`
+ * service directly.
+ *
+ * Adding via the `script` tag:
+ * <pre>
+ * <html ng-app>
+ * <head>
+ * <script type="text/ng-template" id="templateId.html">
+ * This is the content of the template
+ * </script>
+ * </head>
+ * ...
+ * </html>
+ * </pre>
+ *
+ * **Note:** the `script` tag containing the template does not need to be included in the `head` of the document, but
+ * it must be below the `ng-app` definition.
+ *
+ * Adding via the $templateCache service:
+ *
+ * <pre>
+ * var myApp = angular.module('myApp', []);
+ * myApp.run(function($templateCache) {
+ * $templateCache.put('templateId.html', 'This is the content of the template');
+ * });
+ * </pre>
+ *
+ * To retrieve the template later, simply use it in your HTML:
+ * <pre>
+ * <div ng-include=" 'templateId.html' "></div>
+ * </pre>
+ *
+ * or get it via Javascript:
+ * <pre>
+ * $templateCache.get('templateId.html')
+ * </pre>
+ *
+ * See {@link ng.$cacheFactory $cacheFactory}.
+ *
+ */
+function $TemplateCacheProvider() {
+ this.$get = ['$cacheFactory', function($cacheFactory) {
+ return $cacheFactory('templates');
+ }];
+}
+
+/* ! VARIABLE/FUNCTION NAMING CONVENTIONS THAT APPLY TO THIS FILE!
+ *
+ * DOM-related variables:
+ *
+ * - "node" - DOM Node
+ * - "element" - DOM Element or Node
+ * - "$node" or "$element" - jqLite-wrapped node or element
+ *
+ *
+ * Compiler related stuff:
+ *
+ * - "linkFn" - linking fn of a single directive
+ * - "nodeLinkFn" - function that aggregates all linking fns for a particular node
+ * - "childLinkFn" - function that aggregates all linking fns for child nodes of a particular node
+ * - "compositeLinkFn" - function that aggregates all linking fns for a compilation root (nodeList)
+ */
+
+
+var NON_ASSIGNABLE_MODEL_EXPRESSION = 'Non-assignable model expression: ';
+
+
+/**
+ * @ngdoc function
+ * @name ng.$compile
+ * @function
+ *
+ * @description
+ * Compiles a piece of HTML string or DOM into a template and produces a template function, which
+ * can then be used to link {@link ng.$rootScope.Scope scope} and the template together.
+ *
+ * The compilation is a process of walking the DOM tree and trying to match DOM elements to
+ * {@link ng.$compileProvider#directive directives}. For each match it
+ * executes corresponding template function and collects the
+ * instance functions into a single template function which is then returned.
+ *
+ * The template function can then be used once to produce the view or as it is the case with
+ * {@link ng.directive:ngRepeat repeater} many-times, in which
+ * case each call results in a view that is a DOM clone of the original template.
+ *
+ <doc:example module="compile">
+ <doc:source>
+ <script>
+ // declare a new module, and inject the $compileProvider
+ angular.module('compile', [], function($compileProvider) {
+ // configure new 'compile' directive by passing a directive
+ // factory function. The factory function injects the '$compile'
+ $compileProvider.directive('compile', function($compile) {
+ // directive factory creates a link function
+ return function(scope, element, attrs) {
+ scope.$watch(
+ function(scope) {
+ // watch the 'compile' expression for changes
+ return scope.$eval(attrs.compile);
+ },
+ function(value) {
+ // when the 'compile' expression changes
+ // assign it into the current DOM
+ element.html(value);
+
+ // compile the new DOM and link it to the current
+ // scope.
+ // NOTE: we only compile .childNodes so that
+ // we don't get into infinite loop compiling ourselves
+ $compile(element.contents())(scope);
+ }
+ );
+ };
+ })
+ });
+
+ function Ctrl($scope) {
+ $scope.name = 'Angular';
+ $scope.html = 'Hello {{name}}';
+ }
+ </script>
+ <div ng-controller="Ctrl">
+ <input ng-model="name"> <br>
+ <textarea ng-model="html"></textarea> <br>
+ <div compile="html"></div>
+ </div>
+ </doc:source>
+ <doc:scenario>
+ it('should auto compile', function() {
+ expect(element('div[compile]').text()).toBe('Hello Angular');
+ input('html').enter('{{name}}!');
+ expect(element('div[compile]').text()).toBe('Angular!');
+ });
+ </doc:scenario>
+ </doc:example>
+
+ *
+ *
+ * @param {string|DOMElement} element Element or HTML string to compile into a template function.
+ * @param {function(angular.Scope[, cloneAttachFn]} transclude function available to directives.
+ * @param {number} maxPriority only apply directives lower then given priority (Only effects the
+ * root element(s), not their children)
+ * @returns {function(scope[, cloneAttachFn])} a link function which is used to bind template
+ * (a DOM element/tree) to a scope. Where:
+ *
+ * * `scope` - A {@link ng.$rootScope.Scope Scope} to bind to.
+ * * `cloneAttachFn` - If `cloneAttachFn` is provided, then the link function will clone the
+ * `template` and call the `cloneAttachFn` function allowing the caller to attach the
+ * cloned elements to the DOM document at the appropriate place. The `cloneAttachFn` is
+ * called as: <br> `cloneAttachFn(clonedElement, scope)` where:
+ *
+ * * `clonedElement` - is a clone of the original `element` passed into the compiler.
+ * * `scope` - is the current scope with which the linking function is working with.
+ *
+ * Calling the linking function returns the element of the template. It is either the original element
+ * passed in, or the clone of the element if the `cloneAttachFn` is provided.
+ *
+ * After linking the view is not updated until after a call to $digest which typically is done by
+ * Angular automatically.
+ *
+ * If you need access to the bound view, there are two ways to do it:
+ *
+ * - If you are not asking the linking function to clone the template, create the DOM element(s)
+ * before you send them to the compiler and keep this reference around.
+ * <pre>
+ * var element = $compile('<p>{{total}}</p>')(scope);
+ * </pre>
+ *
+ * - if on the other hand, you need the element to be cloned, the view reference from the original
+ * example would not point to the clone, but rather to the original template that was cloned. In
+ * this case, you can access the clone via the cloneAttachFn:
+ * <pre>
+ * var templateHTML = angular.element('<p>{{total}}</p>'),
+ * scope = ....;
+ *
+ * var clonedElement = $compile(templateHTML)(scope, function(clonedElement, scope) {
+ * //attach the clone to DOM document at the right place
+ * });
+ *
+ * //now we have reference to the cloned DOM via `clone`
+ * </pre>
+ *
+ *
+ * For information on how the compiler works, see the
+ * {@link guide/compiler Angular HTML Compiler} section of the Developer Guide.
+ */
+
+
+/**
+ * @ngdoc service
+ * @name ng.$compileProvider
+ * @function
+ *
+ * @description
+ */
+$CompileProvider.$inject = ['$provide'];
+function $CompileProvider($provide) {
+ var hasDirectives = {},
+ Suffix = 'Directive',
+ COMMENT_DIRECTIVE_REGEXP = /^\s*directive\:\s*([\d\w\-_]+)\s+(.*)$/,
+ CLASS_DIRECTIVE_REGEXP = /(([\d\w\-_]+)(?:\:([^;]+))?;?)/,
+ MULTI_ROOT_TEMPLATE_ERROR = 'Template must have exactly one root element. was: ',
+ urlSanitizationWhitelist = /^\s*(https?|ftp|mailto|file):/;
+
+
+ /**
+ * @ngdoc function
+ * @name ng.$compileProvider#directive
+ * @methodOf ng.$compileProvider
+ * @function
+ *
+ * @description
+ * Register a new directive with the compiler.
+ *
+ * @param {string} name Name of the directive in camel-case. (ie <code>ngBind</code> which will match as
+ * <code>ng-bind</code>).
+ * @param {function|Array} directiveFactory An injectable directive factory function. See {@link guide/directive} for more
+ * info.
+ * @returns {ng.$compileProvider} Self for chaining.
+ */
+ this.directive = function registerDirective(name, directiveFactory) {
+ if (isString(name)) {
+ assertArg(directiveFactory, 'directive');
+ if (!hasDirectives.hasOwnProperty(name)) {
+ hasDirectives[name] = [];
+ $provide.factory(name + Suffix, ['$injector', '$exceptionHandler',
+ function($injector, $exceptionHandler) {
+ var directives = [];
+ forEach(hasDirectives[name], function(directiveFactory) {
+ try {
+ var directive = $injector.invoke(directiveFactory);
+ if (isFunction(directive)) {
+ directive = { compile: valueFn(directive) };
+ } else if (!directive.compile && directive.link) {
+ directive.compile = valueFn(directive.link);
+ }
+ directive.priority = directive.priority || 0;
+ directive.name = directive.name || name;
+ directive.require = directive.require || (directive.controller && directive.name);
+ directive.restrict = directive.restrict || 'A';
+ directives.push(directive);
+ } catch (e) {
+ $exceptionHandler(e);
+ }
+ });
+ return directives;
+ }]);
+ }
+ hasDirectives[name].push(directiveFactory);
+ } else {
+ forEach(name, reverseParams(registerDirective));
+ }
+ return this;
+ };
+
+
+ /**
+ * @ngdoc function
+ * @name ng.$compileProvider#urlSanitizationWhitelist
+ * @methodOf ng.$compileProvider
+ * @function
+ *
+ * @description
+ * Retrieves or overrides the default regular expression that is used for whitelisting of safe
+ * urls during a[href] sanitization.
+ *
+ * The sanitization is a security measure aimed at prevent XSS attacks via html links.
+ *
+ * Any url about to be assigned to a[href] via data-binding is first normalized and turned into an
+ * absolute url. Afterwards the url is matched against the `urlSanitizationWhitelist` regular
+ * expression. If a match is found the original url is written into the dom. Otherwise the
+ * absolute url is prefixed with `'unsafe:'` string and only then it is written into the DOM.
+ *
+ * @param {RegExp=} regexp New regexp to whitelist urls with.
+ * @returns {RegExp|ng.$compileProvider} Current RegExp if called without value or self for
+ * chaining otherwise.
+ */
+ this.urlSanitizationWhitelist = function(regexp) {
+ if (isDefined(regexp)) {
+ urlSanitizationWhitelist = regexp;
+ return this;
+ }
+ return urlSanitizationWhitelist;
+ };
+
+
+ this.$get = [
+ '$injector', '$interpolate', '$exceptionHandler', '$http', '$templateCache', '$parse',
+ '$controller', '$rootScope', '$document',
+ function($injector, $interpolate, $exceptionHandler, $http, $templateCache, $parse,
+ $controller, $rootScope, $document) {
+
+ var Attributes = function(element, attr) {
+ this.$$element = element;
+ this.$attr = attr || {};
+ };
+
+ Attributes.prototype = {
+ $normalize: directiveNormalize,
+
+
+ /**
+ * Set a normalized attribute on the element in a way such that all directives
+ * can share the attribute. This function properly handles boolean attributes.
+ * @param {string} key Normalized key. (ie ngAttribute)
+ * @param {string|boolean} value The value to set. If `null` attribute will be deleted.
+ * @param {boolean=} writeAttr If false, does not write the value to DOM element attribute.
+ * Defaults to true.
+ * @param {string=} attrName Optional none normalized name. Defaults to key.
+ */
+ $set: function(key, value, writeAttr, attrName) {
+ var booleanKey = getBooleanAttrName(this.$$element[0], key),
+ $$observers = this.$$observers,
+ normalizedVal;
+
+ if (booleanKey) {
+ this.$$element.prop(key, value);
+ attrName = booleanKey;
+ }
+
+ this[key] = value;
+
+ // translate normalized key to actual key
+ if (attrName) {
+ this.$attr[key] = attrName;
+ } else {
+ attrName = this.$attr[key];
+ if (!attrName) {
+ this.$attr[key] = attrName = snake_case(key, '-');
+ }
+ }
+
+
+ // sanitize a[href] values
+ if (nodeName_(this.$$element[0]) === 'A' && key === 'href') {
+ urlSanitizationNode.setAttribute('href', value);
+
+ // href property always returns normalized absolute url, so we can match against that
+ normalizedVal = urlSanitizationNode.href;
+ if (normalizedVal !== '' && !normalizedVal.match(urlSanitizationWhitelist)) {
+ this[key] = value = 'unsafe:' + normalizedVal;
+ }
+ }
+
+
+ if (writeAttr !== false) {
+ if (value === null || value === undefined) {
+ this.$$element.removeAttr(attrName);
+ } else {
+ this.$$element.attr(attrName, value);
+ }
+ }
+
+ // fire observers
+ $$observers && forEach($$observers[key], function(fn) {
+ try {
+ fn(value);
+ } catch (e) {
+ $exceptionHandler(e);
+ }
+ });
+ },
+
+
+ /**
+ * Observe an interpolated attribute.
+ * The observer will never be called, if given attribute is not interpolated.
+ *
+ * @param {string} key Normalized key. (ie ngAttribute) .
+ * @param {function(*)} fn Function that will be called whenever the attribute value changes.
+ * @returns {function(*)} the `fn` Function passed in.
+ */
+ $observe: function(key, fn) {
+ var attrs = this,
+ $$observers = (attrs.$$observers || (attrs.$$observers = {})),
+ listeners = ($$observers[key] || ($$observers[key] = []));
+
+ listeners.push(fn);
+ $rootScope.$evalAsync(function() {
+ if (!listeners.$$inter) {
+ // no one registered attribute interpolation function, so lets call it manually
+ fn(attrs[key]);
+ }
+ });
+ return fn;
+ }
+ };
+
+ var urlSanitizationNode = $document[0].createElement('a'),
+ startSymbol = $interpolate.startSymbol(),
+ endSymbol = $interpolate.endSymbol(),
+ denormalizeTemplate = (startSymbol == '{{' || endSymbol == '}}')
+ ? identity
+ : function denormalizeTemplate(template) {
+ return template.replace(/\{\{/g, startSymbol).replace(/}}/g, endSymbol);
+ };
+
+
+ return compile;
+
+ //================================
+
+ function compile($compileNodes, transcludeFn, maxPriority) {
+ if (!($compileNodes instanceof jqLite)) {
+ // jquery always rewraps, whereas we need to preserve the original selector so that we can modify it.
+ $compileNodes = jqLite($compileNodes);
+ }
+ // We can not compile top level text elements since text nodes can be merged and we will
+ // not be able to attach scope data to them, so we will wrap them in <span>
+ forEach($compileNodes, function(node, index){
+ if (node.nodeType == 3 /* text node */ && node.nodeValue.match(/\S+/) /* non-empty */ ) {
+ $compileNodes[index] = jqLite(node).wrap('<span></span>').parent()[0];
+ }
+ });
+ var compositeLinkFn = compileNodes($compileNodes, transcludeFn, $compileNodes, maxPriority);
+ return function publicLinkFn(scope, cloneConnectFn){
+ assertArg(scope, 'scope');
+ // important!!: we must call our jqLite.clone() since the jQuery one is trying to be smart
+ // and sometimes changes the structure of the DOM.
+ var $linkNode = cloneConnectFn
+ ? JQLitePrototype.clone.call($compileNodes) // IMPORTANT!!!
+ : $compileNodes;
+
+ // Attach scope only to non-text nodes.
+ for(var i = 0, ii = $linkNode.length; i<ii; i++) {
+ var node = $linkNode[i];
+ if (node.nodeType == 1 /* element */ || node.nodeType == 9 /* document */) {
+ $linkNode.eq(i).data('$scope', scope);
+ }
+ }
+ safeAddClass($linkNode, 'ng-scope');
+ if (cloneConnectFn) cloneConnectFn($linkNode, scope);
+ if (compositeLinkFn) compositeLinkFn(scope, $linkNode, $linkNode);
+ return $linkNode;
+ };
+ }
+
+ function wrongMode(localName, mode) {
+ throw Error("Unsupported '" + mode + "' for '" + localName + "'.");
+ }
+
+ function safeAddClass($element, className) {
+ try {
+ $element.addClass(className);
+ } catch(e) {
+ // ignore, since it means that we are trying to set class on
+ // SVG element, where class name is read-only.
+ }
+ }
+
+ /**
+ * Compile function matches each node in nodeList against the directives. Once all directives
+ * for a particular node are collected their compile functions are executed. The compile
+ * functions return values - the linking functions - are combined into a composite linking
+ * function, which is the a linking function for the node.
+ *
+ * @param {NodeList} nodeList an array of nodes or NodeList to compile
+ * @param {function(angular.Scope[, cloneAttachFn]} transcludeFn A linking function, where the
+ * scope argument is auto-generated to the new child of the transcluded parent scope.
+ * @param {DOMElement=} $rootElement If the nodeList is the root of the compilation tree then the
+ * rootElement must be set the jqLite collection of the compile root. This is
+ * needed so that the jqLite collection items can be replaced with widgets.
+ * @param {number=} max directive priority
+ * @returns {?function} A composite linking function of all of the matched directives or null.
+ */
+ function compileNodes(nodeList, transcludeFn, $rootElement, maxPriority) {
+ var linkFns = [],
+ nodeLinkFn, childLinkFn, directives, attrs, linkFnFound;
+
+ for(var i = 0; i < nodeList.length; i++) {
+ attrs = new Attributes();
+
+ // we must always refer to nodeList[i] since the nodes can be replaced underneath us.
+ directives = collectDirectives(nodeList[i], [], attrs, maxPriority);
+
+ nodeLinkFn = (directives.length)
+ ? applyDirectivesToNode(directives, nodeList[i], attrs, transcludeFn, $rootElement)
+ : null;
+
+ childLinkFn = (nodeLinkFn && nodeLinkFn.terminal || !nodeList[i].childNodes || !nodeList[i].childNodes.length)
+ ? null
+ : compileNodes(nodeList[i].childNodes,
+ nodeLinkFn ? nodeLinkFn.transclude : transcludeFn);
+
+ linkFns.push(nodeLinkFn);
+ linkFns.push(childLinkFn);
+ linkFnFound = (linkFnFound || nodeLinkFn || childLinkFn);
+ }
+
+ // return a linking function if we have found anything, null otherwise
+ return linkFnFound ? compositeLinkFn : null;
+
+ function compositeLinkFn(scope, nodeList, $rootElement, boundTranscludeFn) {
+ var nodeLinkFn, childLinkFn, node, childScope, childTranscludeFn, i, ii, n;
+
+ // copy nodeList so that linking doesn't break due to live list updates.
+ var stableNodeList = [];
+ for (i = 0, ii = nodeList.length; i < ii; i++) {
+ stableNodeList.push(nodeList[i]);
+ }
+
+ for(i = 0, n = 0, ii = linkFns.length; i < ii; n++) {
+ node = stableNodeList[n];
+ nodeLinkFn = linkFns[i++];
+ childLinkFn = linkFns[i++];
+
+ if (nodeLinkFn) {
+ if (nodeLinkFn.scope) {
+ childScope = scope.$new(isObject(nodeLinkFn.scope));
+ jqLite(node).data('$scope', childScope);
+ } else {
+ childScope = scope;
+ }
+ childTranscludeFn = nodeLinkFn.transclude;
+ if (childTranscludeFn || (!boundTranscludeFn && transcludeFn)) {
+ nodeLinkFn(childLinkFn, childScope, node, $rootElement,
+ (function(transcludeFn) {
+ return function(cloneFn) {
+ var transcludeScope = scope.$new();
+ transcludeScope.$$transcluded = true;
+
+ return transcludeFn(transcludeScope, cloneFn).
+ bind('$destroy', bind(transcludeScope, transcludeScope.$destroy));
+ };
+ })(childTranscludeFn || transcludeFn)
+ );
+ } else {
+ nodeLinkFn(childLinkFn, childScope, node, undefined, boundTranscludeFn);
+ }
+ } else if (childLinkFn) {
+ childLinkFn(scope, node.childNodes, undefined, boundTranscludeFn);
+ }
+ }
+ }
+ }
+
+
+ /**
+ * Looks for directives on the given node and adds them to the directive collection which is
+ * sorted.
+ *
+ * @param node Node to search.
+ * @param directives An array to which the directives are added to. This array is sorted before
+ * the function returns.
+ * @param attrs The shared attrs object which is used to populate the normalized attributes.
+ * @param {number=} maxPriority Max directive priority.
+ */
+ function collectDirectives(node, directives, attrs, maxPriority) {
+ var nodeType = node.nodeType,
+ attrsMap = attrs.$attr,
+ match,
+ className;
+
+ switch(nodeType) {
+ case 1: /* Element */
+ // use the node name: <directive>
+ addDirective(directives,
+ directiveNormalize(nodeName_(node).toLowerCase()), 'E', maxPriority);
+
+ // iterate over the attributes
+ for (var attr, name, nName, value, nAttrs = node.attributes,
+ j = 0, jj = nAttrs && nAttrs.length; j < jj; j++) {
+ attr = nAttrs[j];
+ if (!msie || msie >= 8 || attr.specified) {
+ name = attr.name;
+ nName = directiveNormalize(name.toLowerCase());
+ attrsMap[nName] = name;
+ attrs[nName] = value = trim((msie && name == 'href')
+ ? decodeURIComponent(node.getAttribute(name, 2))
+ : attr.value);
+ if (getBooleanAttrName(node, nName)) {
+ attrs[nName] = true; // presence means true
+ }
+ addAttrInterpolateDirective(node, directives, value, nName);
+ addDirective(directives, nName, 'A', maxPriority);
+ }
+ }
+
+ // use class as directive
+ className = node.className;
+ if (isString(className) && className !== '') {
+ while (match = CLASS_DIRECTIVE_REGEXP.exec(className)) {
+ nName = directiveNormalize(match[2]);
+ if (addDirective(directives, nName, 'C', maxPriority)) {
+ attrs[nName] = trim(match[3]);
+ }
+ className = className.substr(match.index + match[0].length);
+ }
+ }
+ break;
+ case 3: /* Text Node */
+ addTextInterpolateDirective(directives, node.nodeValue);
+ break;
+ case 8: /* Comment */
+ try {
+ match = COMMENT_DIRECTIVE_REGEXP.exec(node.nodeValue);
+ if (match) {
+ nName = directiveNormalize(match[1]);
+ if (addDirective(directives, nName, 'M', maxPriority)) {
+ attrs[nName] = trim(match[2]);
+ }
+ }
+ } catch (e) {
+ // turns out that under some circumstances IE9 throws errors when one attempts to read comment's node value.
+ // Just ignore it and continue. (Can't seem to reproduce in test case.)
+ }
+ break;
+ }
+
+ directives.sort(byPriority);
+ return directives;
+ }
+
+
+ /**
+ * Once the directives have been collected, their compile functions are executed. This method
+ * is responsible for inlining directive templates as well as terminating the application
+ * of the directives if the terminal directive has been reached.
+ *
+ * @param {Array} directives Array of collected directives to execute their compile function.
+ * this needs to be pre-sorted by priority order.
+ * @param {Node} compileNode The raw DOM node to apply the compile functions to
+ * @param {Object} templateAttrs The shared attribute function
+ * @param {function(angular.Scope[, cloneAttachFn]} transcludeFn A linking function, where the
+ * scope argument is auto-generated to the new child of the transcluded parent scope.
+ * @param {JQLite} jqCollection If we are working on the root of the compile tree then this
+ * argument has the root jqLite array so that we can replace nodes on it.
+ * @returns linkFn
+ */
+ function applyDirectivesToNode(directives, compileNode, templateAttrs, transcludeFn, jqCollection) {
+ var terminalPriority = -Number.MAX_VALUE,
+ preLinkFns = [],
+ postLinkFns = [],
+ newScopeDirective = null,
+ newIsolateScopeDirective = null,
+ templateDirective = null,
+ $compileNode = templateAttrs.$$element = jqLite(compileNode),
+ directive,
+ directiveName,
+ $template,
+ transcludeDirective,
+ childTranscludeFn = transcludeFn,
+ controllerDirectives,
+ linkFn,
+ directiveValue;
+
+ // executes all directives on the current element
+ for(var i = 0, ii = directives.length; i < ii; i++) {
+ directive = directives[i];
+ $template = undefined;
+
+ if (terminalPriority > directive.priority) {
+ break; // prevent further processing of directives
+ }
+
+ if (directiveValue = directive.scope) {
+ assertNoDuplicate('isolated scope', newIsolateScopeDirective, directive, $compileNode);
+ if (isObject(directiveValue)) {
+ safeAddClass($compileNode, 'ng-isolate-scope');
+ newIsolateScopeDirective = directive;
+ }
+ safeAddClass($compileNode, 'ng-scope');
+ newScopeDirective = newScopeDirective || directive;
+ }
+
+ directiveName = directive.name;
+
+ if (directiveValue = directive.controller) {
+ controllerDirectives = controllerDirectives || {};
+ assertNoDuplicate("'" + directiveName + "' controller",
+ controllerDirectives[directiveName], directive, $compileNode);
+ controllerDirectives[directiveName] = directive;
+ }
+
+ if (directiveValue = directive.transclude) {
+ assertNoDuplicate('transclusion', transcludeDirective, directive, $compileNode);
+ transcludeDirective = directive;
+ terminalPriority = directive.priority;
+ if (directiveValue == 'element') {
+ $template = jqLite(compileNode);
+ $compileNode = templateAttrs.$$element =
+ jqLite(document.createComment(' ' + directiveName + ': ' + templateAttrs[directiveName] + ' '));
+ compileNode = $compileNode[0];
+ replaceWith(jqCollection, jqLite($template[0]), compileNode);
+ childTranscludeFn = compile($template, transcludeFn, terminalPriority);
+ } else {
+ $template = jqLite(JQLiteClone(compileNode)).contents();
+ $compileNode.html(''); // clear contents
+ childTranscludeFn = compile($template, transcludeFn);
+ }
+ }
+
+ if ((directiveValue = directive.template)) {
+ assertNoDuplicate('template', templateDirective, directive, $compileNode);
+ templateDirective = directive;
+ directiveValue = denormalizeTemplate(directiveValue);
+
+ if (directive.replace) {
+ $template = jqLite('<div>' +
+ trim(directiveValue) +
+ '</div>').contents();
+ compileNode = $template[0];
+
+ if ($template.length != 1 || compileNode.nodeType !== 1) {
+ throw new Error(MULTI_ROOT_TEMPLATE_ERROR + directiveValue);
+ }
+
+ replaceWith(jqCollection, $compileNode, compileNode);
+
+ var newTemplateAttrs = {$attr: {}};
+
+ // combine directives from the original node and from the template:
+ // - take the array of directives for this element
+ // - split it into two parts, those that were already applied and those that weren't
+ // - collect directives from the template, add them to the second group and sort them
+ // - append the second group with new directives to the first group
+ directives = directives.concat(
+ collectDirectives(
+ compileNode,
+ directives.splice(i + 1, directives.length - (i + 1)),
+ newTemplateAttrs
+ )
+ );
+ mergeTemplateAttributes(templateAttrs, newTemplateAttrs);
+
+ ii = directives.length;
+ } else {
+ $compileNode.html(directiveValue);
+ }
+ }
+
+ if (directive.templateUrl) {
+ assertNoDuplicate('template', templateDirective, directive, $compileNode);
+ templateDirective = directive;
+ nodeLinkFn = compileTemplateUrl(directives.splice(i, directives.length - i),
+ nodeLinkFn, $compileNode, templateAttrs, jqCollection, directive.replace,
+ childTranscludeFn);
+ ii = directives.length;
+ } else if (directive.compile) {
+ try {
+ linkFn = directive.compile($compileNode, templateAttrs, childTranscludeFn);
+ if (isFunction(linkFn)) {
+ addLinkFns(null, linkFn);
+ } else if (linkFn) {
+ addLinkFns(linkFn.pre, linkFn.post);
+ }
+ } catch (e) {
+ $exceptionHandler(e, startingTag($compileNode));
+ }
+ }
+
+ if (directive.terminal) {
+ nodeLinkFn.terminal = true;
+ terminalPriority = Math.max(terminalPriority, directive.priority);
+ }
+
+ }
+
+ nodeLinkFn.scope = newScopeDirective && newScopeDirective.scope;
+ nodeLinkFn.transclude = transcludeDirective && childTranscludeFn;
+
+ // might be normal or delayed nodeLinkFn depending on if templateUrl is present
+ return nodeLinkFn;
+
+ ////////////////////
+
+ function addLinkFns(pre, post) {
+ if (pre) {
+ pre.require = directive.require;
+ preLinkFns.push(pre);
+ }
+ if (post) {
+ post.require = directive.require;
+ postLinkFns.push(post);
+ }
+ }
+
+
+ function getControllers(require, $element) {
+ var value, retrievalMethod = 'data', optional = false;
+ if (isString(require)) {
+ while((value = require.charAt(0)) == '^' || value == '?') {
+ require = require.substr(1);
+ if (value == '^') {
+ retrievalMethod = 'inheritedData';
+ }
+ optional = optional || value == '?';
+ }
+ value = $element[retrievalMethod]('$' + require + 'Controller');
+ if (!value && !optional) {
+ throw Error("No controller: " + require);
+ }
+ return value;
+ } else if (isArray(require)) {
+ value = [];
+ forEach(require, function(require) {
+ value.push(getControllers(require, $element));
+ });
+ }
+ return value;
+ }
+
+
+ function nodeLinkFn(childLinkFn, scope, linkNode, $rootElement, boundTranscludeFn) {
+ var attrs, $element, i, ii, linkFn, controller;
+
+ if (compileNode === linkNode) {
+ attrs = templateAttrs;
+ } else {
+ attrs = shallowCopy(templateAttrs, new Attributes(jqLite(linkNode), templateAttrs.$attr));
+ }
+ $element = attrs.$$element;
+
+ if (newIsolateScopeDirective) {
+ var LOCAL_REGEXP = /^\s*([@=&])\s*(\w*)\s*$/;
+
+ var parentScope = scope.$parent || scope;
+
+ forEach(newIsolateScopeDirective.scope, function(definiton, scopeName) {
+ var match = definiton.match(LOCAL_REGEXP) || [],
+ attrName = match[2]|| scopeName,
+ mode = match[1], // @, =, or &
+ lastValue,
+ parentGet, parentSet;
+
+ scope.$$isolateBindings[scopeName] = mode + attrName;
+
+ switch (mode) {
+
+ case '@': {
+ attrs.$observe(attrName, function(value) {
+ scope[scopeName] = value;
+ });
+ attrs.$$observers[attrName].$$scope = parentScope;
+ break;
+ }
+
+ case '=': {
+ parentGet = $parse(attrs[attrName]);
+ parentSet = parentGet.assign || function() {
+ // reset the change, or we will throw this exception on every $digest
+ lastValue = scope[scopeName] = parentGet(parentScope);
+ throw Error(NON_ASSIGNABLE_MODEL_EXPRESSION + attrs[attrName] +
+ ' (directive: ' + newIsolateScopeDirective.name + ')');
+ };
+ lastValue = scope[scopeName] = parentGet(parentScope);
+ scope.$watch(function parentValueWatch() {
+ var parentValue = parentGet(parentScope);
+
+ if (parentValue !== scope[scopeName]) {
+ // we are out of sync and need to copy
+ if (parentValue !== lastValue) {
+ // parent changed and it has precedence
+ lastValue = scope[scopeName] = parentValue;
+ } else {
+ // if the parent can be assigned then do so
+ parentSet(parentScope, parentValue = lastValue = scope[scopeName]);
+ }
+ }
+ return parentValue;
+ });
+ break;
+ }
+
+ case '&': {
+ parentGet = $parse(attrs[attrName]);
+ scope[scopeName] = function(locals) {
+ return parentGet(parentScope, locals);
+ };
+ break;
+ }
+
+ default: {
+ throw Error('Invalid isolate scope definition for directive ' +
+ newIsolateScopeDirective.name + ': ' + definiton);
+ }
+ }
+ });
+ }
+
+ if (controllerDirectives) {
+ forEach(controllerDirectives, function(directive) {
+ var locals = {
+ $scope: scope,
+ $element: $element,
+ $attrs: attrs,
+ $transclude: boundTranscludeFn
+ };
+
+ controller = directive.controller;
+ if (controller == '@') {
+ controller = attrs[directive.name];
+ }
+
+ $element.data(
+ '$' + directive.name + 'Controller',
+ $controller(controller, locals));
+ });
+ }
+
+ // PRELINKING
+ for(i = 0, ii = preLinkFns.length; i < ii; i++) {
+ try {
+ linkFn = preLinkFns[i];
+ linkFn(scope, $element, attrs,
+ linkFn.require && getControllers(linkFn.require, $element));
+ } catch (e) {
+ $exceptionHandler(e, startingTag($element));
+ }
+ }
+
+ // RECURSION
+ childLinkFn && childLinkFn(scope, linkNode.childNodes, undefined, boundTranscludeFn);
+
+ // POSTLINKING
+ for(i = 0, ii = postLinkFns.length; i < ii; i++) {
+ try {
+ linkFn = postLinkFns[i];
+ linkFn(scope, $element, attrs,
+ linkFn.require && getControllers(linkFn.require, $element));
+ } catch (e) {
+ $exceptionHandler(e, startingTag($element));
+ }
+ }
+ }
+ }
+
+
+ /**
+ * looks up the directive and decorates it with exception handling and proper parameters. We
+ * call this the boundDirective.
+ *
+ * @param {string} name name of the directive to look up.
+ * @param {string} location The directive must be found in specific format.
+ * String containing any of theses characters:
+ *
+ * * `E`: element name
+ * * `A': attribute
+ * * `C`: class
+ * * `M`: comment
+ * @returns true if directive was added.
+ */
+ function addDirective(tDirectives, name, location, maxPriority) {
+ var match = false;
+ if (hasDirectives.hasOwnProperty(name)) {
+ for(var directive, directives = $injector.get(name + Suffix),
+ i = 0, ii = directives.length; i<ii; i++) {
+ try {
+ directive = directives[i];
+ if ( (maxPriority === undefined || maxPriority > directive.priority) &&
+ directive.restrict.indexOf(location) != -1) {
+ tDirectives.push(directive);
+ match = true;
+ }
+ } catch(e) { $exceptionHandler(e); }
+ }
+ }
+ return match;
+ }
+
+
+ /**
+ * When the element is replaced with HTML template then the new attributes
+ * on the template need to be merged with the existing attributes in the DOM.
+ * The desired effect is to have both of the attributes present.
+ *
+ * @param {object} dst destination attributes (original DOM)
+ * @param {object} src source attributes (from the directive template)
+ */
+ function mergeTemplateAttributes(dst, src) {
+ var srcAttr = src.$attr,
+ dstAttr = dst.$attr,
+ $element = dst.$$element;
+
+ // reapply the old attributes to the new element
+ forEach(dst, function(value, key) {
+ if (key.charAt(0) != '$') {
+ if (src[key]) {
+ value += (key === 'style' ? ';' : ' ') + src[key];
+ }
+ dst.$set(key, value, true, srcAttr[key]);
+ }
+ });
+
+ // copy the new attributes on the old attrs object
+ forEach(src, function(value, key) {
+ if (key == 'class') {
+ safeAddClass($element, value);
+ dst['class'] = (dst['class'] ? dst['class'] + ' ' : '') + value;
+ } else if (key == 'style') {
+ $element.attr('style', $element.attr('style') + ';' + value);
+ } else if (key.charAt(0) != '$' && !dst.hasOwnProperty(key)) {
+ dst[key] = value;
+ dstAttr[key] = srcAttr[key];
+ }
+ });
+ }
+
+
+ function compileTemplateUrl(directives, beforeTemplateNodeLinkFn, $compileNode, tAttrs,
+ $rootElement, replace, childTranscludeFn) {
+ var linkQueue = [],
+ afterTemplateNodeLinkFn,
+ afterTemplateChildLinkFn,
+ beforeTemplateCompileNode = $compileNode[0],
+ origAsyncDirective = directives.shift(),
+ // The fact that we have to copy and patch the directive seems wrong!
+ derivedSyncDirective = extend({}, origAsyncDirective, {
+ controller: null, templateUrl: null, transclude: null, scope: null
+ });
+
+ $compileNode.html('');
+
+ $http.get(origAsyncDirective.templateUrl, {cache: $templateCache}).
+ success(function(content) {
+ var compileNode, tempTemplateAttrs, $template;
+
+ content = denormalizeTemplate(content);
+
+ if (replace) {
+ $template = jqLite('<div>' + trim(content) + '</div>').contents();
+ compileNode = $template[0];
+
+ if ($template.length != 1 || compileNode.nodeType !== 1) {
+ throw new Error(MULTI_ROOT_TEMPLATE_ERROR + content);
+ }
+
+ tempTemplateAttrs = {$attr: {}};
+ replaceWith($rootElement, $compileNode, compileNode);
+ collectDirectives(compileNode, directives, tempTemplateAttrs);
+ mergeTemplateAttributes(tAttrs, tempTemplateAttrs);
+ } else {
+ compileNode = beforeTemplateCompileNode;
+ $compileNode.html(content);
+ }
+
+ directives.unshift(derivedSyncDirective);
+ afterTemplateNodeLinkFn = applyDirectivesToNode(directives, compileNode, tAttrs, childTranscludeFn);
+ afterTemplateChildLinkFn = compileNodes($compileNode[0].childNodes, childTranscludeFn);
+
+
+ while(linkQueue.length) {
+ var controller = linkQueue.pop(),
+ linkRootElement = linkQueue.pop(),
+ beforeTemplateLinkNode = linkQueue.pop(),
+ scope = linkQueue.pop(),
+ linkNode = compileNode;
+
+ if (beforeTemplateLinkNode !== beforeTemplateCompileNode) {
+ // it was cloned therefore we have to clone as well.
+ linkNode = JQLiteClone(compileNode);
+ replaceWith(linkRootElement, jqLite(beforeTemplateLinkNode), linkNode);
+ }
+
+ afterTemplateNodeLinkFn(function() {
+ beforeTemplateNodeLinkFn(afterTemplateChildLinkFn, scope, linkNode, $rootElement, controller);
+ }, scope, linkNode, $rootElement, controller);
+ }
+ linkQueue = null;
+ }).
+ error(function(response, code, headers, config) {
+ throw Error('Failed to load template: ' + config.url);
+ });
+
+ return function delayedNodeLinkFn(ignoreChildLinkFn, scope, node, rootElement, controller) {
+ if (linkQueue) {
+ linkQueue.push(scope);
+ linkQueue.push(node);
+ linkQueue.push(rootElement);
+ linkQueue.push(controller);
+ } else {
+ afterTemplateNodeLinkFn(function() {
+ beforeTemplateNodeLinkFn(afterTemplateChildLinkFn, scope, node, rootElement, controller);
+ }, scope, node, rootElement, controller);
+ }
+ };
+ }
+
+
+ /**
+ * Sorting function for bound directives.
+ */
+ function byPriority(a, b) {
+ return b.priority - a.priority;
+ }
+
+
+ function assertNoDuplicate(what, previousDirective, directive, element) {
+ if (previousDirective) {
+ throw Error('Multiple directives [' + previousDirective.name + ', ' +
+ directive.name + '] asking for ' + what + ' on: ' + startingTag(element));
+ }
+ }
+
+
+ function addTextInterpolateDirective(directives, text) {
+ var interpolateFn = $interpolate(text, true);
+ if (interpolateFn) {
+ directives.push({
+ priority: 0,
+ compile: valueFn(function textInterpolateLinkFn(scope, node) {
+ var parent = node.parent(),
+ bindings = parent.data('$binding') || [];
+ bindings.push(interpolateFn);
+ safeAddClass(parent.data('$binding', bindings), 'ng-binding');
+ scope.$watch(interpolateFn, function interpolateFnWatchAction(value) {
+ node[0].nodeValue = value;
+ });
+ })
+ });
+ }
+ }
+
+
+ function addAttrInterpolateDirective(node, directives, value, name) {
+ var interpolateFn = $interpolate(value, true);
+
+ // no interpolation found -> ignore
+ if (!interpolateFn) return;
+
+
+ directives.push({
+ priority: 100,
+ compile: valueFn(function attrInterpolateLinkFn(scope, element, attr) {
+ var $$observers = (attr.$$observers || (attr.$$observers = {}));
+
+ if (name === 'class') {
+ // we need to interpolate classes again, in the case the element was replaced
+ // and therefore the two class attrs got merged - we want to interpolate the result
+ interpolateFn = $interpolate(attr[name], true);
+ }
+
+ attr[name] = undefined;
+ ($$observers[name] || ($$observers[name] = [])).$$inter = true;
+ (attr.$$observers && attr.$$observers[name].$$scope || scope).
+ $watch(interpolateFn, function interpolateFnWatchAction(value) {
+ attr.$set(name, value);
+ });
+ })
+ });
+ }
+
+
+ /**
+ * This is a special jqLite.replaceWith, which can replace items which
+ * have no parents, provided that the containing jqLite collection is provided.
+ *
+ * @param {JqLite=} $rootElement The root of the compile tree. Used so that we can replace nodes
+ * in the root of the tree.
+ * @param {JqLite} $element The jqLite element which we are going to replace. We keep the shell,
+ * but replace its DOM node reference.
+ * @param {Node} newNode The new DOM node.
+ */
+ function replaceWith($rootElement, $element, newNode) {
+ var oldNode = $element[0],
+ parent = oldNode.parentNode,
+ i, ii;
+
+ if ($rootElement) {
+ for(i = 0, ii = $rootElement.length; i < ii; i++) {
+ if ($rootElement[i] == oldNode) {
+ $rootElement[i] = newNode;
+ break;
+ }
+ }
+ }
+
+ if (parent) {
+ parent.replaceChild(newNode, oldNode);
+ }
+
+ newNode[jqLite.expando] = oldNode[jqLite.expando];
+ $element[0] = newNode;
+ }
+ }];
+}
+
+var PREFIX_REGEXP = /^(x[\:\-_]|data[\:\-_])/i;
+/**
+ * Converts all accepted directives format into proper directive name.
+ * All of these will become 'myDirective':
+ * my:DiRective
+ * my-directive
+ * x-my-directive
+ * data-my:directive
+ *
+ * Also there is special case for Moz prefix starting with upper case letter.
+ * @param name Name to normalize
+ */
+function directiveNormalize(name) {
+ return camelCase(name.replace(PREFIX_REGEXP, ''));
+}
+
+/**
+ * @ngdoc object
+ * @name ng.$compile.directive.Attributes
+ * @description
+ *
+ * A shared object between directive compile / linking functions which contains normalized DOM element
+ * attributes. The the values reflect current binding state `{{ }}`. The normalization is needed
+ * since all of these are treated as equivalent in Angular:
+ *
+ * <span ng:bind="a" ng-bind="a" data-ng-bind="a" x-ng-bind="a">
+ */
+
+/**
+ * @ngdoc property
+ * @name ng.$compile.directive.Attributes#$attr
+ * @propertyOf ng.$compile.directive.Attributes
+ * @returns {object} A map of DOM element attribute names to the normalized name. This is
+ * needed to do reverse lookup from normalized name back to actual name.
+ */
+
+
+/**
+ * @ngdoc function
+ * @name ng.$compile.directive.Attributes#$set
+ * @methodOf ng.$compile.directive.Attributes
+ * @function
+ *
+ * @description
+ * Set DOM element attribute value.
+ *
+ *
+ * @param {string} name Normalized element attribute name of the property to modify. The name is
+ * revers translated using the {@link ng.$compile.directive.Attributes#$attr $attr}
+ * property to the original name.
+ * @param {string} value Value to set the attribute to.
+ */
+
+
+
+/**
+ * Closure compiler type information
+ */
+
+function nodesetLinkingFn(
+ /* angular.Scope */ scope,
+ /* NodeList */ nodeList,
+ /* Element */ rootElement,
+ /* function(Function) */ boundTranscludeFn
+){}
+
+function directiveLinkingFn(
+ /* nodesetLinkingFn */ nodesetLinkingFn,
+ /* angular.Scope */ scope,
+ /* Node */ node,
+ /* Element */ rootElement,
+ /* function(Function) */ boundTranscludeFn
+){}
+
+/**
+ * @ngdoc object
+ * @name ng.$controllerProvider
+ * @description
+ * The {@link ng.$controller $controller service} is used by Angular to create new
+ * controllers.
+ *
+ * This provider allows controller registration via the
+ * {@link ng.$controllerProvider#register register} method.
+ */
+function $ControllerProvider() {
+ var controllers = {};
+
+
+ /**
+ * @ngdoc function
+ * @name ng.$controllerProvider#register
+ * @methodOf ng.$controllerProvider
+ * @param {string} name Controller name
+ * @param {Function|Array} constructor Controller constructor fn (optionally decorated with DI
+ * annotations in the array notation).
+ */
+ this.register = function(name, constructor) {
+ if (isObject(name)) {
+ extend(controllers, name)
+ } else {
+ controllers[name] = constructor;
+ }
+ };
+
+
+ this.$get = ['$injector', '$window', function($injector, $window) {
+
+ /**
+ * @ngdoc function
+ * @name ng.$controller
+ * @requires $injector
+ *
+ * @param {Function|string} constructor If called with a function then it's considered to be the
+ * controller constructor function. Otherwise it's considered to be a string which is used
+ * to retrieve the controller constructor using the following steps:
+ *
+ * * check if a controller with given name is registered via `$controllerProvider`
+ * * check if evaluating the string on the current scope returns a constructor
+ * * check `window[constructor]` on the global `window` object
+ *
+ * @param {Object} locals Injection locals for Controller.
+ * @return {Object} Instance of given controller.
+ *
+ * @description
+ * `$controller` service is responsible for instantiating controllers.
+ *
+ * It's just a simple call to {@link AUTO.$injector $injector}, but extracted into
+ * a service, so that one can override this service with {@link https://gist.github.com/1649788
+ * BC version}.
+ */
+ return function(constructor, locals) {
+ if(isString(constructor)) {
+ var name = constructor;
+ constructor = controllers.hasOwnProperty(name)
+ ? controllers[name]
+ : getter(locals.$scope, name, true) || getter($window, name, true);
+
+ assertArgFn(constructor, name, true);
+ }
+
+ return $injector.instantiate(constructor, locals);
+ };
+ }];
+}
+
+/**
+ * @ngdoc object
+ * @name ng.$document
+ * @requires $window
+ *
+ * @description
+ * A {@link angular.element jQuery (lite)}-wrapped reference to the browser's `window.document`
+ * element.
+ */
+function $DocumentProvider(){
+ this.$get = ['$window', function(window){
+ return jqLite(window.document);
+ }];
+}
+
+/**
+ * @ngdoc function
+ * @name ng.$exceptionHandler
+ * @requires $log
+ *
+ * @description
+ * Any uncaught exception in angular expressions is delegated to this service.
+ * The default implementation simply delegates to `$log.error` which logs it into
+ * the browser console.
+ *
+ * In unit tests, if `angular-mocks.js` is loaded, this service is overridden by
+ * {@link ngMock.$exceptionHandler mock $exceptionHandler} which aids in testing.
+ *
+ * @param {Error} exception Exception associated with the error.
+ * @param {string=} cause optional information about the context in which
+ * the error was thrown.
+ *
+ */
+function $ExceptionHandlerProvider() {
+ this.$get = ['$log', function($log) {
+ return function(exception, cause) {
+ $log.error.apply($log, arguments);
+ };
+ }];
+}
+
+/**
+ * @ngdoc object
+ * @name ng.$interpolateProvider
+ * @function
+ *
+ * @description
+ *
+ * Used for configuring the interpolation markup. Defaults to `{{` and `}}`.
+ */
+function $InterpolateProvider() {
+ var startSymbol = '{{';
+ var endSymbol = '}}';
+
+ /**
+ * @ngdoc method
+ * @name ng.$interpolateProvider#startSymbol
+ * @methodOf ng.$interpolateProvider
+ * @description
+ * Symbol to denote start of expression in the interpolated string. Defaults to `{{`.
+ *
+ * @param {string=} value new value to set the starting symbol to.
+ * @returns {string|self} Returns the symbol when used as getter and self if used as setter.
+ */
+ this.startSymbol = function(value){
+ if (value) {
+ startSymbol = value;
+ return this;
+ } else {
+ return startSymbol;
+ }
+ };
+
+ /**
+ * @ngdoc method
+ * @name ng.$interpolateProvider#endSymbol
+ * @methodOf ng.$interpolateProvider
+ * @description
+ * Symbol to denote the end of expression in the interpolated string. Defaults to `}}`.
+ *
+ * @param {string=} value new value to set the ending symbol to.
+ * @returns {string|self} Returns the symbol when used as getter and self if used as setter.
+ */
+ this.endSymbol = function(value){
+ if (value) {
+ endSymbol = value;
+ return this;
+ } else {
+ return endSymbol;
+ }
+ };
+
+
+ this.$get = ['$parse', function($parse) {
+ var startSymbolLength = startSymbol.length,
+ endSymbolLength = endSymbol.length;
+
+ /**
+ * @ngdoc function
+ * @name ng.$interpolate
+ * @function
+ *
+ * @requires $parse
+ *
+ * @description
+ *
+ * Compiles a string with markup into an interpolation function. This service is used by the
+ * HTML {@link ng.$compile $compile} service for data binding. See
+ * {@link ng.$interpolateProvider $interpolateProvider} for configuring the
+ * interpolation markup.
+ *
+ *
+ <pre>
+ var $interpolate = ...; // injected
+ var exp = $interpolate('Hello {{name}}!');
+ expect(exp({name:'Angular'}).toEqual('Hello Angular!');
+ </pre>
+ *
+ *
+ * @param {string} text The text with markup to interpolate.
+ * @param {boolean=} mustHaveExpression if set to true then the interpolation string must have
+ * embedded expression in order to return an interpolation function. Strings with no
+ * embedded expression will return null for the interpolation function.
+ * @returns {function(context)} an interpolation function which is used to compute the interpolated
+ * string. The function has these parameters:
+ *
+ * * `context`: an object against which any expressions embedded in the strings are evaluated
+ * against.
+ *
+ */
+ function $interpolate(text, mustHaveExpression) {
+ var startIndex,
+ endIndex,
+ index = 0,
+ parts = [],
+ length = text.length,
+ hasInterpolation = false,
+ fn,
+ exp,
+ concat = [];
+
+ while(index < length) {
+ if ( ((startIndex = text.indexOf(startSymbol, index)) != -1) &&
+ ((endIndex = text.indexOf(endSymbol, startIndex + startSymbolLength)) != -1) ) {
+ (index != startIndex) && parts.push(text.substring(index, startIndex));
+ parts.push(fn = $parse(exp = text.substring(startIndex + startSymbolLength, endIndex)));
+ fn.exp = exp;
+ index = endIndex + endSymbolLength;
+ hasInterpolation = true;
+ } else {
+ // we did not find anything, so we have to add the remainder to the parts array
+ (index != length) && parts.push(text.substring(index));
+ index = length;
+ }
+ }
+
+ if (!(length = parts.length)) {
+ // we added, nothing, must have been an empty string.
+ parts.push('');
+ length = 1;
+ }
+
+ if (!mustHaveExpression || hasInterpolation) {
+ concat.length = length;
+ fn = function(context) {
+ for(var i = 0, ii = length, part; i<ii; i++) {
+ if (typeof (part = parts[i]) == 'function') {
+ part = part(context);
+ if (part == null || part == undefined) {
+ part = '';
+ } else if (typeof part != 'string') {
+ part = toJson(part);
+ }
+ }
+ concat[i] = part;
+ }
+ return concat.join('');
+ };
+ fn.exp = text;
+ fn.parts = parts;
+ return fn;
+ }
+ }
+
+
+ /**
+ * @ngdoc method
+ * @name ng.$interpolate#startSymbol
+ * @methodOf ng.$interpolate
+ * @description
+ * Symbol to denote the start of expression in the interpolated string. Defaults to `{{`.
+ *
+ * Use {@link ng.$interpolateProvider#startSymbol $interpolateProvider#startSymbol} to change
+ * the symbol.
+ *
+ * @returns {string} start symbol.
+ */
+ $interpolate.startSymbol = function() {
+ return startSymbol;
+ }
+
+
+ /**
+ * @ngdoc method
+ * @name ng.$interpolate#endSymbol
+ * @methodOf ng.$interpolate
+ * @description
+ * Symbol to denote the end of expression in the interpolated string. Defaults to `}}`.
+ *
+ * Use {@link ng.$interpolateProvider#endSymbol $interpolateProvider#endSymbol} to change
+ * the symbol.
+ *
+ * @returns {string} start symbol.
+ */
+ $interpolate.endSymbol = function() {
+ return endSymbol;
+ }
+
+ return $interpolate;
+ }];
+}
+
+var URL_MATCH = /^([^:]+):\/\/(\w+:{0,1}\w*@)?(\{?[\w\.-]*\}?)(:([0-9]+))?(\/[^\?#]*)?(\?([^#]*))?(#(.*))?$/,
+ PATH_MATCH = /^([^\?#]*)?(\?([^#]*))?(#(.*))?$/,
+ HASH_MATCH = PATH_MATCH,
+ DEFAULT_PORTS = {'http': 80, 'https': 443, 'ftp': 21};
+
+
+/**
+ * Encode path using encodeUriSegment, ignoring forward slashes
+ *
+ * @param {string} path Path to encode
+ * @returns {string}
+ */
+function encodePath(path) {
+ var segments = path.split('/'),
+ i = segments.length;
+
+ while (i--) {
+ segments[i] = encodeUriSegment(segments[i]);
+ }
+
+ return segments.join('/');
+}
+
+function stripHash(url) {
+ return url.split('#')[0];
+}
+
+
+function matchUrl(url, obj) {
+ var match = URL_MATCH.exec(url);
+
+ match = {
+ protocol: match[1],
+ host: match[3],
+ port: int(match[5]) || DEFAULT_PORTS[match[1]] || null,
+ path: match[6] || '/',
+ search: match[8],
+ hash: match[10]
+ };
+
+ if (obj) {
+ obj.$$protocol = match.protocol;
+ obj.$$host = match.host;
+ obj.$$port = match.port;
+ }
+
+ return match;
+}
+
+
+function composeProtocolHostPort(protocol, host, port) {
+ return protocol + '://' + host + (port == DEFAULT_PORTS[protocol] ? '' : ':' + port);
+}
+
+
+function pathPrefixFromBase(basePath) {
+ return basePath.substr(0, basePath.lastIndexOf('/'));
+}
+
+
+function convertToHtml5Url(url, basePath, hashPrefix) {
+ var match = matchUrl(url);
+
+ // already html5 url
+ if (decodeURIComponent(match.path) != basePath || isUndefined(match.hash) ||
+ match.hash.indexOf(hashPrefix) !== 0) {
+ return url;
+ // convert hashbang url -> html5 url
+ } else {
+ return composeProtocolHostPort(match.protocol, match.host, match.port) +
+ pathPrefixFromBase(basePath) + match.hash.substr(hashPrefix.length);
+ }
+}
+
+
+function convertToHashbangUrl(url, basePath, hashPrefix) {
+ var match = matchUrl(url);
+
+ // already hashbang url
+ if (decodeURIComponent(match.path) == basePath && !isUndefined(match.hash) &&
+ match.hash.indexOf(hashPrefix) === 0) {
+ return url;
+ // convert html5 url -> hashbang url
+ } else {
+ var search = match.search && '?' + match.search || '',
+ hash = match.hash && '#' + match.hash || '',
+ pathPrefix = pathPrefixFromBase(basePath),
+ path = match.path.substr(pathPrefix.length);
+
+ if (match.path.indexOf(pathPrefix) !== 0) {
+ throw Error('Invalid url "' + url + '", missing path prefix "' + pathPrefix + '" !');
+ }
+
+ return composeProtocolHostPort(match.protocol, match.host, match.port) + basePath +
+ '#' + hashPrefix + path + search + hash;
+ }
+}
+
+
+/**
+ * LocationUrl represents an url
+ * This object is exposed as $location service when HTML5 mode is enabled and supported
+ *
+ * @constructor
+ * @param {string} url HTML5 url
+ * @param {string} pathPrefix
+ */
+function LocationUrl(url, pathPrefix, appBaseUrl) {
+ pathPrefix = pathPrefix || '';
+
+ /**
+ * Parse given html5 (regular) url string into properties
+ * @param {string} newAbsoluteUrl HTML5 url
+ * @private
+ */
+ this.$$parse = function(newAbsoluteUrl) {
+ var match = matchUrl(newAbsoluteUrl, this);
+
+ if (match.path.indexOf(pathPrefix) !== 0) {
+ throw Error('Invalid url "' + newAbsoluteUrl + '", missing path prefix "' + pathPrefix + '" !');
+ }
+
+ this.$$path = decodeURIComponent(match.path.substr(pathPrefix.length));
+ this.$$search = parseKeyValue(match.search);
+ this.$$hash = match.hash && decodeURIComponent(match.hash) || '';
+
+ this.$$compose();
+ };
+
+ /**
+ * Compose url and update `absUrl` property
+ * @private
+ */
+ this.$$compose = function() {
+ var search = toKeyValue(this.$$search),
+ hash = this.$$hash ? '#' + encodeUriSegment(this.$$hash) : '';
+
+ this.$$url = encodePath(this.$$path) + (search ? '?' + search : '') + hash;
+ this.$$absUrl = composeProtocolHostPort(this.$$protocol, this.$$host, this.$$port) +
+ pathPrefix + this.$$url;
+ };
+
+
+ this.$$rewriteAppUrl = function(absoluteLinkUrl) {
+ if(absoluteLinkUrl.indexOf(appBaseUrl) == 0) {
+ return absoluteLinkUrl;
+ }
+ }
+
+
+ this.$$parse(url);
+}
+
+
+/**
+ * LocationHashbangUrl represents url
+ * This object is exposed as $location service when html5 history api is disabled or not supported
+ *
+ * @constructor
+ * @param {string} url Legacy url
+ * @param {string} hashPrefix Prefix for hash part (containing path and search)
+ */
+function LocationHashbangUrl(url, hashPrefix, appBaseUrl) {
+ var basePath;
+
+ /**
+ * Parse given hashbang url into properties
+ * @param {string} url Hashbang url
+ * @private
+ */
+ this.$$parse = function(url) {
+ var match = matchUrl(url, this);
+
+
+ if (match.hash && match.hash.indexOf(hashPrefix) !== 0) {
+ throw Error('Invalid url "' + url + '", missing hash prefix "' + hashPrefix + '" !');
+ }
+
+ basePath = match.path + (match.search ? '?' + match.search : '');
+ match = HASH_MATCH.exec((match.hash || '').substr(hashPrefix.length));
+ if (match[1]) {
+ this.$$path = (match[1].charAt(0) == '/' ? '' : '/') + decodeURIComponent(match[1]);
+ } else {
+ this.$$path = '';
+ }
+
+ this.$$search = parseKeyValue(match[3]);
+ this.$$hash = match[5] && decodeURIComponent(match[5]) || '';
+
+ this.$$compose();
+ };
+
+ /**
+ * Compose hashbang url and update `absUrl` property
+ * @private
+ */
+ this.$$compose = function() {
+ var search = toKeyValue(this.$$search),
+ hash = this.$$hash ? '#' + encodeUriSegment(this.$$hash) : '';
+
+ this.$$url = encodePath(this.$$path) + (search ? '?' + search : '') + hash;
+ this.$$absUrl = composeProtocolHostPort(this.$$protocol, this.$$host, this.$$port) +
+ basePath + (this.$$url ? '#' + hashPrefix + this.$$url : '');
+ };
+
+ this.$$rewriteAppUrl = function(absoluteLinkUrl) {
+ if(absoluteLinkUrl.indexOf(appBaseUrl) == 0) {
+ return absoluteLinkUrl;
+ }
+ }
+
+
+ this.$$parse(url);
+}
+
+
+LocationUrl.prototype = {
+
+ /**
+ * Has any change been replacing ?
+ * @private
+ */
+ $$replace: false,
+
+ /**
+ * @ngdoc method
+ * @name ng.$location#absUrl
+ * @methodOf ng.$location
+ *
+ * @description
+ * This method is getter only.
+ *
+ * Return full url representation with all segments encoded according to rules specified in
+ * {@link http://www.ietf.org/rfc/rfc3986.txt RFC 3986}.
+ *
+ * @return {string} full url
+ */
+ absUrl: locationGetter('$$absUrl'),
+
+ /**
+ * @ngdoc method
+ * @name ng.$location#url
+ * @methodOf ng.$location
+ *
+ * @description
+ * This method is getter / setter.
+ *
+ * Return url (e.g. `/path?a=b#hash`) when called without any parameter.
+ *
+ * Change path, search and hash, when called with parameter and return `$location`.
+ *
+ * @param {string=} url New url without base prefix (e.g. `/path?a=b#hash`)
+ * @return {string} url
+ */
+ url: function(url, replace) {
+ if (isUndefined(url))
+ return this.$$url;
+
+ var match = PATH_MATCH.exec(url);
+ if (match[1]) this.path(decodeURIComponent(match[1]));
+ if (match[2] || match[1]) this.search(match[3] || '');
+ this.hash(match[5] || '', replace);
+
+ return this;
+ },
+
+ /**
+ * @ngdoc method
+ * @name ng.$location#protocol
+ * @methodOf ng.$location
+ *
+ * @description
+ * This method is getter only.
+ *
+ * Return protocol of current url.
+ *
+ * @return {string} protocol of current url
+ */
+ protocol: locationGetter('$$protocol'),
+
+ /**
+ * @ngdoc method
+ * @name ng.$location#host
+ * @methodOf ng.$location
+ *
+ * @description
+ * This method is getter only.
+ *
+ * Return host of current url.
+ *
+ * @return {string} host of current url.
+ */
+ host: locationGetter('$$host'),
+
+ /**
+ * @ngdoc method
+ * @name ng.$location#port
+ * @methodOf ng.$location
+ *
+ * @description
+ * This method is getter only.
+ *
+ * Return port of current url.
+ *
+ * @return {Number} port
+ */
+ port: locationGetter('$$port'),
+
+ /**
+ * @ngdoc method
+ * @name ng.$location#path
+ * @methodOf ng.$location
+ *
+ * @description
+ * This method is getter / setter.
+ *
+ * Return path of current url when called without any parameter.
+ *
+ * Change path when called with parameter and return `$location`.
+ *
+ * Note: Path should always begin with forward slash (/), this method will add the forward slash
+ * if it is missing.
+ *
+ * @param {string=} path New path
+ * @return {string} path
+ */
+ path: locationGetterSetter('$$path', function(path) {
+ return path.charAt(0) == '/' ? path : '/' + path;
+ }),
+
+ /**
+ * @ngdoc method
+ * @name ng.$location#search
+ * @methodOf ng.$location
+ *
+ * @description
+ * This method is getter / setter.
+ *
+ * Return search part (as object) of current url when called without any parameter.
+ *
+ * Change search part when called with parameter and return `$location`.
+ *
+ * @param {string|object<string,string>=} search New search params - string or hash object
+ * @param {string=} paramValue If `search` is a string, then `paramValue` will override only a
+ * single search parameter. If the value is `null`, the parameter will be deleted.
+ *
+ * @return {string} search
+ */
+ search: function(search, paramValue) {
+ if (isUndefined(search))
+ return this.$$search;
+
+ if (isDefined(paramValue)) {
+ if (paramValue === null) {
+ delete this.$$search[search];
+ } else {
+ this.$$search[search] = paramValue;
+ }
+ } else {
+ this.$$search = isString(search) ? parseKeyValue(search) : search;
+ }
+
+ this.$$compose();
+ return this;
+ },
+
+ /**
+ * @ngdoc method
+ * @name ng.$location#hash
+ * @methodOf ng.$location
+ *
+ * @description
+ * This method is getter / setter.
+ *
+ * Return hash fragment when called without any parameter.
+ *
+ * Change hash fragment when called with parameter and return `$location`.
+ *
+ * @param {string=} hash New hash fragment
+ * @return {string} hash
+ */
+ hash: locationGetterSetter('$$hash', identity),
+
+ /**
+ * @ngdoc method
+ * @name ng.$location#replace
+ * @methodOf ng.$location
+ *
+ * @description
+ * If called, all changes to $location during current `$digest` will be replacing current history
+ * record, instead of adding new one.
+ */
+ replace: function() {
+ this.$$replace = true;
+ return this;
+ }
+};
+
+LocationHashbangUrl.prototype = inherit(LocationUrl.prototype);
+
+function LocationHashbangInHtml5Url(url, hashPrefix, appBaseUrl, baseExtra) {
+ LocationHashbangUrl.apply(this, arguments);
+
+
+ this.$$rewriteAppUrl = function(absoluteLinkUrl) {
+ if (absoluteLinkUrl.indexOf(appBaseUrl) == 0) {
+ return appBaseUrl + baseExtra + '#' + hashPrefix + absoluteLinkUrl.substr(appBaseUrl.length);
+ }
+ }
+}
+
+LocationHashbangInHtml5Url.prototype = inherit(LocationHashbangUrl.prototype);
+
+function locationGetter(property) {
+ return function() {
+ return this[property];
+ };
+}
+
+
+function locationGetterSetter(property, preprocess) {
+ return function(value) {
+ if (isUndefined(value))
+ return this[property];
+
+ this[property] = preprocess(value);
+ this.$$compose();
+
+ return this;
+ };
+}
+
+
+/**
+ * @ngdoc object
+ * @name ng.$location
+ *
+ * @requires $browser
+ * @requires $sniffer
+ * @requires $rootElement
+ *
+ * @description
+ * The $location service parses the URL in the browser address bar (based on the
+ * {@link https://developer.mozilla.org/en/window.location window.location}) and makes the URL
+ * available to your application. Changes to the URL in the address bar are reflected into
+ * $location service and changes to $location are reflected into the browser address bar.
+ *
+ * **The $location service:**
+ *
+ * - Exposes the current URL in the browser address bar, so you can
+ * - Watch and observe the URL.
+ * - Change the URL.
+ * - Synchronizes the URL with the browser when the user
+ * - Changes the address bar.
+ * - Clicks the back or forward button (or clicks a History link).
+ * - Clicks on a link.
+ * - Represents the URL object as a set of methods (protocol, host, port, path, search, hash).
+ *
+ * For more information see {@link guide/dev_guide.services.$location Developer Guide: Angular
+ * Services: Using $location}
+ */
+
+/**
+ * @ngdoc object
+ * @name ng.$locationProvider
+ * @description
+ * Use the `$locationProvider` to configure how the application deep linking paths are stored.
+ */
+function $LocationProvider(){
+ var hashPrefix = '',
+ html5Mode = false;
+
+ /**
+ * @ngdoc property
+ * @name ng.$locationProvider#hashPrefix
+ * @methodOf ng.$locationProvider
+ * @description
+ * @param {string=} prefix Prefix for hash part (containing path and search)
+ * @returns {*} current value if used as getter or itself (chaining) if used as setter
+ */
+ this.hashPrefix = function(prefix) {
+ if (isDefined(prefix)) {
+ hashPrefix = prefix;
+ return this;
+ } else {
+ return hashPrefix;
+ }
+ };
+
+ /**
+ * @ngdoc property
+ * @name ng.$locationProvider#html5Mode
+ * @methodOf ng.$locationProvider
+ * @description
+ * @param {string=} mode Use HTML5 strategy if available.
+ * @returns {*} current value if used as getter or itself (chaining) if used as setter
+ */
+ this.html5Mode = function(mode) {
+ if (isDefined(mode)) {
+ html5Mode = mode;
+ return this;
+ } else {
+ return html5Mode;
+ }
+ };
+
+ this.$get = ['$rootScope', '$browser', '$sniffer', '$rootElement',
+ function( $rootScope, $browser, $sniffer, $rootElement) {
+ var $location,
+ basePath,
+ pathPrefix,
+ initUrl = $browser.url(),
+ initUrlParts = matchUrl(initUrl),
+ appBaseUrl;
+
+ if (html5Mode) {
+ basePath = $browser.baseHref() || '/';
+ pathPrefix = pathPrefixFromBase(basePath);
+ appBaseUrl =
+ composeProtocolHostPort(initUrlParts.protocol, initUrlParts.host, initUrlParts.port) +
+ pathPrefix + '/';
+
+ if ($sniffer.history) {
+ $location = new LocationUrl(
+ convertToHtml5Url(initUrl, basePath, hashPrefix),
+ pathPrefix, appBaseUrl);
+ } else {
+ $location = new LocationHashbangInHtml5Url(
+ convertToHashbangUrl(initUrl, basePath, hashPrefix),
+ hashPrefix, appBaseUrl, basePath.substr(pathPrefix.length + 1));
+ }
+ } else {
+ appBaseUrl =
+ composeProtocolHostPort(initUrlParts.protocol, initUrlParts.host, initUrlParts.port) +
+ (initUrlParts.path || '') +
+ (initUrlParts.search ? ('?' + initUrlParts.search) : '') +
+ '#' + hashPrefix + '/';
+
+ $location = new LocationHashbangUrl(initUrl, hashPrefix, appBaseUrl);
+ }
+
+ $rootElement.bind('click', function(event) {
+ // TODO(vojta): rewrite link when opening in new tab/window (in legacy browser)
+ // currently we open nice url link and redirect then
+
+ if (event.ctrlKey || event.metaKey || event.which == 2) return;
+
+ var elm = jqLite(event.target);
+
+ // traverse the DOM up to find first A tag
+ while (lowercase(elm[0].nodeName) !== 'a') {
+ // ignore rewriting if no A tag (reached root element, or no parent - removed from document)
+ if (elm[0] === $rootElement[0] || !(elm = elm.parent())[0]) return;
+ }
+
+ var absHref = elm.prop('href'),
+ rewrittenUrl = $location.$$rewriteAppUrl(absHref);
+
+ if (absHref && !elm.attr('target') && rewrittenUrl) {
+ // update location manually
+ $location.$$parse(rewrittenUrl);
+ $rootScope.$apply();
+ event.preventDefault();
+ // hack to work around FF6 bug 684208 when scenario runner clicks on links
+ window.angular['ff-684208-preventDefault'] = true;
+ }
+ });
+
+
+ // rewrite hashbang url <> html5 url
+ if ($location.absUrl() != initUrl) {
+ $browser.url($location.absUrl(), true);
+ }
+
+ // update $location when $browser url changes
+ $browser.onUrlChange(function(newUrl) {
+ if ($location.absUrl() != newUrl) {
+ if ($rootScope.$broadcast('$locationChangeStart', newUrl, $location.absUrl()).defaultPrevented) {
+ $browser.url($location.absUrl());
+ return;
+ }
+ $rootScope.$evalAsync(function() {
+ var oldUrl = $location.absUrl();
+
+ $location.$$parse(newUrl);
+ afterLocationChange(oldUrl);
+ });
+ if (!$rootScope.$$phase) $rootScope.$digest();
+ }
+ });
+
+ // update browser
+ var changeCounter = 0;
+ $rootScope.$watch(function $locationWatch() {
+ var oldUrl = $browser.url();
+ var currentReplace = $location.$$replace;
+
+ if (!changeCounter || oldUrl != $location.absUrl()) {
+ changeCounter++;
+ $rootScope.$evalAsync(function() {
+ if ($rootScope.$broadcast('$locationChangeStart', $location.absUrl(), oldUrl).
+ defaultPrevented) {
+ $location.$$parse(oldUrl);
+ } else {
+ $browser.url($location.absUrl(), currentReplace);
+ afterLocationChange(oldUrl);
+ }
+ });
+ }
+ $location.$$replace = false;
+
+ return changeCounter;
+ });
+
+ return $location;
+
+ function afterLocationChange(oldUrl) {
+ $rootScope.$broadcast('$locationChangeSuccess', $location.absUrl(), oldUrl);
+ }
+}];
+}
+
+/**
+ * @ngdoc object
+ * @name ng.$log
+ * @requires $window
+ *
+ * @description
+ * Simple service for logging. Default implementation writes the message
+ * into the browser's console (if present).
+ *
+ * The main purpose of this service is to simplify debugging and troubleshooting.
+ *
+ * @example
+ <example>
+ <file name="script.js">
+ function LogCtrl($scope, $log) {
+ $scope.$log = $log;
+ $scope.message = 'Hello World!';
+ }
+ </file>
+ <file name="index.html">
+ <div ng-controller="LogCtrl">
+ <p>Reload this page with open console, enter text and hit the log button...</p>
+ Message:
+ <input type="text" ng-model="message"/>
+ <button ng-click="$log.log(message)">log</button>
+ <button ng-click="$log.warn(message)">warn</button>
+ <button ng-click="$log.info(message)">info</button>
+ <button ng-click="$log.error(message)">error</button>
+ </div>
+ </file>
+ </example>
+ */
+
+function $LogProvider(){
+ this.$get = ['$window', function($window){
+ return {
+ /**
+ * @ngdoc method
+ * @name ng.$log#log
+ * @methodOf ng.$log
+ *
+ * @description
+ * Write a log message
+ */
+ log: consoleLog('log'),
+
+ /**
+ * @ngdoc method
+ * @name ng.$log#warn
+ * @methodOf ng.$log
+ *
+ * @description
+ * Write a warning message
+ */
+ warn: consoleLog('warn'),
+
+ /**
+ * @ngdoc method
+ * @name ng.$log#info
+ * @methodOf ng.$log
+ *
+ * @description
+ * Write an information message
+ */
+ info: consoleLog('info'),
+
+ /**
+ * @ngdoc method
+ * @name ng.$log#error
+ * @methodOf ng.$log
+ *
+ * @description
+ * Write an error message
+ */
+ error: consoleLog('error')
+ };
+
+ function formatError(arg) {
+ if (arg instanceof Error) {
+ if (arg.stack) {
+ arg = (arg.message && arg.stack.indexOf(arg.message) === -1)
+ ? 'Error: ' + arg.message + '\n' + arg.stack
+ : arg.stack;
+ } else if (arg.sourceURL) {
+ arg = arg.message + '\n' + arg.sourceURL + ':' + arg.line;
+ }
+ }
+ return arg;
+ }
+
+ function consoleLog(type) {
+ var console = $window.console || {},
+ logFn = console[type] || console.log || noop;
+
+ if (logFn.apply) {
+ return function() {
+ var args = [];
+ forEach(arguments, function(arg) {
+ args.push(formatError(arg));
+ });
+ return logFn.apply(console, args);
+ };
+ }
+
+ // we are IE which either doesn't have window.console => this is noop and we do nothing,
+ // or we are IE where console.log doesn't have apply so we log at least first 2 args
+ return function(arg1, arg2) {
+ logFn(arg1, arg2);
+ }
+ }
+ }];
+}
+
+var OPERATORS = {
+ 'null':function(){return null;},
+ 'true':function(){return true;},
+ 'false':function(){return false;},
+ undefined:noop,
+ '+':function(self, locals, a,b){
+ a=a(self, locals); b=b(self, locals);
+ if (isDefined(a)) {
+ if (isDefined(b)) {
+ return a + b;
+ }
+ return a;
+ }
+ return isDefined(b)?b:undefined;},
+ '-':function(self, locals, a,b){a=a(self, locals); b=b(self, locals); return (isDefined(a)?a:0)-(isDefined(b)?b:0);},
+ '*':function(self, locals, a,b){return a(self, locals)*b(self, locals);},
+ '/':function(self, locals, a,b){return a(self, locals)/b(self, locals);},
+ '%':function(self, locals, a,b){return a(self, locals)%b(self, locals);},
+ '^':function(self, locals, a,b){return a(self, locals)^b(self, locals);},
+ '=':noop,
+ '==':function(self, locals, a,b){return a(self, locals)==b(self, locals);},
+ '!=':function(self, locals, a,b){return a(self, locals)!=b(self, locals);},
+ '<':function(self, locals, a,b){return a(self, locals)<b(self, locals);},
+ '>':function(self, locals, a,b){return a(self, locals)>b(self, locals);},
+ '<=':function(self, locals, a,b){return a(self, locals)<=b(self, locals);},
+ '>=':function(self, locals, a,b){return a(self, locals)>=b(self, locals);},
+ '&&':function(self, locals, a,b){return a(self, locals)&&b(self, locals);},
+ '||':function(self, locals, a,b){return a(self, locals)||b(self, locals);},
+ '&':function(self, locals, a,b){return a(self, locals)&b(self, locals);},
+// '|':function(self, locals, a,b){return a|b;},
+ '|':function(self, locals, a,b){return b(self, locals)(self, locals, a(self, locals));},
+ '!':function(self, locals, a){return !a(self, locals);}
+};
+var ESCAPE = {"n":"\n", "f":"\f", "r":"\r", "t":"\t", "v":"\v", "'":"'", '"':'"'};
+
+function lex(text, csp){
+ var tokens = [],
+ token,
+ index = 0,
+ json = [],
+ ch,
+ lastCh = ':'; // can start regexp
+
+ while (index < text.length) {
+ ch = text.charAt(index);
+ if (is('"\'')) {
+ readString(ch);
+ } else if (isNumber(ch) || is('.') && isNumber(peek())) {
+ readNumber();
+ } else if (isIdent(ch)) {
+ readIdent();
+ // identifiers can only be if the preceding char was a { or ,
+ if (was('{,') && json[0]=='{' &&
+ (token=tokens[tokens.length-1])) {
+ token.json = token.text.indexOf('.') == -1;
+ }
+ } else if (is('(){}[].,;:')) {
+ tokens.push({
+ index:index,
+ text:ch,
+ json:(was(':[,') && is('{[')) || is('}]:,')
+ });
+ if (is('{[')) json.unshift(ch);
+ if (is('}]')) json.shift();
+ index++;
+ } else if (isWhitespace(ch)) {
+ index++;
+ continue;
+ } else {
+ var ch2 = ch + peek(),
+ fn = OPERATORS[ch],
+ fn2 = OPERATORS[ch2];
+ if (fn2) {
+ tokens.push({index:index, text:ch2, fn:fn2});
+ index += 2;
+ } else if (fn) {
+ tokens.push({index:index, text:ch, fn:fn, json: was('[,:') && is('+-')});
+ index += 1;
+ } else {
+ throwError("Unexpected next character ", index, index+1);
+ }
+ }
+ lastCh = ch;
+ }
+ return tokens;
+
+ function is(chars) {
+ return chars.indexOf(ch) != -1;
+ }
+
+ function was(chars) {
+ return chars.indexOf(lastCh) != -1;
+ }
+
+ function peek() {
+ return index + 1 < text.length ? text.charAt(index + 1) : false;
+ }
+ function isNumber(ch) {
+ return '0' <= ch && ch <= '9';
+ }
+ function isWhitespace(ch) {
+ return ch == ' ' || ch == '\r' || ch == '\t' ||
+ ch == '\n' || ch == '\v' || ch == '\u00A0'; // IE treats non-breaking space as \u00A0
+ }
+ function isIdent(ch) {
+ return 'a' <= ch && ch <= 'z' ||
+ 'A' <= ch && ch <= 'Z' ||
+ '_' == ch || ch == '$';
+ }
+ function isExpOperator(ch) {
+ return ch == '-' || ch == '+' || isNumber(ch);
+ }
+
+ function throwError(error, start, end) {
+ end = end || index;
+ throw Error("Lexer Error: " + error + " at column" +
+ (isDefined(start)
+ ? "s " + start + "-" + index + " [" + text.substring(start, end) + "]"
+ : " " + end) +
+ " in expression [" + text + "].");
+ }
+
+ function readNumber() {
+ var number = "";
+ var start = index;
+ while (index < text.length) {
+ var ch = lowercase(text.charAt(index));
+ if (ch == '.' || isNumber(ch)) {
+ number += ch;
+ } else {
+ var peekCh = peek();
+ if (ch == 'e' && isExpOperator(peekCh)) {
+ number += ch;
+ } else if (isExpOperator(ch) &&
+ peekCh && isNumber(peekCh) &&
+ number.charAt(number.length - 1) == 'e') {
+ number += ch;
+ } else if (isExpOperator(ch) &&
+ (!peekCh || !isNumber(peekCh)) &&
+ number.charAt(number.length - 1) == 'e') {
+ throwError('Invalid exponent');
+ } else {
+ break;
+ }
+ }
+ index++;
+ }
+ number = 1 * number;
+ tokens.push({index:start, text:number, json:true,
+ fn:function() {return number;}});
+ }
+ function readIdent() {
+ var ident = "",
+ start = index,
+ lastDot, peekIndex, methodName, ch;
+
+ while (index < text.length) {
+ ch = text.charAt(index);
+ if (ch == '.' || isIdent(ch) || isNumber(ch)) {
+ if (ch == '.') lastDot = index;
+ ident += ch;
+ } else {
+ break;
+ }
+ index++;
+ }
+
+ //check if this is not a method invocation and if it is back out to last dot
+ if (lastDot) {
+ peekIndex = index;
+ while(peekIndex < text.length) {
+ ch = text.charAt(peekIndex);
+ if (ch == '(') {
+ methodName = ident.substr(lastDot - start + 1);
+ ident = ident.substr(0, lastDot - start);
+ index = peekIndex;
+ break;
+ }
+ if(isWhitespace(ch)) {
+ peekIndex++;
+ } else {
+ break;
+ }
+ }
+ }
+
+
+ var token = {
+ index:start,
+ text:ident
+ };
+
+ if (OPERATORS.hasOwnProperty(ident)) {
+ token.fn = token.json = OPERATORS[ident];
+ } else {
+ var getter = getterFn(ident, csp);
+ token.fn = extend(function(self, locals) {
+ return (getter(self, locals));
+ }, {
+ assign: function(self, value) {
+ return setter(self, ident, value);
+ }
+ });
+ }
+
+ tokens.push(token);
+
+ if (methodName) {
+ tokens.push({
+ index:lastDot,
+ text: '.',
+ json: false
+ });
+ tokens.push({
+ index: lastDot + 1,
+ text: methodName,
+ json: false
+ });
+ }
+ }
+
+ function readString(quote) {
+ var start = index;
+ index++;
+ var string = "";
+ var rawString = quote;
+ var escape = false;
+ while (index < text.length) {
+ var ch = text.charAt(index);
+ rawString += ch;
+ if (escape) {
+ if (ch == 'u') {
+ var hex = text.substring(index + 1, index + 5);
+ if (!hex.match(/[\da-f]{4}/i))
+ throwError( "Invalid unicode escape [\\u" + hex + "]");
+ index += 4;
+ string += String.fromCharCode(parseInt(hex, 16));
+ } else {
+ var rep = ESCAPE[ch];
+ if (rep) {
+ string += rep;
+ } else {
+ string += ch;
+ }
+ }
+ escape = false;
+ } else if (ch == '\\') {
+ escape = true;
+ } else if (ch == quote) {
+ index++;
+ tokens.push({
+ index:start,
+ text:rawString,
+ string:string,
+ json:true,
+ fn:function() { return string; }
+ });
+ return;
+ } else {
+ string += ch;
+ }
+ index++;
+ }
+ throwError("Unterminated quote", start);
+ }
+}
+
+/////////////////////////////////////////
+
+function parser(text, json, $filter, csp){
+ var ZERO = valueFn(0),
+ value,
+ tokens = lex(text, csp),
+ assignment = _assignment,
+ functionCall = _functionCall,
+ fieldAccess = _fieldAccess,
+ objectIndex = _objectIndex,
+ filterChain = _filterChain;
+
+ if(json){
+ // The extra level of aliasing is here, just in case the lexer misses something, so that
+ // we prevent any accidental execution in JSON.
+ assignment = logicalOR;
+ functionCall =
+ fieldAccess =
+ objectIndex =
+ filterChain =
+ function() { throwError("is not valid json", {text:text, index:0}); };
+ value = primary();
+ } else {
+ value = statements();
+ }
+ if (tokens.length !== 0) {
+ throwError("is an unexpected token", tokens[0]);
+ }
+ return value;
+
+ ///////////////////////////////////
+ function throwError(msg, token) {
+ throw Error("Syntax Error: Token '" + token.text +
+ "' " + msg + " at column " +
+ (token.index + 1) + " of the expression [" +
+ text + "] starting at [" + text.substring(token.index) + "].");
+ }
+
+ function peekToken() {
+ if (tokens.length === 0)
+ throw Error("Unexpected end of expression: " + text);
+ return tokens[0];
+ }
+
+ function peek(e1, e2, e3, e4) {
+ if (tokens.length > 0) {
+ var token = tokens[0];
+ var t = token.text;
+ if (t==e1 || t==e2 || t==e3 || t==e4 ||
+ (!e1 && !e2 && !e3 && !e4)) {
+ return token;
+ }
+ }
+ return false;
+ }
+
+ function expect(e1, e2, e3, e4){
+ var token = peek(e1, e2, e3, e4);
+ if (token) {
+ if (json && !token.json) {
+ throwError("is not valid json", token);
+ }
+ tokens.shift();
+ return token;
+ }
+ return false;
+ }
+
+ function consume(e1){
+ if (!expect(e1)) {
+ throwError("is unexpected, expecting [" + e1 + "]", peek());
+ }
+ }
+
+ function unaryFn(fn, right) {
+ return function(self, locals) {
+ return fn(self, locals, right);
+ };
+ }
+
+ function binaryFn(left, fn, right) {
+ return function(self, locals) {
+ return fn(self, locals, left, right);
+ };
+ }
+
+ function statements() {
+ var statements = [];
+ while(true) {
+ if (tokens.length > 0 && !peek('}', ')', ';', ']'))
+ statements.push(filterChain());
+ if (!expect(';')) {
+ // optimize for the common case where there is only one statement.
+ // TODO(size): maybe we should not support multiple statements?
+ return statements.length == 1
+ ? statements[0]
+ : function(self, locals){
+ var value;
+ for ( var i = 0; i < statements.length; i++) {
+ var statement = statements[i];
+ if (statement)
+ value = statement(self, locals);
+ }
+ return value;
+ };
+ }
+ }
+ }
+
+ function _filterChain() {
+ var left = expression();
+ var token;
+ while(true) {
+ if ((token = expect('|'))) {
+ left = binaryFn(left, token.fn, filter());
+ } else {
+ return left;
+ }
+ }
+ }
+
+ function filter() {
+ var token = expect();
+ var fn = $filter(token.text);
+ var argsFn = [];
+ while(true) {
+ if ((token = expect(':'))) {
+ argsFn.push(expression());
+ } else {
+ var fnInvoke = function(self, locals, input){
+ var args = [input];
+ for ( var i = 0; i < argsFn.length; i++) {
+ args.push(argsFn[i](self, locals));
+ }
+ return fn.apply(self, args);
+ };
+ return function() {
+ return fnInvoke;
+ };
+ }
+ }
+ }
+
+ function expression() {
+ return assignment();
+ }
+
+ function _assignment() {
+ var left = logicalOR();
+ var right;
+ var token;
+ if ((token = expect('='))) {
+ if (!left.assign) {
+ throwError("implies assignment but [" +
+ text.substring(0, token.index) + "] can not be assigned to", token);
+ }
+ right = logicalOR();
+ return function(scope, locals){
+ return left.assign(scope, right(scope, locals), locals);
+ };
+ } else {
+ return left;
+ }
+ }
+
+ function logicalOR() {
+ var left = logicalAND();
+ var token;
+ while(true) {
+ if ((token = expect('||'))) {
+ left = binaryFn(left, token.fn, logicalAND());
+ } else {
+ return left;
+ }
+ }
+ }
+
+ function logicalAND() {
+ var left = equality();
+ var token;
+ if ((token = expect('&&'))) {
+ left = binaryFn(left, token.fn, logicalAND());
+ }
+ return left;
+ }
+
+ function equality() {
+ var left = relational();
+ var token;
+ if ((token = expect('==','!='))) {
+ left = binaryFn(left, token.fn, equality());
+ }
+ return left;
+ }
+
+ function relational() {
+ var left = additive();
+ var token;
+ if ((token = expect('<', '>', '<=', '>='))) {
+ left = binaryFn(left, token.fn, relational());
+ }
+ return left;
+ }
+
+ function additive() {
+ var left = multiplicative();
+ var token;
+ while ((token = expect('+','-'))) {
+ left = binaryFn(left, token.fn, multiplicative());
+ }
+ return left;
+ }
+
+ function multiplicative() {
+ var left = unary();
+ var token;
+ while ((token = expect('*','/','%'))) {
+ left = binaryFn(left, token.fn, unary());
+ }
+ return left;
+ }
+
+ function unary() {
+ var token;
+ if (expect('+')) {
+ return primary();
+ } else if ((token = expect('-'))) {
+ return binaryFn(ZERO, token.fn, unary());
+ } else if ((token = expect('!'))) {
+ return unaryFn(token.fn, unary());
+ } else {
+ return primary();
+ }
+ }
+
+
+ function primary() {
+ var primary;
+ if (expect('(')) {
+ primary = filterChain();
+ consume(')');
+ } else if (expect('[')) {
+ primary = arrayDeclaration();
+ } else if (expect('{')) {
+ primary = object();
+ } else {
+ var token = expect();
+ primary = token.fn;
+ if (!primary) {
+ throwError("not a primary expression", token);
+ }
+ }
+
+ var next, context;
+ while ((next = expect('(', '[', '.'))) {
+ if (next.text === '(') {
+ primary = functionCall(primary, context);
+ context = null;
+ } else if (next.text === '[') {
+ context = primary;
+ primary = objectIndex(primary);
+ } else if (next.text === '.') {
+ context = primary;
+ primary = fieldAccess(primary);
+ } else {
+ throwError("IMPOSSIBLE");
+ }
+ }
+ return primary;
+ }
+
+ function _fieldAccess(object) {
+ var field = expect().text;
+ var getter = getterFn(field, csp);
+ return extend(
+ function(scope, locals, self) {
+ return getter(self || object(scope, locals), locals);
+ },
+ {
+ assign:function(scope, value, locals) {
+ return setter(object(scope, locals), field, value);
+ }
+ }
+ );
+ }
+
+ function _objectIndex(obj) {
+ var indexFn = expression();
+ consume(']');
+ return extend(
+ function(self, locals){
+ var o = obj(self, locals),
+ i = indexFn(self, locals),
+ v, p;
+
+ if (!o) return undefined;
+ v = o[i];
+ if (v && v.then) {
+ p = v;
+ if (!('$$v' in v)) {
+ p.$$v = undefined;
+ p.then(function(val) { p.$$v = val; });
+ }
+ v = v.$$v;
+ }
+ return v;
+ }, {
+ assign:function(self, value, locals){
+ return obj(self, locals)[indexFn(self, locals)] = value;
+ }
+ });
+ }
+
+ function _functionCall(fn, contextGetter) {
+ var argsFn = [];
+ if (peekToken().text != ')') {
+ do {
+ argsFn.push(expression());
+ } while (expect(','));
+ }
+ consume(')');
+ return function(scope, locals){
+ var args = [],
+ context = contextGetter ? contextGetter(scope, locals) : scope;
+
+ for ( var i = 0; i < argsFn.length; i++) {
+ args.push(argsFn[i](scope, locals));
+ }
+ var fnPtr = fn(scope, locals, context) || noop;
+ // IE stupidity!
+ return fnPtr.apply
+ ? fnPtr.apply(context, args)
+ : fnPtr(args[0], args[1], args[2], args[3], args[4]);
+ };
+ }
+
+ // This is used with json array declaration
+ function arrayDeclaration () {
+ var elementFns = [];
+ if (peekToken().text != ']') {
+ do {
+ elementFns.push(expression());
+ } while (expect(','));
+ }
+ consume(']');
+ return function(self, locals){
+ var array = [];
+ for ( var i = 0; i < elementFns.length; i++) {
+ array.push(elementFns[i](self, locals));
+ }
+ return array;
+ };
+ }
+
+ function object () {
+ var keyValues = [];
+ if (peekToken().text != '}') {
+ do {
+ var token = expect(),
+ key = token.string || token.text;
+ consume(":");
+ var value = expression();
+ keyValues.push({key:key, value:value});
+ } while (expect(','));
+ }
+ consume('}');
+ return function(self, locals){
+ var object = {};
+ for ( var i = 0; i < keyValues.length; i++) {
+ var keyValue = keyValues[i];
+ object[keyValue.key] = keyValue.value(self, locals);
+ }
+ return object;
+ };
+ }
+}
+
+//////////////////////////////////////////////////
+// Parser helper functions
+//////////////////////////////////////////////////
+
+function setter(obj, path, setValue) {
+ var element = path.split('.');
+ for (var i = 0; element.length > 1; i++) {
+ var key = element.shift();
+ var propertyObj = obj[key];
+ if (!propertyObj) {
+ propertyObj = {};
+ obj[key] = propertyObj;
+ }
+ obj = propertyObj;
+ }
+ obj[element.shift()] = setValue;
+ return setValue;
+}
+
+var getterFnCache = {};
+
+/**
+ * Implementation of the "Black Hole" variant from:
+ * - http://jsperf.com/angularjs-parse-getter/4
+ * - http://jsperf.com/path-evaluation-simplified/7
+ */
+function cspSafeGetterFn(key0, key1, key2, key3, key4) {
+ return function(scope, locals) {
+ var pathVal = (locals && locals.hasOwnProperty(key0)) ? locals : scope,
+ promise;
+
+ if (pathVal === null || pathVal === undefined) return pathVal;
+
+ pathVal = pathVal[key0];
+ if (pathVal && pathVal.then) {
+ if (!("$$v" in pathVal)) {
+ promise = pathVal;
+ promise.$$v = undefined;
+ promise.then(function(val) { promise.$$v = val; });
+ }
+ pathVal = pathVal.$$v;
+ }
+ if (!key1 || pathVal === null || pathVal === undefined) return pathVal;
+
+ pathVal = pathVal[key1];
+ if (pathVal && pathVal.then) {
+ if (!("$$v" in pathVal)) {
+ promise = pathVal;
+ promise.$$v = undefined;
+ promise.then(function(val) { promise.$$v = val; });
+ }
+ pathVal = pathVal.$$v;
+ }
+ if (!key2 || pathVal === null || pathVal === undefined) return pathVal;
+
+ pathVal = pathVal[key2];
+ if (pathVal && pathVal.then) {
+ if (!("$$v" in pathVal)) {
+ promise = pathVal;
+ promise.$$v = undefined;
+ promise.then(function(val) { promise.$$v = val; });
+ }
+ pathVal = pathVal.$$v;
+ }
+ if (!key3 || pathVal === null || pathVal === undefined) return pathVal;
+
+ pathVal = pathVal[key3];
+ if (pathVal && pathVal.then) {
+ if (!("$$v" in pathVal)) {
+ promise = pathVal;
+ promise.$$v = undefined;
+ promise.then(function(val) { promise.$$v = val; });
+ }
+ pathVal = pathVal.$$v;
+ }
+ if (!key4 || pathVal === null || pathVal === undefined) return pathVal;
+
+ pathVal = pathVal[key4];
+ if (pathVal && pathVal.then) {
+ if (!("$$v" in pathVal)) {
+ promise = pathVal;
+ promise.$$v = undefined;
+ promise.then(function(val) { promise.$$v = val; });
+ }
+ pathVal = pathVal.$$v;
+ }
+ return pathVal;
+ };
+}
+
+function getterFn(path, csp) {
+ if (getterFnCache.hasOwnProperty(path)) {
+ return getterFnCache[path];
+ }
+
+ var pathKeys = path.split('.'),
+ pathKeysLength = pathKeys.length,
+ fn;
+
+ if (csp) {
+ fn = (pathKeysLength < 6)
+ ? cspSafeGetterFn(pathKeys[0], pathKeys[1], pathKeys[2], pathKeys[3], pathKeys[4])
+ : function(scope, locals) {
+ var i = 0, val;
+ do {
+ val = cspSafeGetterFn(
+ pathKeys[i++], pathKeys[i++], pathKeys[i++], pathKeys[i++], pathKeys[i++]
+ )(scope, locals);
+
+ locals = undefined; // clear after first iteration
+ scope = val;
+ } while (i < pathKeysLength);
+ return val;
+ }
+ } else {
+ var code = 'var l, fn, p;\n';
+ forEach(pathKeys, function(key, index) {
+ code += 'if(s === null || s === undefined) return s;\n' +
+ 'l=s;\n' +
+ 's='+ (index
+ // we simply dereference 's' on any .dot notation
+ ? 's'
+ // but if we are first then we check locals first, and if so read it first
+ : '((k&&k.hasOwnProperty("' + key + '"))?k:s)') + '["' + key + '"]' + ';\n' +
+ 'if (s && s.then) {\n' +
+ ' if (!("$$v" in s)) {\n' +
+ ' p=s;\n' +
+ ' p.$$v = undefined;\n' +
+ ' p.then(function(v) {p.$$v=v;});\n' +
+ '}\n' +
+ ' s=s.$$v\n' +
+ '}\n';
+ });
+ code += 'return s;';
+ fn = Function('s', 'k', code); // s=scope, k=locals
+ fn.toString = function() { return code; };
+ }
+
+ return getterFnCache[path] = fn;
+}
+
+///////////////////////////////////
+
+/**
+ * @ngdoc function
+ * @name ng.$parse
+ * @function
+ *
+ * @description
+ *
+ * Converts Angular {@link guide/expression expression} into a function.
+ *
+ * <pre>
+ * var getter = $parse('user.name');
+ * var setter = getter.assign;
+ * var context = {user:{name:'angular'}};
+ * var locals = {user:{name:'local'}};
+ *
+ * expect(getter(context)).toEqual('angular');
+ * setter(context, 'newValue');
+ * expect(context.user.name).toEqual('newValue');
+ * expect(getter(context, locals)).toEqual('local');
+ * </pre>
+ *
+ *
+ * @param {string} expression String expression to compile.
+ * @returns {function(context, locals)} a function which represents the compiled expression:
+ *
+ * * `context` – `{object}` – an object against which any expressions embedded in the strings
+ * are evaluated against (tipically a scope object).
+ * * `locals` – `{object=}` – local variables context object, useful for overriding values in
+ * `context`.
+ *
+ * The return function also has an `assign` property, if the expression is assignable, which
+ * allows one to set values to expressions.
+ *
+ */
+function $ParseProvider() {
+ var cache = {};
+ this.$get = ['$filter', '$sniffer', function($filter, $sniffer) {
+ return function(exp) {
+ switch(typeof exp) {
+ case 'string':
+ return cache.hasOwnProperty(exp)
+ ? cache[exp]
+ : cache[exp] = parser(exp, false, $filter, $sniffer.csp);
+ case 'function':
+ return exp;
+ default:
+ return noop;
+ }
+ };
+ }];
+}
+
+/**
+ * @ngdoc service
+ * @name ng.$q
+ * @requires $rootScope
+ *
+ * @description
+ * A promise/deferred implementation inspired by [Kris Kowal's Q](https://github.com/kriskowal/q).
+ *
+ * [The CommonJS Promise proposal](http://wiki.commonjs.org/wiki/Promises) describes a promise as an
+ * interface for interacting with an object that represents the result of an action that is
+ * performed asynchronously, and may or may not be finished at any given point in time.
+ *
+ * From the perspective of dealing with error handling, deferred and promise APIs are to
+ * asynchronous programming what `try`, `catch` and `throw` keywords are to synchronous programming.
+ *
+ * <pre>
+ * // for the purpose of this example let's assume that variables `$q` and `scope` are
+ * // available in the current lexical scope (they could have been injected or passed in).
+ *
+ * function asyncGreet(name) {
+ * var deferred = $q.defer();
+ *
+ * setTimeout(function() {
+ * // since this fn executes async in a future turn of the event loop, we need to wrap
+ * // our code into an $apply call so that the model changes are properly observed.
+ * scope.$apply(function() {
+ * if (okToGreet(name)) {
+ * deferred.resolve('Hello, ' + name + '!');
+ * } else {
+ * deferred.reject('Greeting ' + name + ' is not allowed.');
+ * }
+ * });
+ * }, 1000);
+ *
+ * return deferred.promise;
+ * }
+ *
+ * var promise = asyncGreet('Robin Hood');
+ * promise.then(function(greeting) {
+ * alert('Success: ' + greeting);
+ * }, function(reason) {
+ * alert('Failed: ' + reason);
+ * });
+ * </pre>
+ *
+ * At first it might not be obvious why this extra complexity is worth the trouble. The payoff
+ * comes in the way of
+ * [guarantees that promise and deferred APIs make](https://github.com/kriskowal/uncommonjs/blob/master/promises/specific….
+ *
+ * Additionally the promise api allows for composition that is very hard to do with the
+ * traditional callback ([CPS](http://en.wikipedia.org/wiki/Continuation-passing_style)) approach.
+ * For more on this please see the [Q documentation](https://github.com/kriskowal/q) especially the
+ * section on serial or parallel joining of promises.
+ *
+ *
+ * # The Deferred API
+ *
+ * A new instance of deferred is constructed by calling `$q.defer()`.
+ *
+ * The purpose of the deferred object is to expose the associated Promise instance as well as APIs
+ * that can be used for signaling the successful or unsuccessful completion of the task.
+ *
+ * **Methods**
+ *
+ * - `resolve(value)` – resolves the derived promise with the `value`. If the value is a rejection
+ * constructed via `$q.reject`, the promise will be rejected instead.
+ * - `reject(reason)` – rejects the derived promise with the `reason`. This is equivalent to
+ * resolving it with a rejection constructed via `$q.reject`.
+ *
+ * **Properties**
+ *
+ * - promise – `{Promise}` – promise object associated with this deferred.
+ *
+ *
+ * # The Promise API
+ *
+ * A new promise instance is created when a deferred instance is created and can be retrieved by
+ * calling `deferred.promise`.
+ *
+ * The purpose of the promise object is to allow for interested parties to get access to the result
+ * of the deferred task when it completes.
+ *
+ * **Methods**
+ *
+ * - `then(successCallback, errorCallback)` – regardless of when the promise was or will be resolved
+ * or rejected, `then` calls one of the success or error callbacks asynchronously as soon as the result
+ * is available. The callbacks are called with a single argument: the result or rejection reason.
+ *
+ * This method *returns a new promise* which is resolved or rejected via the return value of the
+ * `successCallback` or `errorCallback`.
+ *
+ *
+ * # Chaining promises
+ *
+ * Because calling the `then` method of a promise returns a new derived promise, it is easily possible
+ * to create a chain of promises:
+ *
+ * <pre>
+ * promiseB = promiseA.then(function(result) {
+ * return result + 1;
+ * });
+ *
+ * // promiseB will be resolved immediately after promiseA is resolved and its value
+ * // will be the result of promiseA incremented by 1
+ * </pre>
+ *
+ * It is possible to create chains of any length and since a promise can be resolved with another
+ * promise (which will defer its resolution further), it is possible to pause/defer resolution of
+ * the promises at any point in the chain. This makes it possible to implement powerful APIs like
+ * $http's response interceptors.
+ *
+ *
+ * # Differences between Kris Kowal's Q and $q
+ *
+ * There are three main differences:
+ *
+ * - $q is integrated with the {@link ng.$rootScope.Scope} Scope model observation
+ * mechanism in angular, which means faster propagation of resolution or rejection into your
+ * models and avoiding unnecessary browser repaints, which would result in flickering UI.
+ * - $q promises are recognized by the templating engine in angular, which means that in templates
+ * you can treat promises attached to a scope as if they were the resulting values.
+ * - Q has many more features than $q, but that comes at a cost of bytes. $q is tiny, but contains
+ * all the important functionality needed for common async tasks.
+ *
+ * # Testing
+ *
+ * <pre>
+ * it('should simulate promise', inject(function($q, $rootScope) {
+ * var deferred = $q.defer();
+ * var promise = deferred.promise;
+ * var resolvedValue;
+ *
+ * promise.then(function(value) { resolvedValue = value; });
+ * expect(resolvedValue).toBeUndefined();
+ *
+ * // Simulate resolving of promise
+ * deferred.resolve(123);
+ * // Note that the 'then' function does not get called synchronously.
+ * // This is because we want the promise API to always be async, whether or not
+ * // it got called synchronously or asynchronously.
+ * expect(resolvedValue).toBeUndefined();
+ *
+ * // Propagate promise resolution to 'then' functions using $apply().
+ * $rootScope.$apply();
+ * expect(resolvedValue).toEqual(123);
+ * });
+ * </pre>
+ */
+function $QProvider() {
+
+ this.$get = ['$rootScope', '$exceptionHandler', function($rootScope, $exceptionHandler) {
+ return qFactory(function(callback) {
+ $rootScope.$evalAsync(callback);
+ }, $exceptionHandler);
+ }];
+}
+
+
+/**
+ * Constructs a promise manager.
+ *
+ * @param {function(function)} nextTick Function for executing functions in the next turn.
+ * @param {function(...*)} exceptionHandler Function into which unexpected exceptions are passed for
+ * debugging purposes.
+ * @returns {object} Promise manager.
+ */
+function qFactory(nextTick, exceptionHandler) {
+
+ /**
+ * @ngdoc
+ * @name ng.$q#defer
+ * @methodOf ng.$q
+ * @description
+ * Creates a `Deferred` object which represents a task which will finish in the future.
+ *
+ * @returns {Deferred} Returns a new instance of deferred.
+ */
+ var defer = function() {
+ var pending = [],
+ value, deferred;
+
+ deferred = {
+
+ resolve: function(val) {
+ if (pending) {
+ var callbacks = pending;
+ pending = undefined;
+ value = ref(val);
+
+ if (callbacks.length) {
+ nextTick(function() {
+ var callback;
+ for (var i = 0, ii = callbacks.length; i < ii; i++) {
+ callback = callbacks[i];
+ value.then(callback[0], callback[1]);
+ }
+ });
+ }
+ }
+ },
+
+
+ reject: function(reason) {
+ deferred.resolve(reject(reason));
+ },
+
+
+ promise: {
+ then: function(callback, errback) {
+ var result = defer();
+
+ var wrappedCallback = function(value) {
+ try {
+ result.resolve((callback || defaultCallback)(value));
+ } catch(e) {
+ result.reject(e);
+ exceptionHandler(e);
+ }
+ };
+
+ var wrappedErrback = function(reason) {
+ try {
+ result.resolve((errback || defaultErrback)(reason));
+ } catch(e) {
+ result.reject(e);
+ exceptionHandler(e);
+ }
+ };
+
+ if (pending) {
+ pending.push([wrappedCallback, wrappedErrback]);
+ } else {
+ value.then(wrappedCallback, wrappedErrback);
+ }
+
+ return result.promise;
+ }
+ }
+ };
+
+ return deferred;
+ };
+
+
+ var ref = function(value) {
+ if (value && value.then) return value;
+ return {
+ then: function(callback) {
+ var result = defer();
+ nextTick(function() {
+ result.resolve(callback(value));
+ });
+ return result.promise;
+ }
+ };
+ };
+
+
+ /**
+ * @ngdoc
+ * @name ng.$q#reject
+ * @methodOf ng.$q
+ * @description
+ * Creates a promise that is resolved as rejected with the specified `reason`. This api should be
+ * used to forward rejection in a chain of promises. If you are dealing with the last promise in
+ * a promise chain, you don't need to worry about it.
+ *
+ * When comparing deferreds/promises to the familiar behavior of try/catch/throw, think of
+ * `reject` as the `throw` keyword in JavaScript. This also means that if you "catch" an error via
+ * a promise error callback and you want to forward the error to the promise derived from the
+ * current promise, you have to "rethrow" the error by returning a rejection constructed via
+ * `reject`.
+ *
+ * <pre>
+ * promiseB = promiseA.then(function(result) {
+ * // success: do something and resolve promiseB
+ * // with the old or a new result
+ * return result;
+ * }, function(reason) {
+ * // error: handle the error if possible and
+ * // resolve promiseB with newPromiseOrValue,
+ * // otherwise forward the rejection to promiseB
+ * if (canHandle(reason)) {
+ * // handle the error and recover
+ * return newPromiseOrValue;
+ * }
+ * return $q.reject(reason);
+ * });
+ * </pre>
+ *
+ * @param {*} reason Constant, message, exception or an object representing the rejection reason.
+ * @returns {Promise} Returns a promise that was already resolved as rejected with the `reason`.
+ */
+ var reject = function(reason) {
+ return {
+ then: function(callback, errback) {
+ var result = defer();
+ nextTick(function() {
+ result.resolve((errback || defaultErrback)(reason));
+ });
+ return result.promise;
+ }
+ };
+ };
+
+
+ /**
+ * @ngdoc
+ * @name ng.$q#when
+ * @methodOf ng.$q
+ * @description
+ * Wraps an object that might be a value or a (3rd party) then-able promise into a $q promise.
+ * This is useful when you are dealing with an object that might or might not be a promise, or if
+ * the promise comes from a source that can't be trusted.
+ *
+ * @param {*} value Value or a promise
+ * @returns {Promise} Returns a promise of the passed value or promise
+ */
+ var when = function(value, callback, errback) {
+ var result = defer(),
+ done;
+
+ var wrappedCallback = function(value) {
+ try {
+ return (callback || defaultCallback)(value);
+ } catch (e) {
+ exceptionHandler(e);
+ return reject(e);
+ }
+ };
+
+ var wrappedErrback = function(reason) {
+ try {
+ return (errback || defaultErrback)(reason);
+ } catch (e) {
+ exceptionHandler(e);
+ return reject(e);
+ }
+ };
+
+ nextTick(function() {
+ ref(value).then(function(value) {
+ if (done) return;
+ done = true;
+ result.resolve(ref(value).then(wrappedCallback, wrappedErrback));
+ }, function(reason) {
+ if (done) return;
+ done = true;
+ result.resolve(wrappedErrback(reason));
+ });
+ });
+
+ return result.promise;
+ };
+
+
+ function defaultCallback(value) {
+ return value;
+ }
+
+
+ function defaultErrback(reason) {
+ return reject(reason);
+ }
+
+
+ /**
+ * @ngdoc
+ * @name ng.$q#all
+ * @methodOf ng.$q
+ * @description
+ * Combines multiple promises into a single promise that is resolved when all of the input
+ * promises are resolved.
+ *
+ * @param {Array.<Promise>} promises An array of promises.
+ * @returns {Promise} Returns a single promise that will be resolved with an array of values,
+ * each value corresponding to the promise at the same index in the `promises` array. If any of
+ * the promises is resolved with a rejection, this resulting promise will be resolved with the
+ * same rejection.
+ */
+ function all(promises) {
+ var deferred = defer(),
+ counter = promises.length,
+ results = [];
+
+ if (counter) {
+ forEach(promises, function(promise, index) {
+ ref(promise).then(function(value) {
+ if (index in results) return;
+ results[index] = value;
+ if (!(--counter)) deferred.resolve(results);
+ }, function(reason) {
+ if (index in results) return;
+ deferred.reject(reason);
+ });
+ });
+ } else {
+ deferred.resolve(results);
+ }
+
+ return deferred.promise;
+ }
+
+ return {
+ defer: defer,
+ reject: reject,
+ when: when,
+ all: all
+ };
+}
+
+/**
+ * @ngdoc object
+ * @name ng.$routeProvider
+ * @function
+ *
+ * @description
+ *
+ * Used for configuring routes. See {@link ng.$route $route} for an example.
+ */
+function $RouteProvider(){
+ var routes = {};
+
+ /**
+ * @ngdoc method
+ * @name ng.$routeProvider#when
+ * @methodOf ng.$routeProvider
+ *
+ * @param {string} path Route path (matched against `$location.path`). If `$location.path`
+ * contains redundant trailing slash or is missing one, the route will still match and the
+ * `$location.path` will be updated to add or drop the trailing slash to exactly match the
+ * route definition.
+ *
+ * `path` can contain named groups starting with a colon (`:name`). All characters up to the
+ * next slash are matched and stored in `$routeParams` under the given `name` after the route
+ * is resolved.
+ *
+ * @param {Object} route Mapping information to be assigned to `$route.current` on route
+ * match.
+ *
+ * Object properties:
+ *
+ * - `controller` – `{(string|function()=}` – Controller fn that should be associated with newly
+ * created scope or the name of a {@link angular.Module#controller registered controller}
+ * if passed as a string.
+ * - `template` – `{string=}` – html template as a string that should be used by
+ * {@link ng.directive:ngView ngView} or
+ * {@link ng.directive:ngInclude ngInclude} directives.
+ * this property takes precedence over `templateUrl`.
+ * - `templateUrl` – `{string=}` – path to an html template that should be used by
+ * {@link ng.directive:ngView ngView}.
+ * - `resolve` - `{Object.<string, function>=}` - An optional map of dependencies which should
+ * be injected into the controller. If any of these dependencies are promises, they will be
+ * resolved and converted to a value before the controller is instantiated and the
+ * `$routeChangeSuccess` event is fired. The map object is:
+ *
+ * - `key` – `{string}`: a name of a dependency to be injected into the controller.
+ * - `factory` - `{string|function}`: If `string` then it is an alias for a service.
+ * Otherwise if function, then it is {@link api/AUTO.$injector#invoke injected}
+ * and the return value is treated as the dependency. If the result is a promise, it is resolved
+ * before its value is injected into the controller. Be aware that `ngRoute.$routeParams` will
+ * still refer to the previous route within these resolve functions. Use `$route.current.params`
+ * to access the new route parameters, instead.
+ *
+ * - `redirectTo` – {(string|function())=} – value to update
+ * {@link ng.$location $location} path with and trigger route redirection.
+ *
+ * If `redirectTo` is a function, it will be called with the following parameters:
+ *
+ * - `{Object.<string>}` - route parameters extracted from the current
+ * `$location.path()` by applying the current route templateUrl.
+ * - `{string}` - current `$location.path()`
+ * - `{Object}` - current `$location.search()`
+ *
+ * The custom `redirectTo` function is expected to return a string which will be used
+ * to update `$location.path()` and `$location.search()`.
+ *
+ * - `[reloadOnSearch=true]` - {boolean=} - reload route when only $location.search()
+ * changes.
+ *
+ * If the option is set to `false` and url in the browser changes, then
+ * `$routeUpdate` event is broadcasted on the root scope.
+ *
+ * @returns {Object} self
+ *
+ * @description
+ * Adds a new route definition to the `$route` service.
+ */
+ this.when = function(path, route) {
+ routes[path] = extend({reloadOnSearch: true}, route);
+
+ // create redirection for trailing slashes
+ if (path) {
+ var redirectPath = (path[path.length-1] == '/')
+ ? path.substr(0, path.length-1)
+ : path +'/';
+
+ routes[redirectPath] = {redirectTo: path};
+ }
+
+ return this;
+ };
+
+ /**
+ * @ngdoc method
+ * @name ng.$routeProvider#otherwise
+ * @methodOf ng.$routeProvider
+ *
+ * @description
+ * Sets route definition that will be used on route change when no other route definition
+ * is matched.
+ *
+ * @param {Object} params Mapping information to be assigned to `$route.current`.
+ * @returns {Object} self
+ */
+ this.otherwise = function(params) {
+ this.when(null, params);
+ return this;
+ };
+
+
+ this.$get = ['$rootScope', '$location', '$routeParams', '$q', '$injector', '$http', '$templateCache',
+ function( $rootScope, $location, $routeParams, $q, $injector, $http, $templateCache) {
+
+ /**
+ * @ngdoc object
+ * @name ng.$route
+ * @requires $location
+ * @requires $routeParams
+ *
+ * @property {Object} current Reference to the current route definition.
+ * The route definition contains:
+ *
+ * - `controller`: The controller constructor as define in route definition.
+ * - `locals`: A map of locals which is used by {@link ng.$controller $controller} service for
+ * controller instantiation. The `locals` contain
+ * the resolved values of the `resolve` map. Additionally the `locals` also contain:
+ *
+ * - `$scope` - The current route scope.
+ * - `$template` - The current route template HTML.
+ *
+ * @property {Array.<Object>} routes Array of all configured routes.
+ *
+ * @description
+ * Is used for deep-linking URLs to controllers and views (HTML partials).
+ * It watches `$location.url()` and tries to map the path to an existing route definition.
+ *
+ * You can define routes through {@link ng.$routeProvider $routeProvider}'s API.
+ *
+ * The `$route` service is typically used in conjunction with {@link ng.directive:ngView ngView}
+ * directive and the {@link ng.$routeParams $routeParams} service.
+ *
+ * @example
+ This example shows how changing the URL hash causes the `$route` to match a route against the
+ URL, and the `ngView` pulls in the partial.
+
+ Note that this example is using {@link ng.directive:script inlined templates}
+ to get it working on jsfiddle as well.
+
+ <example module="ngView">
+ <file name="index.html">
+ <div ng-controller="MainCntl">
+ Choose:
+ <a href="Book/Moby">Moby</a> |
+ <a href="Book/Moby/ch/1">Moby: Ch1</a> |
+ <a href="Book/Gatsby">Gatsby</a> |
+ <a href="Book/Gatsby/ch/4?key=value">Gatsby: Ch4</a> |
+ <a href="Book/Scarlet">Scarlet Letter</a><br/>
+
+ <div ng-view></div>
+ <hr />
+
+ <pre>$location.path() = {{$location.path()}}</pre>
+ <pre>$route.current.templateUrl = {{$route.current.templateUrl}}</pre>
+ <pre>$route.current.params = {{$route.current.params}}</pre>
+ <pre>$route.current.scope.name = {{$route.current.scope.name}}</pre>
+ <pre>$routeParams = {{$routeParams}}</pre>
+ </div>
+ </file>
+
+ <file name="book.html">
+ controller: {{name}}<br />
+ Book Id: {{params.bookId}}<br />
+ </file>
+
+ <file name="chapter.html">
+ controller: {{name}}<br />
+ Book Id: {{params.bookId}}<br />
+ Chapter Id: {{params.chapterId}}
+ </file>
+
+ <file name="script.js">
+ angular.module('ngView', [], function($routeProvider, $locationProvider) {
+ $routeProvider.when('/Book/:bookId', {
+ templateUrl: 'book.html',
+ controller: BookCntl,
+ resolve: {
+ // I will cause a 1 second delay
+ delay: function($q, $timeout) {
+ var delay = $q.defer();
+ $timeout(delay.resolve, 1000);
+ return delay.promise;
+ }
+ }
+ });
+ $routeProvider.when('/Book/:bookId/ch/:chapterId', {
+ templateUrl: 'chapter.html',
+ controller: ChapterCntl
+ });
+
+ // configure html5 to get links working on jsfiddle
+ $locationProvider.html5Mode(true);
+ });
+
+ function MainCntl($scope, $route, $routeParams, $location) {
+ $scope.$route = $route;
+ $scope.$location = $location;
+ $scope.$routeParams = $routeParams;
+ }
+
+ function BookCntl($scope, $routeParams) {
+ $scope.name = "BookCntl";
+ $scope.params = $routeParams;
+ }
+
+ function ChapterCntl($scope, $routeParams) {
+ $scope.name = "ChapterCntl";
+ $scope.params = $routeParams;
+ }
+ </file>
+
+ <file name="scenario.js">
+ it('should load and compile correct template', function() {
+ element('a:contains("Moby: Ch1")').click();
+ var content = element('.doc-example-live [ng-view]').text();
+ expect(content).toMatch(/controller\: ChapterCntl/);
+ expect(content).toMatch(/Book Id\: Moby/);
+ expect(content).toMatch(/Chapter Id\: 1/);
+
+ element('a:contains("Scarlet")').click();
+ sleep(2); // promises are not part of scenario waiting
+ content = element('.doc-example-live [ng-view]').text();
+ expect(content).toMatch(/controller\: BookCntl/);
+ expect(content).toMatch(/Book Id\: Scarlet/);
+ });
+ </file>
+ </example>
+ */
+
+ /**
+ * @ngdoc event
+ * @name ng.$route#$routeChangeStart
+ * @eventOf ng.$route
+ * @eventType broadcast on root scope
+ * @description
+ * Broadcasted before a route change. At this point the route services starts
+ * resolving all of the dependencies needed for the route change to occurs.
+ * Typically this involves fetching the view template as well as any dependencies
+ * defined in `resolve` route property. Once all of the dependencies are resolved
+ * `$routeChangeSuccess` is fired.
+ *
+ * @param {Route} next Future route information.
+ * @param {Route} current Current route information.
+ */
+
+ /**
+ * @ngdoc event
+ * @name ng.$route#$routeChangeSuccess
+ * @eventOf ng.$route
+ * @eventType broadcast on root scope
+ * @description
+ * Broadcasted after a route dependencies are resolved.
+ * {@link ng.directive:ngView ngView} listens for the directive
+ * to instantiate the controller and render the view.
+ *
+ * @param {Object} angularEvent Synthetic event object.
+ * @param {Route} current Current route information.
+ * @param {Route|Undefined} previous Previous route information, or undefined if current is first route entered.
+ */
+
+ /**
+ * @ngdoc event
+ * @name ng.$route#$routeChangeError
+ * @eventOf ng.$route
+ * @eventType broadcast on root scope
+ * @description
+ * Broadcasted if any of the resolve promises are rejected.
+ *
+ * @param {Route} current Current route information.
+ * @param {Route} previous Previous route information.
+ * @param {Route} rejection Rejection of the promise. Usually the error of the failed promise.
+ */
+
+ /**
+ * @ngdoc event
+ * @name ng.$route#$routeUpdate
+ * @eventOf ng.$route
+ * @eventType broadcast on root scope
+ * @description
+ *
+ * The `reloadOnSearch` property has been set to false, and we are reusing the same
+ * instance of the Controller.
+ */
+
+ var forceReload = false,
+ $route = {
+ routes: routes,
+
+ /**
+ * @ngdoc method
+ * @name ng.$route#reload
+ * @methodOf ng.$route
+ *
+ * @description
+ * Causes `$route` service to reload the current route even if
+ * {@link ng.$location $location} hasn't changed.
+ *
+ * As a result of that, {@link ng.directive:ngView ngView}
+ * creates new scope, reinstantiates the controller.
+ */
+ reload: function() {
+ forceReload = true;
+ $rootScope.$evalAsync(updateRoute);
+ }
+ };
+
+ $rootScope.$on('$locationChangeSuccess', updateRoute);
+
+ return $route;
+
+ /////////////////////////////////////////////////////
+
+ /**
+ * @param on {string} current url
+ * @param when {string} route when template to match the url against
+ * @return {?Object}
+ */
+ function switchRouteMatcher(on, when) {
+ // TODO(i): this code is convoluted and inefficient, we should construct the route matching
+ // regex only once and then reuse it
+
+ // Escape regexp special characters.
+ when = '^' + when.replace(/[-\/\\^$*+?.()|[\]{}]/g, "\\$&") + '$';
+ var regex = '',
+ params = [],
+ dst = {};
+
+ var re = /:(\w+)/g,
+ paramMatch,
+ lastMatchedIndex = 0;
+
+ while ((paramMatch = re.exec(when)) !== null) {
+ // Find each :param in `when` and replace it with a capturing group.
+ // Append all other sections of when unchanged.
+ regex += when.slice(lastMatchedIndex, paramMatch.index);
+ regex += '([^\\/]*)';
+ params.push(paramMatch[1]);
+ lastMatchedIndex = re.lastIndex;
+ }
+ // Append trailing path part.
+ regex += when.substr(lastMatchedIndex);
+
+ var match = on.match(new RegExp(regex));
+ if (match) {
+ forEach(params, function(name, index) {
+ dst[name] = match[index + 1];
+ });
+ }
+ return match ? dst : null;
+ }
+
+ function updateRoute() {
+ var next = parseRoute(),
+ last = $route.current;
+
+ if (next && last && next.$$route === last.$$route
+ && equals(next.pathParams, last.pathParams) && !next.reloadOnSearch && !forceReload) {
+ last.params = next.params;
+ copy(last.params, $routeParams);
+ $rootScope.$broadcast('$routeUpdate', last);
+ } else if (next || last) {
+ forceReload = false;
+ $rootScope.$broadcast('$routeChangeStart', next, last);
+ $route.current = next;
+ if (next) {
+ if (next.redirectTo) {
+ if (isString(next.redirectTo)) {
+ $location.path(interpolate(next.redirectTo, next.params)).search(next.params)
+ .replace();
+ } else {
+ $location.url(next.redirectTo(next.pathParams, $location.path(), $location.search()))
+ .replace();
+ }
+ }
+ }
+
+ $q.when(next).
+ then(function() {
+ if (next) {
+ var keys = [],
+ values = [],
+ template;
+
+ forEach(next.resolve || {}, function(value, key) {
+ keys.push(key);
+ values.push(isString(value) ? $injector.get(value) : $injector.invoke(value));
+ });
+ if (isDefined(template = next.template)) {
+ } else if (isDefined(template = next.templateUrl)) {
+ template = $http.get(template, {cache: $templateCache}).
+ then(function(response) { return response.data; });
+ }
+ if (isDefined(template)) {
+ keys.push('$template');
+ values.push(template);
+ }
+ return $q.all(values).then(function(values) {
+ var locals = {};
+ forEach(values, function(value, index) {
+ locals[keys[index]] = value;
+ });
+ return locals;
+ });
+ }
+ }).
+ // after route change
+ then(function(locals) {
+ if (next == $route.current) {
+ if (next) {
+ next.locals = locals;
+ copy(next.params, $routeParams);
+ }
+ $rootScope.$broadcast('$routeChangeSuccess', next, last);
+ }
+ }, function(error) {
+ if (next == $route.current) {
+ $rootScope.$broadcast('$routeChangeError', next, last, error);
+ }
+ });
+ }
+ }
+
+
+ /**
+ * @returns the current active route, by matching it against the URL
+ */
+ function parseRoute() {
+ // Match a route
+ var params, match;
+ forEach(routes, function(route, path) {
+ if (!match && (params = switchRouteMatcher($location.path(), path))) {
+ match = inherit(route, {
+ params: extend({}, $location.search(), params),
+ pathParams: params});
+ match.$$route = route;
+ }
+ });
+ // No route matched; fallback to "otherwise" route
+ return match || routes[null] && inherit(routes[null], {params: {}, pathParams:{}});
+ }
+
+ /**
+ * @returns interpolation of the redirect path with the parametrs
+ */
+ function interpolate(string, params) {
+ var result = [];
+ forEach((string||'').split(':'), function(segment, i) {
+ if (i == 0) {
+ result.push(segment);
+ } else {
+ var segmentMatch = segment.match(/(\w+)(.*)/);
+ var key = segmentMatch[1];
+ result.push(params[key]);
+ result.push(segmentMatch[2] || '');
+ delete params[key];
+ }
+ });
+ return result.join('');
+ }
+ }];
+}
+
+/**
+ * @ngdoc object
+ * @name ng.$routeParams
+ * @requires $route
+ *
+ * @description
+ * Current set of route parameters. The route parameters are a combination of the
+ * {@link ng.$location $location} `search()`, and `path()`. The `path` parameters
+ * are extracted when the {@link ng.$route $route} path is matched.
+ *
+ * In case of parameter name collision, `path` params take precedence over `search` params.
+ *
+ * The service guarantees that the identity of the `$routeParams` object will remain unchanged
+ * (but its properties will likely change) even when a route change occurs.
+ *
+ * Note that the `$routeParams` are only updated *after* a route change completes successfully.
+ * This means that you cannot rely on `$routeParams` being correct in route resolve functions.
+ * Instead you can use `$route.current.params` to access the new route's parameters.
+ *
+ * @example
+ * <pre>
+ * // Given:
+ * // URL: http://server.com/index.html#/Chapter/1/Section/2?search=moby
+ * // Route: /Chapter/:chapterId/Section/:sectionId
+ * //
+ * // Then
+ * $routeParams ==> {chapterId:1, sectionId:2, search:'moby'}
+ * </pre>
+ */
+function $RouteParamsProvider() {
+ this.$get = valueFn({});
+}
+
+/**
+ * DESIGN NOTES
+ *
+ * The design decisions behind the scope are heavily favored for speed and memory consumption.
+ *
+ * The typical use of scope is to watch the expressions, which most of the time return the same
+ * value as last time so we optimize the operation.
+ *
+ * Closures construction is expensive in terms of speed as well as memory:
+ * - No closures, instead use prototypical inheritance for API
+ * - Internal state needs to be stored on scope directly, which means that private state is
+ * exposed as $$____ properties
+ *
+ * Loop operations are optimized by using while(count--) { ... }
+ * - this means that in order to keep the same order of execution as addition we have to add
+ * items to the array at the beginning (shift) instead of at the end (push)
+ *
+ * Child scopes are created and removed often
+ * - Using an array would be slow since inserts in middle are expensive so we use linked list
+ *
+ * There are few watches then a lot of observers. This is why you don't want the observer to be
+ * implemented in the same way as watch. Watch requires return of initialization function which
+ * are expensive to construct.
+ */
+
+
+/**
+ * @ngdoc object
+ * @name ng.$rootScopeProvider
+ * @description
+ *
+ * Provider for the $rootScope service.
+ */
+
+/**
+ * @ngdoc function
+ * @name ng.$rootScopeProvider#digestTtl
+ * @methodOf ng.$rootScopeProvider
+ * @description
+ *
+ * Sets the number of digest iterations the scope should attempt to execute before giving up and
+ * assuming that the model is unstable.
+ *
+ * The current default is 10 iterations.
+ *
+ * @param {number} limit The number of digest iterations.
+ */
+
+
+/**
+ * @ngdoc object
+ * @name ng.$rootScope
+ * @description
+ *
+ * Every application has a single root {@link ng.$rootScope.Scope scope}.
+ * All other scopes are child scopes of the root scope. Scopes provide mechanism for watching the model and provide
+ * event processing life-cycle. See {@link guide/scope developer guide on scopes}.
+ */
+function $RootScopeProvider(){
+ var TTL = 10;
+
+ this.digestTtl = function(value) {
+ if (arguments.length) {
+ TTL = value;
+ }
+ return TTL;
+ };
+
+ this.$get = ['$injector', '$exceptionHandler', '$parse',
+ function( $injector, $exceptionHandler, $parse) {
+
+ /**
+ * @ngdoc function
+ * @name ng.$rootScope.Scope
+ *
+ * @description
+ * A root scope can be retrieved using the {@link ng.$rootScope $rootScope} key from the
+ * {@link AUTO.$injector $injector}. Child scopes are created using the
+ * {@link ng.$rootScope.Scope#$new $new()} method. (Most scopes are created automatically when
+ * compiled HTML template is executed.)
+ *
+ * Here is a simple scope snippet to show how you can interact with the scope.
+ * <pre>
+ angular.injector(['ng']).invoke(function($rootScope) {
+ var scope = $rootScope.$new();
+ scope.salutation = 'Hello';
+ scope.name = 'World';
+
+ expect(scope.greeting).toEqual(undefined);
+
+ scope.$watch('name', function() {
+ scope.greeting = scope.salutation + ' ' + scope.name + '!';
+ }); // initialize the watch
+
+ expect(scope.greeting).toEqual(undefined);
+ scope.name = 'Misko';
+ // still old value, since watches have not been called yet
+ expect(scope.greeting).toEqual(undefined);
+
+ scope.$digest(); // fire all the watches
+ expect(scope.greeting).toEqual('Hello Misko!');
+ });
+ * </pre>
+ *
+ * # Inheritance
+ * A scope can inherit from a parent scope, as in this example:
+ * <pre>
+ var parent = $rootScope;
+ var child = parent.$new();
+
+ parent.salutation = "Hello";
+ child.name = "World";
+ expect(child.salutation).toEqual('Hello');
+
+ child.salutation = "Welcome";
+ expect(child.salutation).toEqual('Welcome');
+ expect(parent.salutation).toEqual('Hello');
+ * </pre>
+ *
+ *
+ * @param {Object.<string, function()>=} providers Map of service factory which need to be provided
+ * for the current scope. Defaults to {@link ng}.
+ * @param {Object.<string, *>=} instanceCache Provides pre-instantiated services which should
+ * append/override services provided by `providers`. This is handy when unit-testing and having
+ * the need to override a default service.
+ * @returns {Object} Newly created scope.
+ *
+ */
+ function Scope() {
+ this.$id = nextUid();
+ this.$$phase = this.$parent = this.$$watchers =
+ this.$$nextSibling = this.$$prevSibling =
+ this.$$childHead = this.$$childTail = null;
+ this['this'] = this.$root = this;
+ this.$$destroyed = false;
+ this.$$asyncQueue = [];
+ this.$$listeners = {};
+ this.$$isolateBindings = {};
+ }
+
+ /**
+ * @ngdoc property
+ * @name ng.$rootScope.Scope#$id
+ * @propertyOf ng.$rootScope.Scope
+ * @returns {number} Unique scope ID (monotonically increasing alphanumeric sequence) useful for
+ * debugging.
+ */
+
+
+ Scope.prototype = {
+ /**
+ * @ngdoc function
+ * @name ng.$rootScope.Scope#$new
+ * @methodOf ng.$rootScope.Scope
+ * @function
+ *
+ * @description
+ * Creates a new child {@link ng.$rootScope.Scope scope}.
+ *
+ * The parent scope will propagate the {@link ng.$rootScope.Scope#$digest $digest()} and
+ * {@link ng.$rootScope.Scope#$digest $digest()} events. The scope can be removed from the scope
+ * hierarchy using {@link ng.$rootScope.Scope#$destroy $destroy()}.
+ *
+ * {@link ng.$rootScope.Scope#$destroy $destroy()} must be called on a scope when it is desired for
+ * the scope and its child scopes to be permanently detached from the parent and thus stop
+ * participating in model change detection and listener notification by invoking.
+ *
+ * @param {boolean} isolate if true then the scope does not prototypically inherit from the
+ * parent scope. The scope is isolated, as it can not see parent scope properties.
+ * When creating widgets it is useful for the widget to not accidentally read parent
+ * state.
+ *
+ * @returns {Object} The newly created child scope.
+ *
+ */
+ $new: function(isolate) {
+ var Child,
+ child;
+
+ if (isFunction(isolate)) {
+ // TODO: remove at some point
+ throw Error('API-CHANGE: Use $controller to instantiate controllers.');
+ }
+ if (isolate) {
+ child = new Scope();
+ child.$root = this.$root;
+ } else {
+ Child = function() {}; // should be anonymous; This is so that when the minifier munges
+ // the name it does not become random set of chars. These will then show up as class
+ // name in the debugger.
+ Child.prototype = this;
+ child = new Child();
+ child.$id = nextUid();
+ }
+ child['this'] = child;
+ child.$$listeners = {};
+ child.$parent = this;
+ child.$$asyncQueue = [];
+ child.$$watchers = child.$$nextSibling = child.$$childHead = child.$$childTail = null;
+ child.$$prevSibling = this.$$childTail;
+ if (this.$$childHead) {
+ this.$$childTail.$$nextSibling = child;
+ this.$$childTail = child;
+ } else {
+ this.$$childHead = this.$$childTail = child;
+ }
+ return child;
+ },
+
+ /**
+ * @ngdoc function
+ * @name ng.$rootScope.Scope#$watch
+ * @methodOf ng.$rootScope.Scope
+ * @function
+ *
+ * @description
+ * Registers a `listener` callback to be executed whenever the `watchExpression` changes.
+ *
+ * - The `watchExpression` is called on every call to {@link ng.$rootScope.Scope#$digest $digest()} and
+ * should return the value which will be watched. (Since {@link ng.$rootScope.Scope#$digest $digest()}
+ * reruns when it detects changes the `watchExpression` can execute multiple times per
+ * {@link ng.$rootScope.Scope#$digest $digest()} and should be idempotent.)
+ * - The `listener` is called only when the value from the current `watchExpression` and the
+ * previous call to `watchExpression` are not equal (with the exception of the initial run,
+ * see below). The inequality is determined according to
+ * {@link angular.equals} function. To save the value of the object for later comparison, the
+ * {@link angular.copy} function is used. It also means that watching complex options will
+ * have adverse memory and performance implications.
+ * - The watch `listener` may change the model, which may trigger other `listener`s to fire. This
+ * is achieved by rerunning the watchers until no changes are detected. The rerun iteration
+ * limit is 10 to prevent an infinite loop deadlock.
+ *
+ *
+ * If you want to be notified whenever {@link ng.$rootScope.Scope#$digest $digest} is called,
+ * you can register a `watchExpression` function with no `listener`. (Since `watchExpression`
+ * can execute multiple times per {@link ng.$rootScope.Scope#$digest $digest} cycle when a change is
+ * detected, be prepared for multiple calls to your listener.)
+ *
+ * After a watcher is registered with the scope, the `listener` fn is called asynchronously
+ * (via {@link ng.$rootScope.Scope#$evalAsync $evalAsync}) to initialize the
+ * watcher. In rare cases, this is undesirable because the listener is called when the result
+ * of `watchExpression` didn't change. To detect this scenario within the `listener` fn, you
+ * can compare the `newVal` and `oldVal`. If these two values are identical (`===`) then the
+ * listener was called due to initialization.
+ *
+ *
+ * # Example
+ * <pre>
+ // let's assume that scope was dependency injected as the $rootScope
+ var scope = $rootScope;
+ scope.name = 'misko';
+ scope.counter = 0;
+
+ expect(scope.counter).toEqual(0);
+ scope.$watch('name', function(newValue, oldValue) { scope.counter = scope.counter + 1; });
+ expect(scope.counter).toEqual(0);
+
+ scope.$digest();
+ // no variable change
+ expect(scope.counter).toEqual(0);
+
+ scope.name = 'adam';
+ scope.$digest();
+ expect(scope.counter).toEqual(1);
+ * </pre>
+ *
+ *
+ *
+ * @param {(function()|string)} watchExpression Expression that is evaluated on each
+ * {@link ng.$rootScope.Scope#$digest $digest} cycle. A change in the return value triggers a
+ * call to the `listener`.
+ *
+ * - `string`: Evaluated as {@link guide/expression expression}
+ * - `function(scope)`: called with current `scope` as a parameter.
+ * @param {(function()|string)=} listener Callback called whenever the return value of
+ * the `watchExpression` changes.
+ *
+ * - `string`: Evaluated as {@link guide/expression expression}
+ * - `function(newValue, oldValue, scope)`: called with current and previous values as parameters.
+ *
+ * @param {boolean=} objectEquality Compare object for equality rather than for reference.
+ * @returns {function()} Returns a deregistration function for this listener.
+ */
+ $watch: function(watchExp, listener, objectEquality) {
+ var scope = this,
+ get = compileToFn(watchExp, 'watch'),
+ array = scope.$$watchers,
+ watcher = {
+ fn: listener,
+ last: initWatchVal,
+ get: get,
+ exp: watchExp,
+ eq: !!objectEquality
+ };
+
+ // in the case user pass string, we need to compile it, do we really need this ?
+ if (!isFunction(listener)) {
+ var listenFn = compileToFn(listener || noop, 'listener');
+ watcher.fn = function(newVal, oldVal, scope) {listenFn(scope);};
+ }
+
+ if (!array) {
+ array = scope.$$watchers = [];
+ }
+ // we use unshift since we use a while loop in $digest for speed.
+ // the while loop reads in reverse order.
+ array.unshift(watcher);
+
+ return function() {
+ arrayRemove(array, watcher);
+ };
+ },
+
+ /**
+ * @ngdoc function
+ * @name ng.$rootScope.Scope#$digest
+ * @methodOf ng.$rootScope.Scope
+ * @function
+ *
+ * @description
+ * Processes all of the {@link ng.$rootScope.Scope#$watch watchers} of the current scope and its children.
+ * Because a {@link ng.$rootScope.Scope#$watch watcher}'s listener can change the model, the
+ * `$digest()` keeps calling the {@link ng.$rootScope.Scope#$watch watchers} until no more listeners are
+ * firing. This means that it is possible to get into an infinite loop. This function will throw
+ * `'Maximum iteration limit exceeded.'` if the number of iterations exceeds 10.
+ *
+ * Usually you don't call `$digest()` directly in
+ * {@link ng.directive:ngController controllers} or in
+ * {@link ng.$compileProvider#directive directives}.
+ * Instead a call to {@link ng.$rootScope.Scope#$apply $apply()} (typically from within a
+ * {@link ng.$compileProvider#directive directives}) will force a `$digest()`.
+ *
+ * If you want to be notified whenever `$digest()` is called,
+ * you can register a `watchExpression` function with {@link ng.$rootScope.Scope#$watch $watch()}
+ * with no `listener`.
+ *
+ * You may have a need to call `$digest()` from within unit-tests, to simulate the scope
+ * life-cycle.
+ *
+ * # Example
+ * <pre>
+ var scope = ...;
+ scope.name = 'misko';
+ scope.counter = 0;
+
+ expect(scope.counter).toEqual(0);
+ scope.$watch('name', function(newValue, oldValue) {
+ scope.counter = scope.counter + 1;
+ });
+ expect(scope.counter).toEqual(0);
+
+ scope.$digest();
+ // no variable change
+ expect(scope.counter).toEqual(0);
+
+ scope.name = 'adam';
+ scope.$digest();
+ expect(scope.counter).toEqual(1);
+ * </pre>
+ *
+ */
+ $digest: function() {
+ var watch, value, last,
+ watchers,
+ asyncQueue,
+ length,
+ dirty, ttl = TTL,
+ next, current, target = this,
+ watchLog = [],
+ logIdx, logMsg;
+
+ beginPhase('$digest');
+
+ do {
+ dirty = false;
+ current = target;
+ do {
+ asyncQueue = current.$$asyncQueue;
+ while(asyncQueue.length) {
+ try {
+ current.$eval(asyncQueue.shift());
+ } catch (e) {
+ $exceptionHandler(e);
+ }
+ }
+ if ((watchers = current.$$watchers)) {
+ // process our watches
+ length = watchers.length;
+ while (length--) {
+ try {
+ watch = watchers[length];
+ // Most common watches are on primitives, in which case we can short
+ // circuit it with === operator, only when === fails do we use .equals
+ if (watch && (value = watch.get(current)) !== (last = watch.last) &&
+ !(watch.eq
+ ? equals(value, last)
+ : (typeof value == 'number' && typeof last == 'number'
+ && isNaN(value) && isNaN(last)))) {
+ dirty = true;
+ watch.last = watch.eq ? copy(value) : value;
+ watch.fn(value, ((last === initWatchVal) ? value : last), current);
+ if (ttl < 5) {
+ logIdx = 4 - ttl;
+ if (!watchLog[logIdx]) watchLog[logIdx] = [];
+ logMsg = (isFunction(watch.exp))
+ ? 'fn: ' + (watch.exp.name || watch.exp.toString())
+ : watch.exp;
+ logMsg += '; newVal: ' + toJson(value) + '; oldVal: ' + toJson(last);
+ watchLog[logIdx].push(logMsg);
+ }
+ }
+ } catch (e) {
+ $exceptionHandler(e);
+ }
+ }
+ }
+
+ // Insanity Warning: scope depth-first traversal
+ // yes, this code is a bit crazy, but it works and we have tests to prove it!
+ // this piece should be kept in sync with the traversal in $broadcast
+ if (!(next = (current.$$childHead || (current !== target && current.$$nextSibling)))) {
+ while(current !== target && !(next = current.$$nextSibling)) {
+ current = current.$parent;
+ }
+ }
+ } while ((current = next));
+
+ if(dirty && !(ttl--)) {
+ clearPhase();
+ throw Error(TTL + ' $digest() iterations reached. Aborting!\n' +
+ 'Watchers fired in the last 5 iterations: ' + toJson(watchLog));
+ }
+ } while (dirty || asyncQueue.length);
+
+ clearPhase();
+ },
+
+
+ /**
+ * @ngdoc event
+ * @name ng.$rootScope.Scope#$destroy
+ * @eventOf ng.$rootScope.Scope
+ * @eventType broadcast on scope being destroyed
+ *
+ * @description
+ * Broadcasted when a scope and its children are being destroyed.
+ *
+ * Note that, in AngularJS, there is also a `$destroy` jQuery event, which can be used to
+ * clean up DOM bindings before an element is removed from the DOM.
+ */
+
+ /**
+ * @ngdoc function
+ * @name ng.$rootScope.Scope#$destroy
+ * @methodOf ng.$rootScope.Scope
+ * @function
+ *
+ * @description
+ * Removes the current scope (and all of its children) from the parent scope. Removal implies
+ * that calls to {@link ng.$rootScope.Scope#$digest $digest()} will no longer
+ * propagate to the current scope and its children. Removal also implies that the current
+ * scope is eligible for garbage collection.
+ *
+ * The `$destroy()` is usually used by directives such as
+ * {@link ng.directive:ngRepeat ngRepeat} for managing the
+ * unrolling of the loop.
+ *
+ * Just before a scope is destroyed a `$destroy` event is broadcasted on this scope.
+ * Application code can register a `$destroy` event handler that will give it chance to
+ * perform any necessary cleanup.
+ *
+ * Note that, in AngularJS, there is also a `$destroy` jQuery event, which can be used to
+ * clean up DOM bindings before an element is removed from the DOM.
+ */
+ $destroy: function() {
+ // we can't destroy the root scope or a scope that has been already destroyed
+ if ($rootScope == this || this.$$destroyed) return;
+ var parent = this.$parent;
+
+ this.$broadcast('$destroy');
+ this.$$destroyed = true;
+
+ if (parent.$$childHead == this) parent.$$childHead = this.$$nextSibling;
+ if (parent.$$childTail == this) parent.$$childTail = this.$$prevSibling;
+ if (this.$$prevSibling) this.$$prevSibling.$$nextSibling = this.$$nextSibling;
+ if (this.$$nextSibling) this.$$nextSibling.$$prevSibling = this.$$prevSibling;
+
+ // This is bogus code that works around Chrome's GC leak
+ // see: https://github.com/angular/angular.js/issues/1313#issuecomment-10378451
+ this.$parent = this.$$nextSibling = this.$$prevSibling = this.$$childHead =
+ this.$$childTail = null;
+ },
+
+ /**
+ * @ngdoc function
+ * @name ng.$rootScope.Scope#$eval
+ * @methodOf ng.$rootScope.Scope
+ * @function
+ *
+ * @description
+ * Executes the `expression` on the current scope returning the result. Any exceptions in the
+ * expression are propagated (uncaught). This is useful when evaluating Angular expressions.
+ *
+ * # Example
+ * <pre>
+ var scope = ng.$rootScope.Scope();
+ scope.a = 1;
+ scope.b = 2;
+
+ expect(scope.$eval('a+b')).toEqual(3);
+ expect(scope.$eval(function(scope){ return scope.a + scope.b; })).toEqual(3);
+ * </pre>
+ *
+ * @param {(string|function())=} expression An angular expression to be executed.
+ *
+ * - `string`: execute using the rules as defined in {@link guide/expression expression}.
+ * - `function(scope)`: execute the function with the current `scope` parameter.
+ *
+ * @returns {*} The result of evaluating the expression.
+ */
+ $eval: function(expr, locals) {
+ return $parse(expr)(this, locals);
+ },
+
+ /**
+ * @ngdoc function
+ * @name ng.$rootScope.Scope#$evalAsync
+ * @methodOf ng.$rootScope.Scope
+ * @function
+ *
+ * @description
+ * Executes the expression on the current scope at a later point in time.
+ *
+ * The `$evalAsync` makes no guarantees as to when the `expression` will be executed, only that:
+ *
+ * - it will execute in the current script execution context (before any DOM rendering).
+ * - at least one {@link ng.$rootScope.Scope#$digest $digest cycle} will be performed after
+ * `expression` execution.
+ *
+ * Any exceptions from the execution of the expression are forwarded to the
+ * {@link ng.$exceptionHandler $exceptionHandler} service.
+ *
+ * @param {(string|function())=} expression An angular expression to be executed.
+ *
+ * - `string`: execute using the rules as defined in {@link guide/expression expression}.
+ * - `function(scope)`: execute the function with the current `scope` parameter.
+ *
+ */
+ $evalAsync: function(expr) {
+ this.$$asyncQueue.push(expr);
+ },
+
+ /**
+ * @ngdoc function
+ * @name ng.$rootScope.Scope#$apply
+ * @methodOf ng.$rootScope.Scope
+ * @function
+ *
+ * @description
+ * `$apply()` is used to execute an expression in angular from outside of the angular framework.
+ * (For example from browser DOM events, setTimeout, XHR or third party libraries).
+ * Because we are calling into the angular framework we need to perform proper scope life-cycle
+ * of {@link ng.$exceptionHandler exception handling},
+ * {@link ng.$rootScope.Scope#$digest executing watches}.
+ *
+ * ## Life cycle
+ *
+ * # Pseudo-Code of `$apply()`
+ * <pre>
+ function $apply(expr) {
+ try {
+ return $eval(expr);
+ } catch (e) {
+ $exceptionHandler(e);
+ } finally {
+ $root.$digest();
+ }
+ }
+ * </pre>
+ *
+ *
+ * Scope's `$apply()` method transitions through the following stages:
+ *
+ * 1. The {@link guide/expression expression} is executed using the
+ * {@link ng.$rootScope.Scope#$eval $eval()} method.
+ * 2. Any exceptions from the execution of the expression are forwarded to the
+ * {@link ng.$exceptionHandler $exceptionHandler} service.
+ * 3. The {@link ng.$rootScope.Scope#$watch watch} listeners are fired immediately after the expression
+ * was executed using the {@link ng.$rootScope.Scope#$digest $digest()} method.
+ *
+ *
+ * @param {(string|function())=} exp An angular expression to be executed.
+ *
+ * - `string`: execute using the rules as defined in {@link guide/expression expression}.
+ * - `function(scope)`: execute the function with current `scope` parameter.
+ *
+ * @returns {*} The result of evaluating the expression.
+ */
+ $apply: function(expr) {
+ try {
+ beginPhase('$apply');
+ return this.$eval(expr);
+ } catch (e) {
+ $exceptionHandler(e);
+ } finally {
+ clearPhase();
+ try {
+ $rootScope.$digest();
+ } catch (e) {
+ $exceptionHandler(e);
+ throw e;
+ }
+ }
+ },
+
+ /**
+ * @ngdoc function
+ * @name ng.$rootScope.Scope#$on
+ * @methodOf ng.$rootScope.Scope
+ * @function
+ *
+ * @description
+ * Listens on events of a given type. See {@link ng.$rootScope.Scope#$emit $emit} for discussion of
+ * event life cycle.
+ *
+ * The event listener function format is: `function(event, args...)`. The `event` object
+ * passed into the listener has the following attributes:
+ *
+ * - `targetScope` - `{Scope}`: the scope on which the event was `$emit`-ed or `$broadcast`-ed.
+ * - `currentScope` - `{Scope}`: the current scope which is handling the event.
+ * - `name` - `{string}`: Name of the event.
+ * - `stopPropagation` - `{function=}`: calling `stopPropagation` function will cancel further event
+ * propagation (available only for events that were `$emit`-ed).
+ * - `preventDefault` - `{function}`: calling `preventDefault` sets `defaultPrevented` flag to true.
+ * - `defaultPrevented` - `{boolean}`: true if `preventDefault` was called.
+ *
+ * @param {string} name Event name to listen on.
+ * @param {function(event, args...)} listener Function to call when the event is emitted.
+ * @returns {function()} Returns a deregistration function for this listener.
+ */
+ $on: function(name, listener) {
+ var namedListeners = this.$$listeners[name];
+ if (!namedListeners) {
+ this.$$listeners[name] = namedListeners = [];
+ }
+ namedListeners.push(listener);
+
+ return function() {
+ namedListeners[indexOf(namedListeners, listener)] = null;
+ };
+ },
+
+
+ /**
+ * @ngdoc function
+ * @name ng.$rootScope.Scope#$emit
+ * @methodOf ng.$rootScope.Scope
+ * @function
+ *
+ * @description
+ * Dispatches an event `name` upwards through the scope hierarchy notifying the
+ * registered {@link ng.$rootScope.Scope#$on} listeners.
+ *
+ * The event life cycle starts at the scope on which `$emit` was called. All
+ * {@link ng.$rootScope.Scope#$on listeners} listening for `name` event on this scope get notified.
+ * Afterwards, the event traverses upwards toward the root scope and calls all registered
+ * listeners along the way. The event will stop propagating if one of the listeners cancels it.
+ *
+ * Any exception emitted from the {@link ng.$rootScope.Scope#$on listeners} will be passed
+ * onto the {@link ng.$exceptionHandler $exceptionHandler} service.
+ *
+ * @param {string} name Event name to emit.
+ * @param {...*} args Optional set of arguments which will be passed onto the event listeners.
+ * @return {Object} Event object, see {@link ng.$rootScope.Scope#$on}
+ */
+ $emit: function(name, args) {
+ var empty = [],
+ namedListeners,
+ scope = this,
+ stopPropagation = false,
+ event = {
+ name: name,
+ targetScope: scope,
+ stopPropagation: function() {stopPropagation = true;},
+ preventDefault: function() {
+ event.defaultPrevented = true;
+ },
+ defaultPrevented: false
+ },
+ listenerArgs = concat([event], arguments, 1),
+ i, length;
+
+ do {
+ namedListeners = scope.$$listeners[name] || empty;
+ event.currentScope = scope;
+ for (i=0, length=namedListeners.length; i<length; i++) {
+
+ // if listeners were deregistered, defragment the array
+ if (!namedListeners[i]) {
+ namedListeners.splice(i, 1);
+ i--;
+ length--;
+ continue;
+ }
+ try {
+ namedListeners[i].apply(null, listenerArgs);
+ if (stopPropagation) return event;
+ } catch (e) {
+ $exceptionHandler(e);
+ }
+ }
+ //traverse upwards
+ scope = scope.$parent;
+ } while (scope);
+
+ return event;
+ },
+
+
+ /**
+ * @ngdoc function
+ * @name ng.$rootScope.Scope#$broadcast
+ * @methodOf ng.$rootScope.Scope
+ * @function
+ *
+ * @description
+ * Dispatches an event `name` downwards to all child scopes (and their children) notifying the
+ * registered {@link ng.$rootScope.Scope#$on} listeners.
+ *
+ * The event life cycle starts at the scope on which `$broadcast` was called. All
+ * {@link ng.$rootScope.Scope#$on listeners} listening for `name` event on this scope get notified.
+ * Afterwards, the event propagates to all direct and indirect scopes of the current scope and
+ * calls all registered listeners along the way. The event cannot be canceled.
+ *
+ * Any exception emmited from the {@link ng.$rootScope.Scope#$on listeners} will be passed
+ * onto the {@link ng.$exceptionHandler $exceptionHandler} service.
+ *
+ * @param {string} name Event name to broadcast.
+ * @param {...*} args Optional set of arguments which will be passed onto the event listeners.
+ * @return {Object} Event object, see {@link ng.$rootScope.Scope#$on}
+ */
+ $broadcast: function(name, args) {
+ var target = this,
+ current = target,
+ next = target,
+ event = {
+ name: name,
+ targetScope: target,
+ preventDefault: function() {
+ event.defaultPrevented = true;
+ },
+ defaultPrevented: false
+ },
+ listenerArgs = concat([event], arguments, 1),
+ listeners, i, length;
+
+ //down while you can, then up and next sibling or up and next sibling until back at root
+ do {
+ current = next;
+ event.currentScope = current;
+ listeners = current.$$listeners[name] || [];
+ for (i=0, length = listeners.length; i<length; i++) {
+ // if listeners were deregistered, defragment the array
+ if (!listeners[i]) {
+ listeners.splice(i, 1);
+ i--;
+ length--;
+ continue;
+ }
+
+ try {
+ listeners[i].apply(null, listenerArgs);
+ } catch(e) {
+ $exceptionHandler(e);
+ }
+ }
+
+ // Insanity Warning: scope depth-first traversal
+ // yes, this code is a bit crazy, but it works and we have tests to prove it!
+ // this piece should be kept in sync with the traversal in $digest
+ if (!(next = (current.$$childHead || (current !== target && current.$$nextSibling)))) {
+ while(current !== target && !(next = current.$$nextSibling)) {
+ current = current.$parent;
+ }
+ }
+ } while ((current = next));
+
+ return event;
+ }
+ };
+
+ var $rootScope = new Scope();
+
+ return $rootScope;
+
+
+ function beginPhase(phase) {
+ if ($rootScope.$$phase) {
+ throw Error($rootScope.$$phase + ' already in progress');
+ }
+
+ $rootScope.$$phase = phase;
+ }
+
+ function clearPhase() {
+ $rootScope.$$phase = null;
+ }
+
+ function compileToFn(exp, name) {
+ var fn = $parse(exp);
+ assertArgFn(fn, name);
+ return fn;
+ }
+
+ /**
+ * function used as an initial value for watchers.
+ * because it's unique we can easily tell it apart from other values
+ */
+ function initWatchVal() {}
+ }];
+}
+
+/**
+ * !!! This is an undocumented "private" service !!!
+ *
+ * @name ng.$sniffer
+ * @requires $window
+ *
+ * @property {boolean} history Does the browser support html5 history api ?
+ * @property {boolean} hashchange Does the browser support hashchange event ?
+ *
+ * @description
+ * This is very simple implementation of testing browser's features.
+ */
+function $SnifferProvider() {
+ this.$get = ['$window', function($window) {
+ var eventSupport = {},
+ android = int((/android (\d+)/.exec(lowercase($window.navigator.userAgent)) || [])[1]);
+
+ return {
+ // Android has history.pushState, but it does not update location correctly
+ // so let's not use the history API at all.
+ // http://code.google.com/p/android/issues/detail?id=17471
+ // https://github.com/angular/angular.js/issues/904
+ history: !!($window.history && $window.history.pushState && !(android < 4)),
+ hashchange: 'onhashchange' in $window &&
+ // IE8 compatible mode lies
+ (!$window.document.documentMode || $window.document.documentMode > 7),
+ hasEvent: function(event) {
+ // IE9 implements 'input' event it's so fubared that we rather pretend that it doesn't have
+ // it. In particular the event is not fired when backspace or delete key are pressed or
+ // when cut operation is performed.
+ if (event == 'input' && msie == 9) return false;
+
+ if (isUndefined(eventSupport[event])) {
+ var divElm = $window.document.createElement('div');
+ eventSupport[event] = 'on' + event in divElm;
+ }
+
+ return eventSupport[event];
+ },
+ // TODO(i): currently there is no way to feature detect CSP without triggering alerts
+ csp: false
+ };
+ }];
+}
+
+/**
+ * @ngdoc object
+ * @name ng.$window
+ *
+ * @description
+ * A reference to the browser's `window` object. While `window`
+ * is globally available in JavaScript, it causes testability problems, because
+ * it is a global variable. In angular we always refer to it through the
+ * `$window` service, so it may be overriden, removed or mocked for testing.
+ *
+ * Expressions, like the one defined for the `ngClick` directive in the example
+ * below, are evaluated with respect to the current scope. Therefore, there is
+ * no risk of inadvertently coding in a dependency on a global value in such an
+ * expression.
+ *
+ * @example
+ <doc:example>
+ <doc:source>
+ <script>
+ function Ctrl($scope, $window) {
+ $scope.$window = $window;
+ $scope.greeting = 'Hello, World!';
+ }
+ </script>
+ <div ng-controller="Ctrl">
+ <input type="text" ng-model="greeting" />
+ <button ng-click="$window.alert(greeting)">ALERT</button>
+ </div>
+ </doc:source>
+ <doc:scenario>
+ it('should display the greeting in the input box', function() {
+ input('greeting').enter('Hello, E2E Tests');
+ // If we click the button it will block the test runner
+ // element(':button').click();
+ });
+ </doc:scenario>
+ </doc:example>
+ */
+function $WindowProvider(){
+ this.$get = valueFn(window);
+}
+
+/**
+ * Parse headers into key value object
+ *
+ * @param {string} headers Raw headers as a string
+ * @returns {Object} Parsed headers as key value object
+ */
+function parseHeaders(headers) {
+ var parsed = {}, key, val, i;
+
+ if (!headers) return parsed;
+
+ forEach(headers.split('\n'), function(line) {
+ i = line.indexOf(':');
+ key = lowercase(trim(line.substr(0, i)));
+ val = trim(line.substr(i + 1));
+
+ if (key) {
+ if (parsed[key]) {
+ parsed[key] += ', ' + val;
+ } else {
+ parsed[key] = val;
+ }
+ }
+ });
+
+ return parsed;
+}
+
+
+/**
+ * Returns a function that provides access to parsed headers.
+ *
+ * Headers are lazy parsed when first requested.
+ * @see parseHeaders
+ *
+ * @param {(string|Object)} headers Headers to provide access to.
+ * @returns {function(string=)} Returns a getter function which if called with:
+ *
+ * - if called with single an argument returns a single header value or null
+ * - if called with no arguments returns an object containing all headers.
+ */
+function headersGetter(headers) {
+ var headersObj = isObject(headers) ? headers : undefined;
+
+ return function(name) {
+ if (!headersObj) headersObj = parseHeaders(headers);
+
+ if (name) {
+ return headersObj[lowercase(name)] || null;
+ }
+
+ return headersObj;
+ };
+}
+
+
+/**
+ * Chain all given functions
+ *
+ * This function is used for both request and response transforming
+ *
+ * @param {*} data Data to transform.
+ * @param {function(string=)} headers Http headers getter fn.
+ * @param {(function|Array.<function>)} fns Function or an array of functions.
+ * @returns {*} Transformed data.
+ */
+function transformData(data, headers, fns) {
+ if (isFunction(fns))
+ return fns(data, headers);
+
+ forEach(fns, function(fn) {
+ data = fn(data, headers);
+ });
+
+ return data;
+}
+
+
+function isSuccess(status) {
+ return 200 <= status && status < 300;
+}
+
+
+function $HttpProvider() {
+ var JSON_START = /^\s*(\[|\{[^\{])/,
+ JSON_END = /[\}\]]\s*$/,
+ PROTECTION_PREFIX = /^\)\]\}',?\n/;
+
+ var $config = this.defaults = {
+ // transform incoming response data
+ transformResponse: [function(data) {
+ if (isString(data)) {
+ // strip json vulnerability protection prefix
+ data = data.replace(PROTECTION_PREFIX, '');
+ if (JSON_START.test(data) && JSON_END.test(data))
+ data = fromJson(data, true);
+ }
+ return data;
+ }],
+
+ // transform outgoing request data
+ transformRequest: [function(d) {
+ return isObject(d) && !isFile(d) ? toJson(d) : d;
+ }],
+
+ // default headers
+ headers: {
+ common: {
+ 'Accept': 'application/json, text/plain, */*',
+ 'X-Requested-With': 'XMLHttpRequest'
+ },
+ post: {'Content-Type': 'application/json;charset=utf-8'},
+ put: {'Content-Type': 'application/json;charset=utf-8'}
+ }
+ };
+
+ var providerResponseInterceptors = this.responseInterceptors = [];
+
+ this.$get = ['$httpBackend', '$browser', '$cacheFactory', '$rootScope', '$q', '$injector',
+ function($httpBackend, $browser, $cacheFactory, $rootScope, $q, $injector) {
+
+ var defaultCache = $cacheFactory('$http'),
+ responseInterceptors = [];
+
+ forEach(providerResponseInterceptors, function(interceptor) {
+ responseInterceptors.push(
+ isString(interceptor)
+ ? $injector.get(interceptor)
+ : $injector.invoke(interceptor)
+ );
+ });
+
+
+ /**
+ * @ngdoc function
+ * @name ng.$http
+ * @requires $httpBackend
+ * @requires $browser
+ * @requires $cacheFactory
+ * @requires $rootScope
+ * @requires $q
+ * @requires $injector
+ *
+ * @description
+ * The `$http` service is a core Angular service that facilitates communication with the remote
+ * HTTP servers via the browser's {@link https://developer.mozilla.org/en/xmlhttprequest
+ * XMLHttpRequest} object or via {@link http://en.wikipedia.org/wiki/JSONP JSONP}.
+ *
+ * For unit testing applications that use `$http` service, see
+ * {@link ngMock.$httpBackend $httpBackend mock}.
+ *
+ * For a higher level of abstraction, please check out the {@link ngResource.$resource
+ * $resource} service.
+ *
+ * The $http API is based on the {@link ng.$q deferred/promise APIs} exposed by
+ * the $q service. While for simple usage patterns this doesn't matter much, for advanced usage
+ * it is important to familiarize yourself with these APIs and the guarantees they provide.
+ *
+ *
+ * # General usage
+ * The `$http` service is a function which takes a single argument — a configuration object —
+ * that is used to generate an HTTP request and returns a {@link ng.$q promise}
+ * with two $http specific methods: `success` and `error`.
+ *
+ * <pre>
+ * $http({method: 'GET', url: '/someUrl'}).
+ * success(function(data, status, headers, config) {
+ * // this callback will be called asynchronously
+ * // when the response is available
+ * }).
+ * error(function(data, status, headers, config) {
+ * // called asynchronously if an error occurs
+ * // or server returns response with an error status.
+ * });
+ * </pre>
+ *
+ * Since the returned value of calling the $http function is a `promise`, you can also use
+ * the `then` method to register callbacks, and these callbacks will receive a single argument –
+ * an object representing the response. See the API signature and type info below for more
+ * details.
+ *
+ * A response status code between 200 and 299 is considered a success status and
+ * will result in the success callback being called. Note that if the response is a redirect,
+ * XMLHttpRequest will transparently follow it, meaning that the error callback will not be
+ * called for such responses.
+ *
+ * # Shortcut methods
+ *
+ * Since all invocations of the $http service require passing in an HTTP method and URL, and
+ * POST/PUT requests require request data to be provided as well, shortcut methods
+ * were created:
+ *
+ * <pre>
+ * $http.get('/someUrl').success(successCallback);
+ * $http.post('/someUrl', data).success(successCallback);
+ * </pre>
+ *
+ * Complete list of shortcut methods:
+ *
+ * - {@link ng.$http#get $http.get}
+ * - {@link ng.$http#head $http.head}
+ * - {@link ng.$http#post $http.post}
+ * - {@link ng.$http#put $http.put}
+ * - {@link ng.$http#delete $http.delete}
+ * - {@link ng.$http#jsonp $http.jsonp}
+ *
+ *
+ * # Setting HTTP Headers
+ *
+ * The $http service will automatically add certain HTTP headers to all requests. These defaults
+ * can be fully configured by accessing the `$httpProvider.defaults.headers` configuration
+ * object, which currently contains this default configuration:
+ *
+ * - `$httpProvider.defaults.headers.common` (headers that are common for all requests):
+ * - `Accept: application/json, text/plain, * / *`
+ * - `X-Requested-With: XMLHttpRequest`
+ * - `$httpProvider.defaults.headers.post`: (header defaults for POST requests)
+ * - `Content-Type: application/json`
+ * - `$httpProvider.defaults.headers.put` (header defaults for PUT requests)
+ * - `Content-Type: application/json`
+ *
+ * To add or overwrite these defaults, simply add or remove a property from these configuration
+ * objects. To add headers for an HTTP method other than POST or PUT, simply add a new object
+ * with the lowercased HTTP method name as the key, e.g.
+ * `$httpProvider.defaults.headers.get['My-Header']='value'`.
+ *
+ * Additionally, the defaults can be set at runtime via the `$http.defaults` object in the same
+ * fashion.
+ *
+ *
+ * # Transforming Requests and Responses
+ *
+ * Both requests and responses can be transformed using transform functions. By default, Angular
+ * applies these transformations:
+ *
+ * Request transformations:
+ *
+ * - If the `data` property of the request configuration object contains an object, serialize it into
+ * JSON format.
+ *
+ * Response transformations:
+ *
+ * - If XSRF prefix is detected, strip it (see Security Considerations section below).
+ * - If JSON response is detected, deserialize it using a JSON parser.
+ *
+ * To globally augment or override the default transforms, modify the `$httpProvider.defaults.transformRequest` and
+ * `$httpProvider.defaults.transformResponse` properties. These properties are by default an
+ * array of transform functions, which allows you to `push` or `unshift` a new transformation function into the
+ * transformation chain. You can also decide to completely override any default transformations by assigning your
+ * transformation functions to these properties directly without the array wrapper.
+ *
+ * Similarly, to locally override the request/response transforms, augment the `transformRequest` and/or
+ * `transformResponse` properties of the configuration object passed into `$http`.
+ *
+ *
+ * # Caching
+ *
+ * To enable caching, set the configuration property `cache` to `true`. When the cache is
+ * enabled, `$http` stores the response from the server in local cache. Next time the
+ * response is served from the cache without sending a request to the server.
+ *
+ * Note that even if the response is served from cache, delivery of the data is asynchronous in
+ * the same way that real requests are.
+ *
+ * If there are multiple GET requests for the same URL that should be cached using the same
+ * cache, but the cache is not populated yet, only one request to the server will be made and
+ * the remaining requests will be fulfilled using the response from the first request.
+ *
+ *
+ * # Response interceptors
+ *
+ * Before you start creating interceptors, be sure to understand the
+ * {@link ng.$q $q and deferred/promise APIs}.
+ *
+ * For purposes of global error handling, authentication or any kind of synchronous or
+ * asynchronous preprocessing of received responses, it is desirable to be able to intercept
+ * responses for http requests before they are handed over to the application code that
+ * initiated these requests. The response interceptors leverage the {@link ng.$q
+ * promise apis} to fulfil this need for both synchronous and asynchronous preprocessing.
+ *
+ * The interceptors are service factories that are registered with the $httpProvider by
+ * adding them to the `$httpProvider.responseInterceptors` array. The factory is called and
+ * injected with dependencies (if specified) and returns the interceptor — a function that
+ * takes a {@link ng.$q promise} and returns the original or a new promise.
+ *
+ * <pre>
+ * // register the interceptor as a service
+ * $provide.factory('myHttpInterceptor', function($q, dependency1, dependency2) {
+ * return function(promise) {
+ * return promise.then(function(response) {
+ * // do something on success
+ * return response;
+ * }, function(response) {
+ * // do something on error
+ * if (canRecover(response)) {
+ * return responseOrNewPromise
+ * }
+ * return $q.reject(response);
+ * });
+ * }
+ * });
+ *
+ * $httpProvider.responseInterceptors.push('myHttpInterceptor');
+ *
+ *
+ * // register the interceptor via an anonymous factory
+ * $httpProvider.responseInterceptors.push(function($q, dependency1, dependency2) {
+ * return function(promise) {
+ * // same as above
+ * }
+ * });
+ * </pre>
+ *
+ *
+ * # Security Considerations
+ *
+ * When designing web applications, consider security threats from:
+ *
+ * - {@link http://haacked.com/archive/2008/11/20/anatomy-of-a-subtle-json-vulnerabilit…
+ * JSON vulnerability}
+ * - {@link http://en.wikipedia.org/wiki/Cross-site_request_forgery XSRF}
+ *
+ * Both server and the client must cooperate in order to eliminate these threats. Angular comes
+ * pre-configured with strategies that address these issues, but for this to work backend server
+ * cooperation is required.
+ *
+ * ## JSON Vulnerability Protection
+ *
+ * A {@link http://haacked.com/archive/2008/11/20/anatomy-of-a-subtle-json-vulnerabilit…
+ * JSON vulnerability} allows third party website to turn your JSON resource URL into
+ * {@link http://en.wikipedia.org/wiki/JSONP JSONP} request under some conditions. To
+ * counter this your server can prefix all JSON requests with following string `")]}',\n"`.
+ * Angular will automatically strip the prefix before processing it as JSON.
+ *
+ * For example if your server needs to return:
+ * <pre>
+ * ['one','two']
+ * </pre>
+ *
+ * which is vulnerable to attack, your server can return:
+ * <pre>
+ * )]}',
+ * ['one','two']
+ * </pre>
+ *
+ * Angular will strip the prefix, before processing the JSON.
+ *
+ *
+ * ## Cross Site Request Forgery (XSRF) Protection
+ *
+ * {@link http://en.wikipedia.org/wiki/Cross-site_request_forgery XSRF} is a technique by which
+ * an unauthorized site can gain your user's private data. Angular provides a mechanism
+ * to counter XSRF. When performing XHR requests, the $http service reads a token from a cookie
+ * called `XSRF-TOKEN` and sets it as the HTTP header `X-XSRF-TOKEN`. Since only JavaScript that
+ * runs on your domain could read the cookie, your server can be assured that the XHR came from
+ * JavaScript running on your domain.
+ *
+ * To take advantage of this, your server needs to set a token in a JavaScript readable session
+ * cookie called `XSRF-TOKEN` on the first HTTP GET request. On subsequent XHR requests the
+ * server can verify that the cookie matches `X-XSRF-TOKEN` HTTP header, and therefore be sure
+ * that only JavaScript running on your domain could have sent the request. The token must be
+ * unique for each user and must be verifiable by the server (to prevent the JavaScript from making
+ * up its own tokens). We recommend that the token is a digest of your site's authentication
+ * cookie with a {@link https://en.wikipedia.org/wiki/Salt_(cryptography) salt} for added security.
+ *
+ *
+ * @param {object} config Object describing the request to be made and how it should be
+ * processed. The object has following properties:
+ *
+ * - **method** – `{string}` – HTTP method (e.g. 'GET', 'POST', etc)
+ * - **url** – `{string}` – Absolute or relative URL of the resource that is being requested.
+ * - **params** – `{Object.<string|Object>}` – Map of strings or objects which will be turned to
+ * `?key1=value1&key2=value2` after the url. If the value is not a string, it will be JSONified.
+ * - **data** – `{string|Object}` – Data to be sent as the request message data.
+ * - **headers** – `{Object}` – Map of strings representing HTTP headers to send to the server.
+ * - **transformRequest** – `{function(data, headersGetter)|Array.<function(data, headersGetter)>}` –
+ * transform function or an array of such functions. The transform function takes the http
+ * request body and headers and returns its transformed (typically serialized) version.
+ * - **transformResponse** – `{function(data, headersGetter)|Array.<function(data, headersGetter)>}` –
+ * transform function or an array of such functions. The transform function takes the http
+ * response body and headers and returns its transformed (typically deserialized) version.
+ * - **cache** – `{boolean|Cache}` – If true, a default $http cache will be used to cache the
+ * GET request, otherwise if a cache instance built with
+ * {@link ng.$cacheFactory $cacheFactory}, this cache will be used for
+ * caching.
+ * - **timeout** – `{number}` – timeout in milliseconds.
+ * - **withCredentials** - `{boolean}` - whether to to set the `withCredentials` flag on the
+ * XHR object. See {@link https://developer.mozilla.org/en/http_access_control#section_5
+ * requests with credentials} for more information.
+ *
+ * @returns {HttpPromise} Returns a {@link ng.$q promise} object with the
+ * standard `then` method and two http specific methods: `success` and `error`. The `then`
+ * method takes two arguments a success and an error callback which will be called with a
+ * response object. The `success` and `error` methods take a single argument - a function that
+ * will be called when the request succeeds or fails respectively. The arguments passed into
+ * these functions are destructured representation of the response object passed into the
+ * `then` method. The response object has these properties:
+ *
+ * - **data** – `{string|Object}` – The response body transformed with the transform functions.
+ * - **status** – `{number}` – HTTP status code of the response.
+ * - **headers** – `{function([headerName])}` – Header getter function.
+ * - **config** – `{Object}` – The configuration object that was used to generate the request.
+ *
+ * @property {Array.<Object>} pendingRequests Array of config objects for currently pending
+ * requests. This is primarily meant to be used for debugging purposes.
+ *
+ *
+ * @example
+ <example>
+ <file name="index.html">
+ <div ng-controller="FetchCtrl">
+ <select ng-model="method">
+ <option>GET</option>
+ <option>JSONP</option>
+ </select>
+ <input type="text" ng-model="url" size="80"/>
+ <button ng-click="fetch()">fetch</button><br>
+ <button ng-click="updateModel('GET', 'http-hello.html')">Sample GET</button>
+ <button ng-click="updateModel('JSONP', 'http://angularjs.org/greet.php?callback=JSON_CALLBACK&name=Super%20Hero')">Sample JSONP</button>
+ <button ng-click="updateModel('JSONP', 'http://angularjs.org/doesntexist&callback=JSON_CALLBACK')">Invalid JSONP</button>
+ <pre>http status code: {{status}}</pre>
+ <pre>http response data: {{data}}</pre>
+ </div>
+ </file>
+ <file name="script.js">
+ function FetchCtrl($scope, $http, $templateCache) {
+ $scope.method = 'GET';
+ $scope.url = 'http-hello.html';
+
+ $scope.fetch = function() {
+ $scope.code = null;
+ $scope.response = null;
+
+ $http({method: $scope.method, url: $scope.url, cache: $templateCache}).
+ success(function(data, status) {
+ $scope.status = status;
+ $scope.data = data;
+ }).
+ error(function(data, status) {
+ $scope.data = data || "Request failed";
+ $scope.status = status;
+ });
+ };
+
+ $scope.updateModel = function(method, url) {
+ $scope.method = method;
+ $scope.url = url;
+ };
+ }
+ </file>
+ <file name="http-hello.html">
+ Hello, $http!
+ </file>
+ <file name="scenario.js">
+ it('should make an xhr GET request', function() {
+ element(':button:contains("Sample GET")').click();
+ element(':button:contains("fetch")').click();
+ expect(binding('status')).toBe('200');
+ expect(binding('data')).toMatch(/Hello, \$http!/);
+ });
+
+ it('should make a JSONP request to angularjs.org', function() {
+ element(':button:contains("Sample JSONP")').click();
+ element(':button:contains("fetch")').click();
+ expect(binding('status')).toBe('200');
+ expect(binding('data')).toMatch(/Super Hero!/);
+ });
+
+ it('should make JSONP request to invalid URL and invoke the error handler',
+ function() {
+ element(':button:contains("Invalid JSONP")').click();
+ element(':button:contains("fetch")').click();
+ expect(binding('status')).toBe('0');
+ expect(binding('data')).toBe('Request failed');
+ });
+ </file>
+ </example>
+ */
+ function $http(config) {
+ config.method = uppercase(config.method);
+
+ var reqTransformFn = config.transformRequest || $config.transformRequest,
+ respTransformFn = config.transformResponse || $config.transformResponse,
+ reqHeaders = extend({}, config.headers),
+ defHeaders = extend(
+ {'X-XSRF-TOKEN': $browser.cookies()['XSRF-TOKEN']},
+ $config.headers.common,
+ $config.headers[lowercase(config.method)]
+ ),
+ reqData,
+ defHeaderName, lowercaseDefHeaderName, headerName,
+ promise;
+
+ // using for-in instead of forEach to avoid unecessary iteration after header has been found
+ defaultHeadersIteration:
+ for(defHeaderName in defHeaders) {
+ lowercaseDefHeaderName = lowercase(defHeaderName);
+ for(headerName in config.headers) {
+ if (lowercase(headerName) === lowercaseDefHeaderName) {
+ continue defaultHeadersIteration;
+ }
+ }
+ reqHeaders[defHeaderName] = defHeaders[defHeaderName];
+ }
+
+ // strip content-type if data is undefined
+ if (isUndefined(config.data)) {
+ for(var header in reqHeaders) {
+ if (lowercase(header) === 'content-type') {
+ delete reqHeaders[header];
+ break;
+ }
+ }
+ }
+
+ reqData = transformData(config.data, headersGetter(reqHeaders), reqTransformFn);
+
+ // send request
+ promise = sendReq(config, reqData, reqHeaders);
+
+
+ // transform future response
+ promise = promise.then(transformResponse, transformResponse);
+
+ // apply interceptors
+ forEach(responseInterceptors, function(interceptor) {
+ promise = interceptor(promise);
+ });
+
+ promise.success = function(fn) {
+ promise.then(function(response) {
+ fn(response.data, response.status, response.headers, config);
+ });
+ return promise;
+ };
+
+ promise.error = function(fn) {
+ promise.then(null, function(response) {
+ fn(response.data, response.status, response.headers, config);
+ });
+ return promise;
+ };
+
+ return promise;
+
+ function transformResponse(response) {
+ // make a copy since the response must be cacheable
+ var resp = extend({}, response, {
+ data: transformData(response.data, response.headers, respTransformFn)
+ });
+ return (isSuccess(response.status))
+ ? resp
+ : $q.reject(resp);
+ }
+ }
+
+ $http.pendingRequests = [];
+
+ /**
+ * @ngdoc method
+ * @name ng.$http#get
+ * @methodOf ng.$http
+ *
+ * @description
+ * Shortcut method to perform `GET` request.
+ *
+ * @param {string} url Relative or absolute URL specifying the destination of the request
+ * @param {Object=} config Optional configuration object
+ * @returns {HttpPromise} Future object
+ */
+
+ /**
+ * @ngdoc method
+ * @name ng.$http#delete
+ * @methodOf ng.$http
+ *
+ * @description
+ * Shortcut method to perform `DELETE` request.
+ *
+ * @param {string} url Relative or absolute URL specifying the destination of the request
+ * @param {Object=} config Optional configuration object
+ * @returns {HttpPromise} Future object
+ */
+
+ /**
+ * @ngdoc method
+ * @name ng.$http#head
+ * @methodOf ng.$http
+ *
+ * @description
+ * Shortcut method to perform `HEAD` request.
+ *
+ * @param {string} url Relative or absolute URL specifying the destination of the request
+ * @param {Object=} config Optional configuration object
+ * @returns {HttpPromise} Future object
+ */
+
+ /**
+ * @ngdoc method
+ * @name ng.$http#jsonp
+ * @methodOf ng.$http
+ *
+ * @description
+ * Shortcut method to perform `JSONP` request.
+ *
+ * @param {string} url Relative or absolute URL specifying the destination of the request.
+ * Should contain `JSON_CALLBACK` string.
+ * @param {Object=} config Optional configuration object
+ * @returns {HttpPromise} Future object
+ */
+ createShortMethods('get', 'delete', 'head', 'jsonp');
+
+ /**
+ * @ngdoc method
+ * @name ng.$http#post
+ * @methodOf ng.$http
+ *
+ * @description
+ * Shortcut method to perform `POST` request.
+ *
+ * @param {string} url Relative or absolute URL specifying the destination of the request
+ * @param {*} data Request content
+ * @param {Object=} config Optional configuration object
+ * @returns {HttpPromise} Future object
+ */
+
+ /**
+ * @ngdoc method
+ * @name ng.$http#put
+ * @methodOf ng.$http
+ *
+ * @description
+ * Shortcut method to perform `PUT` request.
+ *
+ * @param {string} url Relative or absolute URL specifying the destination of the request
+ * @param {*} data Request content
+ * @param {Object=} config Optional configuration object
+ * @returns {HttpPromise} Future object
+ */
+ createShortMethodsWithData('post', 'put');
+
+ /**
+ * @ngdoc property
+ * @name ng.$http#defaults
+ * @propertyOf ng.$http
+ *
+ * @description
+ * Runtime equivalent of the `$httpProvider.defaults` property. Allows configuration of
+ * default headers as well as request and response transformations.
+ *
+ * See "Setting HTTP Headers" and "Transforming Requests and Responses" sections above.
+ */
+ $http.defaults = $config;
+
+
+ return $http;
+
+
+ function createShortMethods(names) {
+ forEach(arguments, function(name) {
+ $http[name] = function(url, config) {
+ return $http(extend(config || {}, {
+ method: name,
+ url: url
+ }));
+ };
+ });
+ }
+
+
+ function createShortMethodsWithData(name) {
+ forEach(arguments, function(name) {
+ $http[name] = function(url, data, config) {
+ return $http(extend(config || {}, {
+ method: name,
+ url: url,
+ data: data
+ }));
+ };
+ });
+ }
+
+
+ /**
+ * Makes the request.
+ *
+ * !!! ACCESSES CLOSURE VARS:
+ * $httpBackend, $config, $log, $rootScope, defaultCache, $http.pendingRequests
+ */
+ function sendReq(config, reqData, reqHeaders) {
+ var deferred = $q.defer(),
+ promise = deferred.promise,
+ cache,
+ cachedResp,
+ url = buildUrl(config.url, config.params);
+
+ $http.pendingRequests.push(config);
+ promise.then(removePendingReq, removePendingReq);
+
+
+ if (config.cache && config.method == 'GET') {
+ cache = isObject(config.cache) ? config.cache : defaultCache;
+ }
+
+ if (cache) {
+ cachedResp = cache.get(url);
+ if (cachedResp) {
+ if (cachedResp.then) {
+ // cached request has already been sent, but there is no response yet
+ cachedResp.then(removePendingReq, removePendingReq);
+ return cachedResp;
+ } else {
+ // serving from cache
+ if (isArray(cachedResp)) {
+ resolvePromise(cachedResp[1], cachedResp[0], copy(cachedResp[2]));
+ } else {
+ resolvePromise(cachedResp, 200, {});
+ }
+ }
+ } else {
+ // put the promise for the non-transformed response into cache as a placeholder
+ cache.put(url, promise);
+ }
+ }
+
+ // if we won't have the response in cache, send the request to the backend
+ if (!cachedResp) {
+ $httpBackend(config.method, url, reqData, done, reqHeaders, config.timeout,
+ config.withCredentials);
+ }
+
+ return promise;
+
+
+ /**
+ * Callback registered to $httpBackend():
+ * - caches the response if desired
+ * - resolves the raw $http promise
+ * - calls $apply
+ */
+ function done(status, response, headersString) {
+ if (cache) {
+ if (isSuccess(status)) {
+ cache.put(url, [status, response, parseHeaders(headersString)]);
+ } else {
+ // remove promise from the cache
+ cache.remove(url);
+ }
+ }
+
+ resolvePromise(response, status, headersString);
+ $rootScope.$apply();
+ }
+
+
+ /**
+ * Resolves the raw $http promise.
+ */
+ function resolvePromise(response, status, headers) {
+ // normalize internal statuses to 0
+ status = Math.max(status, 0);
+
+ (isSuccess(status) ? deferred.resolve : deferred.reject)({
+ data: response,
+ status: status,
+ headers: headersGetter(headers),
+ config: config
+ });
+ }
+
+
+ function removePendingReq() {
+ var idx = indexOf($http.pendingRequests, config);
+ if (idx !== -1) $http.pendingRequests.splice(idx, 1);
+ }
+ }
+
+
+ function buildUrl(url, params) {
+ if (!params) return url;
+ var parts = [];
+ forEachSorted(params, function(value, key) {
+ if (value == null || value == undefined) return;
+ if (isObject(value)) {
+ value = toJson(value);
+ }
+ parts.push(encodeURIComponent(key) + '=' + encodeURIComponent(value));
+ });
+ return url + ((url.indexOf('?') == -1) ? '?' : '&') + parts.join('&');
+ }
+
+
+ }];
+}
+
+var XHR = window.XMLHttpRequest || function() {
+ try { return new ActiveXObject("Msxml2.XMLHTTP.6.0"); } catch (e1) {}
+ try { return new ActiveXObject("Msxml2.XMLHTTP.3.0"); } catch (e2) {}
+ try { return new ActiveXObject("Msxml2.XMLHTTP"); } catch (e3) {}
+ throw new Error("This browser does not support XMLHttpRequest.");
+};
+
+
+/**
+ * @ngdoc object
+ * @name ng.$httpBackend
+ * @requires $browser
+ * @requires $window
+ * @requires $document
+ *
+ * @description
+ * HTTP backend used by the {@link ng.$http service} that delegates to
+ * XMLHttpRequest object or JSONP and deals with browser incompatibilities.
+ *
+ * You should never need to use this service directly, instead use the higher-level abstractions:
+ * {@link ng.$http $http} or {@link ngResource.$resource $resource}.
+ *
+ * During testing this implementation is swapped with {@link ngMock.$httpBackend mock
+ * $httpBackend} which can be trained with responses.
+ */
+function $HttpBackendProvider() {
+ this.$get = ['$browser', '$window', '$document', function($browser, $window, $document) {
+ return createHttpBackend($browser, XHR, $browser.defer, $window.angular.callbacks,
+ $document[0], $window.location.protocol.replace(':', ''));
+ }];
+}
+
+function createHttpBackend($browser, XHR, $browserDefer, callbacks, rawDocument, locationProtocol) {
+ // TODO(vojta): fix the signature
+ return function(method, url, post, callback, headers, timeout, withCredentials) {
+ $browser.$$incOutstandingRequestCount();
+ url = url || $browser.url();
+
+ if (lowercase(method) == 'jsonp') {
+ var callbackId = '_' + (callbacks.counter++).toString(36);
+ callbacks[callbackId] = function(data) {
+ callbacks[callbackId].data = data;
+ };
+
+ jsonpReq(url.replace('JSON_CALLBACK', 'angular.callbacks.' + callbackId),
+ function() {
+ if (callbacks[callbackId].data) {
+ completeRequest(callback, 200, callbacks[callbackId].data);
+ } else {
+ completeRequest(callback, -2);
+ }
+ delete callbacks[callbackId];
+ });
+ } else {
+ var xhr = new XHR();
+ xhr.open(method, url, true);
+ forEach(headers, function(value, key) {
+ if (value) xhr.setRequestHeader(key, value);
+ });
+
+ var status;
+
+ // In IE6 and 7, this might be called synchronously when xhr.send below is called and the
+ // response is in the cache. the promise api will ensure that to the app code the api is
+ // always async
+ xhr.onreadystatechange = function() {
+ if (xhr.readyState == 4) {
+ var responseHeaders = xhr.getAllResponseHeaders();
+
+ // TODO(vojta): remove once Firefox 21 gets released.
+ // begin: workaround to overcome Firefox CORS http response headers bug
+ // https://bugzilla.mozilla.org/show_bug.cgi?id=608735
+ // Firefox already patched in nightly. Should land in Firefox 21.
+
+ // CORS "simple response headers" http://www.w3.org/TR/cors/
+ var value,
+ simpleHeaders = ["Cache-Control", "Content-Language", "Content-Type",
+ "Expires", "Last-Modified", "Pragma"];
+ if (!responseHeaders) {
+ responseHeaders = "";
+ forEach(simpleHeaders, function (header) {
+ var value = xhr.getResponseHeader(header);
+ if (value) {
+ responseHeaders += header + ": " + value + "\n";
+ }
+ });
+ }
+ // end of the workaround.
+
+ completeRequest(callback, status || xhr.status, xhr.responseText,
+ responseHeaders);
+ }
+ };
+
+ if (withCredentials) {
+ xhr.withCredentials = true;
+ }
+
+ xhr.send(post || '');
+
+ if (timeout > 0) {
+ $browserDefer(function() {
+ status = -1;
+ xhr.abort();
+ }, timeout);
+ }
+ }
+
+
+ function completeRequest(callback, status, response, headersString) {
+ // URL_MATCH is defined in src/service/location.js
+ var protocol = (url.match(URL_MATCH) || ['', locationProtocol])[1];
+
+ // fix status code for file protocol (it's always 0)
+ status = (protocol == 'file') ? (response ? 200 : 404) : status;
+
+ // normalize IE bug (http://bugs.jquery.com/ticket/1450)
+ status = status == 1223 ? 204 : status;
+
+ callback(status, response, headersString);
+ $browser.$$completeOutstandingRequest(noop);
+ }
+ };
+
+ function jsonpReq(url, done) {
+ // we can't use jQuery/jqLite here because jQuery does crazy shit with script elements, e.g.:
+ // - fetches local scripts via XHR and evals them
+ // - adds and immediately removes script elements from the document
+ var script = rawDocument.createElement('script'),
+ doneWrapper = function() {
+ rawDocument.body.removeChild(script);
+ if (done) done();
+ };
+
+ script.type = 'text/javascript';
+ script.src = url;
+
+ if (msie) {
+ script.onreadystatechange = function() {
+ if (/loaded|complete/.test(script.readyState)) doneWrapper();
+ };
+ } else {
+ script.onload = script.onerror = doneWrapper;
+ }
+
+ rawDocument.body.appendChild(script);
+ }
+}
+
+/**
+ * @ngdoc object
+ * @name ng.$locale
+ *
+ * @description
+ * $locale service provides localization rules for various Angular components. As of right now the
+ * only public api is:
+ *
+ * * `id` – `{string}` – locale id formatted as `languageId-countryId` (e.g. `en-us`)
+ */
+function $LocaleProvider(){
+ this.$get = function() {
+ return {
+ id: 'en-us',
+
+ NUMBER_FORMATS: {
+ DECIMAL_SEP: '.',
+ GROUP_SEP: ',',
+ PATTERNS: [
+ { // Decimal Pattern
+ minInt: 1,
+ minFrac: 0,
+ maxFrac: 3,
+ posPre: '',
+ posSuf: '',
+ negPre: '-',
+ negSuf: '',
+ gSize: 3,
+ lgSize: 3
+ },{ //Currency Pattern
+ minInt: 1,
+ minFrac: 2,
+ maxFrac: 2,
+ posPre: '\u00A4',
+ posSuf: '',
+ negPre: '(\u00A4',
+ negSuf: ')',
+ gSize: 3,
+ lgSize: 3
+ }
+ ],
+ CURRENCY_SYM: '$'
+ },
+
+ DATETIME_FORMATS: {
+ MONTH: 'January,February,March,April,May,June,July,August,September,October,November,December'
+ .split(','),
+ SHORTMONTH: 'Jan,Feb,Mar,Apr,May,Jun,Jul,Aug,Sep,Oct,Nov,Dec'.split(','),
+ DAY: 'Sunday,Monday,Tuesday,Wednesday,Thursday,Friday,Saturday'.split(','),
+ SHORTDAY: 'Sun,Mon,Tue,Wed,Thu,Fri,Sat'.split(','),
+ AMPMS: ['AM','PM'],
+ medium: 'MMM d, y h:mm:ss a',
+ short: 'M/d/yy h:mm a',
+ fullDate: 'EEEE, MMMM d, y',
+ longDate: 'MMMM d, y',
+ mediumDate: 'MMM d, y',
+ shortDate: 'M/d/yy',
+ mediumTime: 'h:mm:ss a',
+ shortTime: 'h:mm a'
+ },
+
+ pluralCat: function(num) {
+ if (num === 1) {
+ return 'one';
+ }
+ return 'other';
+ }
+ };
+ };
+}
+
+function $TimeoutProvider() {
+ this.$get = ['$rootScope', '$browser', '$q', '$exceptionHandler',
+ function($rootScope, $browser, $q, $exceptionHandler) {
+ var deferreds = {};
+
+
+ /**
+ * @ngdoc function
+ * @name ng.$timeout
+ * @requires $browser
+ *
+ * @description
+ * Angular's wrapper for `window.setTimeout`. The `fn` function is wrapped into a try/catch
+ * block and delegates any exceptions to
+ * {@link ng.$exceptionHandler $exceptionHandler} service.
+ *
+ * The return value of registering a timeout function is a promise, which will be resolved when
+ * the timeout is reached and the timeout function is executed.
+ *
+ * To cancel a timeout request, call `$timeout.cancel(promise)`.
+ *
+ * In tests you can use {@link ngMock.$timeout `$timeout.flush()`} to
+ * synchronously flush the queue of deferred functions.
+ *
+ * @param {function()} fn A function, whose execution should be delayed.
+ * @param {number=} [delay=0] Delay in milliseconds.
+ * @param {boolean=} [invokeApply=true] If set to `false` skips model dirty checking, otherwise
+ * will invoke `fn` within the {@link ng.$rootScope.Scope#$apply $apply} block.
+ * @returns {Promise} Promise that will be resolved when the timeout is reached. The value this
+ * promise will be resolved with is the return value of the `fn` function.
+ */
+ function timeout(fn, delay, invokeApply) {
+ var deferred = $q.defer(),
+ promise = deferred.promise,
+ skipApply = (isDefined(invokeApply) && !invokeApply),
+ timeoutId, cleanup;
+
+ timeoutId = $browser.defer(function() {
+ try {
+ deferred.resolve(fn());
+ } catch(e) {
+ deferred.reject(e);
+ $exceptionHandler(e);
+ }
+ finally {
+ delete deferreds[promise.$$timeoutId];
+ }
+
+ if (!skipApply) $rootScope.$apply();
+ }, delay);
+
+ promise.$$timeoutId = timeoutId;
+ deferreds[timeoutId] = deferred;
+
+ return promise;
+ }
+
+
+ /**
+ * @ngdoc function
+ * @name ng.$timeout#cancel
+ * @methodOf ng.$timeout
+ *
+ * @description
+ * Cancels a task associated with the `promise`. As a result of this, the promise will be
+ * resolved with a rejection.
+ *
+ * @param {Promise=} promise Promise returned by the `$timeout` function.
+ * @returns {boolean} Returns `true` if the task hasn't executed yet and was successfully
+ * canceled.
+ */
+ timeout.cancel = function(promise) {
+ if (promise && promise.$$timeoutId in deferreds) {
+ deferreds[promise.$$timeoutId].reject('canceled');
+ delete deferreds[promise.$$timeoutId];
+ return $browser.defer.cancel(promise.$$timeoutId);
+ }
+ return false;
+ };
+
+ return timeout;
+ }];
+}
+
+/**
+ * @ngdoc object
+ * @name ng.$filterProvider
+ * @description
+ *
+ * Filters are just functions which transform input to an output. However filters need to be Dependency Injected. To
+ * achieve this a filter definition consists of a factory function which is annotated with dependencies and is
+ * responsible for creating a filter function.
+ *
+ * <pre>
+ * // Filter registration
+ * function MyModule($provide, $filterProvider) {
+ * // create a service to demonstrate injection (not always needed)
+ * $provide.value('greet', function(name){
+ * return 'Hello ' + name + '!';
+ * });
+ *
+ * // register a filter factory which uses the
+ * // greet service to demonstrate DI.
+ * $filterProvider.register('greet', function(greet){
+ * // return the filter function which uses the greet service
+ * // to generate salutation
+ * return function(text) {
+ * // filters need to be forgiving so check input validity
+ * return text && greet(text) || text;
+ * };
+ * });
+ * }
+ * </pre>
+ *
+ * The filter function is registered with the `$injector` under the filter name suffixe with `Filter`.
+ * <pre>
+ * it('should be the same instance', inject(
+ * function($filterProvider) {
+ * $filterProvider.register('reverse', function(){
+ * return ...;
+ * });
+ * },
+ * function($filter, reverseFilter) {
+ * expect($filter('reverse')).toBe(reverseFilter);
+ * });
+ * </pre>
+ *
+ *
+ * For more information about how angular filters work, and how to create your own filters, see
+ * {@link guide/dev_guide.templates.filters Understanding Angular Filters} in the angular Developer
+ * Guide.
+ */
+/**
+ * @ngdoc method
+ * @name ng.$filterProvider#register
+ * @methodOf ng.$filterProvider
+ * @description
+ * Register filter factory function.
+ *
+ * @param {String} name Name of the filter.
+ * @param {function} fn The filter factory function which is injectable.
+ */
+
+
+/**
+ * @ngdoc function
+ * @name ng.$filter
+ * @function
+ * @description
+ * Filters are used for formatting data displayed to the user.
+ *
+ * The general syntax in templates is as follows:
+ *
+ * {{ expression [| filter_name[:parameter_value] ... ] }}
+ *
+ * @param {String} name Name of the filter function to retrieve
+ * @return {Function} the filter function
+ */
+$FilterProvider.$inject = ['$provide'];
+function $FilterProvider($provide) {
+ var suffix = 'Filter';
+
+ function register(name, factory) {
+ return $provide.factory(name + suffix, factory);
+ }
+ this.register = register;
+
+ this.$get = ['$injector', function($injector) {
+ return function(name) {
+ return $injector.get(name + suffix);
+ }
+ }];
+
+ ////////////////////////////////////////
+
+ register('currency', currencyFilter);
+ register('date', dateFilter);
+ register('filter', filterFilter);
+ register('json', jsonFilter);
+ register('limitTo', limitToFilter);
+ register('lowercase', lowercaseFilter);
+ register('number', numberFilter);
+ register('orderBy', orderByFilter);
+ register('uppercase', uppercaseFilter);
+}
+
+/**
+ * @ngdoc filter
+ * @name ng.filter:filter
+ * @function
+ *
+ * @description
+ * Selects a subset of items from `array` and returns it as a new array.
+ *
+ * Note: This function is used to augment the `Array` type in Angular expressions. See
+ * {@link ng.$filter} for more information about Angular arrays.
+ *
+ * @param {Array} array The source array.
+ * @param {string|Object|function()} expression The predicate to be used for selecting items from
+ * `array`.
+ *
+ * Can be one of:
+ *
+ * - `string`: Predicate that results in a substring match using the value of `expression`
+ * string. All strings or objects with string properties in `array` that contain this string
+ * will be returned. The predicate can be negated by prefixing the string with `!`.
+ *
+ * - `Object`: A pattern object can be used to filter specific properties on objects contained
+ * by `array`. For example `{name:"M", phone:"1"}` predicate will return an array of items
+ * which have property `name` containing "M" and property `phone` containing "1". A special
+ * property name `$` can be used (as in `{$:"text"}`) to accept a match against any
+ * property of the object. That's equivalent to the simple substring match with a `string`
+ * as described above.
+ *
+ * - `function`: A predicate function can be used to write arbitrary filters. The function is
+ * called for each element of `array`. The final result is an array of those elements that
+ * the predicate returned true for.
+ *
+ * @example
+ <doc:example>
+ <doc:source>
+ <div ng-init="friends = [{name:'John', phone:'555-1276'},
+ {name:'Mary', phone:'800-BIG-MARY'},
+ {name:'Mike', phone:'555-4321'},
+ {name:'Adam', phone:'555-5678'},
+ {name:'Julie', phone:'555-8765'}]"></div>
+
+ Search: <input ng-model="searchText">
+ <table id="searchTextResults">
+ <tr><th>Name</th><th>Phone</th></tr>
+ <tr ng-repeat="friend in friends | filter:searchText">
+ <td>{{friend.name}}</td>
+ <td>{{friend.phone}}</td>
+ </tr>
+ </table>
+ <hr>
+ Any: <input ng-model="search.$"> <br>
+ Name only <input ng-model="search.name"><br>
+ Phone only <input ng-model="search.phone"><br>
+ <table id="searchObjResults">
+ <tr><th>Name</th><th>Phone</th></tr>
+ <tr ng-repeat="friend in friends | filter:search">
+ <td>{{friend.name}}</td>
+ <td>{{friend.phone}}</td>
+ </tr>
+ </table>
+ </doc:source>
+ <doc:scenario>
+ it('should search across all fields when filtering with a string', function() {
+ input('searchText').enter('m');
+ expect(repeater('#searchTextResults tr', 'friend in friends').column('friend.name')).
+ toEqual(['Mary', 'Mike', 'Adam']);
+
+ input('searchText').enter('76');
+ expect(repeater('#searchTextResults tr', 'friend in friends').column('friend.name')).
+ toEqual(['John', 'Julie']);
+ });
+
+ it('should search in specific fields when filtering with a predicate object', function() {
+ input('search.$').enter('i');
+ expect(repeater('#searchObjResults tr', 'friend in friends').column('friend.name')).
+ toEqual(['Mary', 'Mike', 'Julie']);
+ });
+ </doc:scenario>
+ </doc:example>
+ */
+function filterFilter() {
+ return function(array, expression) {
+ if (!isArray(array)) return array;
+ var predicates = [];
+ predicates.check = function(value) {
+ for (var j = 0; j < predicates.length; j++) {
+ if(!predicates[j](value)) {
+ return false;
+ }
+ }
+ return true;
+ };
+ var search = function(obj, text){
+ if (text.charAt(0) === '!') {
+ return !search(obj, text.substr(1));
+ }
+ switch (typeof obj) {
+ case "boolean":
+ case "number":
+ case "string":
+ return ('' + obj).toLowerCase().indexOf(text) > -1;
+ case "object":
+ for ( var objKey in obj) {
+ if (objKey.charAt(0) !== '$' && search(obj[objKey], text)) {
+ return true;
+ }
+ }
+ return false;
+ case "array":
+ for ( var i = 0; i < obj.length; i++) {
+ if (search(obj[i], text)) {
+ return true;
+ }
+ }
+ return false;
+ default:
+ return false;
+ }
+ };
+ switch (typeof expression) {
+ case "boolean":
+ case "number":
+ case "string":
+ expression = {$:expression};
+ case "object":
+ for (var key in expression) {
+ if (key == '$') {
+ (function() {
+ var text = (''+expression[key]).toLowerCase();
+ if (!text) return;
+ predicates.push(function(value) {
+ return search(value, text);
+ });
+ })();
+ } else {
+ (function() {
+ var path = key;
+ var text = (''+expression[key]).toLowerCase();
+ if (!text) return;
+ predicates.push(function(value) {
+ return search(getter(value, path), text);
+ });
+ })();
+ }
+ }
+ break;
+ case 'function':
+ predicates.push(expression);
+ break;
+ default:
+ return array;
+ }
+ var filtered = [];
+ for ( var j = 0; j < array.length; j++) {
+ var value = array[j];
+ if (predicates.check(value)) {
+ filtered.push(value);
+ }
+ }
+ return filtered;
+ }
+}
+
+/**
+ * @ngdoc filter
+ * @name ng.filter:currency
+ * @function
+ *
+ * @description
+ * Formats a number as a currency (ie $1,234.56). When no currency symbol is provided, default
+ * symbol for current locale is used.
+ *
+ * @param {number} amount Input to filter.
+ * @param {string=} symbol Currency symbol or identifier to be displayed.
+ * @returns {string} Formatted number.
+ *
+ *
+ * @example
+ <doc:example>
+ <doc:source>
+ <script>
+ function Ctrl($scope) {
+ $scope.amount = 1234.56;
+ }
+ </script>
+ <div ng-controller="Ctrl">
+ <input type="number" ng-model="amount"> <br>
+ default currency symbol ($): {{amount | currency}}<br>
+ custom currency identifier (USD$): {{amount | currency:"USD$"}}
+ </div>
+ </doc:source>
+ <doc:scenario>
+ it('should init with 1234.56', function() {
+ expect(binding('amount | currency')).toBe('$1,234.56');
+ expect(binding('amount | currency:"USD$"')).toBe('USD$1,234.56');
+ });
+ it('should update', function() {
+ input('amount').enter('-1234');
+ expect(binding('amount | currency')).toBe('($1,234.00)');
+ expect(binding('amount | currency:"USD$"')).toBe('(USD$1,234.00)');
+ });
+ </doc:scenario>
+ </doc:example>
+ */
+currencyFilter.$inject = ['$locale'];
+function currencyFilter($locale) {
+ var formats = $locale.NUMBER_FORMATS;
+ return function(amount, currencySymbol){
+ if (isUndefined(currencySymbol)) currencySymbol = formats.CURRENCY_SYM;
+ return formatNumber(amount, formats.PATTERNS[1], formats.GROUP_SEP, formats.DECIMAL_SEP, 2).
+ replace(/\u00A4/g, currencySymbol);
+ };
+}
+
+/**
+ * @ngdoc filter
+ * @name ng.filter:number
+ * @function
+ *
+ * @description
+ * Formats a number as text.
+ *
+ * If the input is not a number an empty string is returned.
+ *
+ * @param {number|string} number Number to format.
+ * @param {(number|string)=} fractionSize Number of decimal places to round the number to.
+ * If this is not provided then the fraction size is computed from the current locale's number
+ * formatting pattern. In the case of the default locale, it will be 3.
+ * @returns {string} Number rounded to decimalPlaces and places a “,” after each third digit.
+ *
+ * @example
+ <doc:example>
+ <doc:source>
+ <script>
+ function Ctrl($scope) {
+ $scope.val = 1234.56789;
+ }
+ </script>
+ <div ng-controller="Ctrl">
+ Enter number: <input ng-model='val'><br>
+ Default formatting: {{val | number}}<br>
+ No fractions: {{val | number:0}}<br>
+ Negative number: {{-val | number:4}}
+ </div>
+ </doc:source>
+ <doc:scenario>
+ it('should format numbers', function() {
+ expect(binding('val | number')).toBe('1,234.568');
+ expect(binding('val | number:0')).toBe('1,235');
+ expect(binding('-val | number:4')).toBe('-1,234.5679');
+ });
+
+ it('should update', function() {
+ input('val').enter('3374.333');
+ expect(binding('val | number')).toBe('3,374.333');
+ expect(binding('val | number:0')).toBe('3,374');
+ expect(binding('-val | number:4')).toBe('-3,374.3330');
+ });
+ </doc:scenario>
+ </doc:example>
+ */
+
+
+numberFilter.$inject = ['$locale'];
+function numberFilter($locale) {
+ var formats = $locale.NUMBER_FORMATS;
+ return function(number, fractionSize) {
+ return formatNumber(number, formats.PATTERNS[0], formats.GROUP_SEP, formats.DECIMAL_SEP,
+ fractionSize);
+ };
+}
+
+var DECIMAL_SEP = '.';
+function formatNumber(number, pattern, groupSep, decimalSep, fractionSize) {
+ if (isNaN(number) || !isFinite(number)) return '';
+
+ var isNegative = number < 0;
+ number = Math.abs(number);
+ var numStr = number + '',
+ formatedText = '',
+ parts = [];
+
+ var hasExponent = false;
+ if (numStr.indexOf('e') !== -1) {
+ var match = numStr.match(/([\d\.]+)e(-?)(\d+)/);
+ if (match && match[2] == '-' && match[3] > fractionSize + 1) {
+ numStr = '0';
+ } else {
+ formatedText = numStr;
+ hasExponent = true;
+ }
+ }
+
+ if (!hasExponent) {
+ var fractionLen = (numStr.split(DECIMAL_SEP)[1] || '').length;
+
+ // determine fractionSize if it is not specified
+ if (isUndefined(fractionSize)) {
+ fractionSize = Math.min(Math.max(pattern.minFrac, fractionLen), pattern.maxFrac);
+ }
+
+ var pow = Math.pow(10, fractionSize);
+ number = Math.round(number * pow) / pow;
+ var fraction = ('' + number).split(DECIMAL_SEP);
+ var whole = fraction[0];
+ fraction = fraction[1] || '';
+
+ var pos = 0,
+ lgroup = pattern.lgSize,
+ group = pattern.gSize;
+
+ if (whole.length >= (lgroup + group)) {
+ pos = whole.length - lgroup;
+ for (var i = 0; i < pos; i++) {
+ if ((pos - i)%group === 0 && i !== 0) {
+ formatedText += groupSep;
+ }
+ formatedText += whole.charAt(i);
+ }
+ }
+
+ for (i = pos; i < whole.length; i++) {
+ if ((whole.length - i)%lgroup === 0 && i !== 0) {
+ formatedText += groupSep;
+ }
+ formatedText += whole.charAt(i);
+ }
+
+ // format fraction part.
+ while(fraction.length < fractionSize) {
+ fraction += '0';
+ }
+
+ if (fractionSize && fractionSize !== "0") formatedText += decimalSep + fraction.substr(0, fractionSize);
+ } else {
+
+ if (fractionSize > 0 && number > -1 && number < 1) {
+ formatedText = number.toFixed(fractionSize);
+ }
+ }
+
+ parts.push(isNegative ? pattern.negPre : pattern.posPre);
+ parts.push(formatedText);
+ parts.push(isNegative ? pattern.negSuf : pattern.posSuf);
+ return parts.join('');
+}
+
+function padNumber(num, digits, trim) {
+ var neg = '';
+ if (num < 0) {
+ neg = '-';
+ num = -num;
+ }
+ num = '' + num;
+ while(num.length < digits) num = '0' + num;
+ if (trim)
+ num = num.substr(num.length - digits);
+ return neg + num;
+}
+
+
+function dateGetter(name, size, offset, trim) {
+ offset = offset || 0;
+ return function(date) {
+ var value = date['get' + name]();
+ if (offset > 0 || value > -offset)
+ value += offset;
+ if (value === 0 && offset == -12 ) value = 12;
+ return padNumber(value, size, trim);
+ };
+}
+
+function dateStrGetter(name, shortForm) {
+ return function(date, formats) {
+ var value = date['get' + name]();
+ var get = uppercase(shortForm ? ('SHORT' + name) : name);
+
+ return formats[get][value];
+ };
+}
+
+function timeZoneGetter(date) {
+ var zone = -1 * date.getTimezoneOffset();
+ var paddedZone = (zone >= 0) ? "+" : "";
+
+ paddedZone += padNumber(Math[zone > 0 ? 'floor' : 'ceil'](zone / 60), 2) +
+ padNumber(Math.abs(zone % 60), 2);
+
+ return paddedZone;
+}
+
+function ampmGetter(date, formats) {
+ return date.getHours() < 12 ? formats.AMPMS[0] : formats.AMPMS[1];
+}
+
+var DATE_FORMATS = {
+ yyyy: dateGetter('FullYear', 4),
+ yy: dateGetter('FullYear', 2, 0, true),
+ y: dateGetter('FullYear', 1),
+ MMMM: dateStrGetter('Month'),
+ MMM: dateStrGetter('Month', true),
+ MM: dateGetter('Month', 2, 1),
+ M: dateGetter('Month', 1, 1),
+ dd: dateGetter('Date', 2),
+ d: dateGetter('Date', 1),
+ HH: dateGetter('Hours', 2),
+ H: dateGetter('Hours', 1),
+ hh: dateGetter('Hours', 2, -12),
+ h: dateGetter('Hours', 1, -12),
+ mm: dateGetter('Minutes', 2),
+ m: dateGetter('Minutes', 1),
+ ss: dateGetter('Seconds', 2),
+ s: dateGetter('Seconds', 1),
+ EEEE: dateStrGetter('Day'),
+ EEE: dateStrGetter('Day', true),
+ a: ampmGetter,
+ Z: timeZoneGetter
+};
+
+var DATE_FORMATS_SPLIT = /((?:[^yMdHhmsaZE']+)|(?:'(?:[^']|'')*')|(?:E+|y+|M+|d+|H+|h+|m+|s+|a|Z))(.*)/,
+ NUMBER_STRING = /^\d+$/;
+
+/**
+ * @ngdoc filter
+ * @name ng.filter:date
+ * @function
+ *
+ * @description
+ * Formats `date` to a string based on the requested `format`.
+ *
+ * `format` string can be composed of the following elements:
+ *
+ * * `'yyyy'`: 4 digit representation of year (e.g. AD 1 => 0001, AD 2010 => 2010)
+ * * `'yy'`: 2 digit representation of year, padded (00-99). (e.g. AD 2001 => 01, AD 2010 => 10)
+ * * `'y'`: 1 digit representation of year, e.g. (AD 1 => 1, AD 199 => 199)
+ * * `'MMMM'`: Month in year (January-December)
+ * * `'MMM'`: Month in year (Jan-Dec)
+ * * `'MM'`: Month in year, padded (01-12)
+ * * `'M'`: Month in year (1-12)
+ * * `'dd'`: Day in month, padded (01-31)
+ * * `'d'`: Day in month (1-31)
+ * * `'EEEE'`: Day in Week,(Sunday-Saturday)
+ * * `'EEE'`: Day in Week, (Sun-Sat)
+ * * `'HH'`: Hour in day, padded (00-23)
+ * * `'H'`: Hour in day (0-23)
+ * * `'hh'`: Hour in am/pm, padded (01-12)
+ * * `'h'`: Hour in am/pm, (1-12)
+ * * `'mm'`: Minute in hour, padded (00-59)
+ * * `'m'`: Minute in hour (0-59)
+ * * `'ss'`: Second in minute, padded (00-59)
+ * * `'s'`: Second in minute (0-59)
+ * * `'a'`: am/pm marker
+ * * `'Z'`: 4 digit (+sign) representation of the timezone offset (-1200-+1200)
+ *
+ * `format` string can also be one of the following predefined
+ * {@link guide/i18n localizable formats}:
+ *
+ * * `'medium'`: equivalent to `'MMM d, y h:mm:ss a'` for en_US locale
+ * (e.g. Sep 3, 2010 12:05:08 pm)
+ * * `'short'`: equivalent to `'M/d/yy h:mm a'` for en_US locale (e.g. 9/3/10 12:05 pm)
+ * * `'fullDate'`: equivalent to `'EEEE, MMMM d,y'` for en_US locale
+ * (e.g. Friday, September 3, 2010)
+ * * `'longDate'`: equivalent to `'MMMM d, y'` for en_US locale (e.g. September 3, 2010)
+ * * `'mediumDate'`: equivalent to `'MMM d, y'` for en_US locale (e.g. Sep 3, 2010)
+ * * `'shortDate'`: equivalent to `'M/d/yy'` for en_US locale (e.g. 9/3/10)
+ * * `'mediumTime'`: equivalent to `'h:mm:ss a'` for en_US locale (e.g. 12:05:08 pm)
+ * * `'shortTime'`: equivalent to `'h:mm a'` for en_US locale (e.g. 12:05 pm)
+ *
+ * `format` string can contain literal values. These need to be quoted with single quotes (e.g.
+ * `"h 'in the morning'"`). In order to output single quote, use two single quotes in a sequence
+ * (e.g. `"h 'o''clock'"`).
+ *
+ * @param {(Date|number|string)} date Date to format either as Date object, milliseconds (string or
+ * number) or various ISO 8601 datetime string formats (e.g. yyyy-MM-ddTHH:mm:ss.SSSZ and its
+ * shorter versions like yyyy-MM-ddTHH:mmZ, yyyy-MM-dd or yyyyMMddTHHmmssZ). If no timezone is
+ * specified in the string input, the time is considered to be in the local timezone.
+ * @param {string=} format Formatting rules (see Description). If not specified,
+ * `mediumDate` is used.
+ * @returns {string} Formatted string or the input if input is not recognized as date/millis.
+ *
+ * @example
+ <doc:example>
+ <doc:source>
+ <span ng-non-bindable>{{1288323623006 | date:'medium'}}</span>:
+ {{1288323623006 | date:'medium'}}<br>
+ <span ng-non-bindable>{{1288323623006 | date:'yyyy-MM-dd HH:mm:ss Z'}}</span>:
+ {{1288323623006 | date:'yyyy-MM-dd HH:mm:ss Z'}}<br>
+ <span ng-non-bindable>{{1288323623006 | date:'MM/dd/yyyy @ h:mma'}}</span>:
+ {{'1288323623006' | date:'MM/dd/yyyy @ h:mma'}}<br>
+ </doc:source>
+ <doc:scenario>
+ it('should format date', function() {
+ expect(binding("1288323623006 | date:'medium'")).
+ toMatch(/Oct 2\d, 2010 \d{1,2}:\d{2}:\d{2} (AM|PM)/);
+ expect(binding("1288323623006 | date:'yyyy-MM-dd HH:mm:ss Z'")).
+ toMatch(/2010\-10\-2\d \d{2}:\d{2}:\d{2} (\-|\+)?\d{4}/);
+ expect(binding("'1288323623006' | date:'MM/dd/yyyy @ h:mma'")).
+ toMatch(/10\/2\d\/2010 @ \d{1,2}:\d{2}(AM|PM)/);
+ });
+ </doc:scenario>
+ </doc:example>
+ */
+dateFilter.$inject = ['$locale'];
+function dateFilter($locale) {
+
+
+ var R_ISO8601_STR = /^(\d{4})-?(\d\d)-?(\d\d)(?:T(\d\d)(?::?(\d\d)(?::?(\d\d)(?:\.(\d+))?)?)?(Z|([+-])(\d\d):?(\d\d))?)?$/;
+ function jsonStringToDate(string){
+ var match;
+ if (match = string.match(R_ISO8601_STR)) {
+ var date = new Date(0),
+ tzHour = 0,
+ tzMin = 0;
+ if (match[9]) {
+ tzHour = int(match[9] + match[10]);
+ tzMin = int(match[9] + match[11]);
+ }
+ date.setUTCFullYear(int(match[1]), int(match[2]) - 1, int(match[3]));
+ date.setUTCHours(int(match[4]||0) - tzHour, int(match[5]||0) - tzMin, int(match[6]||0), int(match[7]||0));
+ return date;
+ }
+ return string;
+ }
+
+
+ return function(date, format) {
+ var text = '',
+ parts = [],
+ fn, match;
+
+ format = format || 'mediumDate';
+ format = $locale.DATETIME_FORMATS[format] || format;
+ if (isString(date)) {
+ if (NUMBER_STRING.test(date)) {
+ date = int(date);
+ } else {
+ date = jsonStringToDate(date);
+ }
+ }
+
+ if (isNumber(date)) {
+ date = new Date(date);
+ }
+
+ if (!isDate(date)) {
+ return date;
+ }
+
+ while(format) {
+ match = DATE_FORMATS_SPLIT.exec(format);
+ if (match) {
+ parts = concat(parts, match, 1);
+ format = parts.pop();
+ } else {
+ parts.push(format);
+ format = null;
+ }
+ }
+
+ forEach(parts, function(value){
+ fn = DATE_FORMATS[value];
+ text += fn ? fn(date, $locale.DATETIME_FORMATS)
+ : value.replace(/(^'|'$)/g, '').replace(/''/g, "'");
+ });
+
+ return text;
+ };
+}
+
+
+/**
+ * @ngdoc filter
+ * @name ng.filter:json
+ * @function
+ *
+ * @description
+ * Allows you to convert a JavaScript object into JSON string.
+ *
+ * This filter is mostly useful for debugging. When using the double curly {{value}} notation
+ * the binding is automatically converted to JSON.
+ *
+ * @param {*} object Any JavaScript object (including arrays and primitive types) to filter.
+ * @returns {string} JSON string.
+ *
+ *
+ * @example:
+ <doc:example>
+ <doc:source>
+ <pre>{{ {'name':'value'} | json }}</pre>
+ </doc:source>
+ <doc:scenario>
+ it('should jsonify filtered objects', function() {
+ expect(binding("{'name':'value'}")).toMatch(/\{\n "name": ?"value"\n}/);
+ });
+ </doc:scenario>
+ </doc:example>
+ *
+ */
+function jsonFilter() {
+ return function(object) {
+ return toJson(object, true);
+ };
+}
+
+
+/**
+ * @ngdoc filter
+ * @name ng.filter:lowercase
+ * @function
+ * @description
+ * Converts string to lowercase.
+ * @see angular.lowercase
+ */
+var lowercaseFilter = valueFn(lowercase);
+
+
+/**
+ * @ngdoc filter
+ * @name ng.filter:uppercase
+ * @function
+ * @description
+ * Converts string to uppercase.
+ * @see angular.uppercase
+ */
+var uppercaseFilter = valueFn(uppercase);
+
+/**
+ * @ngdoc function
+ * @name ng.filter:limitTo
+ * @function
+ *
+ * @description
+ * Creates a new array containing only a specified number of elements in an array. The elements
+ * are taken from either the beginning or the end of the source array, as specified by the
+ * value and sign (positive or negative) of `limit`.
+ *
+ * Note: This function is used to augment the `Array` type in Angular expressions. See
+ * {@link ng.$filter} for more information about Angular arrays.
+ *
+ * @param {Array} array Source array to be limited.
+ * @param {string|Number} limit The length of the returned array. If the `limit` number is
+ * positive, `limit` number of items from the beginning of the source array are copied.
+ * If the number is negative, `limit` number of items from the end of the source array are
+ * copied. The `limit` will be trimmed if it exceeds `array.length`
+ * @returns {Array} A new sub-array of length `limit` or less if input array had less than `limit`
+ * elements.
+ *
+ * @example
+ <doc:example>
+ <doc:source>
+ <script>
+ function Ctrl($scope) {
+ $scope.numbers = [1,2,3,4,5,6,7,8,9];
+ $scope.limit = 3;
+ }
+ </script>
+ <div ng-controller="Ctrl">
+ Limit {{numbers}} to: <input type="integer" ng-model="limit">
+ <p>Output: {{ numbers | limitTo:limit }}</p>
+ </div>
+ </doc:source>
+ <doc:scenario>
+ it('should limit the numer array to first three items', function() {
+ expect(element('.doc-example-live input[ng-model=limit]').val()).toBe('3');
+ expect(binding('numbers | limitTo:limit')).toEqual('[1,2,3]');
+ });
+
+ it('should update the output when -3 is entered', function() {
+ input('limit').enter(-3);
+ expect(binding('numbers | limitTo:limit')).toEqual('[7,8,9]');
+ });
+
+ it('should not exceed the maximum size of input array', function() {
+ input('limit').enter(100);
+ expect(binding('numbers | limitTo:limit')).toEqual('[1,2,3,4,5,6,7,8,9]');
+ });
+ </doc:scenario>
+ </doc:example>
+ */
+function limitToFilter(){
+ return function(array, limit) {
+ if (!(array instanceof Array)) return array;
+ limit = int(limit);
+ var out = [],
+ i, n;
+
+ // check that array is iterable
+ if (!array || !(array instanceof Array))
+ return out;
+
+ // if abs(limit) exceeds maximum length, trim it
+ if (limit > array.length)
+ limit = array.length;
+ else if (limit < -array.length)
+ limit = -array.length;
+
+ if (limit > 0) {
+ i = 0;
+ n = limit;
+ } else {
+ i = array.length + limit;
+ n = array.length;
+ }
+
+ for (; i<n; i++) {
+ out.push(array[i]);
+ }
+
+ return out;
+ }
+}
+
+/**
+ * @ngdoc function
+ * @name ng.filter:orderBy
+ * @function
+ *
+ * @description
+ * Orders a specified `array` by the `expression` predicate.
+ *
+ * Note: this function is used to augment the `Array` type in Angular expressions. See
+ * {@link ng.$filter} for more informaton about Angular arrays.
+ *
+ * @param {Array} array The array to sort.
+ * @param {function(*)|string|Array.<(function(*)|string)>} expression A predicate to be
+ * used by the comparator to determine the order of elements.
+ *
+ * Can be one of:
+ *
+ * - `function`: Getter function. The result of this function will be sorted using the
+ * `<`, `=`, `>` operator.
+ * - `string`: An Angular expression which evaluates to an object to order by, such as 'name'
+ * to sort by a property called 'name'. Optionally prefixed with `+` or `-` to control
+ * ascending or descending sort order (for example, +name or -name).
+ * - `Array`: An array of function or string predicates. The first predicate in the array
+ * is used for sorting, but when two items are equivalent, the next predicate is used.
+ *
+ * @param {boolean=} reverse Reverse the order the array.
+ * @returns {Array} Sorted copy of the source array.
+ *
+ * @example
+ <doc:example>
+ <doc:source>
+ <script>
+ function Ctrl($scope) {
+ $scope.friends =
+ [{name:'John', phone:'555-1212', age:10},
+ {name:'Mary', phone:'555-9876', age:19},
+ {name:'Mike', phone:'555-4321', age:21},
+ {name:'Adam', phone:'555-5678', age:35},
+ {name:'Julie', phone:'555-8765', age:29}]
+ $scope.predicate = '-age';
+ }
+ </script>
+ <div ng-controller="Ctrl">
+ <pre>Sorting predicate = {{predicate}}; reverse = {{reverse}}</pre>
+ <hr/>
+ [ <a href="" ng-click="predicate=''">unsorted</a> ]
+ <table class="friend">
+ <tr>
+ <th><a href="" ng-click="predicate = 'name'; reverse=false">Name</a>
+ (<a href ng-click="predicate = '-name'; reverse=false">^</a>)</th>
+ <th><a href="" ng-click="predicate = 'phone'; reverse=!reverse">Phone Number</a></th>
+ <th><a href="" ng-click="predicate = 'age'; reverse=!reverse">Age</a></th>
+ </tr>
+ <tr ng-repeat="friend in friends | orderBy:predicate:reverse">
+ <td>{{friend.name}}</td>
+ <td>{{friend.phone}}</td>
+ <td>{{friend.age}}</td>
+ </tr>
+ </table>
+ </div>
+ </doc:source>
+ <doc:scenario>
+ it('should be reverse ordered by aged', function() {
+ expect(binding('predicate')).toBe('-age');
+ expect(repeater('table.friend', 'friend in friends').column('friend.age')).
+ toEqual(['35', '29', '21', '19', '10']);
+ expect(repeater('table.friend', 'friend in friends').column('friend.name')).
+ toEqual(['Adam', 'Julie', 'Mike', 'Mary', 'John']);
+ });
+
+ it('should reorder the table when user selects different predicate', function() {
+ element('.doc-example-live a:contains("Name")').click();
+ expect(repeater('table.friend', 'friend in friends').column('friend.name')).
+ toEqual(['Adam', 'John', 'Julie', 'Mary', 'Mike']);
+ expect(repeater('table.friend', 'friend in friends').column('friend.age')).
+ toEqual(['35', '10', '29', '19', '21']);
+
+ element('.doc-example-live a:contains("Phone")').click();
+ expect(repeater('table.friend', 'friend in friends').column('friend.phone')).
+ toEqual(['555-9876', '555-8765', '555-5678', '555-4321', '555-1212']);
+ expect(repeater('table.friend', 'friend in friends').column('friend.name')).
+ toEqual(['Mary', 'Julie', 'Adam', 'Mike', 'John']);
+ });
+ </doc:scenario>
+ </doc:example>
+ */
+orderByFilter.$inject = ['$parse'];
+function orderByFilter($parse){
+ return function(array, sortPredicate, reverseOrder) {
+ if (!isArray(array)) return array;
+ if (!sortPredicate) return array;
+ sortPredicate = isArray(sortPredicate) ? sortPredicate: [sortPredicate];
+ sortPredicate = map(sortPredicate, function(predicate){
+ var descending = false, get = predicate || identity;
+ if (isString(predicate)) {
+ if ((predicate.charAt(0) == '+' || predicate.charAt(0) == '-')) {
+ descending = predicate.charAt(0) == '-';
+ predicate = predicate.substring(1);
+ }
+ get = $parse(predicate);
+ }
+ return reverseComparator(function(a,b){
+ return compare(get(a),get(b));
+ }, descending);
+ });
+ var arrayCopy = [];
+ for ( var i = 0; i < array.length; i++) { arrayCopy.push(array[i]); }
+ return arrayCopy.sort(reverseComparator(comparator, reverseOrder));
+
+ function comparator(o1, o2){
+ for ( var i = 0; i < sortPredicate.length; i++) {
+ var comp = sortPredicate[i](o1, o2);
+ if (comp !== 0) return comp;
+ }
+ return 0;
+ }
+ function reverseComparator(comp, descending) {
+ return toBoolean(descending)
+ ? function(a,b){return comp(b,a);}
+ : comp;
+ }
+ function compare(v1, v2){
+ var t1 = typeof v1;
+ var t2 = typeof v2;
+ if (t1 == t2) {
+ if (t1 == "string") {
+ v1 = v1.toLowerCase();
+ v2 = v2.toLowerCase();
+ }
+ if (v1 === v2) return 0;
+ return v1 < v2 ? -1 : 1;
+ } else {
+ return t1 < t2 ? -1 : 1;
+ }
+ }
+ }
+}
+
+function ngDirective(directive) {
+ if (isFunction(directive)) {
+ directive = {
+ link: directive
+ }
+ }
+ directive.restrict = directive.restrict || 'AC';
+ return valueFn(directive);
+}
+
+/**
+ * @ngdoc directive
+ * @name ng.directive:a
+ * @restrict E
+ *
+ * @description
+ * Modifies the default behavior of html A tag, so that the default action is prevented when href
+ * attribute is empty.
+ *
+ * The reasoning for this change is to allow easy creation of action links with `ngClick` directive
+ * without changing the location or causing page reloads, e.g.:
+ * `<a href="" ng-click="model.$save()">Save</a>`
+ */
+var htmlAnchorDirective = valueFn({
+ restrict: 'E',
+ compile: function(element, attr) {
+
+ if (msie <= 8) {
+
+ // turn <a href ng-click="..">link</a> into a stylable link in IE
+ // but only if it doesn't have name attribute, in which case it's an anchor
+ if (!attr.href && !attr.name) {
+ attr.$set('href', '');
+ }
+
+ // add a comment node to anchors to workaround IE bug that causes element content to be reset
+ // to new attribute content if attribute is updated with value containing @ and element also
+ // contains value with @
+ // see issue #1949
+ element.append(document.createComment('IE fix'));
+ }
+
+ return function(scope, element) {
+ element.bind('click', function(event){
+ // if we have no href url, then don't navigate anywhere.
+ if (!element.attr('href')) {
+ event.preventDefault();
+ }
+ });
+ }
+ }
+});
+
+/**
+ * @ngdoc directive
+ * @name ng.directive:ngHref
+ * @restrict A
+ *
+ * @description
+ * Using Angular markup like {{hash}} in an href attribute makes
+ * the page open to a wrong URL, if the user clicks that link before
+ * angular has a chance to replace the {{hash}} with actual URL, the
+ * link will be broken and will most likely return a 404 error.
+ * The `ngHref` directive solves this problem.
+ *
+ * The buggy way to write it:
+ * <pre>
+ * <a href="http://www.gravatar.com/avatar/{{hash}}"/>
+ * </pre>
+ *
+ * The correct way to write it:
+ * <pre>
+ * <a ng-href="http://www.gravatar.com/avatar/{{hash}}"/>
+ * </pre>
+ *
+ * @element A
+ * @param {template} ngHref any string which can contain `{{}}` markup.
+ *
+ * @example
+ * This example uses `link` variable inside `href` attribute:
+ <doc:example>
+ <doc:source>
+ <input ng-model="value" /><br />
+ <a id="link-1" href ng-click="value = 1">link 1</a> (link, don't reload)<br />
+ <a id="link-2" href="" ng-click="value = 2">link 2</a> (link, don't reload)<br />
+ <a id="link-3" ng-href="/{{'123'}}">link 3</a> (link, reload!)<br />
+ <a id="link-4" href="" name="xx" ng-click="value = 4">anchor</a> (link, don't reload)<br />
+ <a id="link-5" name="xxx" ng-click="value = 5">anchor</a> (no link)<br />
+ <a id="link-6" ng-href="{{value}}">link</a> (link, change location)
+ </doc:source>
+ <doc:scenario>
+ it('should execute ng-click but not reload when href without value', function() {
+ element('#link-1').click();
+ expect(input('value').val()).toEqual('1');
+ expect(element('#link-1').attr('href')).toBe("");
+ });
+
+ it('should execute ng-click but not reload when href empty string', function() {
+ element('#link-2').click();
+ expect(input('value').val()).toEqual('2');
+ expect(element('#link-2').attr('href')).toBe("");
+ });
+
+ it('should execute ng-click and change url when ng-href specified', function() {
+ expect(element('#link-3').attr('href')).toBe("/123");
+
+ element('#link-3').click();
+ expect(browser().window().path()).toEqual('/123');
+ });
+
+ it('should execute ng-click but not reload when href empty string and name specified', function() {
+ element('#link-4').click();
+ expect(input('value').val()).toEqual('4');
+ expect(element('#link-4').attr('href')).toBe('');
+ });
+
+ it('should execute ng-click but not reload when no href but name specified', function() {
+ element('#link-5').click();
+ expect(input('value').val()).toEqual('5');
+ expect(element('#link-5').attr('href')).toBe(undefined);
+ });
+
+ it('should only change url when only ng-href', function() {
+ input('value').enter('6');
+ expect(element('#link-6').attr('href')).toBe('6');
+
+ element('#link-6').click();
+ expect(browser().location().url()).toEqual('/6');
+ });
+ </doc:scenario>
+ </doc:example>
+ */
+
+/**
+ * @ngdoc directive
+ * @name ng.directive:ngSrc
+ * @restrict A
+ *
+ * @description
+ * Using Angular markup like `{{hash}}` in a `src` attribute doesn't
+ * work right: The browser will fetch from the URL with the literal
+ * text `{{hash}}` until Angular replaces the expression inside
+ * `{{hash}}`. The `ngSrc` directive solves this problem.
+ *
+ * The buggy way to write it:
+ * <pre>
+ * <img src="http://www.gravatar.com/avatar/{{hash}}"/>
+ * </pre>
+ *
+ * The correct way to write it:
+ * <pre>
+ * <img ng-src="http://www.gravatar.com/avatar/{{hash}}"/>
+ * </pre>
+ *
+ * @element IMG
+ * @param {template} ngSrc any string which can contain `{{}}` markup.
+ */
+
+/**
+ * @ngdoc directive
+ * @name ng.directive:ngDisabled
+ * @restrict A
+ *
+ * @description
+ *
+ * The following markup will make the button enabled on Chrome/Firefox but not on IE8 and older IEs:
+ * <pre>
+ * <div ng-init="scope = { isDisabled: false }">
+ * <button disabled="{{scope.isDisabled}}">Disabled</button>
+ * </div>
+ * </pre>
+ *
+ * The HTML specs do not require browsers to preserve the special attributes such as disabled.
+ * (The presence of them means true and absence means false)
+ * This prevents the angular compiler from correctly retrieving the binding expression.
+ * To solve this problem, we introduce the `ngDisabled` directive.
+ *
+ * @example
+ <doc:example>
+ <doc:source>
+ Click me to toggle: <input type="checkbox" ng-model="checked"><br/>
+ <button ng-model="button" ng-disabled="checked">Button</button>
+ </doc:source>
+ <doc:scenario>
+ it('should toggle button', function() {
+ expect(element('.doc-example-live :button').prop('disabled')).toBeFalsy();
+ input('checked').check();
+ expect(element('.doc-example-live :button').prop('disabled')).toBeTruthy();
+ });
+ </doc:scenario>
+ </doc:example>
+ *
+ * @element INPUT
+ * @param {expression} ngDisabled Angular expression that will be evaluated.
+ */
+
+
+/**
+ * @ngdoc directive
+ * @name ng.directive:ngChecked
+ * @restrict A
+ *
+ * @description
+ * The HTML specs do not require browsers to preserve the special attributes such as checked.
+ * (The presence of them means true and absence means false)
+ * This prevents the angular compiler from correctly retrieving the binding expression.
+ * To solve this problem, we introduce the `ngChecked` directive.
+ * @example
+ <doc:example>
+ <doc:source>
+ Check me to check both: <input type="checkbox" ng-model="master"><br/>
+ <input id="checkSlave" type="checkbox" ng-checked="master">
+ </doc:source>
+ <doc:scenario>
+ it('should check both checkBoxes', function() {
+ expect(element('.doc-example-live #checkSlave').prop('checked')).toBeFalsy();
+ input('master').check();
+ expect(element('.doc-example-live #checkSlave').prop('checked')).toBeTruthy();
+ });
+ </doc:scenario>
+ </doc:example>
+ *
+ * @element INPUT
+ * @param {expression} ngChecked Angular expression that will be evaluated.
+ */
+
+
+/**
+ * @ngdoc directive
+ * @name ng.directive:ngMultiple
+ * @restrict A
+ *
+ * @description
+ * The HTML specs do not require browsers to preserve the special attributes such as multiple.
+ * (The presence of them means true and absence means false)
+ * This prevents the angular compiler from correctly retrieving the binding expression.
+ * To solve this problem, we introduce the `ngMultiple` directive.
+ *
+ * @example
+ <doc:example>
+ <doc:source>
+ Check me check multiple: <input type="checkbox" ng-model="checked"><br/>
+ <select id="select" ng-multiple="checked">
+ <option>Misko</option>
+ <option>Igor</option>
+ <option>Vojta</option>
+ <option>Di</option>
+ </select>
+ </doc:source>
+ <doc:scenario>
+ it('should toggle multiple', function() {
+ expect(element('.doc-example-live #select').prop('multiple')).toBeFalsy();
+ input('checked').check();
+ expect(element('.doc-example-live #select').prop('multiple')).toBeTruthy();
+ });
+ </doc:scenario>
+ </doc:example>
+ *
+ * @element SELECT
+ * @param {expression} ngMultiple Angular expression that will be evaluated.
+ */
+
+
+/**
+ * @ngdoc directive
+ * @name ng.directive:ngReadonly
+ * @restrict A
+ *
+ * @description
+ * The HTML specs do not require browsers to preserve the special attributes such as readonly.
+ * (The presence of them means true and absence means false)
+ * This prevents the angular compiler from correctly retrieving the binding expression.
+ * To solve this problem, we introduce the `ngReadonly` directive.
+ * @example
+ <doc:example>
+ <doc:source>
+ Check me to make text readonly: <input type="checkbox" ng-model="checked"><br/>
+ <input type="text" ng-readonly="checked" value="I'm Angular"/>
+ </doc:source>
+ <doc:scenario>
+ it('should toggle readonly attr', function() {
+ expect(element('.doc-example-live :text').prop('readonly')).toBeFalsy();
+ input('checked').check();
+ expect(element('.doc-example-live :text').prop('readonly')).toBeTruthy();
+ });
+ </doc:scenario>
+ </doc:example>
+ *
+ * @element INPUT
+ * @param {string} expression Angular expression that will be evaluated.
+ */
+
+
+/**
+ * @ngdoc directive
+ * @name ng.directive:ngSelected
+ * @restrict A
+ *
+ * @description
+ * The HTML specs do not require browsers to preserve the special attributes such as selected.
+ * (The presence of them means true and absence means false)
+ * This prevents the angular compiler from correctly retrieving the binding expression.
+ * To solve this problem, we introduced the `ngSelected` directive.
+ * @example
+ <doc:example>
+ <doc:source>
+ Check me to select: <input type="checkbox" ng-model="selected"><br/>
+ <select>
+ <option>Hello!</option>
+ <option id="greet" ng-selected="selected">Greetings!</option>
+ </select>
+ </doc:source>
+ <doc:scenario>
+ it('should select Greetings!', function() {
+ expect(element('.doc-example-live #greet').prop('selected')).toBeFalsy();
+ input('selected').check();
+ expect(element('.doc-example-live #greet').prop('selected')).toBeTruthy();
+ });
+ </doc:scenario>
+ </doc:example>
+ *
+ * @element OPTION
+ * @param {string} expression Angular expression that will be evaluated.
+ */
+
+
+var ngAttributeAliasDirectives = {};
+
+
+// boolean attrs are evaluated
+forEach(BOOLEAN_ATTR, function(propName, attrName) {
+ var normalized = directiveNormalize('ng-' + attrName);
+ ngAttributeAliasDirectives[normalized] = function() {
+ return {
+ priority: 100,
+ compile: function() {
+ return function(scope, element, attr) {
+ scope.$watch(attr[normalized], function ngBooleanAttrWatchAction(value) {
+ attr.$set(attrName, !!value);
+ });
+ };
+ }
+ };
+ };
+});
+
+
+// ng-src, ng-href are interpolated
+forEach(['src', 'href'], function(attrName) {
+ var normalized = directiveNormalize('ng-' + attrName);
+ ngAttributeAliasDirectives[normalized] = function() {
+ return {
+ priority: 99, // it needs to run after the attributes are interpolated
+ link: function(scope, element, attr) {
+ attr.$observe(normalized, function(value) {
+ if (!value)
+ return;
+
+ attr.$set(attrName, value);
+
+ // on IE, if "ng:src" directive declaration is used and "src" attribute doesn't exist
+ // then calling element.setAttribute('src', 'foo') doesn't do anything, so we need
+ // to set the property as well to achieve the desired effect.
+ // we use attr[attrName] value since $set can sanitize the url.
+ if (msie) element.prop(attrName, attr[attrName]);
+ });
+ }
+ };
+ };
+});
+
+var nullFormCtrl = {
+ $addControl: noop,
+ $removeControl: noop,
+ $setValidity: noop,
+ $setDirty: noop
+};
+
+/**
+ * @ngdoc object
+ * @name ng.directive:form.FormController
+ *
+ * @property {boolean} $pristine True if user has not interacted with the form yet.
+ * @property {boolean} $dirty True if user has already interacted with the form.
+ * @property {boolean} $valid True if all of the containing forms and controls are valid.
+ * @property {boolean} $invalid True if at least one containing control or form is invalid.
+ *
+ * @property {Object} $error Is an object hash, containing references to all invalid controls or
+ * forms, where:
+ *
+ * - keys are validation tokens (error names) — such as `required`, `url` or `email`),
+ * - values are arrays of controls or forms that are invalid with given error.
+ *
+ * @description
+ * `FormController` keeps track of all its controls and nested forms as well as state of them,
+ * such as being valid/invalid or dirty/pristine.
+ *
+ * Each {@link ng.directive:form form} directive creates an instance
+ * of `FormController`.
+ *
+ */
+//asks for $scope to fool the BC controller module
+FormController.$inject = ['$element', '$attrs', '$scope'];
+function FormController(element, attrs) {
+ var form = this,
+ parentForm = element.parent().controller('form') || nullFormCtrl,
+ invalidCount = 0, // used to easily determine if we are valid
+ errors = form.$error = {};
+
+ // init state
+ form.$name = attrs.name || attrs.ngForm;
+ form.$dirty = false;
+ form.$pristine = true;
+ form.$valid = true;
+ form.$invalid = false;
+
+ parentForm.$addControl(form);
+
+ // Setup initial state of the control
+ element.addClass(PRISTINE_CLASS);
+ toggleValidCss(true);
+
+ // convenience method for easy toggling of classes
+ function toggleValidCss(isValid, validationErrorKey) {
+ validationErrorKey = validationErrorKey ? '-' + snake_case(validationErrorKey, '-') : '';
+ element.
+ removeClass((isValid ? INVALID_CLASS : VALID_CLASS) + validationErrorKey).
+ addClass((isValid ? VALID_CLASS : INVALID_CLASS) + validationErrorKey);
+ }
+
+ /**
+ * @ngdoc function
+ * @name ng.directive:form.FormController#$addControl
+ * @methodOf ng.directive:form.FormController
+ *
+ * @description
+ * Register a control with the form.
+ *
+ * Input elements using ngModelController do this automatically when they are linked.
+ */
+ form.$addControl = function(control) {
+ if (control.$name && !form.hasOwnProperty(control.$name)) {
+ form[control.$name] = control;
+ }
+ };
+
+ /**
+ * @ngdoc function
+ * @name ng.directive:form.FormController#$removeControl
+ * @methodOf ng.directive:form.FormController
+ *
+ * @description
+ * Deregister a control from the form.
+ *
+ * Input elements using ngModelController do this automatically when they are destroyed.
+ */
+ form.$removeControl = function(control) {
+ if (control.$name && form[control.$name] === control) {
+ delete form[control.$name];
+ }
+ forEach(errors, function(queue, validationToken) {
+ form.$setValidity(validationToken, true, control);
+ });
+ };
+
+ /**
+ * @ngdoc function
+ * @name ng.directive:form.FormController#$setValidity
+ * @methodOf ng.directive:form.FormController
+ *
+ * @description
+ * Sets the validity of a form control.
+ *
+ * This method will also propagate to parent forms.
+ */
+ form.$setValidity = function(validationToken, isValid, control) {
+ var queue = errors[validationToken];
+
+ if (isValid) {
+ if (queue) {
+ arrayRemove(queue, control);
+ if (!queue.length) {
+ invalidCount--;
+ if (!invalidCount) {
+ toggleValidCss(isValid);
+ form.$valid = true;
+ form.$invalid = false;
+ }
+ errors[validationToken] = false;
+ toggleValidCss(true, validationToken);
+ parentForm.$setValidity(validationToken, true, form);
+ }
+ }
+
+ } else {
+ if (!invalidCount) {
+ toggleValidCss(isValid);
+ }
+ if (queue) {
+ if (includes(queue, control)) return;
+ } else {
+ errors[validationToken] = queue = [];
+ invalidCount++;
+ toggleValidCss(false, validationToken);
+ parentForm.$setValidity(validationToken, false, form);
+ }
+ queue.push(control);
+
+ form.$valid = false;
+ form.$invalid = true;
+ }
+ };
+
+ /**
+ * @ngdoc function
+ * @name ng.directive:form.FormController#$setDirty
+ * @methodOf ng.directive:form.FormController
+ *
+ * @description
+ * Sets the form to a dirty state.
+ *
+ * This method can be called to add the 'ng-dirty' class and set the form to a dirty
+ * state (ng-dirty class). This method will also propagate to parent forms.
+ */
+ form.$setDirty = function() {
+ element.removeClass(PRISTINE_CLASS).addClass(DIRTY_CLASS);
+ form.$dirty = true;
+ form.$pristine = false;
+ parentForm.$setDirty();
+ };
+
+}
+
+
+/**
+ * @ngdoc directive
+ * @name ng.directive:ngForm
+ * @restrict EAC
+ *
+ * @description
+ * Nestable alias of {@link ng.directive:form `form`} directive. HTML
+ * does not allow nesting of form elements. It is useful to nest forms, for example if the validity of a
+ * sub-group of controls needs to be determined.
+ *
+ * @param {string=} name|ngForm Name of the form. If specified, the form controller will be published into
+ * related scope, under this name.
+ *
+ */
+
+ /**
+ * @ngdoc directive
+ * @name ng.directive:form
+ * @restrict E
+ *
+ * @description
+ * Directive that instantiates
+ * {@link ng.directive:form.FormController FormController}.
+ *
+ * If `name` attribute is specified, the form controller is published onto the current scope under
+ * this name.
+ *
+ * # Alias: {@link ng.directive:ngForm `ngForm`}
+ *
+ * In angular forms can be nested. This means that the outer form is valid when all of the child
+ * forms are valid as well. However browsers do not allow nesting of `<form>` elements, for this
+ * reason angular provides {@link ng.directive:ngForm `ngForm`} alias
+ * which behaves identical to `<form>` but allows form nesting.
+ *
+ *
+ * # CSS classes
+ * - `ng-valid` Is set if the form is valid.
+ * - `ng-invalid` Is set if the form is invalid.
+ * - `ng-pristine` Is set if the form is pristine.
+ * - `ng-dirty` Is set if the form is dirty.
+ *
+ *
+ * # Submitting a form and preventing default action
+ *
+ * Since the role of forms in client-side Angular applications is different than in classical
+ * roundtrip apps, it is desirable for the browser not to translate the form submission into a full
+ * page reload that sends the data to the server. Instead some javascript logic should be triggered
+ * to handle the form submission in application specific way.
+ *
+ * For this reason, Angular prevents the default action (form submission to the server) unless the
+ * `<form>` element has an `action` attribute specified.
+ *
+ * You can use one of the following two ways to specify what javascript method should be called when
+ * a form is submitted:
+ *
+ * - {@link ng.directive:ngSubmit ngSubmit} directive on the form element
+ * - {@link ng.directive:ngClick ngClick} directive on the first
+ * button or input field of type submit (input[type=submit])
+ *
+ * To prevent double execution of the handler, use only one of ngSubmit or ngClick directives. This
+ * is because of the following form submission rules coming from the html spec:
+ *
+ * - If a form has only one input field then hitting enter in this field triggers form submit
+ * (`ngSubmit`)
+ * - if a form has has 2+ input fields and no buttons or input[type=submit] then hitting enter
+ * doesn't trigger submit
+ * - if a form has one or more input fields and one or more buttons or input[type=submit] then
+ * hitting enter in any of the input fields will trigger the click handler on the *first* button or
+ * input[type=submit] (`ngClick`) *and* a submit handler on the enclosing form (`ngSubmit`)
+ *
+ * @param {string=} name Name of the form. If specified, the form controller will be published into
+ * related scope, under this name.
+ *
+ * @example
+ <doc:example>
+ <doc:source>
+ <script>
+ function Ctrl($scope) {
+ $scope.userType = 'guest';
+ }
+ </script>
+ <form name="myForm" ng-controller="Ctrl">
+ userType: <input name="input" ng-model="userType" required>
+ <span class="error" ng-show="myForm.input.$error.required">Required!</span><br>
+ <tt>userType = {{userType}}</tt><br>
+ <tt>myForm.input.$valid = {{myForm.input.$valid}}</tt><br>
+ <tt>myForm.input.$error = {{myForm.input.$error}}</tt><br>
+ <tt>myForm.$valid = {{myForm.$valid}}</tt><br>
+ <tt>myForm.$error.required = {{!!myForm.$error.required}}</tt><br>
+ </form>
+ </doc:source>
+ <doc:scenario>
+ it('should initialize to model', function() {
+ expect(binding('userType')).toEqual('guest');
+ expect(binding('myForm.input.$valid')).toEqual('true');
+ });
+
+ it('should be invalid if empty', function() {
+ input('userType').enter('');
+ expect(binding('userType')).toEqual('');
+ expect(binding('myForm.input.$valid')).toEqual('false');
+ });
+ </doc:scenario>
+ </doc:example>
+ */
+var formDirectiveFactory = function(isNgForm) {
+ return ['$timeout', function($timeout) {
+ var formDirective = {
+ name: 'form',
+ restrict: 'E',
+ controller: FormController,
+ compile: function() {
+ return {
+ pre: function(scope, formElement, attr, controller) {
+ if (!attr.action) {
+ // we can't use jq events because if a form is destroyed during submission the default
+ // action is not prevented. see #1238
+ //
+ // IE 9 is not affected because it doesn't fire a submit event and try to do a full
+ // page reload if the form was destroyed by submission of the form via a click handler
+ // on a button in the form. Looks like an IE9 specific bug.
+ var preventDefaultListener = function(event) {
+ event.preventDefault
+ ? event.preventDefault()
+ : event.returnValue = false; // IE
+ };
+
+ addEventListenerFn(formElement[0], 'submit', preventDefaultListener);
+
+ // unregister the preventDefault listener so that we don't not leak memory but in a
+ // way that will achieve the prevention of the default action.
+ formElement.bind('$destroy', function() {
+ $timeout(function() {
+ removeEventListenerFn(formElement[0], 'submit', preventDefaultListener);
+ }, 0, false);
+ });
+ }
+
+ var parentFormCtrl = formElement.parent().controller('form'),
+ alias = attr.name || attr.ngForm;
+
+ if (alias) {
+ scope[alias] = controller;
+ }
+ if (parentFormCtrl) {
+ formElement.bind('$destroy', function() {
+ parentFormCtrl.$removeControl(controller);
+ if (alias) {
+ scope[alias] = undefined;
+ }
+ extend(controller, nullFormCtrl); //stop propagating child destruction handlers upwards
+ });
+ }
+ }
+ };
+ }
+ };
+
+ return isNgForm ? extend(copy(formDirective), {restrict: 'EAC'}) : formDirective;
+ }];
+};
+
+var formDirective = formDirectiveFactory();
+var ngFormDirective = formDirectiveFactory(true);
+
+var URL_REGEXP = /^(ftp|http|https):\/\/(\w+:{0,1}\w*@)?(\S+)(:[0-9]+)?(\/|\/([\w#!:.?+=&%@!\-\/]))?$/;
+var EMAIL_REGEXP = /^[A-Za-z0-9._%+-]+(a)[A-Za-z0-9.-]+\.[A-Za-z]{2,6}$/;
+var NUMBER_REGEXP = /^\s*(\-|\+)?(\d+|(\d*(\.\d*)))\s*$/;
+
+var inputType = {
+
+ /**
+ * @ngdoc inputType
+ * @name ng.directive:input.text
+ *
+ * @description
+ * Standard HTML text input with angular data binding.
+ *
+ * @param {string} ngModel Assignable angular expression to data-bind to.
+ * @param {string=} name Property name of the form under which the control is published.
+ * @param {string=} required Adds `required` validation error key if the value is not entered.
+ * @param {string=} ngRequired Adds `required` attribute and `required` validation constraint to
+ * the element when the ngRequired expression evaluates to true. Use `ngRequired` instead of
+ * `required` when you want to data-bind to the `required` attribute.
+ * @param {number=} ngMinlength Sets `minlength` validation error key if the value is shorter than
+ * minlength.
+ * @param {number=} ngMaxlength Sets `maxlength` validation error key if the value is longer than
+ * maxlength.
+ * @param {string=} ngPattern Sets `pattern` validation error key if the value does not match the
+ * RegExp pattern expression. Expected value is `/regexp/` for inline patterns or `regexp` for
+ * patterns defined as scope expressions.
+ * @param {string=} ngChange Angular expression to be executed when input changes due to user
+ * interaction with the input element.
+ *
+ * @example
+ <doc:example>
+ <doc:source>
+ <script>
+ function Ctrl($scope) {
+ $scope.text = 'guest';
+ $scope.word = /^\w*$/;
+ }
+ </script>
+ <form name="myForm" ng-controller="Ctrl">
+ Single word: <input type="text" name="input" ng-model="text"
+ ng-pattern="word" required>
+ <span class="error" ng-show="myForm.input.$error.required">
+ Required!</span>
+ <span class="error" ng-show="myForm.input.$error.pattern">
+ Single word only!</span>
+
+ <tt>text = {{text}}</tt><br/>
+ <tt>myForm.input.$valid = {{myForm.input.$valid}}</tt><br/>
+ <tt>myForm.input.$error = {{myForm.input.$error}}</tt><br/>
+ <tt>myForm.$valid = {{myForm.$valid}}</tt><br/>
+ <tt>myForm.$error.required = {{!!myForm.$error.required}}</tt><br/>
+ </form>
+ </doc:source>
+ <doc:scenario>
+ it('should initialize to model', function() {
+ expect(binding('text')).toEqual('guest');
+ expect(binding('myForm.input.$valid')).toEqual('true');
+ });
+
+ it('should be invalid if empty', function() {
+ input('text').enter('');
+ expect(binding('text')).toEqual('');
+ expect(binding('myForm.input.$valid')).toEqual('false');
+ });
+
+ it('should be invalid if multi word', function() {
+ input('text').enter('hello world');
+ expect(binding('myForm.input.$valid')).toEqual('false');
+ });
+ </doc:scenario>
+ </doc:example>
+ */
+ 'text': textInputType,
+
+
+ /**
+ * @ngdoc inputType
+ * @name ng.directive:input.number
+ *
+ * @description
+ * Text input with number validation and transformation. Sets the `number` validation
+ * error if not a valid number.
+ *
+ * @param {string} ngModel Assignable angular expression to data-bind to.
+ * @param {string=} name Property name of the form under which the control is published.
+ * @param {string=} min Sets the `min` validation error key if the value entered is less than `min`.
+ * @param {string=} max Sets the `max` validation error key if the value entered is greater than `max`.
+ * @param {string=} required Sets `required` validation error key if the value is not entered.
+ * @param {string=} ngRequired Adds `required` attribute and `required` validation constraint to
+ * the element when the ngRequired expression evaluates to true. Use `ngRequired` instead of
+ * `required` when you want to data-bind to the `required` attribute.
+ * @param {number=} ngMinlength Sets `minlength` validation error key if the value is shorter than
+ * minlength.
+ * @param {number=} ngMaxlength Sets `maxlength` validation error key if the value is longer than
+ * maxlength.
+ * @param {string=} ngPattern Sets `pattern` validation error key if the value does not match the
+ * RegExp pattern expression. Expected value is `/regexp/` for inline patterns or `regexp` for
+ * patterns defined as scope expressions.
+ * @param {string=} ngChange Angular expression to be executed when input changes due to user
+ * interaction with the input element.
+ *
+ * @example
+ <doc:example>
+ <doc:source>
+ <script>
+ function Ctrl($scope) {
+ $scope.value = 12;
+ }
+ </script>
+ <form name="myForm" ng-controller="Ctrl">
+ Number: <input type="number" name="input" ng-model="value"
+ min="0" max="99" required>
+ <span class="error" ng-show="myForm.input.$error.required">
+ Required!</span>
+ <span class="error" ng-show="myForm.input.$error.number">
+ Not valid number!</span>
+ <tt>value = {{value}}</tt><br/>
+ <tt>myForm.input.$valid = {{myForm.input.$valid}}</tt><br/>
+ <tt>myForm.input.$error = {{myForm.input.$error}}</tt><br/>
+ <tt>myForm.$valid = {{myForm.$valid}}</tt><br/>
+ <tt>myForm.$error.required = {{!!myForm.$error.required}}</tt><br/>
+ </form>
+ </doc:source>
+ <doc:scenario>
+ it('should initialize to model', function() {
+ expect(binding('value')).toEqual('12');
+ expect(binding('myForm.input.$valid')).toEqual('true');
+ });
+
+ it('should be invalid if empty', function() {
+ input('value').enter('');
+ expect(binding('value')).toEqual('');
+ expect(binding('myForm.input.$valid')).toEqual('false');
+ });
+
+ it('should be invalid if over max', function() {
+ input('value').enter('123');
+ expect(binding('value')).toEqual('');
+ expect(binding('myForm.input.$valid')).toEqual('false');
+ });
+ </doc:scenario>
+ </doc:example>
+ */
+ 'number': numberInputType,
+
+
+ /**
+ * @ngdoc inputType
+ * @name ng.directive:input.url
+ *
+ * @description
+ * Text input with URL validation. Sets the `url` validation error key if the content is not a
+ * valid URL.
+ *
+ * @param {string} ngModel Assignable angular expression to data-bind to.
+ * @param {string=} name Property name of the form under which the control is published.
+ * @param {string=} required Sets `required` validation error key if the value is not entered.
+ * @param {string=} ngRequired Adds `required` attribute and `required` validation constraint to
+ * the element when the ngRequired expression evaluates to true. Use `ngRequired` instead of
+ * `required` when you want to data-bind to the `required` attribute.
+ * @param {number=} ngMinlength Sets `minlength` validation error key if the value is shorter than
+ * minlength.
+ * @param {number=} ngMaxlength Sets `maxlength` validation error key if the value is longer than
+ * maxlength.
+ * @param {string=} ngPattern Sets `pattern` validation error key if the value does not match the
+ * RegExp pattern expression. Expected value is `/regexp/` for inline patterns or `regexp` for
+ * patterns defined as scope expressions.
+ * @param {string=} ngChange Angular expression to be executed when input changes due to user
+ * interaction with the input element.
+ *
+ * @example
+ <doc:example>
+ <doc:source>
+ <script>
+ function Ctrl($scope) {
+ $scope.text = 'http://google.com';
+ }
+ </script>
+ <form name="myForm" ng-controller="Ctrl">
+ URL: <input type="url" name="input" ng-model="text" required>
+ <span class="error" ng-show="myForm.input.$error.required">
+ Required!</span>
+ <span class="error" ng-show="myForm.input.$error.url">
+ Not valid url!</span>
+ <tt>text = {{text}}</tt><br/>
+ <tt>myForm.input.$valid = {{myForm.input.$valid}}</tt><br/>
+ <tt>myForm.input.$error = {{myForm.input.$error}}</tt><br/>
+ <tt>myForm.$valid = {{myForm.$valid}}</tt><br/>
+ <tt>myForm.$error.required = {{!!myForm.$error.required}}</tt><br/>
+ <tt>myForm.$error.url = {{!!myForm.$error.url}}</tt><br/>
+ </form>
+ </doc:source>
+ <doc:scenario>
+ it('should initialize to model', function() {
+ expect(binding('text')).toEqual('http://google.com');
+ expect(binding('myForm.input.$valid')).toEqual('true');
+ });
+
+ it('should be invalid if empty', function() {
+ input('text').enter('');
+ expect(binding('text')).toEqual('');
+ expect(binding('myForm.input.$valid')).toEqual('false');
+ });
+
+ it('should be invalid if not url', function() {
+ input('text').enter('xxx');
+ expect(binding('myForm.input.$valid')).toEqual('false');
+ });
+ </doc:scenario>
+ </doc:example>
+ */
+ 'url': urlInputType,
+
+
+ /**
+ * @ngdoc inputType
+ * @name ng.directive:input.email
+ *
+ * @description
+ * Text input with email validation. Sets the `email` validation error key if not a valid email
+ * address.
+ *
+ * @param {string} ngModel Assignable angular expression to data-bind to.
+ * @param {string=} name Property name of the form under which the control is published.
+ * @param {string=} required Sets `required` validation error key if the value is not entered.
+ * @param {string=} ngRequired Adds `required` attribute and `required` validation constraint to
+ * the element when the ngRequired expression evaluates to true. Use `ngRequired` instead of
+ * `required` when you want to data-bind to the `required` attribute.
+ * @param {number=} ngMinlength Sets `minlength` validation error key if the value is shorter than
+ * minlength.
+ * @param {number=} ngMaxlength Sets `maxlength` validation error key if the value is longer than
+ * maxlength.
+ * @param {string=} ngPattern Sets `pattern` validation error key if the value does not match the
+ * RegExp pattern expression. Expected value is `/regexp/` for inline patterns or `regexp` for
+ * patterns defined as scope expressions.
+ * @param {string=} ngChange Angular expression to be executed when input changes due to user
+ * interaction with the input element.
+ *
+ * @example
+ <doc:example>
+ <doc:source>
+ <script>
+ function Ctrl($scope) {
+ $scope.text = 'me(a)example.com';
+ }
+ </script>
+ <form name="myForm" ng-controller="Ctrl">
+ Email: <input type="email" name="input" ng-model="text" required>
+ <span class="error" ng-show="myForm.input.$error.required">
+ Required!</span>
+ <span class="error" ng-show="myForm.input.$error.email">
+ Not valid email!</span>
+ <tt>text = {{text}}</tt><br/>
+ <tt>myForm.input.$valid = {{myForm.input.$valid}}</tt><br/>
+ <tt>myForm.input.$error = {{myForm.input.$error}}</tt><br/>
+ <tt>myForm.$valid = {{myForm.$valid}}</tt><br/>
+ <tt>myForm.$error.required = {{!!myForm.$error.required}}</tt><br/>
+ <tt>myForm.$error.email = {{!!myForm.$error.email}}</tt><br/>
+ </form>
+ </doc:source>
+ <doc:scenario>
+ it('should initialize to model', function() {
+ expect(binding('text')).toEqual('me(a)example.com');
+ expect(binding('myForm.input.$valid')).toEqual('true');
+ });
+
+ it('should be invalid if empty', function() {
+ input('text').enter('');
+ expect(binding('text')).toEqual('');
+ expect(binding('myForm.input.$valid')).toEqual('false');
+ });
+
+ it('should be invalid if not email', function() {
+ input('text').enter('xxx');
+ expect(binding('myForm.input.$valid')).toEqual('false');
+ });
+ </doc:scenario>
+ </doc:example>
+ */
+ 'email': emailInputType,
+
+
+ /**
+ * @ngdoc inputType
+ * @name ng.directive:input.radio
+ *
+ * @description
+ * HTML radio button.
+ *
+ * @param {string} ngModel Assignable angular expression to data-bind to.
+ * @param {string} value The value to which the expression should be set when selected.
+ * @param {string=} name Property name of the form under which the control is published.
+ * @param {string=} ngChange Angular expression to be executed when input changes due to user
+ * interaction with the input element.
+ *
+ * @example
+ <doc:example>
+ <doc:source>
+ <script>
+ function Ctrl($scope) {
+ $scope.color = 'blue';
+ }
+ </script>
+ <form name="myForm" ng-controller="Ctrl">
+ <input type="radio" ng-model="color" value="red"> Red <br/>
+ <input type="radio" ng-model="color" value="green"> Green <br/>
+ <input type="radio" ng-model="color" value="blue"> Blue <br/>
+ <tt>color = {{color}}</tt><br/>
+ </form>
+ </doc:source>
+ <doc:scenario>
+ it('should change state', function() {
+ expect(binding('color')).toEqual('blue');
+
+ input('color').select('red');
+ expect(binding('color')).toEqual('red');
+ });
+ </doc:scenario>
+ </doc:example>
+ */
+ 'radio': radioInputType,
+
+
+ /**
+ * @ngdoc inputType
+ * @name ng.directive:input.checkbox
+ *
+ * @description
+ * HTML checkbox.
+ *
+ * @param {string} ngModel Assignable angular expression to data-bind to.
+ * @param {string=} name Property name of the form under which the control is published.
+ * @param {string=} ngTrueValue The value to which the expression should be set when selected.
+ * @param {string=} ngFalseValue The value to which the expression should be set when not selected.
+ * @param {string=} ngChange Angular expression to be executed when input changes due to user
+ * interaction with the input element.
+ *
+ * @example
+ <doc:example>
+ <doc:source>
+ <script>
+ function Ctrl($scope) {
+ $scope.value1 = true;
+ $scope.value2 = 'YES'
+ }
+ </script>
+ <form name="myForm" ng-controller="Ctrl">
+ Value1: <input type="checkbox" ng-model="value1"> <br/>
+ Value2: <input type="checkbox" ng-model="value2"
+ ng-true-value="YES" ng-false-value="NO"> <br/>
+ <tt>value1 = {{value1}}</tt><br/>
+ <tt>value2 = {{value2}}</tt><br/>
+ </form>
+ </doc:source>
+ <doc:scenario>
+ it('should change state', function() {
+ expect(binding('value1')).toEqual('true');
+ expect(binding('value2')).toEqual('YES');
+
+ input('value1').check();
+ input('value2').check();
+ expect(binding('value1')).toEqual('false');
+ expect(binding('value2')).toEqual('NO');
+ });
+ </doc:scenario>
+ </doc:example>
+ */
+ 'checkbox': checkboxInputType,
+
+ 'hidden': noop,
+ 'button': noop,
+ 'submit': noop,
+ 'reset': noop
+};
+
+
+function isEmpty(value) {
+ return isUndefined(value) || value === '' || value === null || value !== value;
+}
+
+
+function textInputType(scope, element, attr, ctrl, $sniffer, $browser) {
+
+ var listener = function() {
+ var value = trim(element.val());
+
+ if (ctrl.$viewValue !== value) {
+ scope.$apply(function() {
+ ctrl.$setViewValue(value);
+ });
+ }
+ };
+
+ // if the browser does support "input" event, we are fine - except on IE9 which doesn't fire the
+ // input event on backspace, delete or cut
+ if ($sniffer.hasEvent('input')) {
+ element.bind('input', listener);
+ } else {
+ var timeout;
+
+ var deferListener = function() {
+ if (!timeout) {
+ timeout = $browser.defer(function() {
+ listener();
+ timeout = null;
+ });
+ }
+ };
+
+ element.bind('keydown', function(event) {
+ var key = event.keyCode;
+
+ // ignore
+ // command modifiers arrows
+ if (key === 91 || (15 < key && key < 19) || (37 <= key && key <= 40)) return;
+
+ deferListener();
+ });
+
+ // if user paste into input using mouse, we need "change" event to catch it
+ element.bind('change', listener);
+
+ // if user modifies input value using context menu in IE, we need "paste" and "cut" events to catch it
+ if ($sniffer.hasEvent('paste')) {
+ element.bind('paste cut', deferListener);
+ }
+ }
+
+
+ ctrl.$render = function() {
+ element.val(isEmpty(ctrl.$viewValue) ? '' : ctrl.$viewValue);
+ };
+
+ // pattern validator
+ var pattern = attr.ngPattern,
+ patternValidator;
+
+ var validate = function(regexp, value) {
+ if (isEmpty(value) || regexp.test(value)) {
+ ctrl.$setValidity('pattern', true);
+ return value;
+ } else {
+ ctrl.$setValidity('pattern', false);
+ return undefined;
+ }
+ };
+
+ if (pattern) {
+ if (pattern.match(/^\/(.*)\/$/)) {
+ pattern = new RegExp(pattern.substr(1, pattern.length - 2));
+ patternValidator = function(value) {
+ return validate(pattern, value)
+ };
+ } else {
+ patternValidator = function(value) {
+ var patternObj = scope.$eval(pattern);
+
+ if (!patternObj || !patternObj.test) {
+ throw new Error('Expected ' + pattern + ' to be a RegExp but was ' + patternObj);
+ }
+ return validate(patternObj, value);
+ };
+ }
+
+ ctrl.$formatters.push(patternValidator);
+ ctrl.$parsers.push(patternValidator);
+ }
+
+ // min length validator
+ if (attr.ngMinlength) {
+ var minlength = int(attr.ngMinlength);
+ var minLengthValidator = function(value) {
+ if (!isEmpty(value) && value.length < minlength) {
+ ctrl.$setValidity('minlength', false);
+ return undefined;
+ } else {
+ ctrl.$setValidity('minlength', true);
+ return value;
+ }
+ };
+
+ ctrl.$parsers.push(minLengthValidator);
+ ctrl.$formatters.push(minLengthValidator);
+ }
+
+ // max length validator
+ if (attr.ngMaxlength) {
+ var maxlength = int(attr.ngMaxlength);
+ var maxLengthValidator = function(value) {
+ if (!isEmpty(value) && value.length > maxlength) {
+ ctrl.$setValidity('maxlength', false);
+ return undefined;
+ } else {
+ ctrl.$setValidity('maxlength', true);
+ return value;
+ }
+ };
+
+ ctrl.$parsers.push(maxLengthValidator);
+ ctrl.$formatters.push(maxLengthValidator);
+ }
+}
+
+function numberInputType(scope, element, attr, ctrl, $sniffer, $browser) {
+ textInputType(scope, element, attr, ctrl, $sniffer, $browser);
+
+ ctrl.$parsers.push(function(value) {
+ var empty = isEmpty(value);
+ if (empty || NUMBER_REGEXP.test(value)) {
+ ctrl.$setValidity('number', true);
+ return value === '' ? null : (empty ? value : parseFloat(value));
+ } else {
+ ctrl.$setValidity('number', false);
+ return undefined;
+ }
+ });
+
+ ctrl.$formatters.push(function(value) {
+ return isEmpty(value) ? '' : '' + value;
+ });
+
+ if (attr.min) {
+ var min = parseFloat(attr.min);
+ var minValidator = function(value) {
+ if (!isEmpty(value) && value < min) {
+ ctrl.$setValidity('min', false);
+ return undefined;
+ } else {
+ ctrl.$setValidity('min', true);
+ return value;
+ }
+ };
+
+ ctrl.$parsers.push(minValidator);
+ ctrl.$formatters.push(minValidator);
+ }
+
+ if (attr.max) {
+ var max = parseFloat(attr.max);
+ var maxValidator = function(value) {
+ if (!isEmpty(value) && value > max) {
+ ctrl.$setValidity('max', false);
+ return undefined;
+ } else {
+ ctrl.$setValidity('max', true);
+ return value;
+ }
+ };
+
+ ctrl.$parsers.push(maxValidator);
+ ctrl.$formatters.push(maxValidator);
+ }
+
+ ctrl.$formatters.push(function(value) {
+
+ if (isEmpty(value) || isNumber(value)) {
+ ctrl.$setValidity('number', true);
+ return value;
+ } else {
+ ctrl.$setValidity('number', false);
+ return undefined;
+ }
+ });
+}
+
+function urlInputType(scope, element, attr, ctrl, $sniffer, $browser) {
+ textInputType(scope, element, attr, ctrl, $sniffer, $browser);
+
+ var urlValidator = function(value) {
+ if (isEmpty(value) || URL_REGEXP.test(value)) {
+ ctrl.$setValidity('url', true);
+ return value;
+ } else {
+ ctrl.$setValidity('url', false);
+ return undefined;
+ }
+ };
+
+ ctrl.$formatters.push(urlValidator);
+ ctrl.$parsers.push(urlValidator);
+}
+
+function emailInputType(scope, element, attr, ctrl, $sniffer, $browser) {
+ textInputType(scope, element, attr, ctrl, $sniffer, $browser);
+
+ var emailValidator = function(value) {
+ if (isEmpty(value) || EMAIL_REGEXP.test(value)) {
+ ctrl.$setValidity('email', true);
+ return value;
+ } else {
+ ctrl.$setValidity('email', false);
+ return undefined;
+ }
+ };
+
+ ctrl.$formatters.push(emailValidator);
+ ctrl.$parsers.push(emailValidator);
+}
+
+function radioInputType(scope, element, attr, ctrl) {
+ // make the name unique, if not defined
+ if (isUndefined(attr.name)) {
+ element.attr('name', nextUid());
+ }
+
+ element.bind('click', function() {
+ if (element[0].checked) {
+ scope.$apply(function() {
+ ctrl.$setViewValue(attr.value);
+ });
+ }
+ });
+
+ ctrl.$render = function() {
+ var value = attr.value;
+ element[0].checked = (value == ctrl.$viewValue);
+ };
+
+ attr.$observe('value', ctrl.$render);
+}
+
+function checkboxInputType(scope, element, attr, ctrl) {
+ var trueValue = attr.ngTrueValue,
+ falseValue = attr.ngFalseValue;
+
+ if (!isString(trueValue)) trueValue = true;
+ if (!isString(falseValue)) falseValue = false;
+
+ element.bind('click', function() {
+ scope.$apply(function() {
+ ctrl.$setViewValue(element[0].checked);
+ });
+ });
+
+ ctrl.$render = function() {
+ element[0].checked = ctrl.$viewValue;
+ };
+
+ ctrl.$formatters.push(function(value) {
+ return value === trueValue;
+ });
+
+ ctrl.$parsers.push(function(value) {
+ return value ? trueValue : falseValue;
+ });
+}
+
+
+/**
+ * @ngdoc directive
+ * @name ng.directive:textarea
+ * @restrict E
+ *
+ * @description
+ * HTML textarea element control with angular data-binding. The data-binding and validation
+ * properties of this element are exactly the same as those of the
+ * {@link ng.directive:input input element}.
+ *
+ * @param {string} ngModel Assignable angular expression to data-bind to.
+ * @param {string=} name Property name of the form under which the control is published.
+ * @param {string=} required Sets `required` validation error key if the value is not entered.
+ * @param {string=} ngRequired Adds `required` attribute and `required` validation constraint to
+ * the element when the ngRequired expression evaluates to true. Use `ngRequired` instead of
+ * `required` when you want to data-bind to the `required` attribute.
+ * @param {number=} ngMinlength Sets `minlength` validation error key if the value is shorter than
+ * minlength.
+ * @param {number=} ngMaxlength Sets `maxlength` validation error key if the value is longer than
+ * maxlength.
+ * @param {string=} ngPattern Sets `pattern` validation error key if the value does not match the
+ * RegExp pattern expression. Expected value is `/regexp/` for inline patterns or `regexp` for
+ * patterns defined as scope expressions.
+ * @param {string=} ngChange Angular expression to be executed when input changes due to user
+ * interaction with the input element.
+ */
+
+
+/**
+ * @ngdoc directive
+ * @name ng.directive:input
+ * @restrict E
+ *
+ * @description
+ * HTML input element control with angular data-binding. Input control follows HTML5 input types
+ * and polyfills the HTML5 validation behavior for older browsers.
+ *
+ * @param {string} ngModel Assignable angular expression to data-bind to.
+ * @param {string=} name Property name of the form under which the control is published.
+ * @param {string=} required Sets `required` validation error key if the value is not entered.
+ * @param {boolean=} ngRequired Sets `required` attribute if set to true
+ * @param {number=} ngMinlength Sets `minlength` validation error key if the value is shorter than
+ * minlength.
+ * @param {number=} ngMaxlength Sets `maxlength` validation error key if the value is longer than
+ * maxlength.
+ * @param {string=} ngPattern Sets `pattern` validation error key if the value does not match the
+ * RegExp pattern expression. Expected value is `/regexp/` for inline patterns or `regexp` for
+ * patterns defined as scope expressions.
+ * @param {string=} ngChange Angular expression to be executed when input changes due to user
+ * interaction with the input element.
+ *
+ * @example
+ <doc:example>
+ <doc:source>
+ <script>
+ function Ctrl($scope) {
+ $scope.user = {name: 'guest', last: 'visitor'};
+ }
+ </script>
+ <div ng-controller="Ctrl">
+ <form name="myForm">
+ User name: <input type="text" name="userName" ng-model="user.name" required>
+ <span class="error" ng-show="myForm.userName.$error.required">
+ Required!</span><br>
+ Last name: <input type="text" name="lastName" ng-model="user.last"
+ ng-minlength="3" ng-maxlength="10">
+ <span class="error" ng-show="myForm.lastName.$error.minlength">
+ Too short!</span>
+ <span class="error" ng-show="myForm.lastName.$error.maxlength">
+ Too long!</span><br>
+ </form>
+ <hr>
+ <tt>user = {{user}}</tt><br/>
+ <tt>myForm.userName.$valid = {{myForm.userName.$valid}}</tt><br>
+ <tt>myForm.userName.$error = {{myForm.userName.$error}}</tt><br>
+ <tt>myForm.lastName.$valid = {{myForm.lastName.$valid}}</tt><br>
+ <tt>myForm.lastName.$error = {{myForm.lastName.$error}}</tt><br>
+ <tt>myForm.$valid = {{myForm.$valid}}</tt><br>
+ <tt>myForm.$error.required = {{!!myForm.$error.required}}</tt><br>
+ <tt>myForm.$error.minlength = {{!!myForm.$error.minlength}}</tt><br>
+ <tt>myForm.$error.maxlength = {{!!myForm.$error.maxlength}}</tt><br>
+ </div>
+ </doc:source>
+ <doc:scenario>
+ it('should initialize to model', function() {
+ expect(binding('user')).toEqual('{"name":"guest","last":"visitor"}');
+ expect(binding('myForm.userName.$valid')).toEqual('true');
+ expect(binding('myForm.$valid')).toEqual('true');
+ });
+
+ it('should be invalid if empty when required', function() {
+ input('user.name').enter('');
+ expect(binding('user')).toEqual('{"last":"visitor"}');
+ expect(binding('myForm.userName.$valid')).toEqual('false');
+ expect(binding('myForm.$valid')).toEqual('false');
+ });
+
+ it('should be valid if empty when min length is set', function() {
+ input('user.last').enter('');
+ expect(binding('user')).toEqual('{"name":"guest","last":""}');
+ expect(binding('myForm.lastName.$valid')).toEqual('true');
+ expect(binding('myForm.$valid')).toEqual('true');
+ });
+
+ it('should be invalid if less than required min length', function() {
+ input('user.last').enter('xx');
+ expect(binding('user')).toEqual('{"name":"guest"}');
+ expect(binding('myForm.lastName.$valid')).toEqual('false');
+ expect(binding('myForm.lastName.$error')).toMatch(/minlength/);
+ expect(binding('myForm.$valid')).toEqual('false');
+ });
+
+ it('should be invalid if longer than max length', function() {
+ input('user.last').enter('some ridiculously long name');
+ expect(binding('user'))
+ .toEqual('{"name":"guest"}');
+ expect(binding('myForm.lastName.$valid')).toEqual('false');
+ expect(binding('myForm.lastName.$error')).toMatch(/maxlength/);
+ expect(binding('myForm.$valid')).toEqual('false');
+ });
+ </doc:scenario>
+ </doc:example>
+ */
+var inputDirective = ['$browser', '$sniffer', function($browser, $sniffer) {
+ return {
+ restrict: 'E',
+ require: '?ngModel',
+ link: function(scope, element, attr, ctrl) {
+ if (ctrl) {
+ (inputType[lowercase(attr.type)] || inputType.text)(scope, element, attr, ctrl, $sniffer,
+ $browser);
+ }
+ }
+ };
+}];
+
+var VALID_CLASS = 'ng-valid',
+ INVALID_CLASS = 'ng-invalid',
+ PRISTINE_CLASS = 'ng-pristine',
+ DIRTY_CLASS = 'ng-dirty';
+
+/**
+ * @ngdoc object
+ * @name ng.directive:ngModel.NgModelController
+ *
+ * @property {string} $viewValue Actual string value in the view.
+ * @property {*} $modelValue The value in the model, that the control is bound to.
+ * @property {Array.<Function>} $parsers Array of functions to execute, as a pipeline, whenever
+ the control reads value from the DOM. Each function is called, in turn, passing the value
+ through to the next. Used to sanitize / convert the value as well as validation.
+
+ For validation, the parsers should update the validity state using
+ {@link ng.directive:ngModel.NgModelController#$setValidity $setValidity()},
+ and return `undefined` for invalid values.
+ *
+ * @property {Array.<Function>} $formatters Array of functions to execute, as a pipeline, whenever
+ * the model value changes. Each function is called, in turn, passing the value through to the
+ * next. Used to format / convert values for display in the control and validation.
+ * <pre>
+ * function formatter(value) {
+ * if (value) {
+ * return value.toUpperCase();
+ * }
+ * }
+ * ngModel.$formatters.push(formatter);
+ * </pre>
+ * @property {Object} $error An bject hash with all errors as keys.
+ *
+ * @property {boolean} $pristine True if user has not interacted with the control yet.
+ * @property {boolean} $dirty True if user has already interacted with the control.
+ * @property {boolean} $valid True if there is no error.
+ * @property {boolean} $invalid True if at least one error on the control.
+ *
+ * @description
+ *
+ * `NgModelController` provides API for the `ng-model` directive. The controller contains
+ * services for data-binding, validation, CSS update, value formatting and parsing. It
+ * specifically does not contain any logic which deals with DOM rendering or listening to
+ * DOM events. The `NgModelController` is meant to be extended by other directives where, the
+ * directive provides DOM manipulation and the `NgModelController` provides the data-binding.
+ * Note that you cannot use `NgModelController` in a directive with an isolated scope,
+ * as, in that case, the `ng-model` value gets put into the isolated scope and does not get
+ * propogated to the parent scope.
+ *
+ *
+ * This example shows how to use `NgModelController` with a custom control to achieve
+ * data-binding. Notice how different directives (`contenteditable`, `ng-model`, and `required`)
+ * collaborate together to achieve the desired result.
+ *
+ * <example module="customControl">
+ <file name="style.css">
+ [contenteditable] {
+ border: 1px solid black;
+ background-color: white;
+ min-height: 20px;
+ }
+
+ .ng-invalid {
+ border: 1px solid red;
+ }
+
+ </file>
+ <file name="script.js">
+ angular.module('customControl', []).
+ directive('contenteditable', function() {
+ return {
+ restrict: 'A', // only activate on element attribute
+ require: '?ngModel', // get a hold of NgModelController
+ link: function(scope, element, attrs, ngModel) {
+ if(!ngModel) return; // do nothing if no ng-model
+
+ // Specify how UI should be updated
+ ngModel.$render = function() {
+ element.html(ngModel.$viewValue || '');
+ };
+
+ // Listen for change events to enable binding
+ element.bind('blur keyup change', function() {
+ scope.$apply(read);
+ });
+ read(); // initialize
+
+ // Write data to the model
+ function read() {
+ var html = element.html();
+ // When we clear the content editable the browser leaves a <br> behind
+ // If strip-br attribute is provided then we strip this out
+ if( attrs.stripBr && html == '<br>' ) {
+ html = '';
+ }
+ ngModel.$setViewValue(html);
+ }
+ }
+ };
+ });
+ </file>
+ <file name="index.html">
+ <form name="myForm">
+ <div contenteditable
+ name="myWidget" ng-model="userContent"
+ strip-br="true"
+ required>Change me!</div>
+ <span ng-show="myForm.myWidget.$error.required">Required!</span>
+ <hr>
+ <textarea ng-model="userContent"></textarea>
+ </form>
+ </file>
+ <file name="scenario.js">
+ it('should data-bind and become invalid', function() {
+ var contentEditable = element('[contenteditable]');
+
+ expect(contentEditable.text()).toEqual('Change me!');
+ input('userContent').enter('');
+ expect(contentEditable.text()).toEqual('');
+ expect(contentEditable.prop('className')).toMatch(/ng-invalid-required/);
+ });
+ </file>
+ * </example>
+ *
+ */
+var NgModelController = ['$scope', '$exceptionHandler', '$attrs', '$element', '$parse',
+ function($scope, $exceptionHandler, $attr, $element, $parse) {
+ this.$viewValue = Number.NaN;
+ this.$modelValue = Number.NaN;
+ this.$parsers = [];
+ this.$formatters = [];
+ this.$viewChangeListeners = [];
+ this.$pristine = true;
+ this.$dirty = false;
+ this.$valid = true;
+ this.$invalid = false;
+ this.$name = $attr.name;
+
+ var ngModelGet = $parse($attr.ngModel),
+ ngModelSet = ngModelGet.assign;
+
+ if (!ngModelSet) {
+ throw Error(NON_ASSIGNABLE_MODEL_EXPRESSION + $attr.ngModel +
+ ' (' + startingTag($element) + ')');
+ }
+
+ /**
+ * @ngdoc function
+ * @name ng.directive:ngModel.NgModelController#$render
+ * @methodOf ng.directive:ngModel.NgModelController
+ *
+ * @description
+ * Called when the view needs to be updated. It is expected that the user of the ng-model
+ * directive will implement this method.
+ */
+ this.$render = noop;
+
+ var parentForm = $element.inheritedData('$formController') || nullFormCtrl,
+ invalidCount = 0, // used to easily determine if we are valid
+ $error = this.$error = {}; // keep invalid keys here
+
+
+ // Setup initial state of the control
+ $element.addClass(PRISTINE_CLASS);
+ toggleValidCss(true);
+
+ // convenience method for easy toggling of classes
+ function toggleValidCss(isValid, validationErrorKey) {
+ validationErrorKey = validationErrorKey ? '-' + snake_case(validationErrorKey, '-') : '';
+ $element.
+ removeClass((isValid ? INVALID_CLASS : VALID_CLASS) + validationErrorKey).
+ addClass((isValid ? VALID_CLASS : INVALID_CLASS) + validationErrorKey);
+ }
+
+ /**
+ * @ngdoc function
+ * @name ng.directive:ngModel.NgModelController#$setValidity
+ * @methodOf ng.directive:ngModel.NgModelController
+ *
+ * @description
+ * Change the validity state, and notifies the form when the control changes validity. (i.e. it
+ * does not notify form if given validator is already marked as invalid).
+ *
+ * This method should be called by validators - i.e. the parser or formatter functions.
+ *
+ * @param {string} validationErrorKey Name of the validator. the `validationErrorKey` will assign
+ * to `$error[validationErrorKey]=isValid` so that it is available for data-binding.
+ * The `validationErrorKey` should be in camelCase and will get converted into dash-case
+ * for class name. Example: `myError` will result in `ng-valid-my-error` and `ng-invalid-my-error`
+ * class and can be bound to as `{{someForm.someControl.$error.myError}}` .
+ * @param {boolean} isValid Whether the current state is valid (true) or invalid (false).
+ */
+ this.$setValidity = function(validationErrorKey, isValid) {
+ if ($error[validationErrorKey] === !isValid) return;
+
+ if (isValid) {
+ if ($error[validationErrorKey]) invalidCount--;
+ if (!invalidCount) {
+ toggleValidCss(true);
+ this.$valid = true;
+ this.$invalid = false;
+ }
+ } else {
+ toggleValidCss(false);
+ this.$invalid = true;
+ this.$valid = false;
+ invalidCount++;
+ }
+
+ $error[validationErrorKey] = !isValid;
+ toggleValidCss(isValid, validationErrorKey);
+
+ parentForm.$setValidity(validationErrorKey, isValid, this);
+ };
+
+
+ /**
+ * @ngdoc function
+ * @name ng.directive:ngModel.NgModelController#$setViewValue
+ * @methodOf ng.directive:ngModel.NgModelController
+ *
+ * @description
+ * Read a value from view.
+ *
+ * This method should be called from within a DOM event handler.
+ * For example {@link ng.directive:input input} or
+ * {@link ng.directive:select select} directives call it.
+ *
+ * It internally calls all `$parsers` (including validators) and updates the `$modelValue` and the actual model path.
+ * Lastly it calls all registered change listeners.
+ *
+ * @param {string} value Value from the view.
+ */
+ this.$setViewValue = function(value) {
+ this.$viewValue = value;
+
+ // change to dirty
+ if (this.$pristine) {
+ this.$dirty = true;
+ this.$pristine = false;
+ $element.removeClass(PRISTINE_CLASS).addClass(DIRTY_CLASS);
+ parentForm.$setDirty();
+ }
+
+ forEach(this.$parsers, function(fn) {
+ value = fn(value);
+ });
+
+ if (this.$modelValue !== value) {
+ this.$modelValue = value;
+ ngModelSet($scope, value);
+ forEach(this.$viewChangeListeners, function(listener) {
+ try {
+ listener();
+ } catch(e) {
+ $exceptionHandler(e);
+ }
+ })
+ }
+ };
+
+ // model -> value
+ var ctrl = this;
+
+ $scope.$watch(function ngModelWatch() {
+ var value = ngModelGet($scope);
+
+ // if scope model value and ngModel value are out of sync
+ if (ctrl.$modelValue !== value) {
+
+ var formatters = ctrl.$formatters,
+ idx = formatters.length;
+
+ ctrl.$modelValue = value;
+ while(idx--) {
+ value = formatters[idx](value);
+ }
+
+ if (ctrl.$viewValue !== value) {
+ ctrl.$viewValue = value;
+ ctrl.$render();
+ }
+ }
+ });
+}];
+
+
+/**
+ * @ngdoc directive
+ * @name ng.directive:ngModel
+ *
+ * @element input
+ *
+ * @description
+ * Is a directive that tells Angular to do two-way data binding. It works together with `input`,
+ * `select`, `textarea`. You can easily write your own directives to use `ngModel` as well.
+ *
+ * `ngModel` is responsible for:
+ *
+ * - binding the view into the model, which other directives such as `input`, `textarea` or `select`
+ * require,
+ * - providing validation behavior (i.e. required, number, email, url),
+ * - keeping state of the control (valid/invalid, dirty/pristine, validation errors),
+ * - setting related css class onto the element (`ng-valid`, `ng-invalid`, `ng-dirty`, `ng-pristine`),
+ * - register the control with parent {@link ng.directive:form form}.
+ *
+ * Note: `ngModel` will try to bind to the property given by evaluating the expression on the
+ * current scope. If the property doesn't already exist on this scope, it will be created
+ * implicitly and added to the scope.
+ *
+ * For basic examples, how to use `ngModel`, see:
+ *
+ * - {@link ng.directive:input input}
+ * - {@link ng.directive:input.text text}
+ * - {@link ng.directive:input.checkbox checkbox}
+ * - {@link ng.directive:input.radio radio}
+ * - {@link ng.directive:input.number number}
+ * - {@link ng.directive:input.email email}
+ * - {@link ng.directive:input.url url}
+ * - {@link ng.directive:select select}
+ * - {@link ng.directive:textarea textarea}
+ *
+ */
+var ngModelDirective = function() {
+ return {
+ require: ['ngModel', '^?form'],
+ controller: NgModelController,
+ link: function(scope, element, attr, ctrls) {
+ // notify others, especially parent forms
+
+ var modelCtrl = ctrls[0],
+ formCtrl = ctrls[1] || nullFormCtrl;
+
+ formCtrl.$addControl(modelCtrl);
+
+ element.bind('$destroy', function() {
+ formCtrl.$removeControl(modelCtrl);
+ });
+ }
+ };
+};
+
+
+/**
+ * @ngdoc directive
+ * @name ng.directive:ngChange
+ * @restrict E
+ *
+ * @description
+ * Evaluate given expression when user changes the input.
+ * The expression is not evaluated when the value change is coming from the model.
+ *
+ * Note, this directive requires `ngModel` to be present.
+ *
+ * @element input
+ *
+ * @example
+ * <doc:example>
+ * <doc:source>
+ * <script>
+ * function Controller($scope) {
+ * $scope.counter = 0;
+ * $scope.change = function() {
+ * $scope.counter++;
+ * };
+ * }
+ * </script>
+ * <div ng-controller="Controller">
+ * <input type="checkbox" ng-model="confirmed" ng-change="change()" id="ng-change-example1" />
+ * <input type="checkbox" ng-model="confirmed" id="ng-change-example2" />
+ * <label for="ng-change-example2">Confirmed</label><br />
+ * debug = {{confirmed}}<br />
+ * counter = {{counter}}
+ * </div>
+ * </doc:source>
+ * <doc:scenario>
+ * it('should evaluate the expression if changing from view', function() {
+ * expect(binding('counter')).toEqual('0');
+ * element('#ng-change-example1').click();
+ * expect(binding('counter')).toEqual('1');
+ * expect(binding('confirmed')).toEqual('true');
+ * });
+ *
+ * it('should not evaluate the expression if changing from model', function() {
+ * element('#ng-change-example2').click();
+ * expect(binding('counter')).toEqual('0');
+ * expect(binding('confirmed')).toEqual('true');
+ * });
+ * </doc:scenario>
+ * </doc:example>
+ */
+var ngChangeDirective = valueFn({
+ require: 'ngModel',
+ link: function(scope, element, attr, ctrl) {
+ ctrl.$viewChangeListeners.push(function() {
+ scope.$eval(attr.ngChange);
+ });
+ }
+});
+
+
+var requiredDirective = function() {
+ return {
+ require: '?ngModel',
+ link: function(scope, elm, attr, ctrl) {
+ if (!ctrl) return;
+ attr.required = true; // force truthy in case we are on non input element
+
+ var validator = function(value) {
+ if (attr.required && (isEmpty(value) || value === false)) {
+ ctrl.$setValidity('required', false);
+ return;
+ } else {
+ ctrl.$setValidity('required', true);
+ return value;
+ }
+ };
+
+ ctrl.$formatters.push(validator);
+ ctrl.$parsers.unshift(validator);
+
+ attr.$observe('required', function() {
+ validator(ctrl.$viewValue);
+ });
+ }
+ };
+};
+
+
+/**
+ * @ngdoc directive
+ * @name ng.directive:ngList
+ *
+ * @description
+ * Text input that converts between comma-separated string into an array of strings.
+ *
+ * @element input
+ * @param {string=} ngList optional delimiter that should be used to split the value. If
+ * specified in form `/something/` then the value will be converted into a regular expression.
+ *
+ * @example
+ <doc:example>
+ <doc:source>
+ <script>
+ function Ctrl($scope) {
+ $scope.names = ['igor', 'misko', 'vojta'];
+ }
+ </script>
+ <form name="myForm" ng-controller="Ctrl">
+ List: <input name="namesInput" ng-model="names" ng-list required>
+ <span class="error" ng-show="myForm.namesInput.$error.required">
+ Required!</span>
+ <br>
+ <tt>names = {{names}}</tt><br/>
+ <tt>myForm.namesInput.$valid = {{myForm.namesInput.$valid}}</tt><br/>
+ <tt>myForm.namesInput.$error = {{myForm.namesInput.$error}}</tt><br/>
+ <tt>myForm.$valid = {{myForm.$valid}}</tt><br/>
+ <tt>myForm.$error.required = {{!!myForm.$error.required}}</tt><br/>
+ </form>
+ </doc:source>
+ <doc:scenario>
+ it('should initialize to model', function() {
+ expect(binding('names')).toEqual('["igor","misko","vojta"]');
+ expect(binding('myForm.namesInput.$valid')).toEqual('true');
+ expect(element('span.error').css('display')).toBe('none');
+ });
+
+ it('should be invalid if empty', function() {
+ input('names').enter('');
+ expect(binding('names')).toEqual('[]');
+ expect(binding('myForm.namesInput.$valid')).toEqual('false');
+ expect(element('span.error').css('display')).not().toBe('none');
+ });
+ </doc:scenario>
+ </doc:example>
+ */
+var ngListDirective = function() {
+ return {
+ require: 'ngModel',
+ link: function(scope, element, attr, ctrl) {
+ var match = /\/(.*)\//.exec(attr.ngList),
+ separator = match && new RegExp(match[1]) || attr.ngList || ',';
+
+ var parse = function(viewValue) {
+ var list = [];
+
+ if (viewValue) {
+ forEach(viewValue.split(separator), function(value) {
+ if (value) list.push(trim(value));
+ });
+ }
+
+ return list;
+ };
+
+ ctrl.$parsers.push(parse);
+ ctrl.$formatters.push(function(value) {
+ if (isArray(value)) {
+ return value.join(', ');
+ }
+
+ return undefined;
+ });
+ }
+ };
+};
+
+
+var CONSTANT_VALUE_REGEXP = /^(true|false|\d+)$/;
+
+var ngValueDirective = function() {
+ return {
+ priority: 100,
+ compile: function(tpl, tplAttr) {
+ if (CONSTANT_VALUE_REGEXP.test(tplAttr.ngValue)) {
+ return function(scope, elm, attr) {
+ attr.$set('value', scope.$eval(attr.ngValue));
+ };
+ } else {
+ return function(scope, elm, attr) {
+ scope.$watch(attr.ngValue, function valueWatchAction(value) {
+ attr.$set('value', value);
+ });
+ };
+ }
+ }
+ };
+};
+
+/**
+ * @ngdoc directive
+ * @name ng.directive:ngBind
+ *
+ * @description
+ * The `ngBind` attribute tells Angular to replace the text content of the specified HTML element
+ * with the value of a given expression, and to update the text content when the value of that
+ * expression changes.
+ *
+ * Typically, you don't use `ngBind` directly, but instead you use the double curly markup like
+ * `{{ expression }}` which is similar but less verbose.
+ *
+ * It is preferrable to use `ngBind` instead of `{{ expression }}` when a template is momentarily
+ * displayed by the browser in its raw state before Angular compiles it. Since `ngBind` is an
+ * element attribute, it makes the bindings invisible to the user while the page is loading.
+ *
+ * An alternative solution to this problem would be using the
+ * {@link ng.directive:ngCloak ngCloak} directive.
+ *
+ *
+ * @element ANY
+ * @param {expression} ngBind {@link guide/expression Expression} to evaluate.
+ *
+ * @example
+ * Enter a name in the Live Preview text box; the greeting below the text box changes instantly.
+ <doc:example>
+ <doc:source>
+ <script>
+ function Ctrl($scope) {
+ $scope.name = 'Whirled';
+ }
+ </script>
+ <div ng-controller="Ctrl">
+ Enter name: <input type="text" ng-model="name"><br>
+ Hello <span ng-bind="name"></span>!
+ </div>
+ </doc:source>
+ <doc:scenario>
+ it('should check ng-bind', function() {
+ expect(using('.doc-example-live').binding('name')).toBe('Whirled');
+ using('.doc-example-live').input('name').enter('world');
+ expect(using('.doc-example-live').binding('name')).toBe('world');
+ });
+ </doc:scenario>
+ </doc:example>
+ */
+var ngBindDirective = ngDirective(function(scope, element, attr) {
+ element.addClass('ng-binding').data('$binding', attr.ngBind);
+ scope.$watch(attr.ngBind, function ngBindWatchAction(value) {
+ element.text(value == undefined ? '' : value);
+ });
+});
+
+
+/**
+ * @ngdoc directive
+ * @name ng.directive:ngBindTemplate
+ *
+ * @description
+ * The `ngBindTemplate` directive specifies that the element
+ * text content should be replaced with the interpolation of the template
+ * in the `ngBindTemplate` attribute.
+ * Unlike `ngBind`, the `ngBindTemplate` can contain multiple `{{` `}}`
+ * expressions. This directive is needed since some HTML elements
+ * (such as TITLE and OPTION) cannot contain SPAN elements.
+ *
+ * @element ANY
+ * @param {string} ngBindTemplate template of form
+ * <tt>{{</tt> <tt>expression</tt> <tt>}}</tt> to eval.
+ *
+ * @example
+ * Try it here: enter text in text box and watch the greeting change.
+ <doc:example>
+ <doc:source>
+ <script>
+ function Ctrl($scope) {
+ $scope.salutation = 'Hello';
+ $scope.name = 'World';
+ }
+ </script>
+ <div ng-controller="Ctrl">
+ Salutation: <input type="text" ng-model="salutation"><br>
+ Name: <input type="text" ng-model="name"><br>
+ <pre ng-bind-template="{{salutation}} {{name}}!"></pre>
+ </div>
+ </doc:source>
+ <doc:scenario>
+ it('should check ng-bind', function() {
+ expect(using('.doc-example-live').binding('salutation')).
+ toBe('Hello');
+ expect(using('.doc-example-live').binding('name')).
+ toBe('World');
+ using('.doc-example-live').input('salutation').enter('Greetings');
+ using('.doc-example-live').input('name').enter('user');
+ expect(using('.doc-example-live').binding('salutation')).
+ toBe('Greetings');
+ expect(using('.doc-example-live').binding('name')).
+ toBe('user');
+ });
+ </doc:scenario>
+ </doc:example>
+ */
+var ngBindTemplateDirective = ['$interpolate', function($interpolate) {
+ return function(scope, element, attr) {
+ // TODO: move this to scenario runner
+ var interpolateFn = $interpolate(element.attr(attr.$attr.ngBindTemplate));
+ element.addClass('ng-binding').data('$binding', interpolateFn);
+ attr.$observe('ngBindTemplate', function(value) {
+ element.text(value);
+ });
+ }
+}];
+
+
+/**
+ * @ngdoc directive
+ * @name ng.directive:ngBindHtmlUnsafe
+ *
+ * @description
+ * Creates a binding that will innerHTML the result of evaluating the `expression` into the current
+ * element. *The innerHTML-ed content will not be sanitized!* You should use this directive only if
+ * {@link ngSanitize.directive:ngBindHtml ngBindHtml} directive is too
+ * restrictive and when you absolutely trust the source of the content you are binding to.
+ *
+ * See {@link ngSanitize.$sanitize $sanitize} docs for examples.
+ *
+ * @element ANY
+ * @param {expression} ngBindHtmlUnsafe {@link guide/expression Expression} to evaluate.
+ */
+var ngBindHtmlUnsafeDirective = [function() {
+ return function(scope, element, attr) {
+ element.addClass('ng-binding').data('$binding', attr.ngBindHtmlUnsafe);
+ scope.$watch(attr.ngBindHtmlUnsafe, function ngBindHtmlUnsafeWatchAction(value) {
+ element.html(value || '');
+ });
+ };
+}];
+
+function classDirective(name, selector) {
+ name = 'ngClass' + name;
+ return ngDirective(function(scope, element, attr) {
+ var oldVal = undefined;
+
+ scope.$watch(attr[name], ngClassWatchAction, true);
+
+ attr.$observe('class', function(value) {
+ var ngClass = scope.$eval(attr[name]);
+ ngClassWatchAction(ngClass, ngClass);
+ });
+
+
+ if (name !== 'ngClass') {
+ scope.$watch('$index', function($index, old$index) {
+ var mod = $index & 1;
+ if (mod !== old$index & 1) {
+ if (mod === selector) {
+ addClass(scope.$eval(attr[name]));
+ } else {
+ removeClass(scope.$eval(attr[name]));
+ }
+ }
+ });
+ }
+
+
+ function ngClassWatchAction(newVal) {
+ if (selector === true || scope.$index % 2 === selector) {
+ if (oldVal && !equals(newVal,oldVal)) {
+ removeClass(oldVal);
+ }
+ addClass(newVal);
+ }
+ oldVal = copy(newVal);
+ }
+
+
+ function removeClass(classVal) {
+ if (isObject(classVal) && !isArray(classVal)) {
+ classVal = map(classVal, function(v, k) { if (v) return k });
+ }
+ element.removeClass(isArray(classVal) ? classVal.join(' ') : classVal);
+ }
+
+
+ function addClass(classVal) {
+ if (isObject(classVal) && !isArray(classVal)) {
+ classVal = map(classVal, function(v, k) { if (v) return k });
+ }
+ if (classVal) {
+ element.addClass(isArray(classVal) ? classVal.join(' ') : classVal);
+ }
+ }
+ });
+}
+
+/**
+ * @ngdoc directive
+ * @name ng.directive:ngClass
+ *
+ * @description
+ * The `ngClass` allows you to set CSS classes on HTML an element, dynamically, by databinding
+ * an expression that represents all classes to be added.
+ *
+ * The directive won't add duplicate classes if a particular class was already set.
+ *
+ * When the expression changes, the previously added classes are removed and only then the
+ * new classes are added.
+ *
+ * @element ANY
+ * @param {expression} ngClass {@link guide/expression Expression} to eval. The result
+ * of the evaluation can be a string representing space delimited class
+ * names, an array, or a map of class names to boolean values. In the case of a map, the
+ * names of the properties whose values are truthy will be added as css classes to the
+ * element.
+ *
+ * @example
+ <example>
+ <file name="index.html">
+ <input type="button" value="set" ng-click="myVar='my-class'">
+ <input type="button" value="clear" ng-click="myVar=''">
+ <br>
+ <span ng-class="myVar">Sample Text</span>
+ </file>
+ <file name="style.css">
+ .my-class {
+ color: red;
+ }
+ </file>
+ <file name="scenario.js">
+ it('should check ng-class', function() {
+ expect(element('.doc-example-live span').prop('className')).not().
+ toMatch(/my-class/);
+
+ using('.doc-example-live').element(':button:first').click();
+
+ expect(element('.doc-example-live span').prop('className')).
+ toMatch(/my-class/);
+
+ using('.doc-example-live').element(':button:last').click();
+
+ expect(element('.doc-example-live span').prop('className')).not().
+ toMatch(/my-class/);
+ });
+ </file>
+ </example>
+ */
+var ngClassDirective = classDirective('', true);
+
+/**
+ * @ngdoc directive
+ * @name ng.directive:ngClassOdd
+ *
+ * @description
+ * The `ngClassOdd` and `ngClassEven` directives work exactly as
+ * {@link ng.directive:ngClass ngClass}, except it works in
+ * conjunction with `ngRepeat` and takes affect only on odd (even) rows.
+ *
+ * This directive can be applied only within a scope of an
+ * {@link ng.directive:ngRepeat ngRepeat}.
+ *
+ * @element ANY
+ * @param {expression} ngClassOdd {@link guide/expression Expression} to eval. The result
+ * of the evaluation can be a string representing space delimited class names or an array.
+ *
+ * @example
+ <example>
+ <file name="index.html">
+ <ol ng-init="names=['John', 'Mary', 'Cate', 'Suz']">
+ <li ng-repeat="name in names">
+ <span ng-class-odd="'odd'" ng-class-even="'even'">
+ {{name}}
+ </span>
+ </li>
+ </ol>
+ </file>
+ <file name="style.css">
+ .odd {
+ color: red;
+ }
+ .even {
+ color: blue;
+ }
+ </file>
+ <file name="scenario.js">
+ it('should check ng-class-odd and ng-class-even', function() {
+ expect(element('.doc-example-live li:first span').prop('className')).
+ toMatch(/odd/);
+ expect(element('.doc-example-live li:last span').prop('className')).
+ toMatch(/even/);
+ });
+ </file>
+ </example>
+ */
+var ngClassOddDirective = classDirective('Odd', 0);
+
+/**
+ * @ngdoc directive
+ * @name ng.directive:ngClassEven
+ *
+ * @description
+ * The `ngClassOdd` and `ngClassEven` directives work exactly as
+ * {@link ng.directive:ngClass ngClass}, except it works in
+ * conjunction with `ngRepeat` and takes affect only on odd (even) rows.
+ *
+ * This directive can be applied only within a scope of an
+ * {@link ng.directive:ngRepeat ngRepeat}.
+ *
+ * @element ANY
+ * @param {expression} ngClassEven {@link guide/expression Expression} to eval. The
+ * result of the evaluation can be a string representing space delimited class names or an array.
+ *
+ * @example
+ <example>
+ <file name="index.html">
+ <ol ng-init="names=['John', 'Mary', 'Cate', 'Suz']">
+ <li ng-repeat="name in names">
+ <span ng-class-odd="'odd'" ng-class-even="'even'">
+ {{name}}
+ </span>
+ </li>
+ </ol>
+ </file>
+ <file name="style.css">
+ .odd {
+ color: red;
+ }
+ .even {
+ color: blue;
+ }
+ </file>
+ <file name="scenario.js">
+ it('should check ng-class-odd and ng-class-even', function() {
+ expect(element('.doc-example-live li:first span').prop('className')).
+ toMatch(/odd/);
+ expect(element('.doc-example-live li:last span').prop('className')).
+ toMatch(/even/);
+ });
+ </file>
+ </example>
+ */
+var ngClassEvenDirective = classDirective('Even', 1);
+
+/**
+ * @ngdoc directive
+ * @name ng.directive:ngCloak
+ *
+ * @description
+ * The `ngCloak` directive is used to prevent the Angular html template from being briefly
+ * displayed by the browser in its raw (uncompiled) form while your application is loading. Use this
+ * directive to avoid the undesirable flicker effect caused by the html template display.
+ *
+ * The directive can be applied to the `<body>` element, but typically a fine-grained application is
+ * prefered in order to benefit from progressive rendering of the browser view.
+ *
+ * `ngCloak` works in cooperation with a css rule that is embedded within `angular.js` and
+ * `angular.min.js` files. Following is the css rule:
+ *
+ * <pre>
+ * [ng\:cloak], [ng-cloak], [data-ng-cloak], [x-ng-cloak], .ng-cloak, .x-ng-cloak {
+ * display: none !important;
+ * }
+ * </pre>
+ *
+ * When this css rule is loaded by the browser, all html elements (including their children) that
+ * are tagged with the `ng-cloak` directive are hidden. When Angular comes across this directive
+ * during the compilation of the template it deletes the `ngCloak` element attribute, which
+ * makes the compiled element visible.
+ *
+ * For the best result, `angular.js` script must be loaded in the head section of the html file;
+ * alternatively, the css rule (above) must be included in the external stylesheet of the
+ * application.
+ *
+ * Legacy browsers, like IE7, do not provide attribute selector support (added in CSS 2.1) so they
+ * cannot match the `[ng\:cloak]` selector. To work around this limitation, you must add the css
+ * class `ngCloak` in addition to `ngCloak` directive as shown in the example below.
+ *
+ * @element ANY
+ *
+ * @example
+ <doc:example>
+ <doc:source>
+ <div id="template1" ng-cloak>{{ 'hello' }}</div>
+ <div id="template2" ng-cloak class="ng-cloak">{{ 'hello IE7' }}</div>
+ </doc:source>
+ <doc:scenario>
+ it('should remove the template directive and css class', function() {
+ expect(element('.doc-example-live #template1').attr('ng-cloak')).
+ not().toBeDefined();
+ expect(element('.doc-example-live #template2').attr('ng-cloak')).
+ not().toBeDefined();
+ });
+ </doc:scenario>
+ </doc:example>
+ *
+ */
+var ngCloakDirective = ngDirective({
+ compile: function(element, attr) {
+ attr.$set('ngCloak', undefined);
+ element.removeClass('ng-cloak');
+ }
+});
+
+/**
+ * @ngdoc directive
+ * @name ng.directive:ngController
+ *
+ * @description
+ * The `ngController` directive assigns behavior to a scope. This is a key aspect of how angular
+ * supports the principles behind the Model-View-Controller design pattern.
+ *
+ * MVC components in angular:
+ *
+ * * Model — The Model is data in scope properties; scopes are attached to the DOM.
+ * * View — The template (HTML with data bindings) is rendered into the View.
+ * * Controller — The `ngController` directive specifies a Controller class; the class has
+ * methods that typically express the business logic behind the application.
+ *
+ * Note that an alternative way to define controllers is via the {@link ng.$route $route} service.
+ *
+ * @element ANY
+ * @scope
+ * @param {expression} ngController Name of a globally accessible constructor function or an
+ * {@link guide/expression expression} that on the current scope evaluates to a
+ * constructor function.
+ *
+ * @example
+ * Here is a simple form for editing user contact information. Adding, removing, clearing, and
+ * greeting are methods declared on the $scope by the controller (see source tab). These methods can
+ * easily be called from the angular markup. Notice that any changes to the data are automatically
+ * reflected in the View without the need for a manual update.
+ <doc:example>
+ <doc:source>
+ <script>
+ function SettingsController($scope) {
+ $scope.name = "John Smith";
+ $scope.contacts = [
+ {type:'phone', value:'408 555 1212'},
+ {type:'email', value:'john.smith(a)example.org'} ];
+
+ $scope.greet = function() {
+ alert(this.name);
+ };
+
+ $scope.addContact = function() {
+ this.contacts.push({type:'email', value:'yourname(a)example.org'});
+ };
+
+ $scope.removeContact = function(contactToRemove) {
+ var index = this.contacts.indexOf(contactToRemove);
+ this.contacts.splice(index, 1);
+ };
+
+ $scope.clearContact = function(contact) {
+ contact.type = 'phone';
+ contact.value = '';
+ };
+ }
+ </script>
+ <div ng-controller="SettingsController">
+ Name: <input type="text" ng-model="name"/>
+ [ <a href="" ng-click="greet()">greet</a> ]<br/>
+ Contact:
+ <ul>
+ <li ng-repeat="contact in contacts">
+ <select ng-model="contact.type">
+ <option>phone</option>
+ <option>email</option>
+ </select>
+ <input type="text" ng-model="contact.value"/>
+ [ <a href="" ng-click="clearContact(contact)">clear</a>
+ | <a href="" ng-click="removeContact(contact)">X</a> ]
+ </li>
+ <li>[ <a href="" ng-click="addContact()">add</a> ]</li>
+ </ul>
+ </div>
+ </doc:source>
+ <doc:scenario>
+ it('should check controller', function() {
+ expect(element('.doc-example-live div>:input').val()).toBe('John Smith');
+ expect(element('.doc-example-live li:nth-child(1) input').val())
+ .toBe('408 555 1212');
+ expect(element('.doc-example-live li:nth-child(2) input').val())
+ .toBe('john.smith(a)example.org');
+
+ element('.doc-example-live li:first a:contains("clear")').click();
+ expect(element('.doc-example-live li:first input').val()).toBe('');
+
+ element('.doc-example-live li:last a:contains("add")').click();
+ expect(element('.doc-example-live li:nth-child(3) input').val())
+ .toBe('yourname(a)example.org');
+ });
+ </doc:scenario>
+ </doc:example>
+ */
+var ngControllerDirective = [function() {
+ return {
+ scope: true,
+ controller: '@'
+ };
+}];
+
+/**
+ * @ngdoc directive
+ * @name ng.directive:ngCsp
+ * @priority 1000
+ *
+ * @element html
+ * @description
+ * Enables [CSP (Content Security Policy)](https://developer.mozilla.org/en/Security/CSP) support.
+ *
+ * This is necessary when developing things like Google Chrome Extensions.
+ *
+ * CSP forbids apps to use `eval` or `Function(string)` generated functions (among other things).
+ * For us to be compatible, we just need to implement the "getterFn" in $parse without violating
+ * any of these restrictions.
+ *
+ * AngularJS uses `Function(string)` generated functions as a speed optimization. By applying `ngCsp`
+ * it is be possible to opt into the CSP compatible mode. When this mode is on AngularJS will
+ * evaluate all expressions up to 30% slower than in non-CSP mode, but no security violations will
+ * be raised.
+ *
+ * In order to use this feature put `ngCsp` directive on the root element of the application.
+ *
+ * @example
+ * This example shows how to apply the `ngCsp` directive to the `html` tag.
+ <pre>
+ <!doctype html>
+ <html ng-app ng-csp>
+ ...
+ ...
+ </html>
+ </pre>
+ */
+
+var ngCspDirective = ['$sniffer', function($sniffer) {
+ return {
+ priority: 1000,
+ compile: function() {
+ $sniffer.csp = true;
+ }
+ };
+}];
+
+/**
+ * @ngdoc directive
+ * @name ng.directive:ngClick
+ *
+ * @description
+ * The ngClick allows you to specify custom behavior when
+ * element is clicked.
+ *
+ * @element ANY
+ * @param {expression} ngClick {@link guide/expression Expression} to evaluate upon
+ * click. (Event object is available as `$event`)
+ *
+ * @example
+ <doc:example>
+ <doc:source>
+ <button ng-click="count = count + 1" ng-init="count=0">
+ Increment
+ </button>
+ count: {{count}}
+ </doc:source>
+ <doc:scenario>
+ it('should check ng-click', function() {
+ expect(binding('count')).toBe('0');
+ element('.doc-example-live :button').click();
+ expect(binding('count')).toBe('1');
+ });
+ </doc:scenario>
+ </doc:example>
+ */
+/*
+ * A directive that allows creation of custom onclick handlers that are defined as angular
+ * expressions and are compiled and executed within the current scope.
+ *
+ * Events that are handled via these handler are always configured not to propagate further.
+ */
+var ngEventDirectives = {};
+forEach(
+ 'click dblclick mousedown mouseup mouseover mouseout mousemove mouseenter mouseleave submit'.split(' '),
+ function(name) {
+ var directiveName = directiveNormalize('ng-' + name);
+ ngEventDirectives[directiveName] = ['$parse', function($parse) {
+ return function(scope, element, attr) {
+ var fn = $parse(attr[directiveName]);
+ element.bind(lowercase(name), function(event) {
+ scope.$apply(function() {
+ fn(scope, {$event:event});
+ });
+ });
+ };
+ }];
+ }
+);
+
+/**
+ * @ngdoc directive
+ * @name ng.directive:ngDblclick
+ *
+ * @description
+ * The `ngDblclick` directive allows you to specify custom behavior on dblclick event.
+ *
+ * @element ANY
+ * @param {expression} ngDblclick {@link guide/expression Expression} to evaluate upon
+ * dblclick. (Event object is available as `$event`)
+ *
+ * @example
+ * See {@link ng.directive:ngClick ngClick}
+ */
+
+
+/**
+ * @ngdoc directive
+ * @name ng.directive:ngMousedown
+ *
+ * @description
+ * The ngMousedown directive allows you to specify custom behavior on mousedown event.
+ *
+ * @element ANY
+ * @param {expression} ngMousedown {@link guide/expression Expression} to evaluate upon
+ * mousedown. (Event object is available as `$event`)
+ *
+ * @example
+ * See {@link ng.directive:ngClick ngClick}
+ */
+
+
+/**
+ * @ngdoc directive
+ * @name ng.directive:ngMouseup
+ *
+ * @description
+ * Specify custom behavior on mouseup event.
+ *
+ * @element ANY
+ * @param {expression} ngMouseup {@link guide/expression Expression} to evaluate upon
+ * mouseup. (Event object is available as `$event`)
+ *
+ * @example
+ * See {@link ng.directive:ngClick ngClick}
+ */
+
+/**
+ * @ngdoc directive
+ * @name ng.directive:ngMouseover
+ *
+ * @description
+ * Specify custom behavior on mouseover event.
+ *
+ * @element ANY
+ * @param {expression} ngMouseover {@link guide/expression Expression} to evaluate upon
+ * mouseover. (Event object is available as `$event`)
+ *
+ * @example
+ * See {@link ng.directive:ngClick ngClick}
+ */
+
+
+/**
+ * @ngdoc directive
+ * @name ng.directive:ngMouseenter
+ *
+ * @description
+ * Specify custom behavior on mouseenter event.
+ *
+ * @element ANY
+ * @param {expression} ngMouseenter {@link guide/expression Expression} to evaluate upon
+ * mouseenter. (Event object is available as `$event`)
+ *
+ * @example
+ * See {@link ng.directive:ngClick ngClick}
+ */
+
+
+/**
+ * @ngdoc directive
+ * @name ng.directive:ngMouseleave
+ *
+ * @description
+ * Specify custom behavior on mouseleave event.
+ *
+ * @element ANY
+ * @param {expression} ngMouseleave {@link guide/expression Expression} to evaluate upon
+ * mouseleave. (Event object is available as `$event`)
+ *
+ * @example
+ * See {@link ng.directive:ngClick ngClick}
+ */
+
+
+/**
+ * @ngdoc directive
+ * @name ng.directive:ngMousemove
+ *
+ * @description
+ * Specify custom behavior on mousemove event.
+ *
+ * @element ANY
+ * @param {expression} ngMousemove {@link guide/expression Expression} to evaluate upon
+ * mousemove. (Event object is available as `$event`)
+ *
+ * @example
+ * See {@link ng.directive:ngClick ngClick}
+ */
+
+
+/**
+ * @ngdoc directive
+ * @name ng.directive:ngKeydown
+ *
+ * @description
+ * Specify custom behavior on keydown event.
+ *
+ * @element ANY
+ * @param {expression} ngKeydown {@link guide/expression Expression} to evaluate upon
+ * keydown. (Event object is available as `$event` and can be interrogated for keyCode, altKey, etc.)
+ *
+ * @example
+ * See {@link ng.directive:ngClick ngClick}
+ */
+
+
+/**
+ * @ngdoc directive
+ * @name ng.directive:ngKeyup
+ *
+ * @description
+ * Specify custom behavior on keyup event.
+ *
+ * @element ANY
+ * @param {expression} ngKeyup {@link guide/expression Expression} to evaluate upon
+ * keyup. (Event object is available as `$event` and can be interrogated for keyCode, altKey, etc.)
+ *
+ * @example
+ * See {@link ng.directive:ngClick ngClick}
+ */
+
+
+/**
+ * @ngdoc directive
+ * @name ng.directive:ngKeypress
+ *
+ * @description
+ * Specify custom behavior on keypress event.
+ *
+ * @element ANY
+ * @param {expression} ngKeypress {@link guide/expression Expression} to evaluate upon
+ * keypress. (Event object is available as `$event` and can be interrogated for keyCode, altKey, etc.)
+ *
+ * @example
+ * See {@link ng.directive:ngClick ngClick}
+ */
+
+
+/**
+ * @ngdoc directive
+ * @name ng.directive:ngSubmit
+ *
+ * @description
+ * Enables binding angular expressions to onsubmit events.
+ *
+ * Additionally it prevents the default action (which for form means sending the request to the
+ * server and reloading the current page) **but only if the form does not contain an `action`
+ * attribute**.
+ *
+ * @element form
+ * @param {expression} ngSubmit {@link guide/expression Expression} to eval. (Event object is available as `$event`)
+ *
+ * @example
+ <doc:example>
+ <doc:source>
+ <script>
+ function Ctrl($scope) {
+ $scope.list = [];
+ $scope.text = 'hello';
+ $scope.submit = function() {
+ if (this.text) {
+ this.list.push(this.text);
+ this.text = '';
+ }
+ };
+ }
+ </script>
+ <form ng-submit="submit()" ng-controller="Ctrl">
+ Enter text and hit enter:
+ <input type="text" ng-model="text" name="text" />
+ <input type="submit" id="submit" value="Submit" />
+ <pre>list={{list}}</pre>
+ </form>
+ </doc:source>
+ <doc:scenario>
+ it('should check ng-submit', function() {
+ expect(binding('list')).toBe('[]');
+ element('.doc-example-live #submit').click();
+ expect(binding('list')).toBe('["hello"]');
+ expect(input('text').val()).toBe('');
+ });
+ it('should ignore empty strings', function() {
+ expect(binding('list')).toBe('[]');
+ element('.doc-example-live #submit').click();
+ element('.doc-example-live #submit').click();
+ expect(binding('list')).toBe('["hello"]');
+ });
+ </doc:scenario>
+ </doc:example>
+ */
+
+/**
+ * @ngdoc directive
+ * @name ng.directive:ngInclude
+ * @restrict ECA
+ *
+ * @description
+ * Fetches, compiles and includes an external HTML fragment.
+ *
+ * Keep in mind that Same Origin Policy applies to included resources
+ * (e.g. ngInclude won't work for cross-domain requests on all browsers and for
+ * file:// access on some browsers).
+ *
+ * @scope
+ *
+ * @param {string} ngInclude|src angular expression evaluating to URL. If the source is a string constant,
+ * make sure you wrap it in quotes, e.g. `src="'myPartialTemplate.html'"`.
+ * @param {string=} onload Expression to evaluate when a new partial is loaded.
+ *
+ * @param {string=} autoscroll Whether `ngInclude` should call {@link ng.$anchorScroll
+ * $anchorScroll} to scroll the viewport after the content is loaded.
+ *
+ * - If the attribute is not set, disable scrolling.
+ * - If the attribute is set without value, enable scrolling.
+ * - Otherwise enable scrolling only if the expression evaluates to truthy value.
+ *
+ * @example
+ <example>
+ <file name="index.html">
+ <div ng-controller="Ctrl">
+ <select ng-model="template" ng-options="t.name for t in templates">
+ <option value="">(blank)</option>
+ </select>
+ url of the template: <tt>{{template.url}}</tt>
+ <hr/>
+ <div ng-include src="template.url"></div>
+ </div>
+ </file>
+ <file name="script.js">
+ function Ctrl($scope) {
+ $scope.templates =
+ [ { name: 'template1.html', url: 'template1.html'}
+ , { name: 'template2.html', url: 'template2.html'} ];
+ $scope.template = $scope.templates[0];
+ }
+ </file>
+ <file name="template1.html">
+ Content of template1.html
+ </file>
+ <file name="template2.html">
+ Content of template2.html
+ </file>
+ <file name="scenario.js">
+ it('should load template1.html', function() {
+ expect(element('.doc-example-live [ng-include]').text()).
+ toMatch(/Content of template1.html/);
+ });
+ it('should load template2.html', function() {
+ select('template').option('1');
+ expect(element('.doc-example-live [ng-include]').text()).
+ toMatch(/Content of template2.html/);
+ });
+ it('should change to blank', function() {
+ select('template').option('');
+ expect(element('.doc-example-live [ng-include]').text()).toEqual('');
+ });
+ </file>
+ </example>
+ */
+
+
+/**
+ * @ngdoc event
+ * @name ng.directive:ngInclude#$includeContentLoaded
+ * @eventOf ng.directive:ngInclude
+ * @eventType emit on the current ngInclude scope
+ * @description
+ * Emitted every time the ngInclude content is reloaded.
+ */
+var ngIncludeDirective = ['$http', '$templateCache', '$anchorScroll', '$compile',
+ function($http, $templateCache, $anchorScroll, $compile) {
+ return {
+ restrict: 'ECA',
+ terminal: true,
+ compile: function(element, attr) {
+ var srcExp = attr.ngInclude || attr.src,
+ onloadExp = attr.onload || '',
+ autoScrollExp = attr.autoscroll;
+
+ return function(scope, element) {
+ var changeCounter = 0,
+ childScope;
+
+ var clearContent = function() {
+ if (childScope) {
+ childScope.$destroy();
+ childScope = null;
+ }
+
+ element.html('');
+ };
+
+ scope.$watch(srcExp, function ngIncludeWatchAction(src) {
+ var thisChangeId = ++changeCounter;
+
+ if (src) {
+ $http.get(src, {cache: $templateCache}).success(function(response) {
+ if (thisChangeId !== changeCounter) return;
+
+ if (childScope) childScope.$destroy();
+ childScope = scope.$new();
+
+ element.html(response);
+ $compile(element.contents())(childScope);
+
+ if (isDefined(autoScrollExp) && (!autoScrollExp || scope.$eval(autoScrollExp))) {
+ $anchorScroll();
+ }
+
+ childScope.$emit('$includeContentLoaded');
+ scope.$eval(onloadExp);
+ }).error(function() {
+ if (thisChangeId === changeCounter) clearContent();
+ });
+ } else clearContent();
+ });
+ };
+ }
+ };
+}];
+
+/**
+ * @ngdoc directive
+ * @name ng.directive:ngInit
+ *
+ * @description
+ * The `ngInit` directive specifies initialization tasks to be executed
+ * before the template enters execution mode during bootstrap.
+ *
+ * @element ANY
+ * @param {expression} ngInit {@link guide/expression Expression} to eval.
+ *
+ * @example
+ <doc:example>
+ <doc:source>
+ <div ng-init="greeting='Hello'; person='World'">
+ {{greeting}} {{person}}!
+ </div>
+ </doc:source>
+ <doc:scenario>
+ it('should check greeting', function() {
+ expect(binding('greeting')).toBe('Hello');
+ expect(binding('person')).toBe('World');
+ });
+ </doc:scenario>
+ </doc:example>
+ */
+var ngInitDirective = ngDirective({
+ compile: function() {
+ return {
+ pre: function(scope, element, attrs) {
+ scope.$eval(attrs.ngInit);
+ }
+ }
+ }
+});
+
+/**
+ * @ngdoc directive
+ * @name ng.directive:ngNonBindable
+ * @priority 1000
+ *
+ * @description
+ * Sometimes it is necessary to write code which looks like bindings but which should be left alone
+ * by angular. Use `ngNonBindable` to make angular ignore a chunk of HTML.
+ *
+ * @element ANY
+ *
+ * @example
+ * In this example there are two location where a simple binding (`{{}}`) is present, but the one
+ * wrapped in `ngNonBindable` is left alone.
+ *
+ * @example
+ <doc:example>
+ <doc:source>
+ <div>Normal: {{1 + 2}}</div>
+ <div ng-non-bindable>Ignored: {{1 + 2}}</div>
+ </doc:source>
+ <doc:scenario>
+ it('should check ng-non-bindable', function() {
+ expect(using('.doc-example-live').binding('1 + 2')).toBe('3');
+ expect(using('.doc-example-live').element('div:last').text()).
+ toMatch(/1 \+ 2/);
+ });
+ </doc:scenario>
+ </doc:example>
+ */
+var ngNonBindableDirective = ngDirective({ terminal: true, priority: 1000 });
+
+/**
+ * @ngdoc directive
+ * @name ng.directive:ngPluralize
+ * @restrict EA
+ *
+ * @description
+ * # Overview
+ * `ngPluralize` is a directive that displays messages according to en-US localization rules.
+ * These rules are bundled with angular.js, but can be overridden
+ * (see {@link guide/i18n Angular i18n} dev guide). You configure ngPluralize directive
+ * by specifying the mappings between
+ * {@link http://unicode.org/repos/cldr-tmp/trunk/diff/supplemental/language_plural_r…
+ * plural categories} and the strings to be displayed.
+ *
+ * # Plural categories and explicit number rules
+ * There are two
+ * {@link http://unicode.org/repos/cldr-tmp/trunk/diff/supplemental/language_plural_r…
+ * plural categories} in Angular's default en-US locale: "one" and "other".
+ *
+ * While a pural category may match many numbers (for example, in en-US locale, "other" can match
+ * any number that is not 1), an explicit number rule can only match one number. For example, the
+ * explicit number rule for "3" matches the number 3. There are examples of plural categories
+ * and explicit number rules throughout the rest of this documentation.
+ *
+ * # Configuring ngPluralize
+ * You configure ngPluralize by providing 2 attributes: `count` and `when`.
+ * You can also provide an optional attribute, `offset`.
+ *
+ * The value of the `count` attribute can be either a string or an {@link guide/expression
+ * Angular expression}; these are evaluated on the current scope for its bound value.
+ *
+ * The `when` attribute specifies the mappings between plural categories and the actual
+ * string to be displayed. The value of the attribute should be a JSON object.
+ *
+ * The following example shows how to configure ngPluralize:
+ *
+ * <pre>
+ * <ng-pluralize count="personCount"
+ when="{'0': 'Nobody is viewing.',
+ * 'one': '1 person is viewing.',
+ * 'other': '{} people are viewing.'}">
+ * </ng-pluralize>
+ *</pre>
+ *
+ * In the example, `"0: Nobody is viewing."` is an explicit number rule. If you did not
+ * specify this rule, 0 would be matched to the "other" category and "0 people are viewing"
+ * would be shown instead of "Nobody is viewing". You can specify an explicit number rule for
+ * other numbers, for example 12, so that instead of showing "12 people are viewing", you can
+ * show "a dozen people are viewing".
+ *
+ * You can use a set of closed braces(`{}`) as a placeholder for the number that you want substituted
+ * into pluralized strings. In the previous example, Angular will replace `{}` with
+ * <span ng-non-bindable>`{{personCount}}`</span>. The closed braces `{}` is a placeholder
+ * for <span ng-non-bindable>{{numberExpression}}</span>.
+ *
+ * # Configuring ngPluralize with offset
+ * The `offset` attribute allows further customization of pluralized text, which can result in
+ * a better user experience. For example, instead of the message "4 people are viewing this document",
+ * you might display "John, Kate and 2 others are viewing this document".
+ * The offset attribute allows you to offset a number by any desired value.
+ * Let's take a look at an example:
+ *
+ * <pre>
+ * <ng-pluralize count="personCount" offset=2
+ * when="{'0': 'Nobody is viewing.',
+ * '1': '{{person1}} is viewing.',
+ * '2': '{{person1}} and {{person2}} are viewing.',
+ * 'one': '{{person1}}, {{person2}} and one other person are viewing.',
+ * 'other': '{{person1}}, {{person2}} and {} other people are viewing.'}">
+ * </ng-pluralize>
+ * </pre>
+ *
+ * Notice that we are still using two plural categories(one, other), but we added
+ * three explicit number rules 0, 1 and 2.
+ * When one person, perhaps John, views the document, "John is viewing" will be shown.
+ * When three people view the document, no explicit number rule is found, so
+ * an offset of 2 is taken off 3, and Angular uses 1 to decide the plural category.
+ * In this case, plural category 'one' is matched and "John, Marry and one other person are viewing"
+ * is shown.
+ *
+ * Note that when you specify offsets, you must provide explicit number rules for
+ * numbers from 0 up to and including the offset. If you use an offset of 3, for example,
+ * you must provide explicit number rules for 0, 1, 2 and 3. You must also provide plural strings for
+ * plural categories "one" and "other".
+ *
+ * @param {string|expression} count The variable to be bounded to.
+ * @param {string} when The mapping between plural category to its correspoding strings.
+ * @param {number=} offset Offset to deduct from the total number.
+ *
+ * @example
+ <doc:example>
+ <doc:source>
+ <script>
+ function Ctrl($scope) {
+ $scope.person1 = 'Igor';
+ $scope.person2 = 'Misko';
+ $scope.personCount = 1;
+ }
+ </script>
+ <div ng-controller="Ctrl">
+ Person 1:<input type="text" ng-model="person1" value="Igor" /><br/>
+ Person 2:<input type="text" ng-model="person2" value="Misko" /><br/>
+ Number of People:<input type="text" ng-model="personCount" value="1" /><br/>
+
+ <!--- Example with simple pluralization rules for en locale --->
+ Without Offset:
+ <ng-pluralize count="personCount"
+ when="{'0': 'Nobody is viewing.',
+ 'one': '1 person is viewing.',
+ 'other': '{} people are viewing.'}">
+ </ng-pluralize><br>
+
+ <!--- Example with offset --->
+ With Offset(2):
+ <ng-pluralize count="personCount" offset=2
+ when="{'0': 'Nobody is viewing.',
+ '1': '{{person1}} is viewing.',
+ '2': '{{person1}} and {{person2}} are viewing.',
+ 'one': '{{person1}}, {{person2}} and one other person are viewing.',
+ 'other': '{{person1}}, {{person2}} and {} other people are viewing.'}">
+ </ng-pluralize>
+ </div>
+ </doc:source>
+ <doc:scenario>
+ it('should show correct pluralized string', function() {
+ expect(element('.doc-example-live ng-pluralize:first').text()).
+ toBe('1 person is viewing.');
+ expect(element('.doc-example-live ng-pluralize:last').text()).
+ toBe('Igor is viewing.');
+
+ using('.doc-example-live').input('personCount').enter('0');
+ expect(element('.doc-example-live ng-pluralize:first').text()).
+ toBe('Nobody is viewing.');
+ expect(element('.doc-example-live ng-pluralize:last').text()).
+ toBe('Nobody is viewing.');
+
+ using('.doc-example-live').input('personCount').enter('2');
+ expect(element('.doc-example-live ng-pluralize:first').text()).
+ toBe('2 people are viewing.');
+ expect(element('.doc-example-live ng-pluralize:last').text()).
+ toBe('Igor and Misko are viewing.');
+
+ using('.doc-example-live').input('personCount').enter('3');
+ expect(element('.doc-example-live ng-pluralize:first').text()).
+ toBe('3 people are viewing.');
+ expect(element('.doc-example-live ng-pluralize:last').text()).
+ toBe('Igor, Misko and one other person are viewing.');
+
+ using('.doc-example-live').input('personCount').enter('4');
+ expect(element('.doc-example-live ng-pluralize:first').text()).
+ toBe('4 people are viewing.');
+ expect(element('.doc-example-live ng-pluralize:last').text()).
+ toBe('Igor, Misko and 2 other people are viewing.');
+ });
+
+ it('should show data-binded names', function() {
+ using('.doc-example-live').input('personCount').enter('4');
+ expect(element('.doc-example-live ng-pluralize:last').text()).
+ toBe('Igor, Misko and 2 other people are viewing.');
+
+ using('.doc-example-live').input('person1').enter('Di');
+ using('.doc-example-live').input('person2').enter('Vojta');
+ expect(element('.doc-example-live ng-pluralize:last').text()).
+ toBe('Di, Vojta and 2 other people are viewing.');
+ });
+ </doc:scenario>
+ </doc:example>
+ */
+var ngPluralizeDirective = ['$locale', '$interpolate', function($locale, $interpolate) {
+ var BRACE = /{}/g;
+ return {
+ restrict: 'EA',
+ link: function(scope, element, attr) {
+ var numberExp = attr.count,
+ whenExp = element.attr(attr.$attr.when), // this is because we have {{}} in attrs
+ offset = attr.offset || 0,
+ whens = scope.$eval(whenExp),
+ whensExpFns = {},
+ startSymbol = $interpolate.startSymbol(),
+ endSymbol = $interpolate.endSymbol();
+
+ forEach(whens, function(expression, key) {
+ whensExpFns[key] =
+ $interpolate(expression.replace(BRACE, startSymbol + numberExp + '-' +
+ offset + endSymbol));
+ });
+
+ scope.$watch(function ngPluralizeWatch() {
+ var value = parseFloat(scope.$eval(numberExp));
+
+ if (!isNaN(value)) {
+ //if explicit number rule such as 1, 2, 3... is defined, just use it. Otherwise,
+ //check it against pluralization rules in $locale service
+ if (!(value in whens)) value = $locale.pluralCat(value - offset);
+ return whensExpFns[value](scope, element, true);
+ } else {
+ return '';
+ }
+ }, function ngPluralizeWatchAction(newVal) {
+ element.text(newVal);
+ });
+ }
+ };
+}];
+
+/**
+ * @ngdoc directive
+ * @name ng.directive:ngRepeat
+ *
+ * @description
+ * The `ngRepeat` directive instantiates a template once per item from a collection. Each template
+ * instance gets its own scope, where the given loop variable is set to the current collection item,
+ * and `$index` is set to the item index or key.
+ *
+ * Special properties are exposed on the local scope of each template instance, including:
+ *
+ * * `$index` – `{number}` – iterator offset of the repeated element (0..length-1)
+ * * `$first` – `{boolean}` – true if the repeated element is first in the iterator.
+ * * `$middle` – `{boolean}` – true if the repeated element is between the first and last in the iterator.
+ * * `$last` – `{boolean}` – true if the repeated element is last in the iterator.
+ *
+ *
+ * @element ANY
+ * @scope
+ * @priority 1000
+ * @param {repeat_expression} ngRepeat The expression indicating how to enumerate a collection. Two
+ * formats are currently supported:
+ *
+ * * `variable in expression` – where variable is the user defined loop variable and `expression`
+ * is a scope expression giving the collection to enumerate.
+ *
+ * For example: `track in cd.tracks`.
+ *
+ * * `(key, value) in expression` – where `key` and `value` can be any user defined identifiers,
+ * and `expression` is the scope expression giving the collection to enumerate.
+ *
+ * For example: `(name, age) in {'adam':10, 'amalie':12}`.
+ *
+ * @example
+ * This example initializes the scope to a list of names and
+ * then uses `ngRepeat` to display every person:
+ <doc:example>
+ <doc:source>
+ <div ng-init="friends = [{name:'John', age:25}, {name:'Mary', age:28}]">
+ I have {{friends.length}} friends. They are:
+ <ul>
+ <li ng-repeat="friend in friends">
+ [{{$index + 1}}] {{friend.name}} who is {{friend.age}} years old.
+ </li>
+ </ul>
+ </div>
+ </doc:source>
+ <doc:scenario>
+ it('should check ng-repeat', function() {
+ var r = using('.doc-example-live').repeater('ul li');
+ expect(r.count()).toBe(2);
+ expect(r.row(0)).toEqual(["1","John","25"]);
+ expect(r.row(1)).toEqual(["2","Mary","28"]);
+ });
+ </doc:scenario>
+ </doc:example>
+ */
+var ngRepeatDirective = ngDirective({
+ transclude: 'element',
+ priority: 1000,
+ terminal: true,
+ compile: function(element, attr, linker) {
+ return function(scope, iterStartElement, attr){
+ var expression = attr.ngRepeat;
+ var match = expression.match(/^\s*(.+)\s+in\s+(.*)\s*$/),
+ lhs, rhs, valueIdent, keyIdent;
+ if (! match) {
+ throw Error("Expected ngRepeat in form of '_item_ in _collection_' but got '" +
+ expression + "'.");
+ }
+ lhs = match[1];
+ rhs = match[2];
+ match = lhs.match(/^(?:([\$\w]+)|\(([\$\w]+)\s*,\s*([\$\w]+)\))$/);
+ if (!match) {
+ throw Error("'item' in 'item in collection' should be identifier or (key, value) but got '" +
+ lhs + "'.");
+ }
+ valueIdent = match[3] || match[1];
+ keyIdent = match[2];
+
+ // Store a list of elements from previous run. This is a hash where key is the item from the
+ // iterator, and the value is an array of objects with following properties.
+ // - scope: bound scope
+ // - element: previous element.
+ // - index: position
+ // We need an array of these objects since the same object can be returned from the iterator.
+ // We expect this to be a rare case.
+ var lastOrder = new HashQueueMap();
+
+ scope.$watch(function ngRepeatWatch(scope){
+ var index, length,
+ collection = scope.$eval(rhs),
+ cursor = iterStartElement, // current position of the node
+ // Same as lastOrder but it has the current state. It will become the
+ // lastOrder on the next iteration.
+ nextOrder = new HashQueueMap(),
+ arrayBound,
+ childScope,
+ key, value, // key/value of iteration
+ array,
+ last; // last object information {scope, element, index}
+
+
+
+ if (!isArray(collection)) {
+ // if object, extract keys, sort them and use to determine order of iteration over obj props
+ array = [];
+ for(key in collection) {
+ if (collection.hasOwnProperty(key) && key.charAt(0) != '$') {
+ array.push(key);
+ }
+ }
+ array.sort();
+ } else {
+ array = collection || [];
+ }
+
+ arrayBound = array.length-1;
+
+ // we are not using forEach for perf reasons (trying to avoid #call)
+ for (index = 0, length = array.length; index < length; index++) {
+ key = (collection === array) ? index : array[index];
+ value = collection[key];
+
+ last = lastOrder.shift(value);
+
+ if (last) {
+ // if we have already seen this object, then we need to reuse the
+ // associated scope/element
+ childScope = last.scope;
+ nextOrder.push(value, last);
+
+ if (index === last.index) {
+ // do nothing
+ cursor = last.element;
+ } else {
+ // existing item which got moved
+ last.index = index;
+ // This may be a noop, if the element is next, but I don't know of a good way to
+ // figure this out, since it would require extra DOM access, so let's just hope that
+ // the browsers realizes that it is noop, and treats it as such.
+ cursor.after(last.element);
+ cursor = last.element;
+ }
+ } else {
+ // new item which we don't know about
+ childScope = scope.$new();
+ }
+
+ childScope[valueIdent] = value;
+ if (keyIdent) childScope[keyIdent] = key;
+ childScope.$index = index;
+
+ childScope.$first = (index === 0);
+ childScope.$last = (index === arrayBound);
+ childScope.$middle = !(childScope.$first || childScope.$last);
+
+ if (!last) {
+ linker(childScope, function(clone){
+ cursor.after(clone);
+ last = {
+ scope: childScope,
+ element: (cursor = clone),
+ index: index
+ };
+ nextOrder.push(value, last);
+ });
+ }
+ }
+
+ //shrink children
+ for (key in lastOrder) {
+ if (lastOrder.hasOwnProperty(key)) {
+ array = lastOrder[key];
+ while(array.length) {
+ value = array.pop();
+ value.element.remove();
+ value.scope.$destroy();
+ }
+ }
+ }
+
+ lastOrder = nextOrder;
+ });
+ };
+ }
+});
+
+/**
+ * @ngdoc directive
+ * @name ng.directive:ngShow
+ *
+ * @description
+ * The `ngShow` and `ngHide` directives show or hide a portion of the DOM tree (HTML)
+ * conditionally.
+ *
+ * @element ANY
+ * @param {expression} ngShow If the {@link guide/expression expression} is truthy
+ * then the element is shown or hidden respectively.
+ *
+ * @example
+ <doc:example>
+ <doc:source>
+ Click me: <input type="checkbox" ng-model="checked"><br/>
+ Show: <span ng-show="checked">I show up when your checkbox is checked.</span> <br/>
+ Hide: <span ng-hide="checked">I hide when your checkbox is checked.</span>
+ </doc:source>
+ <doc:scenario>
+ it('should check ng-show / ng-hide', function() {
+ expect(element('.doc-example-live span:first:hidden').count()).toEqual(1);
+ expect(element('.doc-example-live span:last:visible').count()).toEqual(1);
+
+ input('checked').check();
+
+ expect(element('.doc-example-live span:first:visible').count()).toEqual(1);
+ expect(element('.doc-example-live span:last:hidden').count()).toEqual(1);
+ });
+ </doc:scenario>
+ </doc:example>
+ */
+//TODO(misko): refactor to remove element from the DOM
+var ngShowDirective = ngDirective(function(scope, element, attr){
+ scope.$watch(attr.ngShow, function ngShowWatchAction(value){
+ element.css('display', toBoolean(value) ? '' : 'none');
+ });
+});
+
+
+/**
+ * @ngdoc directive
+ * @name ng.directive:ngHide
+ *
+ * @description
+ * The `ngHide` and `ngShow` directives hide or show a portion of the DOM tree (HTML)
+ * conditionally.
+ *
+ * @element ANY
+ * @param {expression} ngHide If the {@link guide/expression expression} is truthy then
+ * the element is shown or hidden respectively.
+ *
+ * @example
+ <doc:example>
+ <doc:source>
+ Click me: <input type="checkbox" ng-model="checked"><br/>
+ Show: <span ng-show="checked">I show up when you checkbox is checked?</span> <br/>
+ Hide: <span ng-hide="checked">I hide when you checkbox is checked?</span>
+ </doc:source>
+ <doc:scenario>
+ it('should check ng-show / ng-hide', function() {
+ expect(element('.doc-example-live span:first:hidden').count()).toEqual(1);
+ expect(element('.doc-example-live span:last:visible').count()).toEqual(1);
+
+ input('checked').check();
+
+ expect(element('.doc-example-live span:first:visible').count()).toEqual(1);
+ expect(element('.doc-example-live span:last:hidden').count()).toEqual(1);
+ });
+ </doc:scenario>
+ </doc:example>
+ */
+//TODO(misko): refactor to remove element from the DOM
+var ngHideDirective = ngDirective(function(scope, element, attr){
+ scope.$watch(attr.ngHide, function ngHideWatchAction(value){
+ element.css('display', toBoolean(value) ? 'none' : '');
+ });
+});
+
+/**
+ * @ngdoc directive
+ * @name ng.directive:ngStyle
+ *
+ * @description
+ * The `ngStyle` directive allows you to set CSS style on an HTML element conditionally.
+ *
+ * @element ANY
+ * @param {expression} ngStyle {@link guide/expression Expression} which evals to an
+ * object whose keys are CSS style names and values are corresponding values for those CSS
+ * keys.
+ *
+ * @example
+ <example>
+ <file name="index.html">
+ <input type="button" value="set" ng-click="myStyle={color:'red'}">
+ <input type="button" value="clear" ng-click="myStyle={}">
+ <br/>
+ <span ng-style="myStyle">Sample Text</span>
+ <pre>myStyle={{myStyle}}</pre>
+ </file>
+ <file name="style.css">
+ span {
+ color: black;
+ }
+ </file>
+ <file name="scenario.js">
+ it('should check ng-style', function() {
+ expect(element('.doc-example-live span').css('color')).toBe('rgb(0, 0, 0)');
+ element('.doc-example-live :button[value=set]').click();
+ expect(element('.doc-example-live span').css('color')).toBe('rgb(255, 0, 0)');
+ element('.doc-example-live :button[value=clear]').click();
+ expect(element('.doc-example-live span').css('color')).toBe('rgb(0, 0, 0)');
+ });
+ </file>
+ </example>
+ */
+var ngStyleDirective = ngDirective(function(scope, element, attr) {
+ scope.$watch(attr.ngStyle, function ngStyleWatchAction(newStyles, oldStyles) {
+ if (oldStyles && (newStyles !== oldStyles)) {
+ forEach(oldStyles, function(val, style) { element.css(style, '');});
+ }
+ if (newStyles) element.css(newStyles);
+ }, true);
+});
+
+/**
+ * @ngdoc directive
+ * @name ng.directive:ngSwitch
+ * @restrict EA
+ *
+ * @description
+ * Conditionally change the DOM structure.
+ *
+ * @usage
+ * <ANY ng-switch="expression">
+ * <ANY ng-switch-when="matchValue1">...</ANY>
+ * <ANY ng-switch-when="matchValue2">...</ANY>
+ * ...
+ * <ANY ng-switch-default>...</ANY>
+ * </ANY>
+ *
+ * @scope
+ * @param {*} ngSwitch|on expression to match against <tt>ng-switch-when</tt>.
+ * @paramDescription
+ * On child elments add:
+ *
+ * * `ngSwitchWhen`: the case statement to match against. If match then this
+ * case will be displayed.
+ * * `ngSwitchDefault`: the default case when no other casses match.
+ *
+ * @example
+ <doc:example>
+ <doc:source>
+ <script>
+ function Ctrl($scope) {
+ $scope.items = ['settings', 'home', 'other'];
+ $scope.selection = $scope.items[0];
+ }
+ </script>
+ <div ng-controller="Ctrl">
+ <select ng-model="selection" ng-options="item for item in items">
+ </select>
+ <tt>selection={{selection}}</tt>
+ <hr/>
+ <div ng-switch on="selection" >
+ <div ng-switch-when="settings">Settings Div</div>
+ <span ng-switch-when="home">Home Span</span>
+ <span ng-switch-default>default</span>
+ </div>
+ </div>
+ </doc:source>
+ <doc:scenario>
+ it('should start in settings', function() {
+ expect(element('.doc-example-live [ng-switch]').text()).toMatch(/Settings Div/);
+ });
+ it('should change to home', function() {
+ select('selection').option('home');
+ expect(element('.doc-example-live [ng-switch]').text()).toMatch(/Home Span/);
+ });
+ it('should select deafault', function() {
+ select('selection').option('other');
+ expect(element('.doc-example-live [ng-switch]').text()).toMatch(/default/);
+ });
+ </doc:scenario>
+ </doc:example>
+ */
+var NG_SWITCH = 'ng-switch';
+var ngSwitchDirective = valueFn({
+ restrict: 'EA',
+ require: 'ngSwitch',
+ // asks for $scope to fool the BC controller module
+ controller: ['$scope', function ngSwitchController() {
+ this.cases = {};
+ }],
+ link: function(scope, element, attr, ctrl) {
+ var watchExpr = attr.ngSwitch || attr.on,
+ selectedTransclude,
+ selectedElement,
+ selectedScope;
+
+ scope.$watch(watchExpr, function ngSwitchWatchAction(value) {
+ if (selectedElement) {
+ selectedScope.$destroy();
+ selectedElement.remove();
+ selectedElement = selectedScope = null;
+ }
+ if ((selectedTransclude = ctrl.cases['!' + value] || ctrl.cases['?'])) {
+ scope.$eval(attr.change);
+ selectedScope = scope.$new();
+ selectedTransclude(selectedScope, function(caseElement) {
+ selectedElement = caseElement;
+ element.append(caseElement);
+ });
+ }
+ });
+ }
+});
+
+var ngSwitchWhenDirective = ngDirective({
+ transclude: 'element',
+ priority: 500,
+ require: '^ngSwitch',
+ compile: function(element, attrs, transclude) {
+ return function(scope, element, attr, ctrl) {
+ ctrl.cases['!' + attrs.ngSwitchWhen] = transclude;
+ };
+ }
+});
+
+var ngSwitchDefaultDirective = ngDirective({
+ transclude: 'element',
+ priority: 500,
+ require: '^ngSwitch',
+ compile: function(element, attrs, transclude) {
+ return function(scope, element, attr, ctrl) {
+ ctrl.cases['?'] = transclude;
+ };
+ }
+});
+
+/**
+ * @ngdoc directive
+ * @name ng.directive:ngTransclude
+ *
+ * @description
+ * Insert the transcluded DOM here.
+ *
+ * @element ANY
+ *
+ * @example
+ <doc:example module="transclude">
+ <doc:source>
+ <script>
+ function Ctrl($scope) {
+ $scope.title = 'Lorem Ipsum';
+ $scope.text = 'Neque porro quisquam est qui dolorem ipsum quia dolor...';
+ }
+
+ angular.module('transclude', [])
+ .directive('pane', function(){
+ return {
+ restrict: 'E',
+ transclude: true,
+ scope: { title:'@' },
+ template: '<div style="border: 1px solid black;">' +
+ '<div style="background-color: gray">{{title}}</div>' +
+ '<div ng-transclude></div>' +
+ '</div>'
+ };
+ });
+ </script>
+ <div ng-controller="Ctrl">
+ <input ng-model="title"><br>
+ <textarea ng-model="text"></textarea> <br/>
+ <pane title="{{title}}">{{text}}</pane>
+ </div>
+ </doc:source>
+ <doc:scenario>
+ it('should have transcluded', function() {
+ input('title').enter('TITLE');
+ input('text').enter('TEXT');
+ expect(binding('title')).toEqual('TITLE');
+ expect(binding('text')).toEqual('TEXT');
+ });
+ </doc:scenario>
+ </doc:example>
+ *
+ */
+var ngTranscludeDirective = ngDirective({
+ controller: ['$transclude', '$element', function($transclude, $element) {
+ $transclude(function(clone) {
+ $element.append(clone);
+ });
+ }]
+});
+
+/**
+ * @ngdoc directive
+ * @name ng.directive:ngView
+ * @restrict ECA
+ *
+ * @description
+ * # Overview
+ * `ngView` is a directive that complements the {@link ng.$route $route} service by
+ * including the rendered template of the current route into the main layout (`index.html`) file.
+ * Every time the current route changes, the included view changes with it according to the
+ * configuration of the `$route` service.
+ *
+ * @scope
+ * @example
+ <example module="ngView">
+ <file name="index.html">
+ <div ng-controller="MainCntl">
+ Choose:
+ <a href="Book/Moby">Moby</a> |
+ <a href="Book/Moby/ch/1">Moby: Ch1</a> |
+ <a href="Book/Gatsby">Gatsby</a> |
+ <a href="Book/Gatsby/ch/4?key=value">Gatsby: Ch4</a> |
+ <a href="Book/Scarlet">Scarlet Letter</a><br/>
+
+ <div ng-view></div>
+ <hr />
+
+ <pre>$location.path() = {{$location.path()}}</pre>
+ <pre>$route.current.templateUrl = {{$route.current.templateUrl}}</pre>
+ <pre>$route.current.params = {{$route.current.params}}</pre>
+ <pre>$route.current.scope.name = {{$route.current.scope.name}}</pre>
+ <pre>$routeParams = {{$routeParams}}</pre>
+ </div>
+ </file>
+
+ <file name="book.html">
+ controller: {{name}}<br />
+ Book Id: {{params.bookId}}<br />
+ </file>
+
+ <file name="chapter.html">
+ controller: {{name}}<br />
+ Book Id: {{params.bookId}}<br />
+ Chapter Id: {{params.chapterId}}
+ </file>
+
+ <file name="script.js">
+ angular.module('ngView', [], function($routeProvider, $locationProvider) {
+ $routeProvider.when('/Book/:bookId', {
+ templateUrl: 'book.html',
+ controller: BookCntl
+ });
+ $routeProvider.when('/Book/:bookId/ch/:chapterId', {
+ templateUrl: 'chapter.html',
+ controller: ChapterCntl
+ });
+
+ // configure html5 to get links working on jsfiddle
+ $locationProvider.html5Mode(true);
+ });
+
+ function MainCntl($scope, $route, $routeParams, $location) {
+ $scope.$route = $route;
+ $scope.$location = $location;
+ $scope.$routeParams = $routeParams;
+ }
+
+ function BookCntl($scope, $routeParams) {
+ $scope.name = "BookCntl";
+ $scope.params = $routeParams;
+ }
+
+ function ChapterCntl($scope, $routeParams) {
+ $scope.name = "ChapterCntl";
+ $scope.params = $routeParams;
+ }
+ </file>
+
+ <file name="scenario.js">
+ it('should load and compile correct template', function() {
+ element('a:contains("Moby: Ch1")').click();
+ var content = element('.doc-example-live [ng-view]').text();
+ expect(content).toMatch(/controller\: ChapterCntl/);
+ expect(content).toMatch(/Book Id\: Moby/);
+ expect(content).toMatch(/Chapter Id\: 1/);
+
+ element('a:contains("Scarlet")').click();
+ content = element('.doc-example-live [ng-view]').text();
+ expect(content).toMatch(/controller\: BookCntl/);
+ expect(content).toMatch(/Book Id\: Scarlet/);
+ });
+ </file>
+ </example>
+ */
+
+
+/**
+ * @ngdoc event
+ * @name ng.directive:ngView#$viewContentLoaded
+ * @eventOf ng.directive:ngView
+ * @eventType emit on the current ngView scope
+ * @description
+ * Emitted every time the ngView content is reloaded.
+ */
+var ngViewDirective = ['$http', '$templateCache', '$route', '$anchorScroll', '$compile',
+ '$controller',
+ function($http, $templateCache, $route, $anchorScroll, $compile,
+ $controller) {
+ return {
+ restrict: 'ECA',
+ terminal: true,
+ link: function(scope, element, attr) {
+ var lastScope,
+ onloadExp = attr.onload || '';
+
+ scope.$on('$routeChangeSuccess', update);
+ update();
+
+
+ function destroyLastScope() {
+ if (lastScope) {
+ lastScope.$destroy();
+ lastScope = null;
+ }
+ }
+
+ function clearContent() {
+ element.html('');
+ destroyLastScope();
+ }
+
+ function update() {
+ var locals = $route.current && $route.current.locals,
+ template = locals && locals.$template;
+
+ if (template) {
+ element.html(template);
+ destroyLastScope();
+
+ var link = $compile(element.contents()),
+ current = $route.current,
+ controller;
+
+ lastScope = current.scope = scope.$new();
+ if (current.controller) {
+ locals.$scope = lastScope;
+ controller = $controller(current.controller, locals);
+ element.children().data('$ngControllerController', controller);
+ }
+
+ link(lastScope);
+ lastScope.$emit('$viewContentLoaded');
+ lastScope.$eval(onloadExp);
+
+ // $anchorScroll might listen on event...
+ $anchorScroll();
+ } else {
+ clearContent();
+ }
+ }
+ }
+ };
+}];
+
+/**
+ * @ngdoc directive
+ * @name ng.directive:script
+ *
+ * @description
+ * Load content of a script tag, with type `text/ng-template`, into `$templateCache`, so that the
+ * template can be used by `ngInclude`, `ngView` or directive templates.
+ *
+ * @restrict E
+ * @param {'text/ng-template'} type must be set to `'text/ng-template'`
+ *
+ * @example
+ <doc:example>
+ <doc:source>
+ <script type="text/ng-template" id="/tpl.html">
+ Content of the template.
+ </script>
+
+ <a ng-click="currentTpl='/tpl.html'" id="tpl-link">Load inlined template</a>
+ <div id="tpl-content" ng-include src="currentTpl"></div>
+ </doc:source>
+ <doc:scenario>
+ it('should load template defined inside script tag', function() {
+ element('#tpl-link').click();
+ expect(element('#tpl-content').text()).toMatch(/Content of the template/);
+ });
+ </doc:scenario>
+ </doc:example>
+ */
+var scriptDirective = ['$templateCache', function($templateCache) {
+ return {
+ restrict: 'E',
+ terminal: true,
+ compile: function(element, attr) {
+ if (attr.type == 'text/ng-template') {
+ var templateUrl = attr.id,
+ // IE is not consistent, in scripts we have to read .text but in other nodes we have to read .textContent
+ text = element[0].text;
+
+ $templateCache.put(templateUrl, text);
+ }
+ }
+ };
+}];
+
+/**
+ * @ngdoc directive
+ * @name ng.directive:select
+ * @restrict E
+ *
+ * @description
+ * HTML `SELECT` element with angular data-binding.
+ *
+ * # `ngOptions`
+ *
+ * Optionally `ngOptions` attribute can be used to dynamically generate a list of `<option>`
+ * elements for a `<select>` element using an array or an object obtained by evaluating the
+ * `ngOptions` expression.
+ *
+ * When an item in the `<select>` menu is selected, the value of array element or object property
+ * represented by the selected option will be bound to the model identified by the `ngModel`
+ * directive of the parent select element.
+ *
+ * Optionally, a single hard-coded `<option>` element, with the value set to an empty string, can
+ * be nested into the `<select>` element. This element will then represent `null` or "not selected"
+ * option. See example below for demonstration.
+ *
+ * Note: `ngOptions` provides iterator facility for `<option>` element which should be used instead
+ * of {@link ng.directive:ngRepeat ngRepeat} when you want the
+ * `select` model to be bound to a non-string value. This is because an option element can currently
+ * be bound to string values only.
+ *
+ * @param {string} ngModel Assignable angular expression to data-bind to.
+ * @param {string=} name Property name of the form under which the control is published.
+ * @param {string=} required The control is considered valid only if value is entered.
+ * @param {string=} ngRequired Adds `required` attribute and `required` validation constraint to
+ * the element when the ngRequired expression evaluates to true. Use `ngRequired` instead of
+ * `required` when you want to data-bind to the `required` attribute.
+ * @param {comprehension_expression=} ngOptions in one of the following forms:
+ *
+ * * for array data sources:
+ * * `label` **`for`** `value` **`in`** `array`
+ * * `select` **`as`** `label` **`for`** `value` **`in`** `array`
+ * * `label` **`group by`** `group` **`for`** `value` **`in`** `array`
+ * * `select` **`as`** `label` **`group by`** `group` **`for`** `value` **`in`** `array`
+ * * for object data sources:
+ * * `label` **`for (`**`key` **`,`** `value`**`) in`** `object`
+ * * `select` **`as`** `label` **`for (`**`key` **`,`** `value`**`) in`** `object`
+ * * `label` **`group by`** `group` **`for (`**`key`**`,`** `value`**`) in`** `object`
+ * * `select` **`as`** `label` **`group by`** `group`
+ * **`for` `(`**`key`**`,`** `value`**`) in`** `object`
+ *
+ * Where:
+ *
+ * * `array` / `object`: an expression which evaluates to an array / object to iterate over.
+ * * `value`: local variable which will refer to each item in the `array` or each property value
+ * of `object` during iteration.
+ * * `key`: local variable which will refer to a property name in `object` during iteration.
+ * * `label`: The result of this expression will be the label for `<option>` element. The
+ * `expression` will most likely refer to the `value` variable (e.g. `value.propertyName`).
+ * * `select`: The result of this expression will be bound to the model of the parent `<select>`
+ * element. If not specified, `select` expression will default to `value`.
+ * * `group`: The result of this expression will be used to group options using the `<optgroup>`
+ * DOM element.
+ *
+ * @example
+ <doc:example>
+ <doc:source>
+ <script>
+ function MyCntrl($scope) {
+ $scope.colors = [
+ {name:'black', shade:'dark'},
+ {name:'white', shade:'light'},
+ {name:'red', shade:'dark'},
+ {name:'blue', shade:'dark'},
+ {name:'yellow', shade:'light'}
+ ];
+ $scope.color = $scope.colors[2]; // red
+ }
+ </script>
+ <div ng-controller="MyCntrl">
+ <ul>
+ <li ng-repeat="color in colors">
+ Name: <input ng-model="color.name">
+ [<a href ng-click="colors.splice($index, 1)">X</a>]
+ </li>
+ <li>
+ [<a href ng-click="colors.push({})">add</a>]
+ </li>
+ </ul>
+ <hr/>
+ Color (null not allowed):
+ <select ng-model="color" ng-options="c.name for c in colors"></select><br>
+
+ Color (null allowed):
+ <span class="nullable">
+ <select ng-model="color" ng-options="c.name for c in colors">
+ <option value="">-- chose color --</option>
+ </select>
+ </span><br/>
+
+ Color grouped by shade:
+ <select ng-model="color" ng-options="c.name group by c.shade for c in colors">
+ </select><br/>
+
+
+ Select <a href ng-click="color={name:'not in list'}">bogus</a>.<br>
+ <hr/>
+ Currently selected: {{ {selected_color:color} }}
+ <div style="border:solid 1px black; height:20px"
+ ng-style="{'background-color':color.name}">
+ </div>
+ </div>
+ </doc:source>
+ <doc:scenario>
+ it('should check ng-options', function() {
+ expect(binding('{selected_color:color}')).toMatch('red');
+ select('color').option('0');
+ expect(binding('{selected_color:color}')).toMatch('black');
+ using('.nullable').select('color').option('');
+ expect(binding('{selected_color:color}')).toMatch('null');
+ });
+ </doc:scenario>
+ </doc:example>
+ */
+
+var ngOptionsDirective = valueFn({ terminal: true });
+var selectDirective = ['$compile', '$parse', function($compile, $parse) {
+ //0000111110000000000022220000000000000000000000333300000000000000444444444444444440000000005555555555555555500000006666666666666666600000000000000077770
+ var NG_OPTIONS_REGEXP = /^\s*(.*?)(?:\s+as\s+(.*?))?(?:\s+group\s+by\s+(.*))?\s+for\s+(?:([\$\w][\$\w\d]*)|(?:\(\s*([\$\w][\$\w\d]*)\s*,\s*([\$\w][\$\w\d]*)\s*\)))\s+in\s+(.*)$/,
+ nullModelCtrl = {$setViewValue: noop};
+
+ return {
+ restrict: 'E',
+ require: ['select', '?ngModel'],
+ controller: ['$element', '$scope', '$attrs', function($element, $scope, $attrs) {
+ var self = this,
+ optionsMap = {},
+ ngModelCtrl = nullModelCtrl,
+ nullOption,
+ unknownOption;
+
+
+ self.databound = $attrs.ngModel;
+
+
+ self.init = function(ngModelCtrl_, nullOption_, unknownOption_) {
+ ngModelCtrl = ngModelCtrl_;
+ nullOption = nullOption_;
+ unknownOption = unknownOption_;
+ }
+
+
+ self.addOption = function(value) {
+ optionsMap[value] = true;
+
+ if (ngModelCtrl.$viewValue == value) {
+ $element.val(value);
+ if (unknownOption.parent()) unknownOption.remove();
+ }
+ };
+
+
+ self.removeOption = function(value) {
+ if (this.hasOption(value)) {
+ delete optionsMap[value];
+ if (ngModelCtrl.$viewValue == value) {
+ this.renderUnknownOption(value);
+ }
+ }
+ };
+
+
+ self.renderUnknownOption = function(val) {
+ var unknownVal = '? ' + hashKey(val) + ' ?';
+ unknownOption.val(unknownVal);
+ $element.prepend(unknownOption);
+ $element.val(unknownVal);
+ unknownOption.prop('selected', true); // needed for IE
+ }
+
+
+ self.hasOption = function(value) {
+ return optionsMap.hasOwnProperty(value);
+ }
+
+ $scope.$on('$destroy', function() {
+ // disable unknown option so that we don't do work when the whole select is being destroyed
+ self.renderUnknownOption = noop;
+ });
+ }],
+
+ link: function(scope, element, attr, ctrls) {
+ // if ngModel is not defined, we don't need to do anything
+ if (!ctrls[1]) return;
+
+ var selectCtrl = ctrls[0],
+ ngModelCtrl = ctrls[1],
+ multiple = attr.multiple,
+ optionsExp = attr.ngOptions,
+ nullOption = false, // if false, user will not be able to select it (used by ngOptions)
+ emptyOption,
+ // we can't just jqLite('<option>') since jqLite is not smart enough
+ // to create it in <select> and IE barfs otherwise.
+ optionTemplate = jqLite(document.createElement('option')),
+ optGroupTemplate =jqLite(document.createElement('optgroup')),
+ unknownOption = optionTemplate.clone();
+
+ // find "null" option
+ for(var i = 0, children = element.children(), ii = children.length; i < ii; i++) {
+ if (children[i].value == '') {
+ emptyOption = nullOption = children.eq(i);
+ break;
+ }
+ }
+
+ selectCtrl.init(ngModelCtrl, nullOption, unknownOption);
+
+ // required validator
+ if (multiple && (attr.required || attr.ngRequired)) {
+ var requiredValidator = function(value) {
+ ngModelCtrl.$setValidity('required', !attr.required || (value && value.length));
+ return value;
+ };
+
+ ngModelCtrl.$parsers.push(requiredValidator);
+ ngModelCtrl.$formatters.unshift(requiredValidator);
+
+ attr.$observe('required', function() {
+ requiredValidator(ngModelCtrl.$viewValue);
+ });
+ }
+
+ if (optionsExp) Options(scope, element, ngModelCtrl);
+ else if (multiple) Multiple(scope, element, ngModelCtrl);
+ else Single(scope, element, ngModelCtrl, selectCtrl);
+
+
+ ////////////////////////////
+
+
+
+ function Single(scope, selectElement, ngModelCtrl, selectCtrl) {
+ ngModelCtrl.$render = function() {
+ var viewValue = ngModelCtrl.$viewValue;
+
+ if (selectCtrl.hasOption(viewValue)) {
+ if (unknownOption.parent()) unknownOption.remove();
+ selectElement.val(viewValue);
+ if (viewValue === '') emptyOption.prop('selected', true); // to make IE9 happy
+ } else {
+ if (isUndefined(viewValue) && emptyOption) {
+ selectElement.val('');
+ } else {
+ selectCtrl.renderUnknownOption(viewValue);
+ }
+ }
+ };
+
+ selectElement.bind('change', function() {
+ scope.$apply(function() {
+ if (unknownOption.parent()) unknownOption.remove();
+ ngModelCtrl.$setViewValue(selectElement.val());
+ });
+ });
+ }
+
+ function Multiple(scope, selectElement, ctrl) {
+ var lastView;
+ ctrl.$render = function() {
+ var items = new HashMap(ctrl.$viewValue);
+ forEach(selectElement.find('option'), function(option) {
+ option.selected = isDefined(items.get(option.value));
+ });
+ };
+
+ // we have to do it on each watch since ngModel watches reference, but
+ // we need to work of an array, so we need to see if anything was inserted/removed
+ scope.$watch(function selectMultipleWatch() {
+ if (!equals(lastView, ctrl.$viewValue)) {
+ lastView = copy(ctrl.$viewValue);
+ ctrl.$render();
+ }
+ });
+
+ selectElement.bind('change', function() {
+ scope.$apply(function() {
+ var array = [];
+ forEach(selectElement.find('option'), function(option) {
+ if (option.selected) {
+ array.push(option.value);
+ }
+ });
+ ctrl.$setViewValue(array);
+ });
+ });
+ }
+
+ function Options(scope, selectElement, ctrl) {
+ var match;
+
+ if (! (match = optionsExp.match(NG_OPTIONS_REGEXP))) {
+ throw Error(
+ "Expected ngOptions in form of '_select_ (as _label_)? for (_key_,)?_value_ in _collection_'" +
+ " but got '" + optionsExp + "'.");
+ }
+
+ var displayFn = $parse(match[2] || match[1]),
+ valueName = match[4] || match[6],
+ keyName = match[5],
+ groupByFn = $parse(match[3] || ''),
+ valueFn = $parse(match[2] ? match[1] : valueName),
+ valuesFn = $parse(match[7]),
+ // This is an array of array of existing option groups in DOM. We try to reuse these if possible
+ // optionGroupsCache[0] is the options with no option group
+ // optionGroupsCache[?][0] is the parent: either the SELECT or OPTGROUP element
+ optionGroupsCache = [[{element: selectElement, label:''}]];
+
+ if (nullOption) {
+ // compile the element since there might be bindings in it
+ $compile(nullOption)(scope);
+
+ // remove the class, which is added automatically because we recompile the element and it
+ // becomes the compilation root
+ nullOption.removeClass('ng-scope');
+
+ // we need to remove it before calling selectElement.html('') because otherwise IE will
+ // remove the label from the element. wtf?
+ nullOption.remove();
+ }
+
+ // clear contents, we'll add what's needed based on the model
+ selectElement.html('');
+
+ selectElement.bind('change', function() {
+ scope.$apply(function() {
+ var optionGroup,
+ collection = valuesFn(scope) || [],
+ locals = {},
+ key, value, optionElement, index, groupIndex, length, groupLength;
+
+ if (multiple) {
+ value = [];
+ for (groupIndex = 0, groupLength = optionGroupsCache.length;
+ groupIndex < groupLength;
+ groupIndex++) {
+ // list of options for that group. (first item has the parent)
+ optionGroup = optionGroupsCache[groupIndex];
+
+ for(index = 1, length = optionGroup.length; index < length; index++) {
+ if ((optionElement = optionGroup[index].element)[0].selected) {
+ key = optionElement.val();
+ if (keyName) locals[keyName] = key;
+ locals[valueName] = collection[key];
+ value.push(valueFn(scope, locals));
+ }
+ }
+ }
+ } else {
+ key = selectElement.val();
+ if (key == '?') {
+ value = undefined;
+ } else if (key == ''){
+ value = null;
+ } else {
+ locals[valueName] = collection[key];
+ if (keyName) locals[keyName] = key;
+ value = valueFn(scope, locals);
+ }
+ }
+ ctrl.$setViewValue(value);
+ });
+ });
+
+ ctrl.$render = render;
+
+ // TODO(vojta): can't we optimize this ?
+ scope.$watch(render);
+
+ function render() {
+ var optionGroups = {'':[]}, // Temporary location for the option groups before we render them
+ optionGroupNames = [''],
+ optionGroupName,
+ optionGroup,
+ option,
+ existingParent, existingOptions, existingOption,
+ modelValue = ctrl.$modelValue,
+ values = valuesFn(scope) || [],
+ keys = keyName ? sortedKeys(values) : values,
+ groupLength, length,
+ groupIndex, index,
+ locals = {},
+ selected,
+ selectedSet = false, // nothing is selected yet
+ lastElement,
+ element,
+ label;
+
+ if (multiple) {
+ selectedSet = new HashMap(modelValue);
+ }
+
+ // We now build up the list of options we need (we merge later)
+ for (index = 0; length = keys.length, index < length; index++) {
+ locals[valueName] = values[keyName ? locals[keyName]=keys[index]:index];
+ optionGroupName = groupByFn(scope, locals) || '';
+ if (!(optionGroup = optionGroups[optionGroupName])) {
+ optionGroup = optionGroups[optionGroupName] = [];
+ optionGroupNames.push(optionGroupName);
+ }
+ if (multiple) {
+ selected = selectedSet.remove(valueFn(scope, locals)) != undefined;
+ } else {
+ selected = modelValue === valueFn(scope, locals);
+ selectedSet = selectedSet || selected; // see if at least one item is selected
+ }
+ label = displayFn(scope, locals); // what will be seen by the user
+ label = label === undefined ? '' : label; // doing displayFn(scope, locals) || '' overwrites zero values
+ optionGroup.push({
+ id: keyName ? keys[index] : index, // either the index into array or key from object
+ label: label,
+ selected: selected // determine if we should be selected
+ });
+ }
+ if (!multiple) {
+ if (nullOption || modelValue === null) {
+ // insert null option if we have a placeholder, or the model is null
+ optionGroups[''].unshift({id:'', label:'', selected:!selectedSet});
+ } else if (!selectedSet) {
+ // option could not be found, we have to insert the undefined item
+ optionGroups[''].unshift({id:'?', label:'', selected:true});
+ }
+ }
+
+ // Now we need to update the list of DOM nodes to match the optionGroups we computed above
+ for (groupIndex = 0, groupLength = optionGroupNames.length;
+ groupIndex < groupLength;
+ groupIndex++) {
+ // current option group name or '' if no group
+ optionGroupName = optionGroupNames[groupIndex];
+
+ // list of options for that group. (first item has the parent)
+ optionGroup = optionGroups[optionGroupName];
+
+ if (optionGroupsCache.length <= groupIndex) {
+ // we need to grow the optionGroups
+ existingParent = {
+ element: optGroupTemplate.clone().attr('label', optionGroupName),
+ label: optionGroup.label
+ };
+ existingOptions = [existingParent];
+ optionGroupsCache.push(existingOptions);
+ selectElement.append(existingParent.element);
+ } else {
+ existingOptions = optionGroupsCache[groupIndex];
+ existingParent = existingOptions[0]; // either SELECT (no group) or OPTGROUP element
+
+ // update the OPTGROUP label if not the same.
+ if (existingParent.label != optionGroupName) {
+ existingParent.element.attr('label', existingParent.label = optionGroupName);
+ }
+ }
+
+ lastElement = null; // start at the beginning
+ for(index = 0, length = optionGroup.length; index < length; index++) {
+ option = optionGroup[index];
+ if ((existingOption = existingOptions[index+1])) {
+ // reuse elements
+ lastElement = existingOption.element;
+ if (existingOption.label !== option.label) {
+ lastElement.text(existingOption.label = option.label);
+ }
+ if (existingOption.id !== option.id) {
+ lastElement.val(existingOption.id = option.id);
+ }
+ // lastElement.prop('selected') provided by jQuery has side-effects
+ if (lastElement[0].selected !== option.selected) {
+ lastElement.prop('selected', (existingOption.selected = option.selected));
+ }
+ } else {
+ // grow elements
+
+ // if it's a null option
+ if (option.id === '' && nullOption) {
+ // put back the pre-compiled element
+ element = nullOption;
+ } else {
+ // jQuery(v1.4.2) Bug: We should be able to chain the method calls, but
+ // in this version of jQuery on some browser the .text() returns a string
+ // rather then the element.
+ (element = optionTemplate.clone())
+ .val(option.id)
+ .attr('selected', option.selected)
+ .text(option.label);
+ }
+
+ existingOptions.push(existingOption = {
+ element: element,
+ label: option.label,
+ id: option.id,
+ selected: option.selected
+ });
+ if (lastElement) {
+ lastElement.after(element);
+ } else {
+ existingParent.element.append(element);
+ }
+ lastElement = element;
+ }
+ }
+ // remove any excessive OPTIONs in a group
+ index++; // increment since the existingOptions[0] is parent element not OPTION
+ while(existingOptions.length > index) {
+ existingOptions.pop().element.remove();
+ }
+ }
+ // remove any excessive OPTGROUPs from select
+ while(optionGroupsCache.length > groupIndex) {
+ optionGroupsCache.pop()[0].element.remove();
+ }
+ }
+ }
+ }
+ }
+}];
+
+var optionDirective = ['$interpolate', function($interpolate) {
+ var nullSelectCtrl = {
+ addOption: noop,
+ removeOption: noop
+ };
+
+ return {
+ restrict: 'E',
+ priority: 100,
+ compile: function(element, attr) {
+ if (isUndefined(attr.value)) {
+ var interpolateFn = $interpolate(element.text(), true);
+ if (!interpolateFn) {
+ attr.$set('value', element.text());
+ }
+ }
+
+ return function (scope, element, attr) {
+ var selectCtrlName = '$selectController',
+ parent = element.parent(),
+ selectCtrl = parent.data(selectCtrlName) ||
+ parent.parent().data(selectCtrlName); // in case we are in optgroup
+
+ if (selectCtrl && selectCtrl.databound) {
+ // For some reason Opera defaults to true and if not overridden this messes up the repeater.
+ // We don't want the view to drive the initialization of the model anyway.
+ element.prop('selected', false);
+ } else {
+ selectCtrl = nullSelectCtrl;
+ }
+
+ if (interpolateFn) {
+ scope.$watch(interpolateFn, function interpolateWatchAction(newVal, oldVal) {
+ attr.$set('value', newVal);
+ if (newVal !== oldVal) selectCtrl.removeOption(oldVal);
+ selectCtrl.addOption(newVal);
+ });
+ } else {
+ selectCtrl.addOption(attr.value);
+ }
+
+ element.bind('$destroy', function() {
+ selectCtrl.removeOption(attr.value);
+ });
+ };
+ }
+ }
+}];
+
+var styleDirective = valueFn({
+ restrict: 'E',
+ terminal: true
+});
+
+ //try to bind to jquery now so that one can write angular.element().read()
+ //but we will rebind on bootstrap again.
+ bindJQuery();
+
+ publishExternalAPI(angular);
+
+ jqLite(document).ready(function() {
+ angularInit(document, bootstrap);
+ });
+
+})(window, document);
+angular.element(document).find('head').append('<style type="text/css">@charset "UTF-8";[ng\\:cloak],[ng-cloak],[data-ng-cloak],[x-ng-cloak],.ng-cloak,.x-ng-cloak{display:none !important;}ng\\:form{display:block;}</style>');
\ No newline at end of file
Added: test-js-frameworks/angularjs/js/app.js
===================================================================
--- test-js-frameworks/angularjs/js/app.js (rev 0)
+++ test-js-frameworks/angularjs/js/app.js 2014-04-17 08:34:32 UTC (rev 700)
@@ -0,0 +1,25 @@
+angular.module('poll', ['pollControllers'])
+.config(['$routeProvider', function ($routeProvider) {
+ $routeProvider.when('/', {templateUrl:'partials/home.html', controller:'HomeCtl'})
+ .when('/create', {templateUrl:'partials/create.html', controller:'pollCreateCtl'})
+ .when('/view', {templateUrl:'partials/view.html', controller:'pollViewCtl'})
+ .otherwise({redirectTo:'/'})
+}])
+.factory('pollStorage', function () {
+ var storageId='poll-storage';
+ return {
+ get: function () {
+ return JSON.parse(localStorage.getItem(storageId) || '{}');
+ },
+ put: function (data) {
+ localStorage.setItem(storageId, JSON.stringify(data));
+ },
+ remove: function () {
+ localStorage.setItem(storageId, '{}');
+ }
+
+ }
+
+})
+;
+
Added: test-js-frameworks/angularjs/js/controllers.js
===================================================================
--- test-js-frameworks/angularjs/js/controllers.js (rev 0)
+++ test-js-frameworks/angularjs/js/controllers.js 2014-04-17 08:34:32 UTC (rev 700)
@@ -0,0 +1,31 @@
+angular.module('pollControllers', []).controller('HomeCtl', ['$scope', function ($scope) {
+}])
+.controller('pollCreateCtl', function ($scope, $location, pollStorage) {
+ function init() {
+ $scope.step = 1;
+ $scope.poll = pollStorage.get();
+ if (JSON.stringify($scope.poll) == '{}') {
+ $scope.poll.choices = [{},{}];
+ }
+ }
+ init();
+
+ $scope.next = function () { $scope.step++; };
+ $scope.prev = function () { $scope.step--; };
+ $scope.addChoice = function () { $scope.poll.choices.push({}) };
+ $scope.submit = function () {
+ pollStorage.put($scope.poll);
+ $location.url('/view');
+ };
+ $scope.cleanPoll = function() {
+ pollStorage.remove();
+ init();
+ }
+})
+.controller('pollViewCtl', function ($scope, pollStorage, $location) {
+ $scope.poll = pollStorage.get();
+ $scope.goTo = function(adr) {
+ $location.url(adr);
+ }
+});
+
Added: test-js-frameworks/angularjs/partials/create.html
===================================================================
--- test-js-frameworks/angularjs/partials/create.html (rev 0)
+++ test-js-frameworks/angularjs/partials/create.html 2014-04-17 08:34:32 UTC (rev 700)
@@ -0,0 +1,31 @@
+<form ng-submit="submit()">
+ <div ng-show="step == 1">
+ <h3>Informations générales</h3>
+ <div>
+ <label for="pollCreationFormTitle">Titre</label>
+ <input name="title" id="pollCreationFormTitle" placeholder="Titre" value="" type="text" ng-model="poll.title" />
+ </div>
+ <div>
+ <label for="pollCreationFormDescription">Description</label><br/>
+ <textarea id="pollCreationFormDescription" name="description" placeholder="Description" ng-model="poll.description"></textarea>
+ </div>
+ <div>
+ <label for="pollCreationFormCreatorName">Votre nom</label>
+ <input name="creator.name" id="pollCreationFormCreatorName" placeholder="Votre nom" value="" type="text" ng-model="poll.name"/>
+ </div>
+ <div>
+ <input type="button" ng-click="next()" value="Suivant" />
+ </div>
+ </div>
+ <div ng-show="step == 2">
+ <h3>Choix</h3>
+ <div id="choices">
+ <div ng-repeat="choice in poll.choices"><label for="pollChoice{{choice.num}}">Choix n°{{$index+1}}</label><input type="text" value="{{$index+1}}" id="pollChoice{{$index+1}}" ng-model="choice.value"/></div>
+ </div>
+ <input type="button" value="prev" ng-click="prev()" />
+ <input type="button" ng-click="addChoice()" value="Ajouter un choix" />
+ <input type="submit" value="save"/>
+ <input type="button" value="reset" ng-click="cleanPoll()" />
+ </div>
+</form>
+
Added: test-js-frameworks/angularjs/partials/home.html
===================================================================
--- test-js-frameworks/angularjs/partials/home.html (rev 0)
+++ test-js-frameworks/angularjs/partials/home.html 2014-04-17 08:34:32 UTC (rev 700)
@@ -0,0 +1,4 @@
+<p>
+ <a href="#/create">create poll</a>
+</p>
+
Added: test-js-frameworks/angularjs/partials/view.html
===================================================================
--- test-js-frameworks/angularjs/partials/view.html (rev 0)
+++ test-js-frameworks/angularjs/partials/view.html 2014-04-17 08:34:32 UTC (rev 700)
@@ -0,0 +1,15 @@
+<h3>{{poll.title}}</h3>
+<p>
+ {{poll.description}}
+ <br/><br/>
+ by {{poll.name}}
+</p>
+
+<form>
+ <div ng-repeat="choice in poll.choices">
+ <input type="radio" value="{{$index+1}}" name="choices" id="choice{{$index+1}}" /><label for="choice{{$index+1}}">{{choice.value}}</label>
+ </div>
+</form>
+
+<input type="button" value="edit" ng-click="goTo('/create')" />
+
1
0
Author: dralagen
Date: 2014-04-17 10:20:18 +0200 (Thu, 17 Apr 2014)
New Revision: 699
Url: http://forge.nuiton.org/projects/sandbox/repository/revisions/699
Log:
Ajout du test de JQuery
Added:
test-js-frameworks/jquery/
test-js-frameworks/jquery/index.html
test-js-frameworks/jquery/js/
test-js-frameworks/jquery/js/app.js
test-js-frameworks/jquery/js/jquery-2.1.0.min.js
Added: test-js-frameworks/jquery/index.html
===================================================================
--- test-js-frameworks/jquery/index.html (rev 0)
+++ test-js-frameworks/jquery/index.html 2014-04-17 08:20:18 UTC (rev 699)
@@ -0,0 +1,27 @@
+<html>
+ <head>
+ <meta charset="utf-8"/>
+ <title> Test JQuery </title>
+ <script src="js/jquery-2.1.0.min.js"></script>
+ </head>
+ <body>
+ <h1> Test JQuery </h1>
+ title : <input type="text" id="title" /> <br/>
+
+ <input type="button" value="addChoice" id="addChoice"/>
+ <form id="editChoice">
+ </form>
+ <input type="button" value="valid" id="valid" />
+
+ <div id="view">
+ <h2 id="title"></h2>
+ <form>
+ <div id="choice"></div>
+ <input type="submit" value="Vote" />
+ </form>
+ </div>
+
+ <script src="js/app.js"></script>
+ </body>
+</html>
+
Added: test-js-frameworks/jquery/js/app.js
===================================================================
--- test-js-frameworks/jquery/js/app.js (rev 0)
+++ test-js-frameworks/jquery/js/app.js 2014-04-17 08:20:18 UTC (rev 699)
@@ -0,0 +1,15 @@
+$('#title').change(function() {
+ $('#view #title').html($(this).val());
+});
+
+$('#addChoice').click(function() {
+ $('<div><input type="text" /></div>').appendTo('#editChoice');
+});
+
+$("#valid").click(function() {
+ $('#choice').html("");
+ $('#editChoice input').each(function(index) {
+ $('<input type="radio" name="choice" value="'+index+'" /> '+ $(this).val() +' <br/>').appendTo('#choice');
+ });
+});
+
Added: test-js-frameworks/jquery/js/jquery-2.1.0.min.js
===================================================================
--- test-js-frameworks/jquery/js/jquery-2.1.0.min.js (rev 0)
+++ test-js-frameworks/jquery/js/jquery-2.1.0.min.js 2014-04-17 08:20:18 UTC (rev 699)
@@ -0,0 +1,4 @@
+/*! jQuery v2.1.0 | (c) 2005, 2014 jQuery Foundation, Inc. | jquery.org/license */
+!function(a,b){"object"==typeof module&&"object"==typeof module.exports?module.exports=a.document?b(a,!0):function(a){if(!a.document)throw new Error("jQuery requires a window with a document");return b(a)}:b(a)}("undefined"!=typeof window?window:this,function(a,b){var c=[],d=c.slice,e=c.concat,f=c.push,g=c.indexOf,h={},i=h.toString,j=h.hasOwnProperty,k="".trim,l={},m=a.document,n="2.1.0",o=function(a,b){return new o.fn.init(a,b)},p=/^-ms-/,q=/-([\da-z])/gi,r=function(a,b){return b.toUpperCase()};o.fn=o.prototype={jquery:n,constructor:o,selector:"",length:0,toArray:function(){return d.call(this)},get:function(a){return null!=a?0>a?this[a+this.length]:this[a]:d.call(this)},pushStack:function(a){var b=o.merge(this.constructor(),a);return b.prevObject=this,b.context=this.context,b},each:function(a,b){return o.each(this,a,b)},map:function(a){return this.pushStack(o.map(this,function(b,c){return a.call(b,c,b)}))},slice:function(){return this.pushStack(d.apply(this,arguments))},first:function(){return this.eq(0)},last:function(){return this.eq(-1)},eq:function(a){var b=this.length,c=+a+(0>a?b:0);return this.pushStack(c>=0&&b>c?[this[c]]:[])},end:function(){return this.prevObject||this.constructor(null)},push:f,sort:c.sort,splice:c.splice},o.extend=o.fn.extend=function(){var a,b,c,d,e,f,g=arguments[0]||{},h=1,i=arguments.length,j=!1;for("boolean"==typeof g&&(j=g,g=arguments[h]||{},h++),"object"==typeof g||o.isFunction(g)||(g={}),h===i&&(g=this,h--);i>h;h++)if(null!=(a=arguments[h]))for(b in a)c=g[b],d=a[b],g!==d&&(j&&d&&(o.isPlainObject(d)||(e=o.isArray(d)))?(e?(e=!1,f=c&&o.isArray(c)?c:[]):f=c&&o.isPlainObject(c)?c:{},g[b]=o.extend(j,f,d)):void 0!==d&&(g[b]=d));return g},o.extend({expando:"jQuery"+(n+Math.random()).replace(/\D/g,""),isReady:!0,error:function(a){throw new Error(a)},noop:function(){},isFunction:function(a){return"function"===o.type(a)},isArray:Array.isArray,isWindow:function(a){return null!=a&&a===a.window},isNumeric:function(a){return a-parseFloat(a)>=0},isPlainObject:function(a){if("object"!==o.type(a)||a.nodeType||o.isWindow(a))return!1;try{if(a.constructor&&!j.call(a.constructor.prototype,"isPrototypeOf"))return!1}catch(b){return!1}return!0},isEmptyObject:function(a){var b;for(b in a)return!1;return!0},type:function(a){return null==a?a+"":"object"==typeof a||"function"==typeof a?h[i.call(a)]||"object":typeof a},globalEval:function(a){var b,c=eval;a=o.trim(a),a&&(1===a.indexOf("use strict")?(b=m.createElement("script"),b.text=a,m.head.appendChild(b).parentNode.removeChild(b)):c(a))},camelCase:function(a){return a.replace(p,"ms-").replace(q,r)},nodeName:function(a,b){return a.nodeName&&a.nodeName.toLowerCase()===b.toLowerCase()},each:function(a,b,c){var d,e=0,f=a.length,g=s(a);if(c){if(g){for(;f>e;e++)if(d=b.apply(a[e],c),d===!1)break}else for(e in a)if(d=b.apply(a[e],c),d===!1)break}else if(g){for(;f>e;e++)if(d=b.call(a[e],e,a[e]),d===!1)break}else for(e in a)if(d=b.call(a[e],e,a[e]),d===!1)break;return a},trim:function(a){return null==a?"":k.call(a)},makeArray:function(a,b){var c=b||[];return null!=a&&(s(Object(a))?o.merge(c,"string"==typeof a?[a]:a):f.call(c,a)),c},inArray:function(a,b,c){return null==b?-1:g.call(b,a,c)},merge:function(a,b){for(var c=+b.length,d=0,e=a.length;c>d;d++)a[e++]=b[d];return a.length=e,a},grep:function(a,b,c){for(var d,e=[],f=0,g=a.length,h=!c;g>f;f++)d=!b(a[f],f),d!==h&&e.push(a[f]);return e},map:function(a,b,c){var d,f=0,g=a.length,h=s(a),i=[];if(h)for(;g>f;f++)d=b(a[f],f,c),null!=d&&i.push(d);else for(f in a)d=b(a[f],f,c),null!=d&&i.push(d);return e.apply([],i)},guid:1,proxy:function(a,b){var c,e,f;return"string"==typeof b&&(c=a[b],b=a,a=c),o.isFunction(a)?(e=d.call(arguments,2),f=function(){return a.apply(b||this,e.concat(d.call(arguments)))},f.guid=a.guid=a.guid||o.guid++,f):void 0},now:Date.now,support:l}),o.each("Boolean Number String Function Array Date RegExp Object Error".split(" "),function(a,b){h["[object "+b+"]"]=b.toLowerCase()});function s(a){var b=a.length,c=o.type(a);return"function"===c||o.isWindow(a)?!1:1===a.nodeType&&b?!0:"array"===c||0===b||"number"==typeof b&&b>0&&b-1 in a}var t=function(a){var b,c,d,e,f,g,h,i,j,k,l,m,n,o,p,q,r,s="sizzle"+-new Date,t=a.document,u=0,v=0,w=eb(),x=eb(),y=eb(),z=function(a,b){return a===b&&(j=!0),0},A="undefined",B=1<<31,C={}.hasOwnProperty,D=[],E=D.pop,F=D.push,G=D.push,H=D.slice,I=D.indexOf||function(a){for(var b=0,c=this.length;c>b;b++)if(this[b]===a)return b;return-1},J="checked|selected|async|autofocus|autoplay|controls|defer|disabled|hidden|ismap|loop|multiple|open|readonly|required|scoped",K="[\\x20\\t\\r\\n\\f]",L="(?:\\\\.|[\\w-]|[^\\x00-\\xa0])+",M=L.replace("w","w#"),N="\\["+K+"*("+L+")"+K+"*(?:([*^$|!~]?=)"+K+"*(?:(['\"])((?:\\\\.|[^\\\\])*?)\\3|("+M+")|)|)"+K+"*\\]",O=":("+L+")(?:\\(((['\"])((?:\\\\.|[^\\\\])*?)\\3|((?:\\\\.|[^\\\\()[\\]]|"+N.replace(3,8)+")*)|.*)\\)|)",P=new RegExp("^"+K+"+|((?:^|[^\\\\])(?:\\\\.)*)"+K+"+$","g"),Q=new RegExp("^"+K+"*,"+K+"*"),R=new RegExp("^"+K+"*([>+~]|"+K+")"+K+"*"),S=new RegExp("="+K+"*([^\\]'\"]*?)"+K+"*\\]","g"),T=new RegExp(O),U=new RegExp("^"+M+"$"),V={ID:new RegExp("^#("+L+")"),CLASS:new RegExp("^\\.("+L+")"),TAG:new RegExp("^("+L.replace("w","w*")+")"),ATTR:new RegExp("^"+N),PSEUDO:new RegExp("^"+O),CHILD:new RegExp("^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\("+K+"*(even|odd|(([+-]|)(\\d*)n|)"+K+"*(?:([+-]|)"+K+"*(\\d+)|))"+K+"*\\)|)","i"),bool:new RegExp("^(?:"+J+")$","i"),needsContext:new RegExp("^"+K+"*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\("+K+"*((?:-\\d)?\\d*)"+K+"*\\)|)(?=[^-]|$)","i")},W=/^(?:input|select|textarea|button)$/i,X=/^h\d$/i,Y=/^[^{]+\{\s*\[native \w/,Z=/^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/,$=/[+~]/,_=/'|\\/g,ab=new RegExp("\\\\([\\da-f]{1,6}"+K+"?|("+K+")|.)","ig"),bb=function(a,b,c){var d="0x"+b-65536;return d!==d||c?b:0>d?String.fromCharCode(d+65536):String.fromCharCode(d>>10|55296,1023&d|56320)};try{G.apply(D=H.call(t.childNodes),t.childNodes),D[t.childNodes.length].nodeType}catch(cb){G={apply:D.length?function(a,b){F.apply(a,H.call(b))}:function(a,b){var c=a.length,d=0;while(a[c++]=b[d++]);a.length=c-1}}}function db(a,b,d,e){var f,g,h,i,j,m,p,q,u,v;if((b?b.ownerDocument||b:t)!==l&&k(b),b=b||l,d=d||[],!a||"string"!=typeof a)return d;if(1!==(i=b.nodeType)&&9!==i)return[];if(n&&!e){if(f=Z.exec(a))if(h=f[1]){if(9===i){if(g=b.getElementById(h),!g||!g.parentNode)return d;if(g.id===h)return d.push(g),d}else if(b.ownerDocument&&(g=b.ownerDocument.getElementById(h))&&r(b,g)&&g.id===h)return d.push(g),d}else{if(f[2])return G.apply(d,b.getElementsByTagName(a)),d;if((h=f[3])&&c.getElementsByClassName&&b.getElementsByClassName)return G.apply(d,b.getElementsByClassName(h)),d}if(c.qsa&&(!o||!o.test(a))){if(q=p=s,u=b,v=9===i&&a,1===i&&"object"!==b.nodeName.toLowerCase()){m=ob(a),(p=b.getAttribute("id"))?q=p.replace(_,"\\$&"):b.setAttribute("id",q),q="[id='"+q+"'] ",j=m.length;while(j--)m[j]=q+pb(m[j]);u=$.test(a)&&mb(b.parentNode)||b,v=m.join(",")}if(v)try{return G.apply(d,u.querySelectorAll(v)),d}catch(w){}finally{p||b.removeAttribute("id")}}}return xb(a.replace(P,"$1"),b,d,e)}function eb(){var a=[];function b(c,e){return a.push(c+" ")>d.cacheLength&&delete b[a.shift()],b[c+" "]=e}return b}function fb(a){return a[s]=!0,a}function gb(a){var b=l.createElement("div");try{return!!a(b)}catch(c){return!1}finally{b.parentNode&&b.parentNode.removeChild(b),b=null}}function hb(a,b){var c=a.split("|"),e=a.length;while(e--)d.attrHandle[c[e]]=b}function ib(a,b){var c=b&&a,d=c&&1===a.nodeType&&1===b.nodeType&&(~b.sourceIndex||B)-(~a.sourceIndex||B);if(d)return d;if(c)while(c=c.nextSibling)if(c===b)return-1;return a?1:-1}function jb(a){return function(b){var c=b.nodeName.toLowerCase();return"input"===c&&b.type===a}}function kb(a){return function(b){var c=b.nodeName.toLowerCase();return("input"===c||"button"===c)&&b.type===a}}function lb(a){return fb(function(b){return b=+b,fb(function(c,d){var e,f=a([],c.length,b),g=f.length;while(g--)c[e=f[g]]&&(c[e]=!(d[e]=c[e]))})})}function mb(a){return a&&typeof a.getElementsByTagName!==A&&a}c=db.support={},f=db.isXML=function(a){var b=a&&(a.ownerDocument||a).documentElement;return b?"HTML"!==b.nodeName:!1},k=db.setDocument=function(a){var b,e=a?a.ownerDocument||a:t,g=e.defaultView;return e!==l&&9===e.nodeType&&e.documentElement?(l=e,m=e.documentElement,n=!f(e),g&&g!==g.top&&(g.addEventListener?g.addEventListener("unload",function(){k()},!1):g.attachEvent&&g.attachEvent("onunload",function(){k()})),c.attributes=gb(function(a){return a.className="i",!a.getAttribute("className")}),c.getElementsByTagName=gb(function(a){return a.appendChild(e.createComment("")),!a.getElementsByTagName("*").length}),c.getElementsByClassName=Y.test(e.getElementsByClassName)&&gb(function(a){return a.innerHTML="<div class='a'></div><div class='a i'></div>",a.firstChild.className="i",2===a.getElementsByClassName("i").length}),c.getById=gb(function(a){return m.appendChild(a).id=s,!e.getElementsByName||!e.getElementsByName(s).length}),c.getById?(d.find.ID=function(a,b){if(typeof b.getElementById!==A&&n){var c=b.getElementById(a);return c&&c.parentNode?[c]:[]}},d.filter.ID=function(a){var b=a.replace(ab,bb);return function(a){return a.getAttribute("id")===b}}):(delete d.find.ID,d.filter.ID=function(a){var b=a.replace(ab,bb);return function(a){var c=typeof a.getAttributeNode!==A&&a.getAttributeNode("id");return c&&c.value===b}}),d.find.TAG=c.getElementsByTagName?function(a,b){return typeof b.getElementsByTagName!==A?b.getElementsByTagName(a):void 0}:function(a,b){var c,d=[],e=0,f=b.getElementsByTagName(a);if("*"===a){while(c=f[e++])1===c.nodeType&&d.push(c);return d}return f},d.find.CLASS=c.getElementsByClassName&&function(a,b){return typeof b.getElementsByClassName!==A&&n?b.getElementsByClassName(a):void 0},p=[],o=[],(c.qsa=Y.test(e.querySelectorAll))&&(gb(function(a){a.innerHTML="<select t=''><option selected=''></option></select>",a.querySelectorAll("[t^='']").length&&o.push("[*^$]="+K+"*(?:''|\"\")"),a.querySelectorAll("[selected]").length||o.push("\\["+K+"*(?:value|"+J+")"),a.querySelectorAll(":checked").length||o.push(":checked")}),gb(function(a){var b=e.createElement("input");b.setAttribute("type","hidden"),a.appendChild(b).setAttribute("name","D"),a.querySelectorAll("[name=d]").length&&o.push("name"+K+"*[*^$|!~]?="),a.querySelectorAll(":enabled").length||o.push(":enabled",":disabled"),a.querySelectorAll("*,:x"),o.push(",.*:")})),(c.matchesSelector=Y.test(q=m.webkitMatchesSelector||m.mozMatchesSelector||m.oMatchesSelector||m.msMatchesSelector))&&gb(function(a){c.disconnectedMatch=q.call(a,"div"),q.call(a,"[s!='']:x"),p.push("!=",O)}),o=o.length&&new RegExp(o.join("|")),p=p.length&&new RegExp(p.join("|")),b=Y.test(m.compareDocumentPosition),r=b||Y.test(m.contains)?function(a,b){var c=9===a.nodeType?a.documentElement:a,d=b&&b.parentNode;return a===d||!(!d||1!==d.nodeType||!(c.contains?c.contains(d):a.compareDocumentPosition&&16&a.compareDocumentPosition(d)))}:function(a,b){if(b)while(b=b.parentNode)if(b===a)return!0;return!1},z=b?function(a,b){if(a===b)return j=!0,0;var d=!a.compareDocumentPosition-!b.compareDocumentPosition;return d?d:(d=(a.ownerDocument||a)===(b.ownerDocument||b)?a.compareDocumentPosition(b):1,1&d||!c.sortDetached&&b.compareDocumentPosition(a)===d?a===e||a.ownerDocument===t&&r(t,a)?-1:b===e||b.ownerDocument===t&&r(t,b)?1:i?I.call(i,a)-I.call(i,b):0:4&d?-1:1)}:function(a,b){if(a===b)return j=!0,0;var c,d=0,f=a.parentNode,g=b.parentNode,h=[a],k=[b];if(!f||!g)return a===e?-1:b===e?1:f?-1:g?1:i?I.call(i,a)-I.call(i,b):0;if(f===g)return ib(a,b);c=a;while(c=c.parentNode)h.unshift(c);c=b;while(c=c.parentNode)k.unshift(c);while(h[d]===k[d])d++;return d?ib(h[d],k[d]):h[d]===t?-1:k[d]===t?1:0},e):l},db.matches=function(a,b){return db(a,null,null,b)},db.matchesSelector=function(a,b){if((a.ownerDocument||a)!==l&&k(a),b=b.replace(S,"='$1']"),!(!c.matchesSelector||!n||p&&p.test(b)||o&&o.test(b)))try{var d=q.call(a,b);if(d||c.disconnectedMatch||a.document&&11!==a.document.nodeType)return d}catch(e){}return db(b,l,null,[a]).length>0},db.contains=function(a,b){return(a.ownerDocument||a)!==l&&k(a),r(a,b)},db.attr=function(a,b){(a.ownerDocument||a)!==l&&k(a);var e=d.attrHandle[b.toLowerCase()],f=e&&C.call(d.attrHandle,b.toLowerCase())?e(a,b,!n):void 0;return void 0!==f?f:c.attributes||!n?a.getAttribute(b):(f=a.getAttributeNode(b))&&f.specified?f.value:null},db.error=function(a){throw new Error("Syntax error, unrecognized expression: "+a)},db.uniqueSort=function(a){var b,d=[],e=0,f=0;if(j=!c.detectDuplicates,i=!c.sortStable&&a.slice(0),a.sort(z),j){while(b=a[f++])b===a[f]&&(e=d.push(f));while(e--)a.splice(d[e],1)}return i=null,a},e=db.getText=function(a){var b,c="",d=0,f=a.nodeType;if(f){if(1===f||9===f||11===f){if("string"==typeof a.textContent)return a.textContent;for(a=a.firstChild;a;a=a.nextSibling)c+=e(a)}else if(3===f||4===f)return a.nodeValue}else while(b=a[d++])c+=e(b);return c},d=db.selectors={cacheLength:50,createPseudo:fb,match:V,attrHandle:{},find:{},relative:{">":{dir:"parentNode",first:!0}," ":{dir:"parentNode"},"+":{dir:"previousSibling",first:!0},"~":{dir:"previousSibling"}},preFilter:{ATTR:function(a){return a[1]=a[1].replace(ab,bb),a[3]=(a[4]||a[5]||"").replace(ab,bb),"~="===a[2]&&(a[3]=" "+a[3]+" "),a.slice(0,4)},CHILD:function(a){return a[1]=a[1].toLowerCase(),"nth"===a[1].slice(0,3)?(a[3]||db.error(a[0]),a[4]=+(a[4]?a[5]+(a[6]||1):2*("even"===a[3]||"odd"===a[3])),a[5]=+(a[7]+a[8]||"odd"===a[3])):a[3]&&db.error(a[0]),a},PSEUDO:function(a){var b,c=!a[5]&&a[2];return V.CHILD.test(a[0])?null:(a[3]&&void 0!==a[4]?a[2]=a[4]:c&&T.test(c)&&(b=ob(c,!0))&&(b=c.indexOf(")",c.length-b)-c.length)&&(a[0]=a[0].slice(0,b),a[2]=c.slice(0,b)),a.slice(0,3))}},filter:{TAG:function(a){var b=a.replace(ab,bb).toLowerCase();return"*"===a?function(){return!0}:function(a){return a.nodeName&&a.nodeName.toLowerCase()===b}},CLASS:function(a){var b=w[a+" "];return b||(b=new RegExp("(^|"+K+")"+a+"("+K+"|$)"))&&w(a,function(a){return b.test("string"==typeof a.className&&a.className||typeof a.getAttribute!==A&&a.getAttribute("class")||"")})},ATTR:function(a,b,c){return function(d){var e=db.attr(d,a);return null==e?"!="===b:b?(e+="","="===b?e===c:"!="===b?e!==c:"^="===b?c&&0===e.indexOf(c):"*="===b?c&&e.indexOf(c)>-1:"$="===b?c&&e.slice(-c.length)===c:"~="===b?(" "+e+" ").indexOf(c)>-1:"|="===b?e===c||e.slice(0,c.length+1)===c+"-":!1):!0}},CHILD:function(a,b,c,d,e){var f="nth"!==a.slice(0,3),g="last"!==a.slice(-4),h="of-type"===b;return 1===d&&0===e?function(a){return!!a.parentNode}:function(b,c,i){var j,k,l,m,n,o,p=f!==g?"nextSibling":"previousSibling",q=b.parentNode,r=h&&b.nodeName.toLowerCase(),t=!i&&!h;if(q){if(f){while(p){l=b;while(l=l[p])if(h?l.nodeName.toLowerCase()===r:1===l.nodeType)return!1;o=p="only"===a&&!o&&"nextSibling"}return!0}if(o=[g?q.firstChild:q.lastChild],g&&t){k=q[s]||(q[s]={}),j=k[a]||[],n=j[0]===u&&j[1],m=j[0]===u&&j[2],l=n&&q.childNodes[n];while(l=++n&&l&&l[p]||(m=n=0)||o.pop())if(1===l.nodeType&&++m&&l===b){k[a]=[u,n,m];break}}else if(t&&(j=(b[s]||(b[s]={}))[a])&&j[0]===u)m=j[1];else while(l=++n&&l&&l[p]||(m=n=0)||o.pop())if((h?l.nodeName.toLowerCase()===r:1===l.nodeType)&&++m&&(t&&((l[s]||(l[s]={}))[a]=[u,m]),l===b))break;return m-=e,m===d||m%d===0&&m/d>=0}}},PSEUDO:function(a,b){var c,e=d.pseudos[a]||d.setFilters[a.toLowerCase()]||db.error("unsupported pseudo: "+a);return e[s]?e(b):e.length>1?(c=[a,a,"",b],d.setFilters.hasOwnProperty(a.toLowerCase())?fb(function(a,c){var d,f=e(a,b),g=f.length;while(g--)d=I.call(a,f[g]),a[d]=!(c[d]=f[g])}):function(a){return e(a,0,c)}):e}},pseudos:{not:fb(function(a){var b=[],c=[],d=g(a.replace(P,"$1"));return d[s]?fb(function(a,b,c,e){var f,g=d(a,null,e,[]),h=a.length;while(h--)(f=g[h])&&(a[h]=!(b[h]=f))}):function(a,e,f){return b[0]=a,d(b,null,f,c),!c.pop()}}),has:fb(function(a){return function(b){return db(a,b).length>0}}),contains:fb(function(a){return function(b){return(b.textContent||b.innerText||e(b)).indexOf(a)>-1}}),lang:fb(function(a){return U.test(a||"")||db.error("unsupported lang: "+a),a=a.replace(ab,bb).toLowerCase(),function(b){var c;do if(c=n?b.lang:b.getAttribute("xml:lang")||b.getAttribute("lang"))return c=c.toLowerCase(),c===a||0===c.indexOf(a+"-");while((b=b.parentNode)&&1===b.nodeType);return!1}}),target:function(b){var c=a.location&&a.location.hash;return c&&c.slice(1)===b.id},root:function(a){return a===m},focus:function(a){return a===l.activeElement&&(!l.hasFocus||l.hasFocus())&&!!(a.type||a.href||~a.tabIndex)},enabled:function(a){return a.disabled===!1},disabled:function(a){return a.disabled===!0},checked:function(a){var b=a.nodeName.toLowerCase();return"input"===b&&!!a.checked||"option"===b&&!!a.selected},selected:function(a){return a.parentNode&&a.parentNode.selectedIndex,a.selected===!0},empty:function(a){for(a=a.firstChild;a;a=a.nextSibling)if(a.nodeType<6)return!1;return!0},parent:function(a){return!d.pseudos.empty(a)},header:function(a){return X.test(a.nodeName)},input:function(a){return W.test(a.nodeName)},button:function(a){var b=a.nodeName.toLowerCase();return"input"===b&&"button"===a.type||"button"===b},text:function(a){var b;return"input"===a.nodeName.toLowerCase()&&"text"===a.type&&(null==(b=a.getAttribute("type"))||"text"===b.toLowerCase())},first:lb(function(){return[0]}),last:lb(function(a,b){return[b-1]}),eq:lb(function(a,b,c){return[0>c?c+b:c]}),even:lb(function(a,b){for(var c=0;b>c;c+=2)a.push(c);return a}),odd:lb(function(a,b){for(var c=1;b>c;c+=2)a.push(c);return a}),lt:lb(function(a,b,c){for(var d=0>c?c+b:c;--d>=0;)a.push(d);return a}),gt:lb(function(a,b,c){for(var d=0>c?c+b:c;++d<b;)a.push(d);return a})}},d.pseudos.nth=d.pseudos.eq;for(b in{radio:!0,checkbox:!0,file:!0,password:!0,image:!0})d.pseudos[b]=jb(b);for(b in{submit:!0,reset:!0})d.pseudos[b]=kb(b);function nb(){}nb.prototype=d.filters=d.pseudos,d.setFilters=new nb;function ob(a,b){var c,e,f,g,h,i,j,k=x[a+" "];if(k)return b?0:k.slice(0);h=a,i=[],j=d.preFilter;while(h){(!c||(e=Q.exec(h)))&&(e&&(h=h.slice(e[0].length)||h),i.push(f=[])),c=!1,(e=R.exec(h))&&(c=e.shift(),f.push({value:c,type:e[0].replace(P," ")}),h=h.slice(c.length));for(g in d.filter)!(e=V[g].exec(h))||j[g]&&!(e=j[g](e))||(c=e.shift(),f.push({value:c,type:g,matches:e}),h=h.slice(c.length));if(!c)break}return b?h.length:h?db.error(a):x(a,i).slice(0)}function pb(a){for(var b=0,c=a.length,d="";c>b;b++)d+=a[b].value;return d}function qb(a,b,c){var d=b.dir,e=c&&"parentNode"===d,f=v++;return b.first?function(b,c,f){while(b=b[d])if(1===b.nodeType||e)return a(b,c,f)}:function(b,c,g){var h,i,j=[u,f];if(g){while(b=b[d])if((1===b.nodeType||e)&&a(b,c,g))return!0}else while(b=b[d])if(1===b.nodeType||e){if(i=b[s]||(b[s]={}),(h=i[d])&&h[0]===u&&h[1]===f)return j[2]=h[2];if(i[d]=j,j[2]=a(b,c,g))return!0}}}function rb(a){return a.length>1?function(b,c,d){var e=a.length;while(e--)if(!a[e](b,c,d))return!1;return!0}:a[0]}function sb(a,b,c,d,e){for(var f,g=[],h=0,i=a.length,j=null!=b;i>h;h++)(f=a[h])&&(!c||c(f,d,e))&&(g.push(f),j&&b.push(h));return g}function tb(a,b,c,d,e,f){return d&&!d[s]&&(d=tb(d)),e&&!e[s]&&(e=tb(e,f)),fb(function(f,g,h,i){var j,k,l,m=[],n=[],o=g.length,p=f||wb(b||"*",h.nodeType?[h]:h,[]),q=!a||!f&&b?p:sb(p,m,a,h,i),r=c?e||(f?a:o||d)?[]:g:q;if(c&&c(q,r,h,i),d){j=sb(r,n),d(j,[],h,i),k=j.length;while(k--)(l=j[k])&&(r[n[k]]=!(q[n[k]]=l))}if(f){if(e||a){if(e){j=[],k=r.length;while(k--)(l=r[k])&&j.push(q[k]=l);e(null,r=[],j,i)}k=r.length;while(k--)(l=r[k])&&(j=e?I.call(f,l):m[k])>-1&&(f[j]=!(g[j]=l))}}else r=sb(r===g?r.splice(o,r.length):r),e?e(null,g,r,i):G.apply(g,r)})}function ub(a){for(var b,c,e,f=a.length,g=d.relative[a[0].type],i=g||d.relative[" "],j=g?1:0,k=qb(function(a){return a===b},i,!0),l=qb(function(a){return I.call(b,a)>-1},i,!0),m=[function(a,c,d){return!g&&(d||c!==h)||((b=c).nodeType?k(a,c,d):l(a,c,d))}];f>j;j++)if(c=d.relative[a[j].type])m=[qb(rb(m),c)];else{if(c=d.filter[a[j].type].apply(null,a[j].matches),c[s]){for(e=++j;f>e;e++)if(d.relative[a[e].type])break;return tb(j>1&&rb(m),j>1&&pb(a.slice(0,j-1).concat({value:" "===a[j-2].type?"*":""})).replace(P,"$1"),c,e>j&&ub(a.slice(j,e)),f>e&&ub(a=a.slice(e)),f>e&&pb(a))}m.push(c)}return rb(m)}function vb(a,b){var c=b.length>0,e=a.length>0,f=function(f,g,i,j,k){var m,n,o,p=0,q="0",r=f&&[],s=[],t=h,v=f||e&&d.find.TAG("*",k),w=u+=null==t?1:Math.random()||.1,x=v.length;for(k&&(h=g!==l&&g);q!==x&&null!=(m=v[q]);q++){if(e&&m){n=0;while(o=a[n++])if(o(m,g,i)){j.push(m);break}k&&(u=w)}c&&((m=!o&&m)&&p--,f&&r.push(m))}if(p+=q,c&&q!==p){n=0;while(o=b[n++])o(r,s,g,i);if(f){if(p>0)while(q--)r[q]||s[q]||(s[q]=E.call(j));s=sb(s)}G.apply(j,s),k&&!f&&s.length>0&&p+b.length>1&&db.uniqueSort(j)}return k&&(u=w,h=t),r};return c?fb(f):f}g=db.compile=function(a,b){var c,d=[],e=[],f=y[a+" "];if(!f){b||(b=ob(a)),c=b.length;while(c--)f=ub(b[c]),f[s]?d.push(f):e.push(f);f=y(a,vb(e,d))}return f};function wb(a,b,c){for(var d=0,e=b.length;e>d;d++)db(a,b[d],c);return c}function xb(a,b,e,f){var h,i,j,k,l,m=ob(a);if(!f&&1===m.length){if(i=m[0]=m[0].slice(0),i.length>2&&"ID"===(j=i[0]).type&&c.getById&&9===b.nodeType&&n&&d.relative[i[1].type]){if(b=(d.find.ID(j.matches[0].replace(ab,bb),b)||[])[0],!b)return e;a=a.slice(i.shift().value.length)}h=V.needsContext.test(a)?0:i.length;while(h--){if(j=i[h],d.relative[k=j.type])break;if((l=d.find[k])&&(f=l(j.matches[0].replace(ab,bb),$.test(i[0].type)&&mb(b.parentNode)||b))){if(i.splice(h,1),a=f.length&&pb(i),!a)return G.apply(e,f),e;break}}}return g(a,m)(f,b,!n,e,$.test(a)&&mb(b.parentNode)||b),e}return c.sortStable=s.split("").sort(z).join("")===s,c.detectDuplicates=!!j,k(),c.sortDetached=gb(function(a){return 1&a.compareDocumentPosition(l.createElement("div"))}),gb(function(a){return a.innerHTML="<a href='#'></a>","#"===a.firstChild.getAttribute("href")})||hb("type|href|height|width",function(a,b,c){return c?void 0:a.getAttribute(b,"type"===b.toLowerCase()?1:2)}),c.attributes&&gb(function(a){return a.innerHTML="<input/>",a.firstChild.setAttribute("value",""),""===a.firstChild.getAttribute("value")})||hb("value",function(a,b,c){return c||"input"!==a.nodeName.toLowerCase()?void 0:a.defaultValue}),gb(function(a){return null==a.getAttribute("disabled")})||hb(J,function(a,b,c){var d;return c?void 0:a[b]===!0?b.toLowerCase():(d=a.getAttributeNode(b))&&d.specified?d.value:null}),db}(a);o.find=t,o.expr=t.selectors,o.expr[":"]=o.expr.pseudos,o.unique=t.uniqueSort,o.text=t.getText,o.isXMLDoc=t.isXML,o.contains=t.contains;var u=o.expr.match.needsContext,v=/^<(\w+)\s*\/?>(?:<\/\1>|)$/,w=/^.[^:#\[\.,]*$/;function x(a,b,c){if(o.isFunction(b))return o.grep(a,function(a,d){return!!b.call(a,d,a)!==c});if(b.nodeType)return o.grep(a,function(a){return a===b!==c});if("string"==typeof b){if(w.test(b))return o.filter(b,a,c);b=o.filter(b,a)}return o.grep(a,function(a){return g.call(b,a)>=0!==c})}o.filter=function(a,b,c){var d=b[0];return c&&(a=":not("+a+")"),1===b.length&&1===d.nodeType?o.find.matchesSelector(d,a)?[d]:[]:o.find.matches(a,o.grep(b,function(a){return 1===a.nodeType}))},o.fn.extend({find:function(a){var b,c=this.length,d=[],e=this;if("string"!=typeof a)return this.pushStack(o(a).filter(function(){for(b=0;c>b;b++)if(o.contains(e[b],this))return!0}));for(b=0;c>b;b++)o.find(a,e[b],d);return d=this.pushStack(c>1?o.unique(d):d),d.selector=this.selector?this.selector+" "+a:a,d},filter:function(a){return this.pushStack(x(this,a||[],!1))},not:function(a){return this.pushStack(x(this,a||[],!0))},is:function(a){return!!x(this,"string"==typeof a&&u.test(a)?o(a):a||[],!1).length}});var y,z=/^(?:\s*(<[\w\W]+>)[^>]*|#([\w-]*))$/,A=o.fn.init=function(a,b){var c,d;if(!a)return this;if("string"==typeof a){if(c="<"===a[0]&&">"===a[a.length-1]&&a.length>=3?[null,a,null]:z.exec(a),!c||!c[1]&&b)return!b||b.jquery?(b||y).find(a):this.constructor(b).find(a);if(c[1]){if(b=b instanceof o?b[0]:b,o.merge(this,o.parseHTML(c[1],b&&b.nodeType?b.ownerDocument||b:m,!0)),v.test(c[1])&&o.isPlainObject(b))for(c in b)o.isFunction(this[c])?this[c](b[c]):this.attr(c,b[c]);return this}return d=m.getElementById(c[2]),d&&d.parentNode&&(this.length=1,this[0]=d),this.context=m,this.selector=a,this}return a.nodeType?(this.context=this[0]=a,this.length=1,this):o.isFunction(a)?"undefined"!=typeof y.ready?y.ready(a):a(o):(void 0!==a.selector&&(this.selector=a.selector,this.context=a.context),o.makeArray(a,this))};A.prototype=o.fn,y=o(m);var B=/^(?:parents|prev(?:Until|All))/,C={children:!0,contents:!0,next:!0,prev:!0};o.extend({dir:function(a,b,c){var d=[],e=void 0!==c;while((a=a[b])&&9!==a.nodeType)if(1===a.nodeType){if(e&&o(a).is(c))break;d.push(a)}return d},sibling:function(a,b){for(var c=[];a;a=a.nextSibling)1===a.nodeType&&a!==b&&c.push(a);return c}}),o.fn.extend({has:function(a){var b=o(a,this),c=b.length;return this.filter(function(){for(var a=0;c>a;a++)if(o.contains(this,b[a]))return!0})},closest:function(a,b){for(var c,d=0,e=this.length,f=[],g=u.test(a)||"string"!=typeof a?o(a,b||this.context):0;e>d;d++)for(c=this[d];c&&c!==b;c=c.parentNode)if(c.nodeType<11&&(g?g.index(c)>-1:1===c.nodeType&&o.find.matchesSelector(c,a))){f.push(c);break}return this.pushStack(f.length>1?o.unique(f):f)},index:function(a){return a?"string"==typeof a?g.call(o(a),this[0]):g.call(this,a.jquery?a[0]:a):this[0]&&this[0].parentNode?this.first().prevAll().length:-1},add:function(a,b){return this.pushStack(o.unique(o.merge(this.get(),o(a,b))))},addBack:function(a){return this.add(null==a?this.prevObject:this.prevObject.filter(a))}});function D(a,b){while((a=a[b])&&1!==a.nodeType);return a}o.each({parent:function(a){var b=a.parentNode;return b&&11!==b.nodeType?b:null},parents:function(a){return o.dir(a,"parentNode")},parentsUntil:function(a,b,c){return o.dir(a,"parentNode",c)},next:function(a){return D(a,"nextSibling")},prev:function(a){return D(a,"previousSibling")},nextAll:function(a){return o.dir(a,"nextSibling")},prevAll:function(a){return o.dir(a,"previousSibling")},nextUntil:function(a,b,c){return o.dir(a,"nextSibling",c)},prevUntil:function(a,b,c){return o.dir(a,"previousSibling",c)},siblings:function(a){return o.sibling((a.parentNode||{}).firstChild,a)},children:function(a){return o.sibling(a.firstChild)},contents:function(a){return a.contentDocument||o.merge([],a.childNodes)}},function(a,b){o.fn[a]=function(c,d){var e=o.map(this,b,c);return"Until"!==a.slice(-5)&&(d=c),d&&"string"==typeof d&&(e=o.filter(d,e)),this.length>1&&(C[a]||o.unique(e),B.test(a)&&e.reverse()),this.pushStack(e)}});var E=/\S+/g,F={};function G(a){var b=F[a]={};return o.each(a.match(E)||[],function(a,c){b[c]=!0}),b}o.Callbacks=function(a){a="string"==typeof a?F[a]||G(a):o.extend({},a);var b,c,d,e,f,g,h=[],i=!a.once&&[],j=function(l){for(b=a.memory&&l,c=!0,g=e||0,e=0,f=h.length,d=!0;h&&f>g;g++)if(h[g].apply(l[0],l[1])===!1&&a.stopOnFalse){b=!1;break}d=!1,h&&(i?i.length&&j(i.shift()):b?h=[]:k.disable())},k={add:function(){if(h){var c=h.length;!function g(b){o.each(b,function(b,c){var d=o.type(c);"function"===d?a.unique&&k.has(c)||h.push(c):c&&c.length&&"string"!==d&&g(c)})}(arguments),d?f=h.length:b&&(e=c,j(b))}return this},remove:function(){return h&&o.each(arguments,function(a,b){var c;while((c=o.inArray(b,h,c))>-1)h.splice(c,1),d&&(f>=c&&f--,g>=c&&g--)}),this},has:function(a){return a?o.inArray(a,h)>-1:!(!h||!h.length)},empty:function(){return h=[],f=0,this},disable:function(){return h=i=b=void 0,this},disabled:function(){return!h},lock:function(){return i=void 0,b||k.disable(),this},locked:function(){return!i},fireWith:function(a,b){return!h||c&&!i||(b=b||[],b=[a,b.slice?b.slice():b],d?i.push(b):j(b)),this},fire:function(){return k.fireWith(this,arguments),this},fired:function(){return!!c}};return k},o.extend({Deferred:function(a){var b=[["resolve","done",o.Callbacks("once memory"),"resolved"],["reject","fail",o.Callbacks("once memory"),"rejected"],["notify","progress",o.Callbacks("memory")]],c="pending",d={state:function(){return c},always:function(){return e.done(arguments).fail(arguments),this},then:function(){var a=arguments;return o.Deferred(function(c){o.each(b,function(b,f){var g=o.isFunction(a[b])&&a[b];e[f[1]](function(){var a=g&&g.apply(this,arguments);a&&o.isFunction(a.promise)?a.promise().done(c.resolve).fail(c.reject).progress(c.notify):c[f[0]+"With"](this===d?c.promise():this,g?[a]:arguments)})}),a=null}).promise()},promise:function(a){return null!=a?o.extend(a,d):d}},e={};return d.pipe=d.then,o.each(b,function(a,f){var g=f[2],h=f[3];d[f[1]]=g.add,h&&g.add(function(){c=h},b[1^a][2].disable,b[2][2].lock),e[f[0]]=function(){return e[f[0]+"With"](this===e?d:this,arguments),this},e[f[0]+"With"]=g.fireWith}),d.promise(e),a&&a.call(e,e),e},when:function(a){var b=0,c=d.call(arguments),e=c.length,f=1!==e||a&&o.isFunction(a.promise)?e:0,g=1===f?a:o.Deferred(),h=function(a,b,c){return function(e){b[a]=this,c[a]=arguments.length>1?d.call(arguments):e,c===i?g.notifyWith(b,c):--f||g.resolveWith(b,c)}},i,j,k;if(e>1)for(i=new Array(e),j=new Array(e),k=new Array(e);e>b;b++)c[b]&&o.isFunction(c[b].promise)?c[b].promise().done(h(b,k,c)).fail(g.reject).progress(h(b,j,i)):--f;return f||g.resolveWith(k,c),g.promise()}});var H;o.fn.ready=function(a){return o.ready.promise().done(a),this},o.extend({isReady:!1,readyWait:1,holdReady:function(a){a?o.readyWait++:o.ready(!0)},ready:function(a){(a===!0?--o.readyWait:o.isReady)||(o.isReady=!0,a!==!0&&--o.readyWait>0||(H.resolveWith(m,[o]),o.fn.trigger&&o(m).trigger("ready").off("ready")))}});function I(){m.removeEventListener("DOMContentLoaded",I,!1),a.removeEventListener("load",I,!1),o.ready()}o.ready.promise=function(b){return H||(H=o.Deferred(),"complete"===m.readyState?setTimeout(o.ready):(m.addEventListener("DOMContentLoaded",I,!1),a.addEventListener("load",I,!1))),H.promise(b)},o.ready.promise();var J=o.access=function(a,b,c,d,e,f,g){var h=0,i=a.length,j=null==c;if("object"===o.type(c)){e=!0;for(h in c)o.access(a,b,h,c[h],!0,f,g)}else if(void 0!==d&&(e=!0,o.isFunction(d)||(g=!0),j&&(g?(b.call(a,d),b=null):(j=b,b=function(a,b,c){return j.call(o(a),c)})),b))for(;i>h;h++)b(a[h],c,g?d:d.call(a[h],h,b(a[h],c)));return e?a:j?b.call(a):i?b(a[0],c):f};o.acceptData=function(a){return 1===a.nodeType||9===a.nodeType||!+a.nodeType};function K(){Object.defineProperty(this.cache={},0,{get:function(){return{}}}),this.expando=o.expando+Math.random()}K.uid=1,K.accepts=o.acceptData,K.prototype={key:function(a){if(!K.accepts(a))return 0;var b={},c=a[this.expando];if(!c){c=K.uid++;try{b[this.expando]={value:c},Object.defineProperties(a,b)}catch(d){b[this.expando]=c,o.extend(a,b)}}return this.cache[c]||(this.cache[c]={}),c},set:function(a,b,c){var d,e=this.key(a),f=this.cache[e];if("string"==typeof b)f[b]=c;else if(o.isEmptyObject(f))o.extend(this.cache[e],b);else for(d in b)f[d]=b[d];return f},get:function(a,b){var c=this.cache[this.key(a)];return void 0===b?c:c[b]},access:function(a,b,c){var d;return void 0===b||b&&"string"==typeof b&&void 0===c?(d=this.get(a,b),void 0!==d?d:this.get(a,o.camelCase(b))):(this.set(a,b,c),void 0!==c?c:b)},remove:function(a,b){var c,d,e,f=this.key(a),g=this.cache[f];if(void 0===b)this.cache[f]={};else{o.isArray(b)?d=b.concat(b.map(o.camelCase)):(e=o.camelCase(b),b in g?d=[b,e]:(d=e,d=d in g?[d]:d.match(E)||[])),c=d.length;while(c--)delete g[d[c]]}},hasData:function(a){return!o.isEmptyObject(this.cache[a[this.expando]]||{})},discard:function(a){a[this.expando]&&delete this.cache[a[this.expando]]}};var L=new K,M=new K,N=/^(?:\{[\w\W]*\}|\[[\w\W]*\])$/,O=/([A-Z])/g;function P(a,b,c){var d;if(void 0===c&&1===a.nodeType)if(d="data-"+b.replace(O,"-$1").toLowerCase(),c=a.getAttribute(d),"string"==typeof c){try{c="true"===c?!0:"false"===c?!1:"null"===c?null:+c+""===c?+c:N.test(c)?o.parseJSON(c):c}catch(e){}M.set(a,b,c)}else c=void 0;return c}o.extend({hasData:function(a){return M.hasData(a)||L.hasData(a)},data:function(a,b,c){return M.access(a,b,c)},removeData:function(a,b){M.remove(a,b)},_data:function(a,b,c){return L.access(a,b,c)},_removeData:function(a,b){L.remove(a,b)}}),o.fn.extend({data:function(a,b){var c,d,e,f=this[0],g=f&&f.attributes;if(void 0===a){if(this.length&&(e=M.get(f),1===f.nodeType&&!L.get(f,"hasDataAttrs"))){c=g.length;
+while(c--)d=g[c].name,0===d.indexOf("data-")&&(d=o.camelCase(d.slice(5)),P(f,d,e[d]));L.set(f,"hasDataAttrs",!0)}return e}return"object"==typeof a?this.each(function(){M.set(this,a)}):J(this,function(b){var c,d=o.camelCase(a);if(f&&void 0===b){if(c=M.get(f,a),void 0!==c)return c;if(c=M.get(f,d),void 0!==c)return c;if(c=P(f,d,void 0),void 0!==c)return c}else this.each(function(){var c=M.get(this,d);M.set(this,d,b),-1!==a.indexOf("-")&&void 0!==c&&M.set(this,a,b)})},null,b,arguments.length>1,null,!0)},removeData:function(a){return this.each(function(){M.remove(this,a)})}}),o.extend({queue:function(a,b,c){var d;return a?(b=(b||"fx")+"queue",d=L.get(a,b),c&&(!d||o.isArray(c)?d=L.access(a,b,o.makeArray(c)):d.push(c)),d||[]):void 0},dequeue:function(a,b){b=b||"fx";var c=o.queue(a,b),d=c.length,e=c.shift(),f=o._queueHooks(a,b),g=function(){o.dequeue(a,b)};"inprogress"===e&&(e=c.shift(),d--),e&&("fx"===b&&c.unshift("inprogress"),delete f.stop,e.call(a,g,f)),!d&&f&&f.empty.fire()},_queueHooks:function(a,b){var c=b+"queueHooks";return L.get(a,c)||L.access(a,c,{empty:o.Callbacks("once memory").add(function(){L.remove(a,[b+"queue",c])})})}}),o.fn.extend({queue:function(a,b){var c=2;return"string"!=typeof a&&(b=a,a="fx",c--),arguments.length<c?o.queue(this[0],a):void 0===b?this:this.each(function(){var c=o.queue(this,a,b);o._queueHooks(this,a),"fx"===a&&"inprogress"!==c[0]&&o.dequeue(this,a)})},dequeue:function(a){return this.each(function(){o.dequeue(this,a)})},clearQueue:function(a){return this.queue(a||"fx",[])},promise:function(a,b){var c,d=1,e=o.Deferred(),f=this,g=this.length,h=function(){--d||e.resolveWith(f,[f])};"string"!=typeof a&&(b=a,a=void 0),a=a||"fx";while(g--)c=L.get(f[g],a+"queueHooks"),c&&c.empty&&(d++,c.empty.add(h));return h(),e.promise(b)}});var Q=/[+-]?(?:\d*\.|)\d+(?:[eE][+-]?\d+|)/.source,R=["Top","Right","Bottom","Left"],S=function(a,b){return a=b||a,"none"===o.css(a,"display")||!o.contains(a.ownerDocument,a)},T=/^(?:checkbox|radio)$/i;!function(){var a=m.createDocumentFragment(),b=a.appendChild(m.createElement("div"));b.innerHTML="<input type='radio' checked='checked' name='t'/>",l.checkClone=b.cloneNode(!0).cloneNode(!0).lastChild.checked,b.innerHTML="<textarea>x</textarea>",l.noCloneChecked=!!b.cloneNode(!0).lastChild.defaultValue}();var U="undefined";l.focusinBubbles="onfocusin"in a;var V=/^key/,W=/^(?:mouse|contextmenu)|click/,X=/^(?:focusinfocus|focusoutblur)$/,Y=/^([^.]*)(?:\.(.+)|)$/;function Z(){return!0}function $(){return!1}function _(){try{return m.activeElement}catch(a){}}o.event={global:{},add:function(a,b,c,d,e){var f,g,h,i,j,k,l,m,n,p,q,r=L.get(a);if(r){c.handler&&(f=c,c=f.handler,e=f.selector),c.guid||(c.guid=o.guid++),(i=r.events)||(i=r.events={}),(g=r.handle)||(g=r.handle=function(b){return typeof o!==U&&o.event.triggered!==b.type?o.event.dispatch.apply(a,arguments):void 0}),b=(b||"").match(E)||[""],j=b.length;while(j--)h=Y.exec(b[j])||[],n=q=h[1],p=(h[2]||"").split(".").sort(),n&&(l=o.event.special[n]||{},n=(e?l.delegateType:l.bindType)||n,l=o.event.special[n]||{},k=o.extend({type:n,origType:q,data:d,handler:c,guid:c.guid,selector:e,needsContext:e&&o.expr.match.needsContext.test(e),namespace:p.join(".")},f),(m=i[n])||(m=i[n]=[],m.delegateCount=0,l.setup&&l.setup.call(a,d,p,g)!==!1||a.addEventListener&&a.addEventListener(n,g,!1)),l.add&&(l.add.call(a,k),k.handler.guid||(k.handler.guid=c.guid)),e?m.splice(m.delegateCount++,0,k):m.push(k),o.event.global[n]=!0)}},remove:function(a,b,c,d,e){var f,g,h,i,j,k,l,m,n,p,q,r=L.hasData(a)&&L.get(a);if(r&&(i=r.events)){b=(b||"").match(E)||[""],j=b.length;while(j--)if(h=Y.exec(b[j])||[],n=q=h[1],p=(h[2]||"").split(".").sort(),n){l=o.event.special[n]||{},n=(d?l.delegateType:l.bindType)||n,m=i[n]||[],h=h[2]&&new RegExp("(^|\\.)"+p.join("\\.(?:.*\\.|)")+"(\\.|$)"),g=f=m.length;while(f--)k=m[f],!e&&q!==k.origType||c&&c.guid!==k.guid||h&&!h.test(k.namespace)||d&&d!==k.selector&&("**"!==d||!k.selector)||(m.splice(f,1),k.selector&&m.delegateCount--,l.remove&&l.remove.call(a,k));g&&!m.length&&(l.teardown&&l.teardown.call(a,p,r.handle)!==!1||o.removeEvent(a,n,r.handle),delete i[n])}else for(n in i)o.event.remove(a,n+b[j],c,d,!0);o.isEmptyObject(i)&&(delete r.handle,L.remove(a,"events"))}},trigger:function(b,c,d,e){var f,g,h,i,k,l,n,p=[d||m],q=j.call(b,"type")?b.type:b,r=j.call(b,"namespace")?b.namespace.split("."):[];if(g=h=d=d||m,3!==d.nodeType&&8!==d.nodeType&&!X.test(q+o.event.triggered)&&(q.indexOf(".")>=0&&(r=q.split("."),q=r.shift(),r.sort()),k=q.indexOf(":")<0&&"on"+q,b=b[o.expando]?b:new o.Event(q,"object"==typeof b&&b),b.isTrigger=e?2:3,b.namespace=r.join("."),b.namespace_re=b.namespace?new RegExp("(^|\\.)"+r.join("\\.(?:.*\\.|)")+"(\\.|$)"):null,b.result=void 0,b.target||(b.target=d),c=null==c?[b]:o.makeArray(c,[b]),n=o.event.special[q]||{},e||!n.trigger||n.trigger.apply(d,c)!==!1)){if(!e&&!n.noBubble&&!o.isWindow(d)){for(i=n.delegateType||q,X.test(i+q)||(g=g.parentNode);g;g=g.parentNode)p.push(g),h=g;h===(d.ownerDocument||m)&&p.push(h.defaultView||h.parentWindow||a)}f=0;while((g=p[f++])&&!b.isPropagationStopped())b.type=f>1?i:n.bindType||q,l=(L.get(g,"events")||{})[b.type]&&L.get(g,"handle"),l&&l.apply(g,c),l=k&&g[k],l&&l.apply&&o.acceptData(g)&&(b.result=l.apply(g,c),b.result===!1&&b.preventDefault());return b.type=q,e||b.isDefaultPrevented()||n._default&&n._default.apply(p.pop(),c)!==!1||!o.acceptData(d)||k&&o.isFunction(d[q])&&!o.isWindow(d)&&(h=d[k],h&&(d[k]=null),o.event.triggered=q,d[q](),o.event.triggered=void 0,h&&(d[k]=h)),b.result}},dispatch:function(a){a=o.event.fix(a);var b,c,e,f,g,h=[],i=d.call(arguments),j=(L.get(this,"events")||{})[a.type]||[],k=o.event.special[a.type]||{};if(i[0]=a,a.delegateTarget=this,!k.preDispatch||k.preDispatch.call(this,a)!==!1){h=o.event.handlers.call(this,a,j),b=0;while((f=h[b++])&&!a.isPropagationStopped()){a.currentTarget=f.elem,c=0;while((g=f.handlers[c++])&&!a.isImmediatePropagationStopped())(!a.namespace_re||a.namespace_re.test(g.namespace))&&(a.handleObj=g,a.data=g.data,e=((o.event.special[g.origType]||{}).handle||g.handler).apply(f.elem,i),void 0!==e&&(a.result=e)===!1&&(a.preventDefault(),a.stopPropagation()))}return k.postDispatch&&k.postDispatch.call(this,a),a.result}},handlers:function(a,b){var c,d,e,f,g=[],h=b.delegateCount,i=a.target;if(h&&i.nodeType&&(!a.button||"click"!==a.type))for(;i!==this;i=i.parentNode||this)if(i.disabled!==!0||"click"!==a.type){for(d=[],c=0;h>c;c++)f=b[c],e=f.selector+" ",void 0===d[e]&&(d[e]=f.needsContext?o(e,this).index(i)>=0:o.find(e,this,null,[i]).length),d[e]&&d.push(f);d.length&&g.push({elem:i,handlers:d})}return h<b.length&&g.push({elem:this,handlers:b.slice(h)}),g},props:"altKey bubbles cancelable ctrlKey currentTarget eventPhase metaKey relatedTarget shiftKey target timeStamp view which".split(" "),fixHooks:{},keyHooks:{props:"char charCode key keyCode".split(" "),filter:function(a,b){return null==a.which&&(a.which=null!=b.charCode?b.charCode:b.keyCode),a}},mouseHooks:{props:"button buttons clientX clientY offsetX offsetY pageX pageY screenX screenY toElement".split(" "),filter:function(a,b){var c,d,e,f=b.button;return null==a.pageX&&null!=b.clientX&&(c=a.target.ownerDocument||m,d=c.documentElement,e=c.body,a.pageX=b.clientX+(d&&d.scrollLeft||e&&e.scrollLeft||0)-(d&&d.clientLeft||e&&e.clientLeft||0),a.pageY=b.clientY+(d&&d.scrollTop||e&&e.scrollTop||0)-(d&&d.clientTop||e&&e.clientTop||0)),a.which||void 0===f||(a.which=1&f?1:2&f?3:4&f?2:0),a}},fix:function(a){if(a[o.expando])return a;var b,c,d,e=a.type,f=a,g=this.fixHooks[e];g||(this.fixHooks[e]=g=W.test(e)?this.mouseHooks:V.test(e)?this.keyHooks:{}),d=g.props?this.props.concat(g.props):this.props,a=new o.Event(f),b=d.length;while(b--)c=d[b],a[c]=f[c];return a.target||(a.target=m),3===a.target.nodeType&&(a.target=a.target.parentNode),g.filter?g.filter(a,f):a},special:{load:{noBubble:!0},focus:{trigger:function(){return this!==_()&&this.focus?(this.focus(),!1):void 0},delegateType:"focusin"},blur:{trigger:function(){return this===_()&&this.blur?(this.blur(),!1):void 0},delegateType:"focusout"},click:{trigger:function(){return"checkbox"===this.type&&this.click&&o.nodeName(this,"input")?(this.click(),!1):void 0},_default:function(a){return o.nodeName(a.target,"a")}},beforeunload:{postDispatch:function(a){void 0!==a.result&&(a.originalEvent.returnValue=a.result)}}},simulate:function(a,b,c,d){var e=o.extend(new o.Event,c,{type:a,isSimulated:!0,originalEvent:{}});d?o.event.trigger(e,null,b):o.event.dispatch.call(b,e),e.isDefaultPrevented()&&c.preventDefault()}},o.removeEvent=function(a,b,c){a.removeEventListener&&a.removeEventListener(b,c,!1)},o.Event=function(a,b){return this instanceof o.Event?(a&&a.type?(this.originalEvent=a,this.type=a.type,this.isDefaultPrevented=a.defaultPrevented||void 0===a.defaultPrevented&&a.getPreventDefault&&a.getPreventDefault()?Z:$):this.type=a,b&&o.extend(this,b),this.timeStamp=a&&a.timeStamp||o.now(),void(this[o.expando]=!0)):new o.Event(a,b)},o.Event.prototype={isDefaultPrevented:$,isPropagationStopped:$,isImmediatePropagationStopped:$,preventDefault:function(){var a=this.originalEvent;this.isDefaultPrevented=Z,a&&a.preventDefault&&a.preventDefault()},stopPropagation:function(){var a=this.originalEvent;this.isPropagationStopped=Z,a&&a.stopPropagation&&a.stopPropagation()},stopImmediatePropagation:function(){this.isImmediatePropagationStopped=Z,this.stopPropagation()}},o.each({mouseenter:"mouseover",mouseleave:"mouseout"},function(a,b){o.event.special[a]={delegateType:b,bindType:b,handle:function(a){var c,d=this,e=a.relatedTarget,f=a.handleObj;return(!e||e!==d&&!o.contains(d,e))&&(a.type=f.origType,c=f.handler.apply(this,arguments),a.type=b),c}}}),l.focusinBubbles||o.each({focus:"focusin",blur:"focusout"},function(a,b){var c=function(a){o.event.simulate(b,a.target,o.event.fix(a),!0)};o.event.special[b]={setup:function(){var d=this.ownerDocument||this,e=L.access(d,b);e||d.addEventListener(a,c,!0),L.access(d,b,(e||0)+1)},teardown:function(){var d=this.ownerDocument||this,e=L.access(d,b)-1;e?L.access(d,b,e):(d.removeEventListener(a,c,!0),L.remove(d,b))}}}),o.fn.extend({on:function(a,b,c,d,e){var f,g;if("object"==typeof a){"string"!=typeof b&&(c=c||b,b=void 0);for(g in a)this.on(g,b,c,a[g],e);return this}if(null==c&&null==d?(d=b,c=b=void 0):null==d&&("string"==typeof b?(d=c,c=void 0):(d=c,c=b,b=void 0)),d===!1)d=$;else if(!d)return this;return 1===e&&(f=d,d=function(a){return o().off(a),f.apply(this,arguments)},d.guid=f.guid||(f.guid=o.guid++)),this.each(function(){o.event.add(this,a,d,c,b)})},one:function(a,b,c,d){return this.on(a,b,c,d,1)},off:function(a,b,c){var d,e;if(a&&a.preventDefault&&a.handleObj)return d=a.handleObj,o(a.delegateTarget).off(d.namespace?d.origType+"."+d.namespace:d.origType,d.selector,d.handler),this;if("object"==typeof a){for(e in a)this.off(e,b,a[e]);return this}return(b===!1||"function"==typeof b)&&(c=b,b=void 0),c===!1&&(c=$),this.each(function(){o.event.remove(this,a,c,b)})},trigger:function(a,b){return this.each(function(){o.event.trigger(a,b,this)})},triggerHandler:function(a,b){var c=this[0];return c?o.event.trigger(a,b,c,!0):void 0}});var ab=/<(?!area|br|col|embed|hr|img|input|link|meta|param)(([\w:]+)[^>]*)\/>/gi,bb=/<([\w:]+)/,cb=/<|&#?\w+;/,db=/<(?:script|style|link)/i,eb=/checked\s*(?:[^=]|=\s*.checked.)/i,fb=/^$|\/(?:java|ecma)script/i,gb=/^true\/(.*)/,hb=/^\s*<!(?:\[CDATA\[|--)|(?:\]\]|--)>\s*$/g,ib={option:[1,"<select multiple='multiple'>","</select>"],thead:[1,"<table>","</table>"],col:[2,"<table><colgroup>","</colgroup></table>"],tr:[2,"<table><tbody>","</tbody></table>"],td:[3,"<table><tbody><tr>","</tr></tbody></table>"],_default:[0,"",""]};ib.optgroup=ib.option,ib.tbody=ib.tfoot=ib.colgroup=ib.caption=ib.thead,ib.th=ib.td;function jb(a,b){return o.nodeName(a,"table")&&o.nodeName(11!==b.nodeType?b:b.firstChild,"tr")?a.getElementsByTagName("tbody")[0]||a.appendChild(a.ownerDocument.createElement("tbody")):a}function kb(a){return a.type=(null!==a.getAttribute("type"))+"/"+a.type,a}function lb(a){var b=gb.exec(a.type);return b?a.type=b[1]:a.removeAttribute("type"),a}function mb(a,b){for(var c=0,d=a.length;d>c;c++)L.set(a[c],"globalEval",!b||L.get(b[c],"globalEval"))}function nb(a,b){var c,d,e,f,g,h,i,j;if(1===b.nodeType){if(L.hasData(a)&&(f=L.access(a),g=L.set(b,f),j=f.events)){delete g.handle,g.events={};for(e in j)for(c=0,d=j[e].length;d>c;c++)o.event.add(b,e,j[e][c])}M.hasData(a)&&(h=M.access(a),i=o.extend({},h),M.set(b,i))}}function ob(a,b){var c=a.getElementsByTagName?a.getElementsByTagName(b||"*"):a.querySelectorAll?a.querySelectorAll(b||"*"):[];return void 0===b||b&&o.nodeName(a,b)?o.merge([a],c):c}function pb(a,b){var c=b.nodeName.toLowerCase();"input"===c&&T.test(a.type)?b.checked=a.checked:("input"===c||"textarea"===c)&&(b.defaultValue=a.defaultValue)}o.extend({clone:function(a,b,c){var d,e,f,g,h=a.cloneNode(!0),i=o.contains(a.ownerDocument,a);if(!(l.noCloneChecked||1!==a.nodeType&&11!==a.nodeType||o.isXMLDoc(a)))for(g=ob(h),f=ob(a),d=0,e=f.length;e>d;d++)pb(f[d],g[d]);if(b)if(c)for(f=f||ob(a),g=g||ob(h),d=0,e=f.length;e>d;d++)nb(f[d],g[d]);else nb(a,h);return g=ob(h,"script"),g.length>0&&mb(g,!i&&ob(a,"script")),h},buildFragment:function(a,b,c,d){for(var e,f,g,h,i,j,k=b.createDocumentFragment(),l=[],m=0,n=a.length;n>m;m++)if(e=a[m],e||0===e)if("object"===o.type(e))o.merge(l,e.nodeType?[e]:e);else if(cb.test(e)){f=f||k.appendChild(b.createElement("div")),g=(bb.exec(e)||["",""])[1].toLowerCase(),h=ib[g]||ib._default,f.innerHTML=h[1]+e.replace(ab,"<$1></$2>")+h[2],j=h[0];while(j--)f=f.lastChild;o.merge(l,f.childNodes),f=k.firstChild,f.textContent=""}else l.push(b.createTextNode(e));k.textContent="",m=0;while(e=l[m++])if((!d||-1===o.inArray(e,d))&&(i=o.contains(e.ownerDocument,e),f=ob(k.appendChild(e),"script"),i&&mb(f),c)){j=0;while(e=f[j++])fb.test(e.type||"")&&c.push(e)}return k},cleanData:function(a){for(var b,c,d,e,f,g,h=o.event.special,i=0;void 0!==(c=a[i]);i++){if(o.acceptData(c)&&(f=c[L.expando],f&&(b=L.cache[f]))){if(d=Object.keys(b.events||{}),d.length)for(g=0;void 0!==(e=d[g]);g++)h[e]?o.event.remove(c,e):o.removeEvent(c,e,b.handle);L.cache[f]&&delete L.cache[f]}delete M.cache[c[M.expando]]}}}),o.fn.extend({text:function(a){return J(this,function(a){return void 0===a?o.text(this):this.empty().each(function(){(1===this.nodeType||11===this.nodeType||9===this.nodeType)&&(this.textContent=a)})},null,a,arguments.length)},append:function(){return this.domManip(arguments,function(a){if(1===this.nodeType||11===this.nodeType||9===this.nodeType){var b=jb(this,a);b.appendChild(a)}})},prepend:function(){return this.domManip(arguments,function(a){if(1===this.nodeType||11===this.nodeType||9===this.nodeType){var b=jb(this,a);b.insertBefore(a,b.firstChild)}})},before:function(){return this.domManip(arguments,function(a){this.parentNode&&this.parentNode.insertBefore(a,this)})},after:function(){return this.domManip(arguments,function(a){this.parentNode&&this.parentNode.insertBefore(a,this.nextSibling)})},remove:function(a,b){for(var c,d=a?o.filter(a,this):this,e=0;null!=(c=d[e]);e++)b||1!==c.nodeType||o.cleanData(ob(c)),c.parentNode&&(b&&o.contains(c.ownerDocument,c)&&mb(ob(c,"script")),c.parentNode.removeChild(c));return this},empty:function(){for(var a,b=0;null!=(a=this[b]);b++)1===a.nodeType&&(o.cleanData(ob(a,!1)),a.textContent="");return this},clone:function(a,b){return a=null==a?!1:a,b=null==b?a:b,this.map(function(){return o.clone(this,a,b)})},html:function(a){return J(this,function(a){var b=this[0]||{},c=0,d=this.length;if(void 0===a&&1===b.nodeType)return b.innerHTML;if("string"==typeof a&&!db.test(a)&&!ib[(bb.exec(a)||["",""])[1].toLowerCase()]){a=a.replace(ab,"<$1></$2>");try{for(;d>c;c++)b=this[c]||{},1===b.nodeType&&(o.cleanData(ob(b,!1)),b.innerHTML=a);b=0}catch(e){}}b&&this.empty().append(a)},null,a,arguments.length)},replaceWith:function(){var a=arguments[0];return this.domManip(arguments,function(b){a=this.parentNode,o.cleanData(ob(this)),a&&a.replaceChild(b,this)}),a&&(a.length||a.nodeType)?this:this.remove()},detach:function(a){return this.remove(a,!0)},domManip:function(a,b){a=e.apply([],a);var c,d,f,g,h,i,j=0,k=this.length,m=this,n=k-1,p=a[0],q=o.isFunction(p);if(q||k>1&&"string"==typeof p&&!l.checkClone&&eb.test(p))return this.each(function(c){var d=m.eq(c);q&&(a[0]=p.call(this,c,d.html())),d.domManip(a,b)});if(k&&(c=o.buildFragment(a,this[0].ownerDocument,!1,this),d=c.firstChild,1===c.childNodes.length&&(c=d),d)){for(f=o.map(ob(c,"script"),kb),g=f.length;k>j;j++)h=c,j!==n&&(h=o.clone(h,!0,!0),g&&o.merge(f,ob(h,"script"))),b.call(this[j],h,j);if(g)for(i=f[f.length-1].ownerDocument,o.map(f,lb),j=0;g>j;j++)h=f[j],fb.test(h.type||"")&&!L.access(h,"globalEval")&&o.contains(i,h)&&(h.src?o._evalUrl&&o._evalUrl(h.src):o.globalEval(h.textContent.replace(hb,"")))}return this}}),o.each({appendTo:"append",prependTo:"prepend",insertBefore:"before",insertAfter:"after",replaceAll:"replaceWith"},function(a,b){o.fn[a]=function(a){for(var c,d=[],e=o(a),g=e.length-1,h=0;g>=h;h++)c=h===g?this:this.clone(!0),o(e[h])[b](c),f.apply(d,c.get());return this.pushStack(d)}});var qb,rb={};function sb(b,c){var d=o(c.createElement(b)).appendTo(c.body),e=a.getDefaultComputedStyle?a.getDefaultComputedStyle(d[0]).display:o.css(d[0],"display");return d.detach(),e}function tb(a){var b=m,c=rb[a];return c||(c=sb(a,b),"none"!==c&&c||(qb=(qb||o("<iframe frameborder='0' width='0' height='0'/>")).appendTo(b.documentElement),b=qb[0].contentDocument,b.write(),b.close(),c=sb(a,b),qb.detach()),rb[a]=c),c}var ub=/^margin/,vb=new RegExp("^("+Q+")(?!px)[a-z%]+$","i"),wb=function(a){return a.ownerDocument.defaultView.getComputedStyle(a,null)};function xb(a,b,c){var d,e,f,g,h=a.style;return c=c||wb(a),c&&(g=c.getPropertyValue(b)||c[b]),c&&(""!==g||o.contains(a.ownerDocument,a)||(g=o.style(a,b)),vb.test(g)&&ub.test(b)&&(d=h.width,e=h.minWidth,f=h.maxWidth,h.minWidth=h.maxWidth=h.width=g,g=c.width,h.width=d,h.minWidth=e,h.maxWidth=f)),void 0!==g?g+"":g}function yb(a,b){return{get:function(){return a()?void delete this.get:(this.get=b).apply(this,arguments)}}}!function(){var b,c,d="padding:0;margin:0;border:0;display:block;-webkit-box-sizing:content-box;-moz-box-sizing:content-box;box-sizing:content-box",e=m.documentElement,f=m.createElement("div"),g=m.createElement("div");g.style.backgroundClip="content-box",g.cloneNode(!0).style.backgroundClip="",l.clearCloneStyle="content-box"===g.style.backgroundClip,f.style.cssText="border:0;width:0;height:0;position:absolute;top:0;left:-9999px;margin-top:1px",f.appendChild(g);function h(){g.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%",e.appendChild(f);var d=a.getComputedStyle(g,null);b="1%"!==d.top,c="4px"===d.width,e.removeChild(f)}a.getComputedStyle&&o.extend(l,{pixelPosition:function(){return h(),b},boxSizingReliable:function(){return null==c&&h(),c},reliableMarginRight:function(){var b,c=g.appendChild(m.createElement("div"));return c.style.cssText=g.style.cssText=d,c.style.marginRight=c.style.width="0",g.style.width="1px",e.appendChild(f),b=!parseFloat(a.getComputedStyle(c,null).marginRight),e.removeChild(f),g.innerHTML="",b}})}(),o.swap=function(a,b,c,d){var e,f,g={};for(f in b)g[f]=a.style[f],a.style[f]=b[f];e=c.apply(a,d||[]);for(f in b)a.style[f]=g[f];return e};var zb=/^(none|table(?!-c[ea]).+)/,Ab=new RegExp("^("+Q+")(.*)$","i"),Bb=new RegExp("^([+-])=("+Q+")","i"),Cb={position:"absolute",visibility:"hidden",display:"block"},Db={letterSpacing:0,fontWeight:400},Eb=["Webkit","O","Moz","ms"];function Fb(a,b){if(b in a)return b;var c=b[0].toUpperCase()+b.slice(1),d=b,e=Eb.length;while(e--)if(b=Eb[e]+c,b in a)return b;return d}function Gb(a,b,c){var d=Ab.exec(b);return d?Math.max(0,d[1]-(c||0))+(d[2]||"px"):b}function Hb(a,b,c,d,e){for(var f=c===(d?"border":"content")?4:"width"===b?1:0,g=0;4>f;f+=2)"margin"===c&&(g+=o.css(a,c+R[f],!0,e)),d?("content"===c&&(g-=o.css(a,"padding"+R[f],!0,e)),"margin"!==c&&(g-=o.css(a,"border"+R[f]+"Width",!0,e))):(g+=o.css(a,"padding"+R[f],!0,e),"padding"!==c&&(g+=o.css(a,"border"+R[f]+"Width",!0,e)));return g}function Ib(a,b,c){var d=!0,e="width"===b?a.offsetWidth:a.offsetHeight,f=wb(a),g="border-box"===o.css(a,"boxSizing",!1,f);if(0>=e||null==e){if(e=xb(a,b,f),(0>e||null==e)&&(e=a.style[b]),vb.test(e))return e;d=g&&(l.boxSizingReliable()||e===a.style[b]),e=parseFloat(e)||0}return e+Hb(a,b,c||(g?"border":"content"),d,f)+"px"}function Jb(a,b){for(var c,d,e,f=[],g=0,h=a.length;h>g;g++)d=a[g],d.style&&(f[g]=L.get(d,"olddisplay"),c=d.style.display,b?(f[g]||"none"!==c||(d.style.display=""),""===d.style.display&&S(d)&&(f[g]=L.access(d,"olddisplay",tb(d.nodeName)))):f[g]||(e=S(d),(c&&"none"!==c||!e)&&L.set(d,"olddisplay",e?c:o.css(d,"display"))));for(g=0;h>g;g++)d=a[g],d.style&&(b&&"none"!==d.style.display&&""!==d.style.display||(d.style.display=b?f[g]||"":"none"));return a}o.extend({cssHooks:{opacity:{get:function(a,b){if(b){var c=xb(a,"opacity");return""===c?"1":c}}}},cssNumber:{columnCount:!0,fillOpacity:!0,fontWeight:!0,lineHeight:!0,opacity:!0,order:!0,orphans:!0,widows:!0,zIndex:!0,zoom:!0},cssProps:{"float":"cssFloat"},style:function(a,b,c,d){if(a&&3!==a.nodeType&&8!==a.nodeType&&a.style){var e,f,g,h=o.camelCase(b),i=a.style;return b=o.cssProps[h]||(o.cssProps[h]=Fb(i,h)),g=o.cssHooks[b]||o.cssHooks[h],void 0===c?g&&"get"in g&&void 0!==(e=g.get(a,!1,d))?e:i[b]:(f=typeof c,"string"===f&&(e=Bb.exec(c))&&(c=(e[1]+1)*e[2]+parseFloat(o.css(a,b)),f="number"),null!=c&&c===c&&("number"!==f||o.cssNumber[h]||(c+="px"),l.clearCloneStyle||""!==c||0!==b.indexOf("background")||(i[b]="inherit"),g&&"set"in g&&void 0===(c=g.set(a,c,d))||(i[b]="",i[b]=c)),void 0)}},css:function(a,b,c,d){var e,f,g,h=o.camelCase(b);return b=o.cssProps[h]||(o.cssProps[h]=Fb(a.style,h)),g=o.cssHooks[b]||o.cssHooks[h],g&&"get"in g&&(e=g.get(a,!0,c)),void 0===e&&(e=xb(a,b,d)),"normal"===e&&b in Db&&(e=Db[b]),""===c||c?(f=parseFloat(e),c===!0||o.isNumeric(f)?f||0:e):e}}),o.each(["height","width"],function(a,b){o.cssHooks[b]={get:function(a,c,d){return c?0===a.offsetWidth&&zb.test(o.css(a,"display"))?o.swap(a,Cb,function(){return Ib(a,b,d)}):Ib(a,b,d):void 0},set:function(a,c,d){var e=d&&wb(a);return Gb(a,c,d?Hb(a,b,d,"border-box"===o.css(a,"boxSizing",!1,e),e):0)}}}),o.cssHooks.marginRight=yb(l.reliableMarginRight,function(a,b){return b?o.swap(a,{display:"inline-block"},xb,[a,"marginRight"]):void 0}),o.each({margin:"",padding:"",border:"Width"},function(a,b){o.cssHooks[a+b]={expand:function(c){for(var d=0,e={},f="string"==typeof c?c.split(" "):[c];4>d;d++)e[a+R[d]+b]=f[d]||f[d-2]||f[0];return e}},ub.test(a)||(o.cssHooks[a+b].set=Gb)}),o.fn.extend({css:function(a,b){return J(this,function(a,b,c){var d,e,f={},g=0;if(o.isArray(b)){for(d=wb(a),e=b.length;e>g;g++)f[b[g]]=o.css(a,b[g],!1,d);return f}return void 0!==c?o.style(a,b,c):o.css(a,b)},a,b,arguments.length>1)},show:function(){return Jb(this,!0)},hide:function(){return Jb(this)},toggle:function(a){return"boolean"==typeof a?a?this.show():this.hide():this.each(function(){S(this)?o(this).show():o(this).hide()})}});function Kb(a,b,c,d,e){return new Kb.prototype.init(a,b,c,d,e)}o.Tween=Kb,Kb.prototype={constructor:Kb,init:function(a,b,c,d,e,f){this.elem=a,this.prop=c,this.easing=e||"swing",this.options=b,this.start=this.now=this.cur(),this.end=d,this.unit=f||(o.cssNumber[c]?"":"px")},cur:function(){var a=Kb.propHooks[this.prop];return a&&a.get?a.get(this):Kb.propHooks._default.get(this)},run:function(a){var b,c=Kb.propHooks[this.prop];return this.pos=b=this.options.duration?o.easing[this.easing](a,this.options.duration*a,0,1,this.options.duration):a,this.now=(this.end-this.start)*b+this.start,this.options.step&&this.options.step.call(this.elem,this.now,this),c&&c.set?c.set(this):Kb.propHooks._default.set(this),this}},Kb.prototype.init.prototype=Kb.prototype,Kb.propHooks={_default:{get:function(a){var b;return null==a.elem[a.prop]||a.elem.style&&null!=a.elem.style[a.prop]?(b=o.css(a.elem,a.prop,""),b&&"auto"!==b?b:0):a.elem[a.prop]},set:function(a){o.fx.step[a.prop]?o.fx.step[a.prop](a):a.elem.style&&(null!=a.elem.style[o.cssProps[a.prop]]||o.cssHooks[a.prop])?o.style(a.elem,a.prop,a.now+a.unit):a.elem[a.prop]=a.now}}},Kb.propHooks.scrollTop=Kb.propHooks.scrollLeft={set:function(a){a.elem.nodeType&&a.elem.parentNode&&(a.elem[a.prop]=a.now)}},o.easing={linear:function(a){return a},swing:function(a){return.5-Math.cos(a*Math.PI)/2}},o.fx=Kb.prototype.init,o.fx.step={};var Lb,Mb,Nb=/^(?:toggle|show|hide)$/,Ob=new RegExp("^(?:([+-])=|)("+Q+")([a-z%]*)$","i"),Pb=/queueHooks$/,Qb=[Vb],Rb={"*":[function(a,b){var c=this.createTween(a,b),d=c.cur(),e=Ob.exec(b),f=e&&e[3]||(o.cssNumber[a]?"":"px"),g=(o.cssNumber[a]||"px"!==f&&+d)&&Ob.exec(o.css(c.elem,a)),h=1,i=20;if(g&&g[3]!==f){f=f||g[3],e=e||[],g=+d||1;do h=h||".5",g/=h,o.style(c.elem,a,g+f);while(h!==(h=c.cur()/d)&&1!==h&&--i)}return e&&(g=c.start=+g||+d||0,c.unit=f,c.end=e[1]?g+(e[1]+1)*e[2]:+e[2]),c}]};function Sb(){return setTimeout(function(){Lb=void 0}),Lb=o.now()}function Tb(a,b){var c,d=0,e={height:a};for(b=b?1:0;4>d;d+=2-b)c=R[d],e["margin"+c]=e["padding"+c]=a;return b&&(e.opacity=e.width=a),e}function Ub(a,b,c){for(var d,e=(Rb[b]||[]).concat(Rb["*"]),f=0,g=e.length;g>f;f++)if(d=e[f].call(c,b,a))return d}function Vb(a,b,c){var d,e,f,g,h,i,j,k=this,l={},m=a.style,n=a.nodeType&&S(a),p=L.get(a,"fxshow");c.queue||(h=o._queueHooks(a,"fx"),null==h.unqueued&&(h.unqueued=0,i=h.empty.fire,h.empty.fire=function(){h.unqueued||i()}),h.unqueued++,k.always(function(){k.always(function(){h.unqueued--,o.queue(a,"fx").length||h.empty.fire()})})),1===a.nodeType&&("height"in b||"width"in b)&&(c.overflow=[m.overflow,m.overflowX,m.overflowY],j=o.css(a,"display"),"none"===j&&(j=tb(a.nodeName)),"inline"===j&&"none"===o.css(a,"float")&&(m.display="inline-block")),c.overflow&&(m.overflow="hidden",k.always(function(){m.overflow=c.overflow[0],m.overflowX=c.overflow[1],m.overflowY=c.overflow[2]}));for(d in b)if(e=b[d],Nb.exec(e)){if(delete b[d],f=f||"toggle"===e,e===(n?"hide":"show")){if("show"!==e||!p||void 0===p[d])continue;n=!0}l[d]=p&&p[d]||o.style(a,d)}if(!o.isEmptyObject(l)){p?"hidden"in p&&(n=p.hidden):p=L.access(a,"fxshow",{}),f&&(p.hidden=!n),n?o(a).show():k.done(function(){o(a).hide()}),k.done(function(){var b;L.remove(a,"fxshow");for(b in l)o.style(a,b,l[b])});for(d in l)g=Ub(n?p[d]:0,d,k),d in p||(p[d]=g.start,n&&(g.end=g.start,g.start="width"===d||"height"===d?1:0))}}function Wb(a,b){var c,d,e,f,g;for(c in a)if(d=o.camelCase(c),e=b[d],f=a[c],o.isArray(f)&&(e=f[1],f=a[c]=f[0]),c!==d&&(a[d]=f,delete a[c]),g=o.cssHooks[d],g&&"expand"in g){f=g.expand(f),delete a[d];for(c in f)c in a||(a[c]=f[c],b[c]=e)}else b[d]=e}function Xb(a,b,c){var d,e,f=0,g=Qb.length,h=o.Deferred().always(function(){delete i.elem}),i=function(){if(e)return!1;for(var b=Lb||Sb(),c=Math.max(0,j.startTime+j.duration-b),d=c/j.duration||0,f=1-d,g=0,i=j.tweens.length;i>g;g++)j.tweens[g].run(f);return h.notifyWith(a,[j,f,c]),1>f&&i?c:(h.resolveWith(a,[j]),!1)},j=h.promise({elem:a,props:o.extend({},b),opts:o.extend(!0,{specialEasing:{}},c),originalProperties:b,originalOptions:c,startTime:Lb||Sb(),duration:c.duration,tweens:[],createTween:function(b,c){var d=o.Tween(a,j.opts,b,c,j.opts.specialEasing[b]||j.opts.easing);return j.tweens.push(d),d},stop:function(b){var c=0,d=b?j.tweens.length:0;if(e)return this;for(e=!0;d>c;c++)j.tweens[c].run(1);return b?h.resolveWith(a,[j,b]):h.rejectWith(a,[j,b]),this}}),k=j.props;for(Wb(k,j.opts.specialEasing);g>f;f++)if(d=Qb[f].call(j,a,k,j.opts))return d;return o.map(k,Ub,j),o.isFunction(j.opts.start)&&j.opts.start.call(a,j),o.fx.timer(o.extend(i,{elem:a,anim:j,queue:j.opts.queue})),j.progress(j.opts.progress).done(j.opts.done,j.opts.complete).fail(j.opts.fail).always(j.opts.always)}o.Animation=o.extend(Xb,{tweener:function(a,b){o.isFunction(a)?(b=a,a=["*"]):a=a.split(" ");for(var c,d=0,e=a.length;e>d;d++)c=a[d],Rb[c]=Rb[c]||[],Rb[c].unshift(b)},prefilter:function(a,b){b?Qb.unshift(a):Qb.push(a)}}),o.speed=function(a,b,c){var d=a&&"object"==typeof a?o.extend({},a):{complete:c||!c&&b||o.isFunction(a)&&a,duration:a,easing:c&&b||b&&!o.isFunction(b)&&b};return d.duration=o.fx.off?0:"number"==typeof d.duration?d.duration:d.duration in o.fx.speeds?o.fx.speeds[d.duration]:o.fx.speeds._default,(null==d.queue||d.queue===!0)&&(d.queue="fx"),d.old=d.complete,d.complete=function(){o.isFunction(d.old)&&d.old.call(this),d.queue&&o.dequeue(this,d.queue)},d},o.fn.extend({fadeTo:function(a,b,c,d){return this.filter(S).css("opacity",0).show().end().animate({opacity:b},a,c,d)},animate:function(a,b,c,d){var e=o.isEmptyObject(a),f=o.speed(b,c,d),g=function(){var b=Xb(this,o.extend({},a),f);(e||L.get(this,"finish"))&&b.stop(!0)};return g.finish=g,e||f.queue===!1?this.each(g):this.queue(f.queue,g)},stop:function(a,b,c){var d=function(a){var b=a.stop;delete a.stop,b(c)};return"string"!=typeof a&&(c=b,b=a,a=void 0),b&&a!==!1&&this.queue(a||"fx",[]),this.each(function(){var b=!0,e=null!=a&&a+"queueHooks",f=o.timers,g=L.get(this);if(e)g[e]&&g[e].stop&&d(g[e]);else for(e in g)g[e]&&g[e].stop&&Pb.test(e)&&d(g[e]);for(e=f.length;e--;)f[e].elem!==this||null!=a&&f[e].queue!==a||(f[e].anim.stop(c),b=!1,f.splice(e,1));(b||!c)&&o.dequeue(this,a)})},finish:function(a){return a!==!1&&(a=a||"fx"),this.each(function(){var b,c=L.get(this),d=c[a+"queue"],e=c[a+"queueHooks"],f=o.timers,g=d?d.length:0;for(c.finish=!0,o.queue(this,a,[]),e&&e.stop&&e.stop.call(this,!0),b=f.length;b--;)f[b].elem===this&&f[b].queue===a&&(f[b].anim.stop(!0),f.splice(b,1));for(b=0;g>b;b++)d[b]&&d[b].finish&&d[b].finish.call(this);delete c.finish})}}),o.each(["toggle","show","hide"],function(a,b){var c=o.fn[b];o.fn[b]=function(a,d,e){return null==a||"boolean"==typeof a?c.apply(this,arguments):this.animate(Tb(b,!0),a,d,e)}}),o.each({slideDown:Tb("show"),slideUp:Tb("hide"),slideToggle:Tb("toggle"),fadeIn:{opacity:"show"},fadeOut:{opacity:"hide"},fadeToggle:{opacity:"toggle"}},function(a,b){o.fn[a]=function(a,c,d){return this.animate(b,a,c,d)}}),o.timers=[],o.fx.tick=function(){var a,b=0,c=o.timers;for(Lb=o.now();b<c.length;b++)a=c[b],a()||c[b]!==a||c.splice(b--,1);c.length||o.fx.stop(),Lb=void 0},o.fx.timer=function(a){o.timers.push(a),a()?o.fx.start():o.timers.pop()},o.fx.interval=13,o.fx.start=function(){Mb||(Mb=setInterval(o.fx.tick,o.fx.interval))},o.fx.stop=function(){clearInterval(Mb),Mb=null},o.fx.speeds={slow:600,fast:200,_default:400},o.fn.delay=function(a,b){return a=o.fx?o.fx.speeds[a]||a:a,b=b||"fx",this.queue(b,function(b,c){var d=setTimeout(b,a);c.stop=function(){clearTimeout(d)}})},function(){var a=m.createElement("input"),b=m.createElement("select"),c=b.appendChild(m.createElement("option"));a.type="checkbox",l.checkOn=""!==a.value,l.optSelected=c.selected,b.disabled=!0,l.optDisabled=!c.disabled,a=m.createElement("input"),a.value="t",a.type="radio",l.radioValue="t"===a.value}();var Yb,Zb,$b=o.expr.attrHandle;o.fn.extend({attr:function(a,b){return J(this,o.attr,a,b,arguments.length>1)},removeAttr:function(a){return this.each(function(){o.removeAttr(this,a)})}}),o.extend({attr:function(a,b,c){var d,e,f=a.nodeType;if(a&&3!==f&&8!==f&&2!==f)return typeof a.getAttribute===U?o.prop(a,b,c):(1===f&&o.isXMLDoc(a)||(b=b.toLowerCase(),d=o.attrHooks[b]||(o.expr.match.bool.test(b)?Zb:Yb)),void 0===c?d&&"get"in d&&null!==(e=d.get(a,b))?e:(e=o.find.attr(a,b),null==e?void 0:e):null!==c?d&&"set"in d&&void 0!==(e=d.set(a,c,b))?e:(a.setAttribute(b,c+""),c):void o.removeAttr(a,b))},removeAttr:function(a,b){var c,d,e=0,f=b&&b.match(E);if(f&&1===a.nodeType)while(c=f[e++])d=o.propFix[c]||c,o.expr.match.bool.test(c)&&(a[d]=!1),a.removeAttribute(c)},attrHooks:{type:{set:function(a,b){if(!l.radioValue&&"radio"===b&&o.nodeName(a,"input")){var c=a.value;return a.setAttribute("type",b),c&&(a.value=c),b}}}}}),Zb={set:function(a,b,c){return b===!1?o.removeAttr(a,c):a.setAttribute(c,c),c}},o.each(o.expr.match.bool.source.match(/\w+/g),function(a,b){var c=$b[b]||o.find.attr;$b[b]=function(a,b,d){var e,f;
+return d||(f=$b[b],$b[b]=e,e=null!=c(a,b,d)?b.toLowerCase():null,$b[b]=f),e}});var _b=/^(?:input|select|textarea|button)$/i;o.fn.extend({prop:function(a,b){return J(this,o.prop,a,b,arguments.length>1)},removeProp:function(a){return this.each(function(){delete this[o.propFix[a]||a]})}}),o.extend({propFix:{"for":"htmlFor","class":"className"},prop:function(a,b,c){var d,e,f,g=a.nodeType;if(a&&3!==g&&8!==g&&2!==g)return f=1!==g||!o.isXMLDoc(a),f&&(b=o.propFix[b]||b,e=o.propHooks[b]),void 0!==c?e&&"set"in e&&void 0!==(d=e.set(a,c,b))?d:a[b]=c:e&&"get"in e&&null!==(d=e.get(a,b))?d:a[b]},propHooks:{tabIndex:{get:function(a){return a.hasAttribute("tabindex")||_b.test(a.nodeName)||a.href?a.tabIndex:-1}}}}),l.optSelected||(o.propHooks.selected={get:function(a){var b=a.parentNode;return b&&b.parentNode&&b.parentNode.selectedIndex,null}}),o.each(["tabIndex","readOnly","maxLength","cellSpacing","cellPadding","rowSpan","colSpan","useMap","frameBorder","contentEditable"],function(){o.propFix[this.toLowerCase()]=this});var ac=/[\t\r\n\f]/g;o.fn.extend({addClass:function(a){var b,c,d,e,f,g,h="string"==typeof a&&a,i=0,j=this.length;if(o.isFunction(a))return this.each(function(b){o(this).addClass(a.call(this,b,this.className))});if(h)for(b=(a||"").match(E)||[];j>i;i++)if(c=this[i],d=1===c.nodeType&&(c.className?(" "+c.className+" ").replace(ac," "):" ")){f=0;while(e=b[f++])d.indexOf(" "+e+" ")<0&&(d+=e+" ");g=o.trim(d),c.className!==g&&(c.className=g)}return this},removeClass:function(a){var b,c,d,e,f,g,h=0===arguments.length||"string"==typeof a&&a,i=0,j=this.length;if(o.isFunction(a))return this.each(function(b){o(this).removeClass(a.call(this,b,this.className))});if(h)for(b=(a||"").match(E)||[];j>i;i++)if(c=this[i],d=1===c.nodeType&&(c.className?(" "+c.className+" ").replace(ac," "):"")){f=0;while(e=b[f++])while(d.indexOf(" "+e+" ")>=0)d=d.replace(" "+e+" "," ");g=a?o.trim(d):"",c.className!==g&&(c.className=g)}return this},toggleClass:function(a,b){var c=typeof a;return"boolean"==typeof b&&"string"===c?b?this.addClass(a):this.removeClass(a):this.each(o.isFunction(a)?function(c){o(this).toggleClass(a.call(this,c,this.className,b),b)}:function(){if("string"===c){var b,d=0,e=o(this),f=a.match(E)||[];while(b=f[d++])e.hasClass(b)?e.removeClass(b):e.addClass(b)}else(c===U||"boolean"===c)&&(this.className&&L.set(this,"__className__",this.className),this.className=this.className||a===!1?"":L.get(this,"__className__")||"")})},hasClass:function(a){for(var b=" "+a+" ",c=0,d=this.length;d>c;c++)if(1===this[c].nodeType&&(" "+this[c].className+" ").replace(ac," ").indexOf(b)>=0)return!0;return!1}});var bc=/\r/g;o.fn.extend({val:function(a){var b,c,d,e=this[0];{if(arguments.length)return d=o.isFunction(a),this.each(function(c){var e;1===this.nodeType&&(e=d?a.call(this,c,o(this).val()):a,null==e?e="":"number"==typeof e?e+="":o.isArray(e)&&(e=o.map(e,function(a){return null==a?"":a+""})),b=o.valHooks[this.type]||o.valHooks[this.nodeName.toLowerCase()],b&&"set"in b&&void 0!==b.set(this,e,"value")||(this.value=e))});if(e)return b=o.valHooks[e.type]||o.valHooks[e.nodeName.toLowerCase()],b&&"get"in b&&void 0!==(c=b.get(e,"value"))?c:(c=e.value,"string"==typeof c?c.replace(bc,""):null==c?"":c)}}}),o.extend({valHooks:{select:{get:function(a){for(var b,c,d=a.options,e=a.selectedIndex,f="select-one"===a.type||0>e,g=f?null:[],h=f?e+1:d.length,i=0>e?h:f?e:0;h>i;i++)if(c=d[i],!(!c.selected&&i!==e||(l.optDisabled?c.disabled:null!==c.getAttribute("disabled"))||c.parentNode.disabled&&o.nodeName(c.parentNode,"optgroup"))){if(b=o(c).val(),f)return b;g.push(b)}return g},set:function(a,b){var c,d,e=a.options,f=o.makeArray(b),g=e.length;while(g--)d=e[g],(d.selected=o.inArray(o(d).val(),f)>=0)&&(c=!0);return c||(a.selectedIndex=-1),f}}}}),o.each(["radio","checkbox"],function(){o.valHooks[this]={set:function(a,b){return o.isArray(b)?a.checked=o.inArray(o(a).val(),b)>=0:void 0}},l.checkOn||(o.valHooks[this].get=function(a){return null===a.getAttribute("value")?"on":a.value})}),o.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(a,b){o.fn[b]=function(a,c){return arguments.length>0?this.on(b,null,a,c):this.trigger(b)}}),o.fn.extend({hover:function(a,b){return this.mouseenter(a).mouseleave(b||a)},bind:function(a,b,c){return this.on(a,null,b,c)},unbind:function(a,b){return this.off(a,null,b)},delegate:function(a,b,c,d){return this.on(b,a,c,d)},undelegate:function(a,b,c){return 1===arguments.length?this.off(a,"**"):this.off(b,a||"**",c)}});var cc=o.now(),dc=/\?/;o.parseJSON=function(a){return JSON.parse(a+"")},o.parseXML=function(a){var b,c;if(!a||"string"!=typeof a)return null;try{c=new DOMParser,b=c.parseFromString(a,"text/xml")}catch(d){b=void 0}return(!b||b.getElementsByTagName("parsererror").length)&&o.error("Invalid XML: "+a),b};var ec,fc,gc=/#.*$/,hc=/([?&])_=[^&]*/,ic=/^(.*?):[ \t]*([^\r\n]*)$/gm,jc=/^(?:about|app|app-storage|.+-extension|file|res|widget):$/,kc=/^(?:GET|HEAD)$/,lc=/^\/\//,mc=/^([\w.+-]+:)(?:\/\/(?:[^\/?#]*@|)([^\/?#:]*)(?::(\d+)|)|)/,nc={},oc={},pc="*/".concat("*");try{fc=location.href}catch(qc){fc=m.createElement("a"),fc.href="",fc=fc.href}ec=mc.exec(fc.toLowerCase())||[];function rc(a){return function(b,c){"string"!=typeof b&&(c=b,b="*");var d,e=0,f=b.toLowerCase().match(E)||[];if(o.isFunction(c))while(d=f[e++])"+"===d[0]?(d=d.slice(1)||"*",(a[d]=a[d]||[]).unshift(c)):(a[d]=a[d]||[]).push(c)}}function sc(a,b,c,d){var e={},f=a===oc;function g(h){var i;return e[h]=!0,o.each(a[h]||[],function(a,h){var j=h(b,c,d);return"string"!=typeof j||f||e[j]?f?!(i=j):void 0:(b.dataTypes.unshift(j),g(j),!1)}),i}return g(b.dataTypes[0])||!e["*"]&&g("*")}function tc(a,b){var c,d,e=o.ajaxSettings.flatOptions||{};for(c in b)void 0!==b[c]&&((e[c]?a:d||(d={}))[c]=b[c]);return d&&o.extend(!0,a,d),a}function uc(a,b,c){var d,e,f,g,h=a.contents,i=a.dataTypes;while("*"===i[0])i.shift(),void 0===d&&(d=a.mimeType||b.getResponseHeader("Content-Type"));if(d)for(e in h)if(h[e]&&h[e].test(d)){i.unshift(e);break}if(i[0]in c)f=i[0];else{for(e in c){if(!i[0]||a.converters[e+" "+i[0]]){f=e;break}g||(g=e)}f=f||g}return f?(f!==i[0]&&i.unshift(f),c[f]):void 0}function vc(a,b,c,d){var e,f,g,h,i,j={},k=a.dataTypes.slice();if(k[1])for(g in a.converters)j[g.toLowerCase()]=a.converters[g];f=k.shift();while(f)if(a.responseFields[f]&&(c[a.responseFields[f]]=b),!i&&d&&a.dataFilter&&(b=a.dataFilter(b,a.dataType)),i=f,f=k.shift())if("*"===f)f=i;else if("*"!==i&&i!==f){if(g=j[i+" "+f]||j["* "+f],!g)for(e in j)if(h=e.split(" "),h[1]===f&&(g=j[i+" "+h[0]]||j["* "+h[0]])){g===!0?g=j[e]:j[e]!==!0&&(f=h[0],k.unshift(h[1]));break}if(g!==!0)if(g&&a["throws"])b=g(b);else try{b=g(b)}catch(l){return{state:"parsererror",error:g?l:"No conversion from "+i+" to "+f}}}return{state:"success",data:b}}o.extend({active:0,lastModified:{},etag:{},ajaxSettings:{url:fc,type:"GET",isLocal:jc.test(ec[1]),global:!0,processData:!0,async:!0,contentType:"application/x-www-form-urlencoded; charset=UTF-8",accepts:{"*":pc,text:"text/plain",html:"text/html",xml:"application/xml, text/xml",json:"application/json, text/javascript"},contents:{xml:/xml/,html:/html/,json:/json/},responseFields:{xml:"responseXML",text:"responseText",json:"responseJSON"},converters:{"* text":String,"text html":!0,"text json":o.parseJSON,"text xml":o.parseXML},flatOptions:{url:!0,context:!0}},ajaxSetup:function(a,b){return b?tc(tc(a,o.ajaxSettings),b):tc(o.ajaxSettings,a)},ajaxPrefilter:rc(nc),ajaxTransport:rc(oc),ajax:function(a,b){"object"==typeof a&&(b=a,a=void 0),b=b||{};var c,d,e,f,g,h,i,j,k=o.ajaxSetup({},b),l=k.context||k,m=k.context&&(l.nodeType||l.jquery)?o(l):o.event,n=o.Deferred(),p=o.Callbacks("once memory"),q=k.statusCode||{},r={},s={},t=0,u="canceled",v={readyState:0,getResponseHeader:function(a){var b;if(2===t){if(!f){f={};while(b=ic.exec(e))f[b[1].toLowerCase()]=b[2]}b=f[a.toLowerCase()]}return null==b?null:b},getAllResponseHeaders:function(){return 2===t?e:null},setRequestHeader:function(a,b){var c=a.toLowerCase();return t||(a=s[c]=s[c]||a,r[a]=b),this},overrideMimeType:function(a){return t||(k.mimeType=a),this},statusCode:function(a){var b;if(a)if(2>t)for(b in a)q[b]=[q[b],a[b]];else v.always(a[v.status]);return this},abort:function(a){var b=a||u;return c&&c.abort(b),x(0,b),this}};if(n.promise(v).complete=p.add,v.success=v.done,v.error=v.fail,k.url=((a||k.url||fc)+"").replace(gc,"").replace(lc,ec[1]+"//"),k.type=b.method||b.type||k.method||k.type,k.dataTypes=o.trim(k.dataType||"*").toLowerCase().match(E)||[""],null==k.crossDomain&&(h=mc.exec(k.url.toLowerCase()),k.crossDomain=!(!h||h[1]===ec[1]&&h[2]===ec[2]&&(h[3]||("http:"===h[1]?"80":"443"))===(ec[3]||("http:"===ec[1]?"80":"443")))),k.data&&k.processData&&"string"!=typeof k.data&&(k.data=o.param(k.data,k.traditional)),sc(nc,k,b,v),2===t)return v;i=k.global,i&&0===o.active++&&o.event.trigger("ajaxStart"),k.type=k.type.toUpperCase(),k.hasContent=!kc.test(k.type),d=k.url,k.hasContent||(k.data&&(d=k.url+=(dc.test(d)?"&":"?")+k.data,delete k.data),k.cache===!1&&(k.url=hc.test(d)?d.replace(hc,"$1_="+cc++):d+(dc.test(d)?"&":"?")+"_="+cc++)),k.ifModified&&(o.lastModified[d]&&v.setRequestHeader("If-Modified-Since",o.lastModified[d]),o.etag[d]&&v.setRequestHeader("If-None-Match",o.etag[d])),(k.data&&k.hasContent&&k.contentType!==!1||b.contentType)&&v.setRequestHeader("Content-Type",k.contentType),v.setRequestHeader("Accept",k.dataTypes[0]&&k.accepts[k.dataTypes[0]]?k.accepts[k.dataTypes[0]]+("*"!==k.dataTypes[0]?", "+pc+"; q=0.01":""):k.accepts["*"]);for(j in k.headers)v.setRequestHeader(j,k.headers[j]);if(k.beforeSend&&(k.beforeSend.call(l,v,k)===!1||2===t))return v.abort();u="abort";for(j in{success:1,error:1,complete:1})v[j](k[j]);if(c=sc(oc,k,b,v)){v.readyState=1,i&&m.trigger("ajaxSend",[v,k]),k.async&&k.timeout>0&&(g=setTimeout(function(){v.abort("timeout")},k.timeout));try{t=1,c.send(r,x)}catch(w){if(!(2>t))throw w;x(-1,w)}}else x(-1,"No Transport");function x(a,b,f,h){var j,r,s,u,w,x=b;2!==t&&(t=2,g&&clearTimeout(g),c=void 0,e=h||"",v.readyState=a>0?4:0,j=a>=200&&300>a||304===a,f&&(u=uc(k,v,f)),u=vc(k,u,v,j),j?(k.ifModified&&(w=v.getResponseHeader("Last-Modified"),w&&(o.lastModified[d]=w),w=v.getResponseHeader("etag"),w&&(o.etag[d]=w)),204===a||"HEAD"===k.type?x="nocontent":304===a?x="notmodified":(x=u.state,r=u.data,s=u.error,j=!s)):(s=x,(a||!x)&&(x="error",0>a&&(a=0))),v.status=a,v.statusText=(b||x)+"",j?n.resolveWith(l,[r,x,v]):n.rejectWith(l,[v,x,s]),v.statusCode(q),q=void 0,i&&m.trigger(j?"ajaxSuccess":"ajaxError",[v,k,j?r:s]),p.fireWith(l,[v,x]),i&&(m.trigger("ajaxComplete",[v,k]),--o.active||o.event.trigger("ajaxStop")))}return v},getJSON:function(a,b,c){return o.get(a,b,c,"json")},getScript:function(a,b){return o.get(a,void 0,b,"script")}}),o.each(["get","post"],function(a,b){o[b]=function(a,c,d,e){return o.isFunction(c)&&(e=e||d,d=c,c=void 0),o.ajax({url:a,type:b,dataType:e,data:c,success:d})}}),o.each(["ajaxStart","ajaxStop","ajaxComplete","ajaxError","ajaxSuccess","ajaxSend"],function(a,b){o.fn[b]=function(a){return this.on(b,a)}}),o._evalUrl=function(a){return o.ajax({url:a,type:"GET",dataType:"script",async:!1,global:!1,"throws":!0})},o.fn.extend({wrapAll:function(a){var b;return o.isFunction(a)?this.each(function(b){o(this).wrapAll(a.call(this,b))}):(this[0]&&(b=o(a,this[0].ownerDocument).eq(0).clone(!0),this[0].parentNode&&b.insertBefore(this[0]),b.map(function(){var a=this;while(a.firstElementChild)a=a.firstElementChild;return a}).append(this)),this)},wrapInner:function(a){return this.each(o.isFunction(a)?function(b){o(this).wrapInner(a.call(this,b))}:function(){var b=o(this),c=b.contents();c.length?c.wrapAll(a):b.append(a)})},wrap:function(a){var b=o.isFunction(a);return this.each(function(c){o(this).wrapAll(b?a.call(this,c):a)})},unwrap:function(){return this.parent().each(function(){o.nodeName(this,"body")||o(this).replaceWith(this.childNodes)}).end()}}),o.expr.filters.hidden=function(a){return a.offsetWidth<=0&&a.offsetHeight<=0},o.expr.filters.visible=function(a){return!o.expr.filters.hidden(a)};var wc=/%20/g,xc=/\[\]$/,yc=/\r?\n/g,zc=/^(?:submit|button|image|reset|file)$/i,Ac=/^(?:input|select|textarea|keygen)/i;function Bc(a,b,c,d){var e;if(o.isArray(b))o.each(b,function(b,e){c||xc.test(a)?d(a,e):Bc(a+"["+("object"==typeof e?b:"")+"]",e,c,d)});else if(c||"object"!==o.type(b))d(a,b);else for(e in b)Bc(a+"["+e+"]",b[e],c,d)}o.param=function(a,b){var c,d=[],e=function(a,b){b=o.isFunction(b)?b():null==b?"":b,d[d.length]=encodeURIComponent(a)+"="+encodeURIComponent(b)};if(void 0===b&&(b=o.ajaxSettings&&o.ajaxSettings.traditional),o.isArray(a)||a.jquery&&!o.isPlainObject(a))o.each(a,function(){e(this.name,this.value)});else for(c in a)Bc(c,a[c],b,e);return d.join("&").replace(wc,"+")},o.fn.extend({serialize:function(){return o.param(this.serializeArray())},serializeArray:function(){return this.map(function(){var a=o.prop(this,"elements");return a?o.makeArray(a):this}).filter(function(){var a=this.type;return this.name&&!o(this).is(":disabled")&&Ac.test(this.nodeName)&&!zc.test(a)&&(this.checked||!T.test(a))}).map(function(a,b){var c=o(this).val();return null==c?null:o.isArray(c)?o.map(c,function(a){return{name:b.name,value:a.replace(yc,"\r\n")}}):{name:b.name,value:c.replace(yc,"\r\n")}}).get()}}),o.ajaxSettings.xhr=function(){try{return new XMLHttpRequest}catch(a){}};var Cc=0,Dc={},Ec={0:200,1223:204},Fc=o.ajaxSettings.xhr();a.ActiveXObject&&o(a).on("unload",function(){for(var a in Dc)Dc[a]()}),l.cors=!!Fc&&"withCredentials"in Fc,l.ajax=Fc=!!Fc,o.ajaxTransport(function(a){var b;return l.cors||Fc&&!a.crossDomain?{send:function(c,d){var e,f=a.xhr(),g=++Cc;if(f.open(a.type,a.url,a.async,a.username,a.password),a.xhrFields)for(e in a.xhrFields)f[e]=a.xhrFields[e];a.mimeType&&f.overrideMimeType&&f.overrideMimeType(a.mimeType),a.crossDomain||c["X-Requested-With"]||(c["X-Requested-With"]="XMLHttpRequest");for(e in c)f.setRequestHeader(e,c[e]);b=function(a){return function(){b&&(delete Dc[g],b=f.onload=f.onerror=null,"abort"===a?f.abort():"error"===a?d(f.status,f.statusText):d(Ec[f.status]||f.status,f.statusText,"string"==typeof f.responseText?{text:f.responseText}:void 0,f.getAllResponseHeaders()))}},f.onload=b(),f.onerror=b("error"),b=Dc[g]=b("abort"),f.send(a.hasContent&&a.data||null)},abort:function(){b&&b()}}:void 0}),o.ajaxSetup({accepts:{script:"text/javascript, application/javascript, application/ecmascript, application/x-ecmascript"},contents:{script:/(?:java|ecma)script/},converters:{"text script":function(a){return o.globalEval(a),a}}}),o.ajaxPrefilter("script",function(a){void 0===a.cache&&(a.cache=!1),a.crossDomain&&(a.type="GET")}),o.ajaxTransport("script",function(a){if(a.crossDomain){var b,c;return{send:function(d,e){b=o("<script>").prop({async:!0,charset:a.scriptCharset,src:a.url}).on("load error",c=function(a){b.remove(),c=null,a&&e("error"===a.type?404:200,a.type)}),m.head.appendChild(b[0])},abort:function(){c&&c()}}}});var Gc=[],Hc=/(=)\?(?=&|$)|\?\?/;o.ajaxSetup({jsonp:"callback",jsonpCallback:function(){var a=Gc.pop()||o.expando+"_"+cc++;return this[a]=!0,a}}),o.ajaxPrefilter("json jsonp",function(b,c,d){var e,f,g,h=b.jsonp!==!1&&(Hc.test(b.url)?"url":"string"==typeof b.data&&!(b.contentType||"").indexOf("application/x-www-form-urlencoded")&&Hc.test(b.data)&&"data");return h||"jsonp"===b.dataTypes[0]?(e=b.jsonpCallback=o.isFunction(b.jsonpCallback)?b.jsonpCallback():b.jsonpCallback,h?b[h]=b[h].replace(Hc,"$1"+e):b.jsonp!==!1&&(b.url+=(dc.test(b.url)?"&":"?")+b.jsonp+"="+e),b.converters["script json"]=function(){return g||o.error(e+" was not called"),g[0]},b.dataTypes[0]="json",f=a[e],a[e]=function(){g=arguments},d.always(function(){a[e]=f,b[e]&&(b.jsonpCallback=c.jsonpCallback,Gc.push(e)),g&&o.isFunction(f)&&f(g[0]),g=f=void 0}),"script"):void 0}),o.parseHTML=function(a,b,c){if(!a||"string"!=typeof a)return null;"boolean"==typeof b&&(c=b,b=!1),b=b||m;var d=v.exec(a),e=!c&&[];return d?[b.createElement(d[1])]:(d=o.buildFragment([a],b,e),e&&e.length&&o(e).remove(),o.merge([],d.childNodes))};var Ic=o.fn.load;o.fn.load=function(a,b,c){if("string"!=typeof a&&Ic)return Ic.apply(this,arguments);var d,e,f,g=this,h=a.indexOf(" ");return h>=0&&(d=a.slice(h),a=a.slice(0,h)),o.isFunction(b)?(c=b,b=void 0):b&&"object"==typeof b&&(e="POST"),g.length>0&&o.ajax({url:a,type:e,dataType:"html",data:b}).done(function(a){f=arguments,g.html(d?o("<div>").append(o.parseHTML(a)).find(d):a)}).complete(c&&function(a,b){g.each(c,f||[a.responseText,b,a])}),this},o.expr.filters.animated=function(a){return o.grep(o.timers,function(b){return a===b.elem}).length};var Jc=a.document.documentElement;function Kc(a){return o.isWindow(a)?a:9===a.nodeType&&a.defaultView}o.offset={setOffset:function(a,b,c){var d,e,f,g,h,i,j,k=o.css(a,"position"),l=o(a),m={};"static"===k&&(a.style.position="relative"),h=l.offset(),f=o.css(a,"top"),i=o.css(a,"left"),j=("absolute"===k||"fixed"===k)&&(f+i).indexOf("auto")>-1,j?(d=l.position(),g=d.top,e=d.left):(g=parseFloat(f)||0,e=parseFloat(i)||0),o.isFunction(b)&&(b=b.call(a,c,h)),null!=b.top&&(m.top=b.top-h.top+g),null!=b.left&&(m.left=b.left-h.left+e),"using"in b?b.using.call(a,m):l.css(m)}},o.fn.extend({offset:function(a){if(arguments.length)return void 0===a?this:this.each(function(b){o.offset.setOffset(this,a,b)});var b,c,d=this[0],e={top:0,left:0},f=d&&d.ownerDocument;if(f)return b=f.documentElement,o.contains(b,d)?(typeof d.getBoundingClientRect!==U&&(e=d.getBoundingClientRect()),c=Kc(f),{top:e.top+c.pageYOffset-b.clientTop,left:e.left+c.pageXOffset-b.clientLeft}):e},position:function(){if(this[0]){var a,b,c=this[0],d={top:0,left:0};return"fixed"===o.css(c,"position")?b=c.getBoundingClientRect():(a=this.offsetParent(),b=this.offset(),o.nodeName(a[0],"html")||(d=a.offset()),d.top+=o.css(a[0],"borderTopWidth",!0),d.left+=o.css(a[0],"borderLeftWidth",!0)),{top:b.top-d.top-o.css(c,"marginTop",!0),left:b.left-d.left-o.css(c,"marginLeft",!0)}}},offsetParent:function(){return this.map(function(){var a=this.offsetParent||Jc;while(a&&!o.nodeName(a,"html")&&"static"===o.css(a,"position"))a=a.offsetParent;return a||Jc})}}),o.each({scrollLeft:"pageXOffset",scrollTop:"pageYOffset"},function(b,c){var d="pageYOffset"===c;o.fn[b]=function(e){return J(this,function(b,e,f){var g=Kc(b);return void 0===f?g?g[c]:b[e]:void(g?g.scrollTo(d?a.pageXOffset:f,d?f:a.pageYOffset):b[e]=f)},b,e,arguments.length,null)}}),o.each(["top","left"],function(a,b){o.cssHooks[b]=yb(l.pixelPosition,function(a,c){return c?(c=xb(a,b),vb.test(c)?o(a).position()[b]+"px":c):void 0})}),o.each({Height:"height",Width:"width"},function(a,b){o.each({padding:"inner"+a,content:b,"":"outer"+a},function(c,d){o.fn[d]=function(d,e){var f=arguments.length&&(c||"boolean"!=typeof d),g=c||(d===!0||e===!0?"margin":"border");return J(this,function(b,c,d){var e;return o.isWindow(b)?b.document.documentElement["client"+a]:9===b.nodeType?(e=b.documentElement,Math.max(b.body["scroll"+a],e["scroll"+a],b.body["offset"+a],e["offset"+a],e["client"+a])):void 0===d?o.css(b,c,g):o.style(b,c,d,g)},b,f?d:void 0,f,null)}})}),o.fn.size=function(){return this.length},o.fn.andSelf=o.fn.addBack,"function"==typeof define&&define.amd&&define("jquery",[],function(){return o});var Lc=a.jQuery,Mc=a.$;return o.noConflict=function(b){return a.$===o&&(a.$=Mc),b&&a.jQuery===o&&(a.jQuery=Lc),o},typeof b===U&&(a.jQuery=a.$=o),o});
1
0
Author: kmorin
Date: 2014-04-17 10:10:08 +0200 (Thu, 17 Apr 2014)
New Revision: 698
Url: http://forge.nuiton.org/projects/sandbox/repository/revisions/698
Log:
add folder to test JS UI frameworks
Added:
test-js-frameworks/
1
0