diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..d2aa1dd
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,8 @@
+.sass-cache
+
+# cucumber
+webrat.log
+
+# autogenerated stuff
+site/js/jquery.ui.dial.js
+site/css/style.css
diff --git a/.gitmodules b/.gitmodules
new file mode 100644
index 0000000..88cec7f
--- /dev/null
+++ b/.gitmodules
@@ -0,0 +1,6 @@
+[submodule "vendor/turbosass"]
+ path = vendor/turbosass
+ url = git@github.com:thedjinn/turbosass.git
+[submodule "vendor/jquery-ui-dial"]
+ path = vendor/jquery-ui-dial
+ url = git://github.com/thedjinn/jquery-ui-dial.git
diff --git a/COPYING b/COPYING
new file mode 100644
index 0000000..94a9ed0
--- /dev/null
+++ b/COPYING
@@ -0,0 +1,674 @@
+ GNU GENERAL PUBLIC LICENSE
+ Version 3, 29 June 2007
+
+ Copyright (C) 2007 Free Software Foundation, Inc.
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+ Preamble
+
+ The GNU General Public License is a free, copyleft license for
+software and other kinds of works.
+
+ The licenses for most software and other practical works are designed
+to take away your freedom to share and change the works. By contrast,
+the GNU General Public License is intended to guarantee your freedom to
+share and change all versions of a program--to make sure it remains free
+software for all its users. We, the Free Software Foundation, use the
+GNU General Public License for most of our software; it applies also to
+any other work released this way by its authors. You can apply it to
+your programs, too.
+
+ When we speak of free software, we are referring to freedom, not
+price. Our General Public Licenses are designed to make sure that you
+have the freedom to distribute copies of free software (and charge for
+them if you wish), that you receive source code or can get it if you
+want it, that you can change the software or use pieces of it in new
+free programs, and that you know you can do these things.
+
+ To protect your rights, we need to prevent others from denying you
+these rights or asking you to surrender the rights. Therefore, you have
+certain responsibilities if you distribute copies of the software, or if
+you modify it: responsibilities to respect the freedom of others.
+
+ For example, if you distribute copies of such a program, whether
+gratis or for a fee, you must pass on to the recipients the same
+freedoms that you received. You must make sure that they, too, receive
+or can get the source code. And you must show them these terms so they
+know their rights.
+
+ Developers that use the GNU GPL protect your rights with two steps:
+(1) assert copyright on the software, and (2) offer you this License
+giving you legal permission to copy, distribute and/or modify it.
+
+ For the developers' and authors' protection, the GPL clearly explains
+that there is no warranty for this free software. For both users' and
+authors' sake, the GPL requires that modified versions be marked as
+changed, so that their problems will not be attributed erroneously to
+authors of previous versions.
+
+ Some devices are designed to deny users access to install or run
+modified versions of the software inside them, although the manufacturer
+can do so. This is fundamentally incompatible with the aim of
+protecting users' freedom to change the software. The systematic
+pattern of such abuse occurs in the area of products for individuals to
+use, which is precisely where it is most unacceptable. Therefore, we
+have designed this version of the GPL to prohibit the practice for those
+products. If such problems arise substantially in other domains, we
+stand ready to extend this provision to those domains in future versions
+of the GPL, as needed to protect the freedom of users.
+
+ Finally, every program is threatened constantly by software patents.
+States should not allow patents to restrict development and use of
+software on general-purpose computers, but in those that do, we wish to
+avoid the special danger that patents applied to a free program could
+make it effectively proprietary. To prevent this, the GPL assures that
+patents cannot be used to render the program non-free.
+
+ The precise terms and conditions for copying, distribution and
+modification follow.
+
+ TERMS AND CONDITIONS
+
+ 0. Definitions.
+
+ "This License" refers to version 3 of the GNU General Public License.
+
+ "Copyright" also means copyright-like laws that apply to other kinds of
+works, such as semiconductor masks.
+
+ "The Program" refers to any copyrightable work licensed under this
+License. Each licensee is addressed as "you". "Licensees" and
+"recipients" may be individuals or organizations.
+
+ To "modify" a work means to copy from or adapt all or part of the work
+in a fashion requiring copyright permission, other than the making of an
+exact copy. The resulting work is called a "modified version" of the
+earlier work or a work "based on" the earlier work.
+
+ A "covered work" means either the unmodified Program or a work based
+on the Program.
+
+ To "propagate" a work means to do anything with it that, without
+permission, would make you directly or secondarily liable for
+infringement under applicable copyright law, except executing it on a
+computer or modifying a private copy. Propagation includes copying,
+distribution (with or without modification), making available to the
+public, and in some countries other activities as well.
+
+ To "convey" a work means any kind of propagation that enables other
+parties to make or receive copies. Mere interaction with a user through
+a computer network, with no transfer of a copy, is not conveying.
+
+ An interactive user interface displays "Appropriate Legal Notices"
+to the extent that it includes a convenient and prominently visible
+feature that (1) displays an appropriate copyright notice, and (2)
+tells the user that there is no warranty for the work (except to the
+extent that warranties are provided), that licensees may convey the
+work under this License, and how to view a copy of this License. If
+the interface presents a list of user commands or options, such as a
+menu, a prominent item in the list meets this criterion.
+
+ 1. Source Code.
+
+ The "source code" for a work means the preferred form of the work
+for making modifications to it. "Object code" means any non-source
+form of a work.
+
+ A "Standard Interface" means an interface that either is an official
+standard defined by a recognized standards body, or, in the case of
+interfaces specified for a particular programming language, one that
+is widely used among developers working in that language.
+
+ The "System Libraries" of an executable work include anything, other
+than the work as a whole, that (a) is included in the normal form of
+packaging a Major Component, but which is not part of that Major
+Component, and (b) serves only to enable use of the work with that
+Major Component, or to implement a Standard Interface for which an
+implementation is available to the public in source code form. A
+"Major Component", in this context, means a major essential component
+(kernel, window system, and so on) of the specific operating system
+(if any) on which the executable work runs, or a compiler used to
+produce the work, or an object code interpreter used to run it.
+
+ The "Corresponding Source" for a work in object code form means all
+the source code needed to generate, install, and (for an executable
+work) run the object code and to modify the work, including scripts to
+control those activities. However, it does not include the work's
+System Libraries, or general-purpose tools or generally available free
+programs which are used unmodified in performing those activities but
+which are not part of the work. For example, Corresponding Source
+includes interface definition files associated with source files for
+the work, and the source code for shared libraries and dynamically
+linked subprograms that the work is specifically designed to require,
+such as by intimate data communication or control flow between those
+subprograms and other parts of the work.
+
+ The Corresponding Source need not include anything that users
+can regenerate automatically from other parts of the Corresponding
+Source.
+
+ The Corresponding Source for a work in source code form is that
+same work.
+
+ 2. Basic Permissions.
+
+ All rights granted under this License are granted for the term of
+copyright on the Program, and are irrevocable provided the stated
+conditions are met. This License explicitly affirms your unlimited
+permission to run the unmodified Program. The output from running a
+covered work is covered by this License only if the output, given its
+content, constitutes a covered work. This License acknowledges your
+rights of fair use or other equivalent, as provided by copyright law.
+
+ You may make, run and propagate covered works that you do not
+convey, without conditions so long as your license otherwise remains
+in force. You may convey covered works to others for the sole purpose
+of having them make modifications exclusively for you, or provide you
+with facilities for running those works, provided that you comply with
+the terms of this License in conveying all material for which you do
+not control copyright. Those thus making or running the covered works
+for you must do so exclusively on your behalf, under your direction
+and control, on terms that prohibit them from making any copies of
+your copyrighted material outside their relationship with you.
+
+ Conveying under any other circumstances is permitted solely under
+the conditions stated below. Sublicensing is not allowed; section 10
+makes it unnecessary.
+
+ 3. Protecting Users' Legal Rights From Anti-Circumvention Law.
+
+ No covered work shall be deemed part of an effective technological
+measure under any applicable law fulfilling obligations under article
+11 of the WIPO copyright treaty adopted on 20 December 1996, or
+similar laws prohibiting or restricting circumvention of such
+measures.
+
+ When you convey a covered work, you waive any legal power to forbid
+circumvention of technological measures to the extent such circumvention
+is effected by exercising rights under this License with respect to
+the covered work, and you disclaim any intention to limit operation or
+modification of the work as a means of enforcing, against the work's
+users, your or third parties' legal rights to forbid circumvention of
+technological measures.
+
+ 4. Conveying Verbatim Copies.
+
+ You may convey verbatim copies of the Program's source code as you
+receive it, in any medium, provided that you conspicuously and
+appropriately publish on each copy an appropriate copyright notice;
+keep intact all notices stating that this License and any
+non-permissive terms added in accord with section 7 apply to the code;
+keep intact all notices of the absence of any warranty; and give all
+recipients a copy of this License along with the Program.
+
+ You may charge any price or no price for each copy that you convey,
+and you may offer support or warranty protection for a fee.
+
+ 5. Conveying Modified Source Versions.
+
+ You may convey a work based on the Program, or the modifications to
+produce it from the Program, in the form of source code under the
+terms of section 4, provided that you also meet all of these conditions:
+
+ a) The work must carry prominent notices stating that you modified
+ it, and giving a relevant date.
+
+ b) The work must carry prominent notices stating that it is
+ released under this License and any conditions added under section
+ 7. This requirement modifies the requirement in section 4 to
+ "keep intact all notices".
+
+ c) You must license the entire work, as a whole, under this
+ License to anyone who comes into possession of a copy. This
+ License will therefore apply, along with any applicable section 7
+ additional terms, to the whole of the work, and all its parts,
+ regardless of how they are packaged. This License gives no
+ permission to license the work in any other way, but it does not
+ invalidate such permission if you have separately received it.
+
+ d) If the work has interactive user interfaces, each must display
+ Appropriate Legal Notices; however, if the Program has interactive
+ interfaces that do not display Appropriate Legal Notices, your
+ work need not make them do so.
+
+ A compilation of a covered work with other separate and independent
+works, which are not by their nature extensions of the covered work,
+and which are not combined with it such as to form a larger program,
+in or on a volume of a storage or distribution medium, is called an
+"aggregate" if the compilation and its resulting copyright are not
+used to limit the access or legal rights of the compilation's users
+beyond what the individual works permit. Inclusion of a covered work
+in an aggregate does not cause this License to apply to the other
+parts of the aggregate.
+
+ 6. Conveying Non-Source Forms.
+
+ You may convey a covered work in object code form under the terms
+of sections 4 and 5, provided that you also convey the
+machine-readable Corresponding Source under the terms of this License,
+in one of these ways:
+
+ a) Convey the object code in, or embodied in, a physical product
+ (including a physical distribution medium), accompanied by the
+ Corresponding Source fixed on a durable physical medium
+ customarily used for software interchange.
+
+ b) Convey the object code in, or embodied in, a physical product
+ (including a physical distribution medium), accompanied by a
+ written offer, valid for at least three years and valid for as
+ long as you offer spare parts or customer support for that product
+ model, to give anyone who possesses the object code either (1) a
+ copy of the Corresponding Source for all the software in the
+ product that is covered by this License, on a durable physical
+ medium customarily used for software interchange, for a price no
+ more than your reasonable cost of physically performing this
+ conveying of source, or (2) access to copy the
+ Corresponding Source from a network server at no charge.
+
+ c) Convey individual copies of the object code with a copy of the
+ written offer to provide the Corresponding Source. This
+ alternative is allowed only occasionally and noncommercially, and
+ only if you received the object code with such an offer, in accord
+ with subsection 6b.
+
+ d) Convey the object code by offering access from a designated
+ place (gratis or for a charge), and offer equivalent access to the
+ Corresponding Source in the same way through the same place at no
+ further charge. You need not require recipients to copy the
+ Corresponding Source along with the object code. If the place to
+ copy the object code is a network server, the Corresponding Source
+ may be on a different server (operated by you or a third party)
+ that supports equivalent copying facilities, provided you maintain
+ clear directions next to the object code saying where to find the
+ Corresponding Source. Regardless of what server hosts the
+ Corresponding Source, you remain obligated to ensure that it is
+ available for as long as needed to satisfy these requirements.
+
+ e) Convey the object code using peer-to-peer transmission, provided
+ you inform other peers where the object code and Corresponding
+ Source of the work are being offered to the general public at no
+ charge under subsection 6d.
+
+ A separable portion of the object code, whose source code is excluded
+from the Corresponding Source as a System Library, need not be
+included in conveying the object code work.
+
+ A "User Product" is either (1) a "consumer product", which means any
+tangible personal property which is normally used for personal, family,
+or household purposes, or (2) anything designed or sold for incorporation
+into a dwelling. In determining whether a product is a consumer product,
+doubtful cases shall be resolved in favor of coverage. For a particular
+product received by a particular user, "normally used" refers to a
+typical or common use of that class of product, regardless of the status
+of the particular user or of the way in which the particular user
+actually uses, or expects or is expected to use, the product. A product
+is a consumer product regardless of whether the product has substantial
+commercial, industrial or non-consumer uses, unless such uses represent
+the only significant mode of use of the product.
+
+ "Installation Information" for a User Product means any methods,
+procedures, authorization keys, or other information required to install
+and execute modified versions of a covered work in that User Product from
+a modified version of its Corresponding Source. The information must
+suffice to ensure that the continued functioning of the modified object
+code is in no case prevented or interfered with solely because
+modification has been made.
+
+ If you convey an object code work under this section in, or with, or
+specifically for use in, a User Product, and the conveying occurs as
+part of a transaction in which the right of possession and use of the
+User Product is transferred to the recipient in perpetuity or for a
+fixed term (regardless of how the transaction is characterized), the
+Corresponding Source conveyed under this section must be accompanied
+by the Installation Information. But this requirement does not apply
+if neither you nor any third party retains the ability to install
+modified object code on the User Product (for example, the work has
+been installed in ROM).
+
+ The requirement to provide Installation Information does not include a
+requirement to continue to provide support service, warranty, or updates
+for a work that has been modified or installed by the recipient, or for
+the User Product in which it has been modified or installed. Access to a
+network may be denied when the modification itself materially and
+adversely affects the operation of the network or violates the rules and
+protocols for communication across the network.
+
+ Corresponding Source conveyed, and Installation Information provided,
+in accord with this section must be in a format that is publicly
+documented (and with an implementation available to the public in
+source code form), and must require no special password or key for
+unpacking, reading or copying.
+
+ 7. Additional Terms.
+
+ "Additional permissions" are terms that supplement the terms of this
+License by making exceptions from one or more of its conditions.
+Additional permissions that are applicable to the entire Program shall
+be treated as though they were included in this License, to the extent
+that they are valid under applicable law. If additional permissions
+apply only to part of the Program, that part may be used separately
+under those permissions, but the entire Program remains governed by
+this License without regard to the additional permissions.
+
+ When you convey a copy of a covered work, you may at your option
+remove any additional permissions from that copy, or from any part of
+it. (Additional permissions may be written to require their own
+removal in certain cases when you modify the work.) You may place
+additional permissions on material, added by you to a covered work,
+for which you have or can give appropriate copyright permission.
+
+ Notwithstanding any other provision of this License, for material you
+add to a covered work, you may (if authorized by the copyright holders of
+that material) supplement the terms of this License with terms:
+
+ a) Disclaiming warranty or limiting liability differently from the
+ terms of sections 15 and 16 of this License; or
+
+ b) Requiring preservation of specified reasonable legal notices or
+ author attributions in that material or in the Appropriate Legal
+ Notices displayed by works containing it; or
+
+ c) Prohibiting misrepresentation of the origin of that material, or
+ requiring that modified versions of such material be marked in
+ reasonable ways as different from the original version; or
+
+ d) Limiting the use for publicity purposes of names of licensors or
+ authors of the material; or
+
+ e) Declining to grant rights under trademark law for use of some
+ trade names, trademarks, or service marks; or
+
+ f) Requiring indemnification of licensors and authors of that
+ material by anyone who conveys the material (or modified versions of
+ it) with contractual assumptions of liability to the recipient, for
+ any liability that these contractual assumptions directly impose on
+ those licensors and authors.
+
+ All other non-permissive additional terms are considered "further
+restrictions" within the meaning of section 10. If the Program as you
+received it, or any part of it, contains a notice stating that it is
+governed by this License along with a term that is a further
+restriction, you may remove that term. If a license document contains
+a further restriction but permits relicensing or conveying under this
+License, you may add to a covered work material governed by the terms
+of that license document, provided that the further restriction does
+not survive such relicensing or conveying.
+
+ If you add terms to a covered work in accord with this section, you
+must place, in the relevant source files, a statement of the
+additional terms that apply to those files, or a notice indicating
+where to find the applicable terms.
+
+ Additional terms, permissive or non-permissive, may be stated in the
+form of a separately written license, or stated as exceptions;
+the above requirements apply either way.
+
+ 8. Termination.
+
+ You may not propagate or modify a covered work except as expressly
+provided under this License. Any attempt otherwise to propagate or
+modify it is void, and will automatically terminate your rights under
+this License (including any patent licenses granted under the third
+paragraph of section 11).
+
+ However, if you cease all violation of this License, then your
+license from a particular copyright holder is reinstated (a)
+provisionally, unless and until the copyright holder explicitly and
+finally terminates your license, and (b) permanently, if the copyright
+holder fails to notify you of the violation by some reasonable means
+prior to 60 days after the cessation.
+
+ Moreover, your license from a particular copyright holder is
+reinstated permanently if the copyright holder notifies you of the
+violation by some reasonable means, this is the first time you have
+received notice of violation of this License (for any work) from that
+copyright holder, and you cure the violation prior to 30 days after
+your receipt of the notice.
+
+ Termination of your rights under this section does not terminate the
+licenses of parties who have received copies or rights from you under
+this License. If your rights have been terminated and not permanently
+reinstated, you do not qualify to receive new licenses for the same
+material under section 10.
+
+ 9. Acceptance Not Required for Having Copies.
+
+ You are not required to accept this License in order to receive or
+run a copy of the Program. Ancillary propagation of a covered work
+occurring solely as a consequence of using peer-to-peer transmission
+to receive a copy likewise does not require acceptance. However,
+nothing other than this License grants you permission to propagate or
+modify any covered work. These actions infringe copyright if you do
+not accept this License. Therefore, by modifying or propagating a
+covered work, you indicate your acceptance of this License to do so.
+
+ 10. Automatic Licensing of Downstream Recipients.
+
+ Each time you convey a covered work, the recipient automatically
+receives a license from the original licensors, to run, modify and
+propagate that work, subject to this License. You are not responsible
+for enforcing compliance by third parties with this License.
+
+ An "entity transaction" is a transaction transferring control of an
+organization, or substantially all assets of one, or subdividing an
+organization, or merging organizations. If propagation of a covered
+work results from an entity transaction, each party to that
+transaction who receives a copy of the work also receives whatever
+licenses to the work the party's predecessor in interest had or could
+give under the previous paragraph, plus a right to possession of the
+Corresponding Source of the work from the predecessor in interest, if
+the predecessor has it or can get it with reasonable efforts.
+
+ You may not impose any further restrictions on the exercise of the
+rights granted or affirmed under this License. For example, you may
+not impose a license fee, royalty, or other charge for exercise of
+rights granted under this License, and you may not initiate litigation
+(including a cross-claim or counterclaim in a lawsuit) alleging that
+any patent claim is infringed by making, using, selling, offering for
+sale, or importing the Program or any portion of it.
+
+ 11. Patents.
+
+ A "contributor" is a copyright holder who authorizes use under this
+License of the Program or a work on which the Program is based. The
+work thus licensed is called the contributor's "contributor version".
+
+ A contributor's "essential patent claims" are all patent claims
+owned or controlled by the contributor, whether already acquired or
+hereafter acquired, that would be infringed by some manner, permitted
+by this License, of making, using, or selling its contributor version,
+but do not include claims that would be infringed only as a
+consequence of further modification of the contributor version. For
+purposes of this definition, "control" includes the right to grant
+patent sublicenses in a manner consistent with the requirements of
+this License.
+
+ Each contributor grants you a non-exclusive, worldwide, royalty-free
+patent license under the contributor's essential patent claims, to
+make, use, sell, offer for sale, import and otherwise run, modify and
+propagate the contents of its contributor version.
+
+ In the following three paragraphs, a "patent license" is any express
+agreement or commitment, however denominated, not to enforce a patent
+(such as an express permission to practice a patent or covenant not to
+sue for patent infringement). To "grant" such a patent license to a
+party means to make such an agreement or commitment not to enforce a
+patent against the party.
+
+ If you convey a covered work, knowingly relying on a patent license,
+and the Corresponding Source of the work is not available for anyone
+to copy, free of charge and under the terms of this License, through a
+publicly available network server or other readily accessible means,
+then you must either (1) cause the Corresponding Source to be so
+available, or (2) arrange to deprive yourself of the benefit of the
+patent license for this particular work, or (3) arrange, in a manner
+consistent with the requirements of this License, to extend the patent
+license to downstream recipients. "Knowingly relying" means you have
+actual knowledge that, but for the patent license, your conveying the
+covered work in a country, or your recipient's use of the covered work
+in a country, would infringe one or more identifiable patents in that
+country that you have reason to believe are valid.
+
+ If, pursuant to or in connection with a single transaction or
+arrangement, you convey, or propagate by procuring conveyance of, a
+covered work, and grant a patent license to some of the parties
+receiving the covered work authorizing them to use, propagate, modify
+or convey a specific copy of the covered work, then the patent license
+you grant is automatically extended to all recipients of the covered
+work and works based on it.
+
+ A patent license is "discriminatory" if it does not include within
+the scope of its coverage, prohibits the exercise of, or is
+conditioned on the non-exercise of one or more of the rights that are
+specifically granted under this License. You may not convey a covered
+work if you are a party to an arrangement with a third party that is
+in the business of distributing software, under which you make payment
+to the third party based on the extent of your activity of conveying
+the work, and under which the third party grants, to any of the
+parties who would receive the covered work from you, a discriminatory
+patent license (a) in connection with copies of the covered work
+conveyed by you (or copies made from those copies), or (b) primarily
+for and in connection with specific products or compilations that
+contain the covered work, unless you entered into that arrangement,
+or that patent license was granted, prior to 28 March 2007.
+
+ Nothing in this License shall be construed as excluding or limiting
+any implied license or other defenses to infringement that may
+otherwise be available to you under applicable patent law.
+
+ 12. No Surrender of Others' Freedom.
+
+ If conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License. If you cannot convey a
+covered work so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you may
+not convey it at all. For example, if you agree to terms that obligate you
+to collect a royalty for further conveying from those to whom you convey
+the Program, the only way you could satisfy both those terms and this
+License would be to refrain entirely from conveying the Program.
+
+ 13. Use with the GNU Affero General Public License.
+
+ Notwithstanding any other provision of this License, you have
+permission to link or combine any covered work with a work licensed
+under version 3 of the GNU Affero General Public License into a single
+combined work, and to convey the resulting work. The terms of this
+License will continue to apply to the part which is the covered work,
+but the special requirements of the GNU Affero General Public License,
+section 13, concerning interaction through a network will apply to the
+combination as such.
+
+ 14. Revised Versions of this License.
+
+ The Free Software Foundation may publish revised and/or new versions of
+the GNU 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
+Program specifies that a certain numbered version of the GNU General
+Public License "or any later version" applies to it, you have the
+option of following the terms and conditions either of that numbered
+version or of any later version published by the Free Software
+Foundation. If the Program does not specify a version number of the
+GNU General Public License, you may choose any version ever published
+by the Free Software Foundation.
+
+ If the Program specifies that a proxy can decide which future
+versions of the GNU General Public License can be used, that proxy's
+public statement of acceptance of a version permanently authorizes you
+to choose that version for the Program.
+
+ Later license versions may give you additional or different
+permissions. However, no additional obligations are imposed on any
+author or copyright holder as a result of your choosing to follow a
+later version.
+
+ 15. Disclaimer of Warranty.
+
+ THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
+APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
+HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
+OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
+THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
+IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
+ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
+
+ 16. Limitation of Liability.
+
+ IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
+THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
+GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
+USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
+DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
+PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
+EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
+SUCH DAMAGES.
+
+ 17. Interpretation of Sections 15 and 16.
+
+ If the disclaimer of warranty and limitation of liability provided
+above cannot be given local legal effect according to their terms,
+reviewing courts shall apply local law that most closely approximates
+an absolute waiver of all civil liability in connection with the
+Program, unless a warranty or assumption of liability accompanies a
+copy of the Program in return for a fee.
+
+ END OF TERMS AND CONDITIONS
+
+ How to Apply These Terms to Your New Programs
+
+ If you develop a new program, and you want it to be of the greatest
+possible use to the public, the best way to achieve this is to make it
+free software which everyone can redistribute and change under these terms.
+
+ To do so, attach the following notices to the program. It is safest
+to attach them to the start of each source file to most effectively
+state the exclusion of warranty; and each file should have at least
+the "copyright" line and a pointer to where the full notice is found.
+
+
+ Copyright (C)
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU 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 Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see .
+
+Also add information on how to contact you by electronic and paper mail.
+
+ If the program does terminal interaction, make it output a short
+notice like this when it starts in an interactive mode:
+
+ Copyright (C)
+ This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
+ This is free software, and you are welcome to redistribute it
+ under certain conditions; type `show c' for details.
+
+The hypothetical commands `show w' and `show c' should show the appropriate
+parts of the General Public License. Of course, your program's commands
+might be different; for a GUI interface, you would use an "about box".
+
+ You should also get your employer (if you work as a programmer) or school,
+if any, to sign a "copyright disclaimer" for the program, if necessary.
+For more information on this, and how to apply and follow the GNU GPL, see
+.
+
+ The GNU General Public License does not permit incorporating your program
+into proprietary programs. If your program is a subroutine library, you
+may consider it more useful to permit linking proprietary applications with
+the library. If this is what you want to do, use the GNU Lesser General
+Public License instead of this License. But first, please read
+.
diff --git a/Capfile b/Capfile
new file mode 100644
index 0000000..d235f1d
--- /dev/null
+++ b/Capfile
@@ -0,0 +1,95 @@
+load 'deploy' if respond_to?(:namespace)
+
+set :ssh_options, { :forward_agent => true }
+
+set :application, "js303"
+set :user, "deploy"
+set :use_sudo, false
+set :stage, :production
+
+set :scm, :git
+set :scm_verbose, true
+set :git_enable_submodules, 1
+set :repository, "ssh://djinn@zooi.koffietijd.net:22223/home/djinn/git/js303"
+set :branch, "master"
+set :deploy_via, :remote_cache
+set :deploy_to, "/home/#{user}/apps/#{application}"
+
+set :vps, "vps1.koffietijd.net"
+role :app, vps
+role :web, vps
+role :db, vps, :primary => true
+
+set :runner, user
+set :admin_runner, user
+
+set :current_path { fetch(:deploy_to) }
+set(:latest_release) { fetch(:current_path) }
+set(:release_path) { fetch(:current_path) }
+set(:current_release) { fetch(:current_path) }
+
+set(:current_revision) { capture("cd #{current_path}; git rev-parse --short HEAD").strip }
+set(:latest_revision) { capture("cd #{current_path}; git rev-parse --short HEAD").strip }
+set(:previous_revision) { capture("cd #{current_path}; git rev-parse --short HEAD@{1}").strip }
+
+namespace :deploy do
+ task :default do
+ update
+ restart
+ end
+
+ task :setup, :except => { :no_release => true } do
+ dirs = [deploy_to, shared_path]
+ dirs += shared_children.map { |d| File.join(shared_path, d) }
+ run "#{try_sudo} mkdir -p #{dirs.join(' ')} && #{try_sudo} chmod g+w #{dirs.join(' ')}"
+ run "git clone #{repository} #{current_path}"
+ end
+
+ task :update do
+ transaction do
+ update_code
+ end
+ end
+
+ desc "Update the deployed code."
+ task :update_code, :except => { :no_release => true } do
+ run "cd #{current_path}; git fetch origin; git reset --hard #{branch}"
+ #run "cd #{current_path}; rm public/stylesheets/*.css; rm -fr tmp/sass-cache"
+ finalize_update
+ end
+
+ desc "Update the database (overwritten to avoid symlink)"
+ task :migrations do
+ update_code
+ #migrate
+ restart
+ end
+
+ namespace :rollback do
+ desc "Moves the repo back to the previous version of HEAD"
+ task :repo, :except => { :no_release => true } do
+ set :branch, "HEAD@{1}"
+ deploy.default
+ end
+
+ desc "Rewrite reflog so HEAD@{1} will continue to point to at the next previous release."
+ task :cleanup, :except => { :no_release => true } do
+ run "cd #{current_path}; git reflog delete --rewrite HEAD@{1}; git reflog delete --rewrite HEAD@{1}"
+ end
+
+ desc "Rolls back to the previously deployed version."
+ task :default do
+ rollback.repo
+ rollback.cleanup
+ end
+ end
+
+ [:start, :stop, :migrate].each do |t|
+ task t do ; end
+ end
+
+ task :restart, :roles => :app, :except => { :no_release => true } do
+ # Restart Passenger
+ run "#{try_sudo} touch #{File.join(current_path, 'tmp', 'restart.txt')}"
+ end
+end
diff --git a/Gemfile b/Gemfile
new file mode 100644
index 0000000..d8e82d9
--- /dev/null
+++ b/Gemfile
@@ -0,0 +1,8 @@
+source "http://rubygems.org"
+
+gem "sinatra"
+gem "sinatra-asset-pipeline"
+gem "thin"
+gem "slim"
+gem "bower"
+gem "pry"
diff --git a/Gemfile.lock b/Gemfile.lock
new file mode 100644
index 0000000..acc7537
--- /dev/null
+++ b/Gemfile.lock
@@ -0,0 +1,67 @@
+GEM
+ remote: http://rubygems.org/
+ specs:
+ bower (0.0.2)
+ coderay (1.1.0)
+ coffee-script (2.3.0)
+ coffee-script-source
+ execjs
+ coffee-script-source (1.8.0)
+ daemons (1.1.9)
+ eventmachine (1.0.4)
+ execjs (2.2.2)
+ hike (1.2.3)
+ method_source (0.8.2)
+ multi_json (1.10.1)
+ pry (0.10.1)
+ coderay (~> 1.1.0)
+ method_source (~> 0.8.1)
+ slop (~> 3.4)
+ rack (1.6.0)
+ rack-protection (1.5.3)
+ rack
+ rake (10.4.2)
+ sass (3.4.9)
+ sinatra (1.4.5)
+ rack (~> 1.4)
+ rack-protection (~> 1.4)
+ tilt (~> 1.3, >= 1.3.4)
+ sinatra-asset-pipeline (0.6.0)
+ coffee-script (~> 2.3)
+ rake (~> 10.0)
+ sass (~> 3.1)
+ sinatra (~> 1.4)
+ sprockets (~> 2.12)
+ sprockets-helpers (~> 1.1)
+ sprockets-sass (~> 1.2)
+ slim (3.0.1)
+ temple (~> 0.7.3)
+ tilt (>= 1.3.3, < 2.1)
+ slop (3.6.0)
+ sprockets (2.12.3)
+ hike (~> 1.2)
+ multi_json (~> 1.0)
+ rack (~> 1.0)
+ tilt (~> 1.1, != 1.3.0)
+ sprockets-helpers (1.1.0)
+ sprockets (~> 2.0)
+ sprockets-sass (1.3.1)
+ sprockets (~> 2.0)
+ tilt (~> 1.1)
+ temple (0.7.5)
+ thin (1.6.3)
+ daemons (~> 1.0, >= 1.0.9)
+ eventmachine (~> 1.0)
+ rack (~> 1.0)
+ tilt (1.4.1)
+
+PLATFORMS
+ ruby
+
+DEPENDENCIES
+ bower
+ pry
+ sinatra
+ sinatra-asset-pipeline
+ slim
+ thin
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..0b3acf6
--- /dev/null
+++ b/README.md
@@ -0,0 +1,39 @@
+# JS303 - A TB-303 clone in JavaScript
+
+## What's this?
+
+You are looking at the source code for JS303, a fully functional TB-303 clone
+written in JavaScript using the Web Audio API. It was written for the second
+GrunnJS meetup which was held in November 2014.
+
+The accompanying presentation for this project can be found at:
+
+https://www.youtube.com/watch?v=YyVsKlKoNbA#t=2938
+
+## How to use it?
+
+First, make sure you have a recent Ruby installed and also Node.js and
+CoffeeScript.
+
+Then, clone the repository, run `bundle install`, and then `thin start`. Now
+go to `localhost:3000` and rock it!
+
+## License
+
+First of all, if you use this software and make a hit song, buy me some
+coffee!
+
+Copyright (C) 2014 Emil Loer
+
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU 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 Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program. If not, see http://www.gnu.org/licenses/.
diff --git a/Rakefile b/Rakefile
new file mode 100644
index 0000000..756d6d3
--- /dev/null
+++ b/Rakefile
@@ -0,0 +1,12 @@
+ROOT = File.dirname(__FILE__)
+
+task :default do
+ chdir ROOT
+ sh "sass --scss src/style.scss site/css/style.css"
+ cp "vendor/jquery-ui-dial/jquery.ui.dial.js", "site/js/jquery.ui.dial.js"
+end
+
+task :sass do
+ chdir ROOT
+ sh "sass --scss --watch src/style.scss:site/css/style.css"
+end
diff --git a/app.rb b/app.rb
new file mode 100644
index 0000000..b89f176
--- /dev/null
+++ b/app.rb
@@ -0,0 +1,14 @@
+Bundler.require
+
+# Enable asset pipeline
+require 'sinatra/asset_pipeline'
+register Sinatra::AssetPipeline
+
+# Add support for bower components
+configure do
+ settings.sprockets.append_path "./bower_components"
+end
+
+get "/" do
+ slim :index
+end
diff --git a/assets/javascripts/app.js.coffee b/assets/javascripts/app.js.coffee
new file mode 100644
index 0000000..7e78834
--- /dev/null
+++ b/assets/javascripts/app.js.coffee
@@ -0,0 +1,256 @@
+#= require jquery/dist/jquery
+#= require handlebars/handlebars
+#= require emblem/dist/emblem
+#= require ember/ember
+#= require jquery-knob/dist/jquery.knob.min
+#= require synth
+
+lin2exp = (x, inMin, inMax, outMin, outMax) ->
+ tmp = (x - inMin) / (inMax - inMin)
+ outMin * Math.exp(tmp * Math.log(outMax / outMin))
+
+lin2lin = (x, inMin, inMax, outMin, outMax) ->
+ tmp = (x - inMin) / (inMax - inMin)
+ outMin + tmp * (outMax - outMin)
+
+randomBool = -> !!Math.round(Math.random())
+
+Ember.Handlebars.registerHelper "group", (options) ->
+ data = options.data
+ fn = options.fn
+ view = data.view
+
+ childView = view.createChildView Ember._MetamorphView,
+ context: Ember.get(view, "context")
+
+ template: (context, options) ->
+ options.data.insideGroup = true
+ return fn(context, options)
+
+ view.appendChild childView
+
+Step = Ember.Object.extend
+ pitch: 50
+ gate: true
+ slide: false
+ accent: false
+ up: false
+ down: false
+
+ randomize: ->
+ @set "pitch", 40 + [0, 2, 3, 5, 7, 8, 10, 12][Math.round(7 * Math.random())]
+ @set "gate", randomBool()
+ @set "slide", randomBool()
+ @set "accent", randomBool()
+ @set "up", randomBool()
+ @set "down", randomBool()
+
+ clear: ->
+ @set "pitch", 50
+ @set "gate", true
+ @set "slide", false
+ @set "accent", false
+ @set "up", false
+ @set "down", false
+
+Pattern = Ember.Object.extend
+ numberOfSteps: 16
+
+ init: ->
+ @set "steps", (new Step for _ in [0..15])
+
+ randomize: ->
+ step.randomize() for step in @get("steps")
+
+ clear: ->
+ step.clear() for step in @get("steps")
+
+window.App = App = Ember.Application.create()
+
+App.KnobView = Ember.View.extend
+ tagName: "input"
+
+ min: 0
+ max: 1
+ value: 0.5
+ step: 0.01
+
+ initKnob: (->
+ @$().knob
+ fgColor: "#fc3932"
+ bgColor: "#cccccc"
+ inputColor: "#000000"
+ font: "Abel"
+ fontWeight: "normal"
+ min: Number(@get("min"))
+ max: Number(@get("max"))
+ step: Number(@get("step"))
+ width: 106
+ height: 85
+ angleOffset: -125
+ angleArc: 250
+ change: (value) =>
+ @set "value", value
+
+ @$().val(@get("value")).trigger("change")
+ ).on('didInsertElement')
+
+ valueChanged: (->
+ @$().val(@get("value")).trigger("change")
+ @trigger "change"
+ ).observes("value")
+
+App.ToggleButtonView = Ember.View.extend
+ tagName: "button"
+ classNames: ["toggle-button"]
+ classNameBindings: ["active:on"]
+ templateName: "toggle_button"
+
+ active: true
+ title: ""
+
+ mouseDown: ->
+ @set "active", !@get("active")
+
+App.ButtonView = Ember.View.extend Ember.TargetActionSupport,
+ tagName: "button"
+ classNames: ["button"]
+ templateName: "button"
+
+ title: ""
+
+ click: ->
+ @triggerAction
+ action: @get("action")
+ target: @get("target")
+
+App.SelectorButtonView = App.ButtonView.extend
+ classNameBindings: ["isSelected::grey"]
+
+ isSelected: (->
+ @get("value") == @get("variable")
+ ).property("value", "variable")
+
+ click: -> @set "variable", @get("value")
+
+App.PitchButtonView = App.SelectorButtonView.extend
+ templateName: null
+ mouseDown: -> @set "variable", @get("value")
+ click: null
+
+App.ApplicationController = Ember.ObjectController.extend
+ patterns: (new Pattern for _ in [0..7])
+ allowedPitches: [52..40]
+
+ tempo: 120
+ cutoff: 0.5
+ resonance: 0.5
+ envmod: 0.0
+ decay: 0.2
+ accent: 0.5
+ distortion: 0.0
+ foldback: 0.0
+ delaySteps: 3
+ delayMix: 0
+ delayFeedback: 0.5
+ waveform: 0
+
+ cutoffChanged: (->
+ @synth.setcutoff lin2exp(@get("cutoff"), 0, 1, 20, 20000)
+ ).observes("cutoff").on("init")
+
+ resonanceChanged: (->
+ @synth.setresonance @get("resonance")
+ ).observes("resonance").on("init")
+
+ envmodChanged: (->
+ @synth.setenvmod @get("envmod")
+ ).observes("envmod").on("init")
+
+ decayChanged: (->
+ @synth.decay = lin2lin(@get("decay"), 0, 1, 20, 4000)
+ ).observes("decay").on("init")
+
+ accentChanged: (->
+ @synth.accent = @get("accent")
+ ).observes("accent").on("init")
+
+ tempoChanged: (->
+ @synth.settempo @get("tempo")
+ ).observes("tempo").on("init")
+
+ waveformChanged: (->
+ @synth.waveform = @get("waveform") == 1
+ ).observes("waveform").on("init")
+
+ distortionChanged: (->
+ @synth.setdistthreshold @get("distortion")
+ ).observes("distortion").on("init")
+
+ foldbackChanged: (->
+ @synth.dist_shape = @get("foldback")
+ ).observes("foldback").on("init")
+
+ delayStepsChanged: (->
+ @synth.delay_length = @synth.steplength * Math.round(@get("delaySteps"))
+ ).observes("delaySteps", "tempo").on("init")
+
+ delayMixChanged: (->
+ @synth.delay_send = @get("delayMix")
+ ).observes("delayMix").on("init")
+
+ delayFeedbackChanged: (->
+ @synth.delay_feedback = @get("delayFeedback")
+ ).observes("delayFeedback").on("init")
+
+ columns: (->
+ @get("currentPattern").get("steps").map (step, index) ->
+ step: step
+ index: index + 1
+ highlight: index % 4 == 0
+ ).property("currentPattern.steps.@each")
+
+ init: ->
+ @_super.apply this, arguments
+
+ @synth = new TB303 genwavetable()
+
+ @synth.waveform = 1
+
+ @synth.onStepChanged = => @stepChanged()
+
+ @set "audioManager", new AudioManager(@synth)
+ @set "currentPattern", @patterns[0]
+
+ stepChanged: ->
+ $(".playing").removeClass("playing")
+ $(".step-column").eq(@synth.pos + 1).addClass("playing")
+
+ currentPatternChanged: (->
+ @synth.pattern ||= @get("currentPattern")
+ @synth.nextPattern = @get("currentPattern")
+ ).observes("currentPattern").on("init")
+
+class AudioManager
+ constructor: (@synth) ->
+ @context = new AudioContext
+
+ @bufferSize = 4096
+
+ @processor = @context.createScriptProcessor @bufferSize, 0, 1
+ @processor.onaudioprocess = @audioCallback
+ @processor.connect @context.destination
+
+ audioCallback: (e) =>
+ output = e.outputBuffer.getChannelData 0
+
+ for i in [0..@bufferSize] by 1
+ output[i] = @synth.render()
+
+ start: ->
+ @synth.reset()
+ @synth.running = true
+
+ stop: ->
+ @synth.running = false
+ $(".playing").removeClass("playing")
diff --git a/assets/javascripts/htmlbars/htmlbars-compiler.amd.js b/assets/javascripts/htmlbars/htmlbars-compiler.amd.js
new file mode 100644
index 0000000..bcb0174
--- /dev/null
+++ b/assets/javascripts/htmlbars/htmlbars-compiler.amd.js
@@ -0,0 +1,7953 @@
+define("handlebars/base",
+ ["./utils","./exception","exports"],
+ function(__dependency1__, __dependency2__, __exports__) {
+ "use strict";
+ var Utils = __dependency1__;
+ var Exception = __dependency2__["default"];
+
+ var VERSION = "1.3.0";
+ __exports__.VERSION = VERSION;var COMPILER_REVISION = 4;
+ __exports__.COMPILER_REVISION = COMPILER_REVISION;
+ var REVISION_CHANGES = {
+ 1: '<= 1.0.rc.2', // 1.0.rc.2 is actually rev2 but doesn't report it
+ 2: '== 1.0.0-rc.3',
+ 3: '== 1.0.0-rc.4',
+ 4: '>= 1.0.0'
+ };
+ __exports__.REVISION_CHANGES = REVISION_CHANGES;
+ var isArray = Utils.isArray,
+ isFunction = Utils.isFunction,
+ toString = Utils.toString,
+ objectType = '[object Object]';
+
+ function HandlebarsEnvironment(helpers, partials) {
+ this.helpers = helpers || {};
+ this.partials = partials || {};
+
+ registerDefaultHelpers(this);
+ }
+
+ __exports__.HandlebarsEnvironment = HandlebarsEnvironment;HandlebarsEnvironment.prototype = {
+ constructor: HandlebarsEnvironment,
+
+ logger: logger,
+ log: log,
+
+ registerHelper: function(name, fn, inverse) {
+ if (toString.call(name) === objectType) {
+ if (inverse || fn) { throw new Exception('Arg not supported with multiple helpers'); }
+ Utils.extend(this.helpers, name);
+ } else {
+ if (inverse) { fn.not = inverse; }
+ this.helpers[name] = fn;
+ }
+ },
+
+ registerPartial: function(name, str) {
+ if (toString.call(name) === objectType) {
+ Utils.extend(this.partials, name);
+ } else {
+ this.partials[name] = str;
+ }
+ }
+ };
+
+ function registerDefaultHelpers(instance) {
+ instance.registerHelper('helperMissing', function(arg) {
+ if(arguments.length === 2) {
+ return undefined;
+ } else {
+ throw new Exception("Missing helper: '" + arg + "'");
+ }
+ });
+
+ instance.registerHelper('blockHelperMissing', function(context, options) {
+ var inverse = options.inverse || function() {}, fn = options.fn;
+
+ if (isFunction(context)) { context = context.call(this); }
+
+ if(context === true) {
+ return fn(this);
+ } else if(context === false || context == null) {
+ return inverse(this);
+ } else if (isArray(context)) {
+ if(context.length > 0) {
+ return instance.helpers.each(context, options);
+ } else {
+ return inverse(this);
+ }
+ } else {
+ return fn(context);
+ }
+ });
+
+ instance.registerHelper('each', function(context, options) {
+ var fn = options.fn, inverse = options.inverse;
+ var i = 0, ret = "", data;
+
+ if (isFunction(context)) { context = context.call(this); }
+
+ if (options.data) {
+ data = createFrame(options.data);
+ }
+
+ if(context && typeof context === 'object') {
+ if (isArray(context)) {
+ for(var j = context.length; i 0) {
+ throw new Exception("Invalid path: " + original, this);
+ } else if (part === "..") {
+ depth++;
+ } else {
+ this.isScoped = true;
+ }
+ } else {
+ dig.push(part);
+ }
+ }
+
+ this.original = original;
+ this.parts = dig;
+ this.string = dig.join('.');
+ this.depth = depth;
+
+ // an ID is simple if it only has one part, and that part is not
+ // `..` or `this`.
+ this.isSimple = parts.length === 1 && !this.isScoped && depth === 0;
+
+ this.stringModeValue = this.string;
+ },
+
+ PartialNameNode: function(name, locInfo) {
+ LocationInfo.call(this, locInfo);
+ this.type = "PARTIAL_NAME";
+ this.name = name.original;
+ },
+
+ DataNode: function(id, locInfo) {
+ LocationInfo.call(this, locInfo);
+ this.type = "DATA";
+ this.id = id;
+ },
+
+ StringNode: function(string, locInfo) {
+ LocationInfo.call(this, locInfo);
+ this.type = "STRING";
+ this.original =
+ this.string =
+ this.stringModeValue = string;
+ },
+
+ IntegerNode: function(integer, locInfo) {
+ LocationInfo.call(this, locInfo);
+ this.type = "INTEGER";
+ this.original =
+ this.integer = integer;
+ this.stringModeValue = Number(integer);
+ },
+
+ BooleanNode: function(bool, locInfo) {
+ LocationInfo.call(this, locInfo);
+ this.type = "BOOLEAN";
+ this.bool = bool;
+ this.stringModeValue = bool === "true";
+ },
+
+ CommentNode: function(comment, locInfo) {
+ LocationInfo.call(this, locInfo);
+ this.type = "comment";
+ this.comment = comment;
+ }
+ };
+
+ // Must be exported as an object rather than the root of the module as the jison lexer
+ // most modify the object to operate properly.
+ __exports__["default"] = AST;
+ });
+define("handlebars/compiler/base",
+ ["./parser","./ast","exports"],
+ function(__dependency1__, __dependency2__, __exports__) {
+ "use strict";
+ var parser = __dependency1__["default"];
+ var AST = __dependency2__["default"];
+
+ __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;
+ });
+define("handlebars/compiler/compiler",
+ ["../exception","exports"],
+ function(__dependency1__, __exports__) {
+ "use strict";
+ var Exception = __dependency1__["default"];
+
+ function Compiler() {}
+
+ __exports__.Compiler = Compiler;// the foundHelper register will disambiguate helper lookup from finding a
+ // function in a context. This is necessary for mustache compatibility, which
+ // requires that context functions in blocks are evaluated by blockHelperMissing,
+ // and then proceed as if the resulting value was provided to blockHelperMissing.
+
+ Compiler.prototype = {
+ compiler: Compiler,
+
+ disassemble: function() {
+ var opcodes = this.opcodes, opcode, out = [], params, param;
+
+ for (var i=0, l=opcodes.length; i 0) {
+ this.source[1] = this.source[1] + ", " + locals.join(", ");
+ }
+
+ // Generate minimizer alias mappings
+ if (!this.isChild) {
+ for (var alias in this.context.aliases) {
+ if (this.context.aliases.hasOwnProperty(alias)) {
+ this.source[1] = this.source[1] + ', ' + alias + '=' + this.context.aliases[alias];
+ }
+ }
+ }
+
+ if (this.source[1]) {
+ this.source[1] = "var " + this.source[1].substring(2) + ";";
+ }
+
+ // Merge children
+ if (!this.isChild) {
+ this.source[1] += '\n' + this.context.programs.join('\n') + '\n';
+ }
+
+ if (!this.environment.isSimple) {
+ this.pushSource("return buffer;");
+ }
+
+ var params = this.isChild ? ["depth0", "data"] : ["Handlebars", "depth0", "helpers", "partials", "data"];
+
+ for(var i=0, l=this.environment.depths.list.length; i this.stackVars.length) { this.stackVars.push("stack" + this.stackSlot); }
+ return this.topStackName();
+ },
+ topStackName: function() {
+ return "stack" + this.stackSlot;
+ },
+ flushInline: function() {
+ var inlineStack = this.inlineStack;
+ if (inlineStack.length) {
+ this.inlineStack = [];
+ for (var i = 0, len = inlineStack.length; i < len; i++) {
+ var entry = inlineStack[i];
+ if (entry instanceof Literal) {
+ this.compileStack.push(entry);
+ } else {
+ this.pushStack(entry);
+ }
+ }
+ }
+ },
+ isInline: function() {
+ return this.inlineStack.length;
+ },
+
+ popStack: function(wrapped) {
+ var inline = this.isInline(),
+ item = (inline ? this.inlineStack : this.compileStack).pop();
+
+ if (!wrapped && (item instanceof Literal)) {
+ return item.value;
+ } else {
+ if (!inline) {
+ if (!this.stackSlot) {
+ throw new Exception('Invalid stack pop');
+ }
+ this.stackSlot--;
+ }
+ return item;
+ }
+ },
+
+ topStack: function(wrapped) {
+ var stack = (this.isInline() ? this.inlineStack : this.compileStack),
+ item = stack[stack.length - 1];
+
+ if (!wrapped && (item instanceof Literal)) {
+ return item.value;
+ } else {
+ return item;
+ }
+ },
+
+ quotedString: function(str) {
+ return '"' + str
+ .replace(/\\/g, '\\\\')
+ .replace(/"/g, '\\"')
+ .replace(/\n/g, '\\n')
+ .replace(/\r/g, '\\r')
+ .replace(/\u2028/g, '\\u2028') // Per Ecma-262 7.3 + 7.8.4
+ .replace(/\u2029/g, '\\u2029') + '"';
+ },
+
+ setupHelper: function(paramSize, name, missingParams) {
+ var params = [],
+ paramsInit = this.setupParams(paramSize, params, missingParams);
+ var foundHelper = this.nameLookup('helpers', name, 'helper');
+
+ return {
+ params: params,
+ paramsInit: paramsInit,
+ name: foundHelper,
+ callParams: ["depth0"].concat(params).join(", "),
+ helperMissingParams: missingParams && ["depth0", this.quotedString(name)].concat(params).join(", ")
+ };
+ },
+
+ setupOptions: function(paramSize, params) {
+ var options = [], contexts = [], types = [], param, inverse, program;
+
+ options.push("hash:" + this.popStack());
+
+ if (this.options.stringParams) {
+ options.push("hashTypes:" + this.popStack());
+ options.push("hashContexts:" + this.popStack());
+ }
+
+ inverse = this.popStack();
+ program = this.popStack();
+
+ // Avoid setting fn and inverse if neither are set. This allows
+ // helpers to do a check for `if (options.fn)`
+ if (program || inverse) {
+ if (!program) {
+ this.context.aliases.self = "this";
+ program = "self.noop";
+ }
+
+ if (!inverse) {
+ this.context.aliases.self = "this";
+ inverse = "self.noop";
+ }
+
+ options.push("inverse:" + inverse);
+ options.push("fn:" + program);
+ }
+
+ for(var i=0; i 2) {
+ expected.push("'" + this.terminals_[p] + "'");
+ }
+ if (this.lexer.showPosition) {
+ errStr = "Parse error on line " + (yylineno + 1) + ":\n" + this.lexer.showPosition() + "\nExpecting " + expected.join(", ") + ", got '" + (this.terminals_[symbol] || symbol) + "'";
+ } else {
+ errStr = "Parse error on line " + (yylineno + 1) + ": Unexpected " + (symbol == 1?"end of input":"'" + (this.terminals_[symbol] || symbol) + "'");
+ }
+ this.parseError(errStr, {text: this.lexer.match, token: this.terminals_[symbol] || symbol, line: this.lexer.yylineno, loc: yyloc, expected: expected});
+ }
+ }
+ if (action[0] instanceof Array && action.length > 1) {
+ throw new Error("Parse Error: multiple actions possible at state: " + state + ", token: " + symbol);
+ }
+ switch (action[0]) {
+ case 1:
+ stack.push(symbol);
+ vstack.push(this.lexer.yytext);
+ lstack.push(this.lexer.yylloc);
+ stack.push(action[1]);
+ symbol = null;
+ if (!preErrorSymbol) {
+ yyleng = this.lexer.yyleng;
+ yytext = this.lexer.yytext;
+ yylineno = this.lexer.yylineno;
+ yyloc = this.lexer.yylloc;
+ if (recovering > 0)
+ recovering--;
+ } else {
+ symbol = preErrorSymbol;
+ preErrorSymbol = null;
+ }
+ break;
+ case 2:
+ len = this.productions_[action[1]][1];
+ yyval.$ = vstack[vstack.length - len];
+ yyval._$ = {first_line: lstack[lstack.length - (len || 1)].first_line, last_line: lstack[lstack.length - 1].last_line, first_column: lstack[lstack.length - (len || 1)].first_column, last_column: lstack[lstack.length - 1].last_column};
+ if (ranges) {
+ yyval._$.range = [lstack[lstack.length - (len || 1)].range[0], lstack[lstack.length - 1].range[1]];
+ }
+ r = this.performAction.call(yyval, yytext, yyleng, yylineno, this.yy, action[1], vstack, lstack);
+ if (typeof r !== "undefined") {
+ return r;
+ }
+ if (len) {
+ stack = stack.slice(0, -1 * len * 2);
+ vstack = vstack.slice(0, -1 * len);
+ lstack = lstack.slice(0, -1 * len);
+ }
+ stack.push(this.productions_[action[1]][0]);
+ vstack.push(yyval.$);
+ lstack.push(yyval._$);
+ newState = table[stack[stack.length - 2]][stack[stack.length - 1]];
+ stack.push(newState);
+ break;
+ case 3:
+ return true;
+ }
+ }
+ return true;
+ }
+ };
+
+
+ function stripFlags(open, close) {
+ return {
+ left: open.charAt(2) === '~',
+ right: close.charAt(0) === '~' || close.charAt(1) === '~'
+ };
+ }
+
+ /* Jison generated lexer */
+ var lexer = (function(){
+ var lexer = ({EOF:1,
+ parseError:function parseError(str, hash) {
+ if (this.yy.parser) {
+ this.yy.parser.parseError(str, hash);
+ } else {
+ throw new Error(str);
+ }
+ },
+ setInput:function (input) {
+ this._input = input;
+ this._more = this._less = this.done = false;
+ this.yylineno = this.yyleng = 0;
+ this.yytext = this.matched = this.match = '';
+ this.conditionStack = ['INITIAL'];
+ this.yylloc = {first_line:1,first_column:0,last_line:1,last_column:0};
+ if (this.options.ranges) this.yylloc.range = [0,0];
+ this.offset = 0;
+ return this;
+ },
+ input:function () {
+ var ch = this._input[0];
+ this.yytext += ch;
+ this.yyleng++;
+ this.offset++;
+ this.match += ch;
+ this.matched += ch;
+ var lines = ch.match(/(?:\r\n?|\n).*/g);
+ if (lines) {
+ this.yylineno++;
+ this.yylloc.last_line++;
+ } else {
+ this.yylloc.last_column++;
+ }
+ if (this.options.ranges) this.yylloc.range[1]++;
+
+ this._input = this._input.slice(1);
+ return ch;
+ },
+ unput:function (ch) {
+ var len = ch.length;
+ var lines = ch.split(/(?:\r\n?|\n)/g);
+
+ this._input = ch + this._input;
+ this.yytext = this.yytext.substr(0, this.yytext.length-len-1);
+ //this.yyleng -= len;
+ this.offset -= len;
+ var oldLines = this.match.split(/(?:\r\n?|\n)/g);
+ this.match = this.match.substr(0, this.match.length-1);
+ this.matched = this.matched.substr(0, this.matched.length-1);
+
+ if (lines.length-1) this.yylineno -= lines.length-1;
+ var r = this.yylloc.range;
+
+ this.yylloc = {first_line: this.yylloc.first_line,
+ last_line: this.yylineno+1,
+ first_column: this.yylloc.first_column,
+ last_column: lines ?
+ (lines.length === oldLines.length ? this.yylloc.first_column : 0) + oldLines[oldLines.length - lines.length].length - lines[0].length:
+ this.yylloc.first_column - len
+ };
+
+ if (this.options.ranges) {
+ this.yylloc.range = [r[0], r[0] + this.yyleng - len];
+ }
+ return this;
+ },
+ more:function () {
+ this._more = true;
+ return this;
+ },
+ less:function (n) {
+ this.unput(this.match.slice(n));
+ },
+ pastInput:function () {
+ var past = this.matched.substr(0, this.matched.length - this.match.length);
+ return (past.length > 20 ? '...':'') + past.substr(-20).replace(/\n/g, "");
+ },
+ upcomingInput:function () {
+ var next = this.match;
+ if (next.length < 20) {
+ next += this._input.substr(0, 20-next.length);
+ }
+ return (next.substr(0,20)+(next.length > 20 ? '...':'')).replace(/\n/g, "");
+ },
+ showPosition:function () {
+ var pre = this.pastInput();
+ var c = new Array(pre.length + 1).join("-");
+ return pre + this.upcomingInput() + "\n" + c+"^";
+ },
+ next:function () {
+ if (this.done) {
+ return this.EOF;
+ }
+ if (!this._input) this.done = true;
+
+ var token,
+ match,
+ tempMatch,
+ index,
+ col,
+ lines;
+ if (!this._more) {
+ this.yytext = '';
+ this.match = '';
+ }
+ var rules = this._currentRules();
+ for (var i=0;i < rules.length; i++) {
+ tempMatch = this._input.match(this.rules[rules[i]]);
+ if (tempMatch && (!match || tempMatch[0].length > match[0].length)) {
+ match = tempMatch;
+ index = i;
+ if (!this.options.flex) break;
+ }
+ }
+ if (match) {
+ lines = match[0].match(/(?:\r\n?|\n).*/g);
+ if (lines) this.yylineno += lines.length;
+ this.yylloc = {first_line: this.yylloc.last_line,
+ last_line: this.yylineno+1,
+ first_column: this.yylloc.last_column,
+ last_column: lines ? lines[lines.length-1].length-lines[lines.length-1].match(/\r?\n?/)[0].length : this.yylloc.last_column + match[0].length};
+ this.yytext += match[0];
+ this.match += match[0];
+ this.matches = match;
+ this.yyleng = this.yytext.length;
+ if (this.options.ranges) {
+ this.yylloc.range = [this.offset, this.offset += this.yyleng];
+ }
+ this._more = false;
+ this._input = this._input.slice(match[0].length);
+ this.matched += match[0];
+ token = this.performAction.call(this, this.yy, this, rules[index],this.conditionStack[this.conditionStack.length-1]);
+ if (this.done && this._input) this.done = false;
+ if (token) return token;
+ else return;
+ }
+ if (this._input === "") {
+ return this.EOF;
+ } else {
+ return this.parseError('Lexical error on line '+(this.yylineno+1)+'. Unrecognized text.\n'+this.showPosition(),
+ {text: "", token: null, line: this.yylineno});
+ }
+ },
+ lex:function lex() {
+ var r = this.next();
+ if (typeof r !== 'undefined') {
+ return r;
+ } else {
+ return this.lex();
+ }
+ },
+ begin:function begin(condition) {
+ this.conditionStack.push(condition);
+ },
+ popState:function popState() {
+ return this.conditionStack.pop();
+ },
+ _currentRules:function _currentRules() {
+ return this.conditions[this.conditionStack[this.conditionStack.length-1]].rules;
+ },
+ topState:function () {
+ return this.conditionStack[this.conditionStack.length-2];
+ },
+ pushState:function begin(condition) {
+ this.begin(condition);
+ }});
+ lexer.options = {};
+ lexer.performAction = function anonymous(yy,yy_,$avoiding_name_collisions,YY_START) {
+
+
+ function strip(start, end) {
+ return yy_.yytext = yy_.yytext.substr(start, yy_.yyleng-end);
+ }
+
+
+ var YYSTATE=YY_START
+ switch($avoiding_name_collisions) {
+ case 0:
+ if(yy_.yytext.slice(-2) === "\\\\") {
+ strip(0,1);
+ this.begin("mu");
+ } else if(yy_.yytext.slice(-1) === "\\") {
+ strip(0,1);
+ this.begin("emu");
+ } else {
+ this.begin("mu");
+ }
+ if(yy_.yytext) return 14;
+
+ break;
+ case 1:return 14;
+ break;
+ case 2:
+ this.popState();
+ return 14;
+
+ break;
+ case 3:strip(0,4); this.popState(); return 15;
+ break;
+ case 4:return 35;
+ break;
+ case 5:return 36;
+ break;
+ case 6:return 25;
+ break;
+ case 7:return 16;
+ break;
+ case 8:return 20;
+ break;
+ case 9:return 19;
+ break;
+ case 10:return 19;
+ break;
+ case 11:return 23;
+ break;
+ case 12:return 22;
+ break;
+ case 13:this.popState(); this.begin('com');
+ break;
+ case 14:strip(3,5); this.popState(); return 15;
+ break;
+ case 15:return 22;
+ break;
+ case 16:return 41;
+ break;
+ case 17:return 40;
+ break;
+ case 18:return 40;
+ break;
+ case 19:return 44;
+ break;
+ case 20:// ignore whitespace
+ break;
+ case 21:this.popState(); return 24;
+ break;
+ case 22:this.popState(); return 18;
+ break;
+ case 23:yy_.yytext = strip(1,2).replace(/\\"/g,'"'); return 32;
+ break;
+ case 24:yy_.yytext = strip(1,2).replace(/\\'/g,"'"); return 32;
+ break;
+ case 25:return 42;
+ break;
+ case 26:return 34;
+ break;
+ case 27:return 34;
+ break;
+ case 28:return 33;
+ break;
+ case 29:return 40;
+ break;
+ case 30:yy_.yytext = strip(1,2); return 40;
+ break;
+ case 31:return 'INVALID';
+ break;
+ case 32:return 5;
+ break;
+ }
+ };
+ lexer.rules = [/^(?:[^\x00]*?(?=(\{\{)))/,/^(?:[^\x00]+)/,/^(?:[^\x00]{2,}?(?=(\{\{|\\\{\{|\\\\\{\{|$)))/,/^(?:[\s\S]*?--\}\})/,/^(?:\()/,/^(?:\))/,/^(?:\{\{(~)?>)/,/^(?:\{\{(~)?#)/,/^(?:\{\{(~)?\/)/,/^(?:\{\{(~)?\^)/,/^(?:\{\{(~)?\s*else\b)/,/^(?:\{\{(~)?\{)/,/^(?:\{\{(~)?&)/,/^(?:\{\{!--)/,/^(?:\{\{![\s\S]*?\}\})/,/^(?:\{\{(~)?)/,/^(?:=)/,/^(?:\.\.)/,/^(?:\.(?=([=~}\s\/.)])))/,/^(?:[\/.])/,/^(?:\s+)/,/^(?:\}(~)?\}\})/,/^(?:(~)?\}\})/,/^(?:"(\\["]|[^"])*")/,/^(?:'(\\[']|[^'])*')/,/^(?:@)/,/^(?:true(?=([~}\s)])))/,/^(?:false(?=([~}\s)])))/,/^(?:-?[0-9]+(?=([~}\s)])))/,/^(?:([^\s!"#%-,\.\/;->@\[-\^`\{-~]+(?=([=~}\s\/.)]))))/,/^(?:\[[^\]]*\])/,/^(?:.)/,/^(?:$)/];
+ lexer.conditions = {"mu":{"rules":[4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32],"inclusive":false},"emu":{"rules":[2],"inclusive":false},"com":{"rules":[3],"inclusive":false},"INITIAL":{"rules":[0,1,32],"inclusive":true}};
+ return lexer;})()
+ parser.lexer = lexer;
+ function Parser () { this.yy = {}; }Parser.prototype = parser;parser.Parser = Parser;
+ return new Parser;
+ })();__exports__["default"] = handlebars;
+ /* jshint ignore:end */
+ });
+define("handlebars/compiler/printer",
+ ["./visitor","exports"],
+ function(__dependency1__, __exports__) {
+ "use strict";
+ var Visitor = __dependency1__["default"];
+
+ function print(ast) {
+ return new PrintVisitor().accept(ast);
+ }
+
+ __exports__.print = print;function PrintVisitor() {
+ this.padding = 0;
+ }
+
+ __exports__.PrintVisitor = PrintVisitor;PrintVisitor.prototype = new Visitor();
+
+ PrintVisitor.prototype.pad = function(string, newline) {
+ var out = "";
+
+ for(var i=0,l=this.padding; i " + content + " }}");
+ };
+
+ PrintVisitor.prototype.hash = function(hash) {
+ var pairs = hash.pairs;
+ var joinedPairs = [], left, right;
+
+ for(var i=0, l=pairs.length; i 1) {
+ return "PATH:" + path;
+ } else {
+ return "ID:" + path;
+ }
+ };
+
+ PrintVisitor.prototype.PARTIAL_NAME = function(partialName) {
+ return "PARTIAL:" + partialName.name;
+ };
+
+ PrintVisitor.prototype.DATA = function(data) {
+ return "@" + this.accept(data.id);
+ };
+
+ PrintVisitor.prototype.content = function(content) {
+ return this.pad("CONTENT[ '" + content.string + "' ]");
+ };
+
+ PrintVisitor.prototype.comment = function(comment) {
+ return this.pad("{{! '" + comment.comment + "' }}");
+ };
+ });
+define("handlebars/compiler/visitor",
+ ["exports"],
+ function(__exports__) {
+ "use strict";
+ function Visitor() {}
+
+ Visitor.prototype = {
+ constructor: Visitor,
+
+ accept: function(object) {
+ return this[object.type](object);
+ }
+ };
+
+ __exports__["default"] = Visitor;
+ });
+define("handlebars/exception",
+ ["exports"],
+ function(__exports__) {
+ "use strict";
+
+ var errorProps = ['description', 'fileName', 'lineNumber', 'message', 'name', 'number', 'stack'];
+
+ function Exception(message, node) {
+ var line;
+ if (node && node.firstLine) {
+ line = node.firstLine;
+
+ message += ' - ' + line + ':' + node.firstColumn;
+ }
+
+ var tmp = Error.prototype.constructor.call(this, message);
+
+ // Unfortunately errors are not enumerable in Chrome (at least), so `for prop in tmp` doesn't work.
+ for (var idx = 0; idx < errorProps.length; idx++) {
+ this[errorProps[idx]] = tmp[errorProps[idx]];
+ }
+
+ if (line) {
+ this.lineNumber = line;
+ this.column = node.firstColumn;
+ }
+ }
+
+ Exception.prototype = new Error();
+
+ __exports__["default"] = Exception;
+ });
+define("handlebars/runtime",
+ ["./utils","./exception","./base","exports"],
+ function(__dependency1__, __dependency2__, __dependency3__, __exports__) {
+ "use strict";
+ var Utils = __dependency1__;
+ var Exception = __dependency2__["default"];
+ 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 Exception("Template was precompiled with an older version of Handlebars than the current runtime. "+
+ "Please update your precompiler to a newer version ("+runtimeVersions+") or downgrade your runtime to an older version ("+compilerVersions+").");
+ } else {
+ // Use the embedded version info since the runtime doesn't know about this revision yet
+ throw new Exception("Template was precompiled with a newer version of Handlebars than the current runtime. "+
+ "Please update your runtime to a newer version ("+compilerInfo[1]+").");
+ }
+ }
+ }
+
+ __exports__.checkRevision = checkRevision;// TODO: Remove this line and break up compilePartial
+
+ function template(templateSpec, env) {
+ if (!env) {
+ throw new Exception("No environment passed to template");
+ }
+
+ // Note: Using env.VM references rather than local var references throughout this section to allow
+ // for external users to override these as psuedo-supported APIs.
+ var invokePartialWrapper = function(partial, name, context, helpers, partials, data) {
+ var result = env.VM.invokePartial.apply(this, arguments);
+ if (result != null) { return result; }
+
+ if (env.compile) {
+ var options = { helpers: helpers, partials: partials, data: data };
+ partials[name] = env.compile(partial, { data: data !== undefined }, env);
+ return partials[name](context, options);
+ } else {
+ throw new Exception("The partial " + name + " could not be compiled when running in runtime-only mode");
+ }
+ };
+
+ // Just add water
+ var container = {
+ escapeExpression: Utils.escapeExpression,
+ invokePartial: invokePartialWrapper,
+ programs: [],
+ program: function(i, fn, data) {
+ var programWrapper = this.programs[i];
+ if(data) {
+ programWrapper = program(i, fn, data);
+ } else if (!programWrapper) {
+ programWrapper = this.programs[i] = program(i, fn);
+ }
+ 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: env.VM.programWithDepth,
+ noop: env.VM.noop,
+ compilerInfo: null
+ };
+
+ return function(context, options) {
+ options = options || {};
+ var namespace = options.partial ? options : env,
+ helpers,
+ partials;
+
+ if (!options.partial) {
+ helpers = options.helpers;
+ partials = options.partials;
+ }
+ var result = templateSpec.call(
+ container,
+ namespace, context,
+ helpers,
+ partials,
+ options.data);
+
+ if (!options.partial) {
+ env.VM.checkRevision(container.compilerInfo);
+ }
+
+ return result;
+ };
+ }
+
+ __exports__.template = template;function programWithDepth(i, fn, data /*, $depth */) {
+ var args = Array.prototype.slice.call(arguments, 3);
+
+ var prog = function(context, options) {
+ options = options || {};
+
+ return fn.apply(this, [context, options.data || data].concat(args));
+ };
+ prog.program = i;
+ prog.depth = args.length;
+ return prog;
+ }
+
+ __exports__.programWithDepth = programWithDepth;function program(i, fn, data) {
+ var prog = function(context, options) {
+ options = options || {};
+
+ return fn(context, options.data || data);
+ };
+ prog.program = i;
+ prog.depth = 0;
+ return prog;
+ }
+
+ __exports__.program = program;function invokePartial(partial, name, context, helpers, partials, data) {
+ var options = { partial: true, helpers: helpers, partials: partials, data: data };
+
+ if(partial === undefined) {
+ throw new Exception("The partial " + name + " could not be found");
+ } else if(partial instanceof Function) {
+ return partial(context, options);
+ }
+ }
+
+ __exports__.invokePartial = invokePartial;function noop() { return ""; }
+
+ __exports__.noop = noop;
+ });
+define("handlebars/safe-string",
+ ["exports"],
+ function(__exports__) {
+ "use strict";
+ // Build out our basic SafeString type
+ function SafeString(string) {
+ this.string = string;
+ }
+
+ SafeString.prototype.toString = function() {
+ return "" + this.string;
+ };
+
+ __exports__["default"] = SafeString;
+ });
+define("handlebars/utils",
+ ["./safe-string","exports"],
+ function(__dependency1__, __exports__) {
+ "use strict";
+ /*jshint -W004 */
+ var SafeString = __dependency1__["default"];
+
+ var escape = {
+ "&": "&",
+ "<": "<",
+ ">": ">",
+ '"': """,
+ "'": "'",
+ "`": "`"
+ };
+
+ var badChars = /[&<>"'`]/g;
+ var possible = /[&<>"'`]/;
+
+ function escapeChar(chr) {
+ return escape[chr] || "&";
+ }
+
+ function extend(obj, value) {
+ for(var key in value) {
+ if(Object.prototype.hasOwnProperty.call(value, key)) {
+ obj[key] = value[key];
+ }
+ }
+ }
+
+ __exports__.extend = extend;var toString = Object.prototype.toString;
+ __exports__.toString = toString;
+ // Sourced from lodash
+ // https://github.com/bestiejs/lodash/blob/master/LICENSE.txt
+ var isFunction = function(value) {
+ return typeof value === 'function';
+ };
+ // fallback for older versions of Chrome and Safari
+ if (isFunction(/x/)) {
+ isFunction = function(value) {
+ return typeof value === 'function' && toString.call(value) === '[object Function]';
+ };
+ }
+ var isFunction;
+ __exports__.isFunction = isFunction;
+ var isArray = Array.isArray || function(value) {
+ return (value && typeof value === 'object') ? toString.call(value) === '[object Array]' : false;
+ };
+ __exports__.isArray = isArray;
+
+ function escapeExpression(string) {
+ // don't escape SafeStrings, since they're already safe
+ if (string instanceof SafeString) {
+ return string.toString();
+ } else if (!string && string !== 0) {
+ return "";
+ }
+
+ // Force a string conversion as this will be done by the append regardless and
+ // the regex test will do this transparently behind the scenes, causing issues if
+ // an object's to string has escaped characters in it.
+ string = "" + string;
+
+ if(!possible.test(string)) { return string; }
+ return string.replace(badChars, escapeChar);
+ }
+
+ __exports__.escapeExpression = escapeExpression;function isEmpty(value) {
+ if (!value && value !== 0) {
+ return true;
+ } else if (isArray(value) && value.length === 0) {
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+ __exports__.isEmpty = isEmpty;
+ });
+define("htmlbars-compiler",
+ ["./htmlbars-compiler/compiler","exports"],
+ function(__dependency1__, __exports__) {
+ "use strict";
+ var compilerSpec = __dependency1__.compilerSpec;
+ var compilerSpec;
+ __exports__.compilerSpec = compilerSpec;
+ });
+define("htmlbars-compiler/ast",
+ ["../handlebars/compiler/ast","exports"],
+ function(__dependency1__, __exports__) {
+ "use strict";
+ var AST = __dependency1__["default"];
+
+ var MustacheNode = AST.MustacheNode;
+ __exports__.MustacheNode = MustacheNode;var SexprNode = AST.SexprNode;
+ __exports__.SexprNode = SexprNode;var HashNode = AST.HashNode;
+ __exports__.HashNode = HashNode;var IdNode = AST.IdNode;
+ __exports__.IdNode = IdNode;var StringNode = AST.StringNode;
+ __exports__.StringNode = StringNode;
+ function ProgramNode(statements, strip) {
+ this.type = 'program';
+ this.statements = statements;
+ this.strip = strip;
+ }
+
+ __exports__.ProgramNode = ProgramNode;function BlockNode(mustache, program, inverse, strip) {
+ this.type = 'block';
+ this.mustache = mustache;
+ this.program = program;
+ this.inverse = inverse;
+ this.strip = strip;
+ }
+
+ __exports__.BlockNode = BlockNode;function ComponentNode(tag, attributes, program) {
+ this.type = 'component';
+ this.tag = tag;
+ this.attributes = attributes;
+ this.program = program;
+ }
+
+ __exports__.ComponentNode = ComponentNode;function ElementNode(tag, attributes, helpers, children) {
+ this.type = 'element';
+ this.tag = tag;
+ this.attributes = attributes;
+ this.helpers = helpers;
+ this.children = children;
+ }
+
+ __exports__.ElementNode = ElementNode;function PartialNode(name) {
+ this.id = {};
+ this.id.string = this.name = 'partial';
+ this.type = 'mustache';
+ this.params = [name];
+ this.program = null;
+ this.inverse = null;
+ this.hash = undefined;
+ this.escaped = true;
+ this.isHelper = true;
+ }
+
+ __exports__.PartialNode = PartialNode;function AttrNode(name, value) {
+ this.type = 'attr';
+ this.name = name;
+ this.value = value;
+ }
+
+ __exports__.AttrNode = AttrNode;function TextNode(chars) {
+ this.type = 'text';
+ this.chars = chars;
+ }
+
+ __exports__.TextNode = TextNode;function childrenFor(node) {
+ if (node.type === 'program') return node.statements;
+ if (node.type === 'element') return node.children;
+ }
+
+ __exports__.childrenFor = childrenFor;function usesMorph(node) {
+ return node.type === 'mustache' || node.type === 'block' || node.type === 'component';
+ }
+
+ __exports__.usesMorph = usesMorph;function appendChild(parent, node) {
+ var children = childrenFor(parent);
+
+ var len = children.length, last;
+ if (len > 0) {
+ last = children[len-1];
+ if (usesMorph(last) && usesMorph(node)) {
+ children.push(new TextNode(''));
+ }
+ }
+ children.push(node);
+ }
+
+ __exports__.appendChild = appendChild;
+ });
+define("htmlbars-compiler/compiler",
+ ["./parser","./compiler/template","exports"],
+ function(__dependency1__, __dependency2__, __exports__) {
+ "use strict";
+ /*jshint evil:true*/
+ var preprocess = __dependency1__.preprocess;
+ var TemplateCompiler = __dependency2__.TemplateCompiler;
+
+ /*
+ * Compile a string into a template rendering function
+ *
+ * Example usage:
+ *
+ * // Template is the hydration portion of the compiled template
+ * var template = compile("Howdy {{name}}");
+ *
+ * // Template accepts three arguments:
+ * //
+ * // 1. A context object
+ * // 2. An env object
+ * // 3. A contextualElement (optional, document.body is the default)
+ * //
+ * // The env object *must* have at least these two properties:
+ * //
+ * // 1. `hooks` - Basic hooks for rendering a template
+ * // 2. `dom` - An instance of DOMHelper
+ * //
+ * import {hooks} from 'htmlbars-runtime';
+ * import {DOMHelper} from 'morph';
+ * var context = {name: 'whatever'},
+ * env = {hooks: hooks, dom: new DOMHelper()},
+ * contextualElement = document.body;
+ * var domFragment = template(context, env, contextualElement);
+ *
+ * @method compile
+ * @param {String} string An htmlbars template string
+ * @return {Function} A function for rendering the template
+ */
+ function compile(string) {
+ var program = compileSpec(string);
+ return new Function("return " + program)();
+ }
+
+ __exports__.compile = compile;/*
+ * Compile a string into a template spec string. The template spec is a string
+ * representation of a template. Usually, you would use compileSpec for
+ * pre-compilation of a template on the server.
+ *
+ * Example usage:
+ *
+ * var templateSpec = compileSpec("Howdy {{name}}");
+ * // This next step is basically what plain compile does
+ * var template = new Function("return " + templateSpec)();
+ *
+ * @method compileSpec
+ * @param {String} string An htmlbars template string
+ * @return {Function} A template spec string
+ */
+ function compileSpec(string) {
+ var ast = preprocess(string);
+ var compiler = new TemplateCompiler();
+ var program = compiler.compile(ast);
+ return program;
+ }
+
+ __exports__.compileSpec = compileSpec;
+ });
+define("htmlbars-compiler/compiler/fragment",
+ ["./utils","./quoting","exports"],
+ function(__dependency1__, __dependency2__, __exports__) {
+ "use strict";
+ var processOpcodes = __dependency1__.processOpcodes;
+ var string = __dependency2__.string;
+
+ function FragmentCompiler() {
+ this.source = [];
+ this.depth = -1;
+ }
+
+ __exports__.FragmentCompiler = FragmentCompiler;FragmentCompiler.prototype.compile = function(opcodes, options) {
+ this.source.length = 0;
+ this.depth = -1;
+ this.indent = (options && options.indent) || "";
+
+ this.source.push(this.indent+'function build(dom) {\n');
+ processOpcodes(this, opcodes);
+ this.source.push(this.indent+'}\n');
+
+ return this.source.join('');
+ };
+
+ FragmentCompiler.prototype.createFragment = function() {
+ var el = 'el'+(++this.depth);
+ this.source.push(this.indent+' var '+el+' = dom.createDocumentFragment();\n');
+ };
+
+ FragmentCompiler.prototype.createElement = function(tagName) {
+ var el = 'el'+(++this.depth);
+ this.source.push(this.indent+' var '+el+' = dom.createElement('+string(tagName)+');\n');
+ };
+
+ FragmentCompiler.prototype.createText = function(str) {
+ var el = 'el'+(++this.depth);
+ this.source.push(this.indent+' var '+el+' = dom.createTextNode('+string(str)+');\n');
+ };
+
+ FragmentCompiler.prototype.returnNode = function() {
+ var el = 'el'+this.depth;
+ this.source.push(this.indent+' return '+el+';\n');
+ };
+
+ FragmentCompiler.prototype.setAttribute = function(name, value) {
+ var el = 'el'+this.depth;
+ this.source.push(this.indent+' dom.setAttribute('+el+','+string(name)+','+string(value)+');\n');
+ };
+
+ FragmentCompiler.prototype.appendChild = function() {
+ var child = 'el'+(this.depth--);
+ var el = 'el'+this.depth;
+ this.source.push(this.indent+' dom.appendChild('+el+', '+child+');\n');
+ };
+
+ FragmentCompiler.prototype.setNamespace = function(namespace) {
+ this.source.push(this.indent+' dom.setNamespace('+(namespace ? string(namespace) : 'null')+');\n');
+ };
+ });
+define("htmlbars-compiler/compiler/fragment_opcode",
+ ["./template_visitor","./utils","../utils","exports"],
+ function(__dependency1__, __dependency2__, __dependency3__, __exports__) {
+ "use strict";
+ var TemplateVisitor = __dependency1__["default"];
+ var processOpcodes = __dependency2__.processOpcodes;
+ var forEach = __dependency3__.forEach;
+
+ function FragmentOpcodeCompiler() {
+ this.opcodes = [];
+ }
+
+ FragmentOpcodeCompiler.prototype.compile = function(ast) {
+ var templateVisitor = new TemplateVisitor();
+ templateVisitor.visit(ast);
+
+ processOpcodes(this, templateVisitor.actions);
+
+ return this.opcodes;
+ };
+
+ FragmentOpcodeCompiler.prototype.opcode = function(type, params) {
+ this.opcodes.push([type, params]);
+ };
+
+ FragmentOpcodeCompiler.prototype.text = function(text, childIndex, childCount, isSingleRoot) {
+ this.opcode('createText', [text.chars]);
+ if (!isSingleRoot) { this.opcode('appendChild'); }
+ };
+
+ FragmentOpcodeCompiler.prototype.openElement = function(element) {
+ this.opcode('createElement', [element.tag]);
+ forEach(element.attributes, this.attribute, this);
+ };
+
+ FragmentOpcodeCompiler.prototype.closeElement = function(element, childIndex, childCount, isSingleRoot) {
+ if (!isSingleRoot) { this.opcode('appendChild'); }
+ };
+
+ FragmentOpcodeCompiler.prototype.startProgram = function(program) {
+ this.opcodes.length = 0;
+ if (program.statements.length !== 1) {
+ this.opcode('createFragment');
+ }
+ };
+
+ FragmentOpcodeCompiler.prototype.endProgram = function(program) {
+ this.opcode('returnNode');
+ };
+
+ FragmentOpcodeCompiler.prototype.mustache = function () {};
+
+ FragmentOpcodeCompiler.prototype.component = function () {};
+
+ FragmentOpcodeCompiler.prototype.block = function () {};
+
+ FragmentOpcodeCompiler.prototype.attribute = function(attr) {
+ if (attr.value.type === 'text') {
+ this.opcode('setAttribute', [attr.name, attr.value.chars]);
+ }
+ };
+
+ FragmentOpcodeCompiler.prototype.setNamespace = function(namespace) {
+ this.opcode('setNamespace', [namespace]);
+ };
+
+ __exports__.FragmentOpcodeCompiler = FragmentOpcodeCompiler;
+ });
+define("htmlbars-compiler/compiler/helpers",
+ ["./quoting","exports"],
+ function(__dependency1__, __exports__) {
+ "use strict";
+ var array = __dependency1__.array;
+ var hash = __dependency1__.hash;
+ var string = __dependency1__.string;
+
+ function prepareHelper(stack, size) {
+ var args = [],
+ types = [],
+ hashPairs = [],
+ hashTypes = [],
+ keyName,
+ i;
+
+ var hashSize = stack.pop();
+
+ for (i=0; i 0){
+ this.opcode( 'repairClonedNode',
+ blankChildTextNodes );
+ }
+ };
+
+ HydrationOpcodeCompiler.prototype.endProgram = function(program) {
+ distributeMorphs(this.morphs, this.opcodes);
+ };
+
+ HydrationOpcodeCompiler.prototype.text = function(string, pos, len) {
+ ++this.currentDOMChildIndex;
+ };
+
+ HydrationOpcodeCompiler.prototype.openElement = function(element, pos, len, isSingleRoot, mustacheCount, blankChildTextNodes) {
+ distributeMorphs(this.morphs, this.opcodes);
+ ++this.currentDOMChildIndex;
+
+ this.element = this.currentDOMChildIndex;
+
+ if (!isSingleRoot) {
+ this.opcode('consumeParent', this.currentDOMChildIndex);
+
+ // If our parent referance will be used more than once, cache its referance.
+ if (mustacheCount > 1) {
+ this.opcode('element', ++this.elementNum);
+ this.element = null; // Set element to null so we don't cache it twice
+ }
+ }
+ var isElementChecked = detectIsElementChecked(element);
+ if (blankChildTextNodes.length > 0 || isElementChecked) {
+ this.opcode( 'repairClonedNode',
+ blankChildTextNodes,
+ isElementChecked );
+ }
+
+ this.paths.push(this.currentDOMChildIndex);
+ this.currentDOMChildIndex = -1;
+
+ forEach(element.attributes, this.attribute, this);
+ forEach(element.helpers, this.nodeHelper, this);
+ };
+
+ HydrationOpcodeCompiler.prototype.closeElement = function(element, pos, len, isSingleRoot) {
+ distributeMorphs(this.morphs, this.opcodes);
+ if (!isSingleRoot) { this.opcode('popParent'); }
+ this.currentDOMChildIndex = this.paths.pop();
+ };
+
+ HydrationOpcodeCompiler.prototype.block = function(block, childIndex, childrenLength) {
+ var currentDOMChildIndex = this.currentDOMChildIndex,
+ mustache = block.mustache;
+
+ var start = (currentDOMChildIndex < 0 ? null : currentDOMChildIndex),
+ end = (childIndex === childrenLength - 1 ? null : currentDOMChildIndex + 1);
+
+ var morphNum = this.morphNum++;
+ this.morphs.push([morphNum, this.paths.slice(), start, end]);
+
+ this.opcode('program', this.templateId++, block.inverse === null ? null : this.templateId++);
+ processParams(this, mustache.params);
+ processHash(this, mustache.hash);
+ this.opcode('helper', mustache.id.string, mustache.params.length, mustache.escaped, morphNum);
+ };
+
+ HydrationOpcodeCompiler.prototype.component = function(component, childIndex, childrenLength) {
+ var currentDOMChildIndex = this.currentDOMChildIndex;
+
+ var start = (currentDOMChildIndex < 0 ? null : currentDOMChildIndex),
+ end = (childIndex === childrenLength - 1 ? null : currentDOMChildIndex + 1);
+
+ var morphNum = this.morphNum++;
+ this.morphs.push([morphNum, this.paths.slice(), start, end]);
+
+ this.opcode('program', this.templateId++, null);
+ processHash(this, buildHashFromAttributes(component.attributes));
+ this.opcode('component', component.tag, morphNum);
+ };
+
+ HydrationOpcodeCompiler.prototype.opcode = function(type) {
+ var params = [].slice.call(arguments, 1);
+ this.opcodes.push([type, params]);
+ };
+
+ HydrationOpcodeCompiler.prototype.attribute = function(attr) {
+ if (attr.value.type === 'text') return;
+
+ // We treat attribute like a attribute helper evaluated by the element hook.
+ //
+ // Unwrapped any mustaches to just be their internal sexprs.
+ this.nodeHelper({
+ params: [attr.name, attr.value.sexpr],
+ hash: null,
+ id: {
+ string: 'attribute'
+ }
+ });
+ };
+
+ HydrationOpcodeCompiler.prototype.nodeHelper = function(mustache) {
+ this.opcode('program', null, null);
+ processParams(this, mustache.params);
+ processHash(this, mustache.hash);
+ // If we have a helper in a node, and this element has not been cached, cache it
+ if(this.element !== null){
+ this.opcode('element', ++this.elementNum);
+ this.element = null; // Reset element so we don't cache it more than once
+ }
+ this.opcode('nodeHelper', mustache.id.string, mustache.params.length, this.elementNum);
+ };
+
+ HydrationOpcodeCompiler.prototype.mustache = function(mustache, childIndex, childrenLength) {
+ var currentDOMChildIndex = this.currentDOMChildIndex;
+
+ var start = currentDOMChildIndex,
+ end = (childIndex === childrenLength - 1 ? -1 : currentDOMChildIndex + 1);
+
+ var morphNum = this.morphNum++;
+ this.morphs.push([morphNum, this.paths.slice(), start, end]);
+
+ if (mustache.isHelper) {
+ this.opcode('program', null, null);
+ processParams(this, mustache.params);
+ processHash(this, mustache.hash);
+ this.opcode('helper', mustache.id.string, mustache.params.length, mustache.escaped, morphNum);
+ } else {
+ this.opcode('ambiguous', mustache.id.string, mustache.escaped, morphNum);
+ }
+ };
+
+ HydrationOpcodeCompiler.prototype.sexpr = function(sexpr) {
+ this.string('sexpr');
+ this.opcode('program', null, null);
+ processParams(this, sexpr.params);
+ processHash(this, sexpr.hash);
+ this.opcode('sexpr', sexpr.id.string, sexpr.params.length);
+ };
+
+ HydrationOpcodeCompiler.prototype.string = function(str) {
+ this.opcode('string', str);
+ };
+
+ HydrationOpcodeCompiler.prototype.mustacheInAttr = function(mustache) {
+ if (mustache.isHelper) {
+ this.opcode('program', null, null);
+ processParams(this, mustache.params);
+ processHash(this, mustache.hash);
+ this.opcode('helperAttr', mustache.id.string, mustache.params.length, mustache.escaped);
+ } else {
+ this.opcode('ambiguousAttr', mustache.id.string, mustache.escaped);
+ }
+ };
+
+ HydrationOpcodeCompiler.prototype.ID = function(id) {
+ this.opcode('id', id.parts);
+ };
+
+ HydrationOpcodeCompiler.prototype.STRING = function(string) {
+ this.opcode('stringLiteral', string.stringModeValue);
+ };
+
+ HydrationOpcodeCompiler.prototype.BOOLEAN = function(boolean) {
+ this.opcode('literal', boolean.stringModeValue);
+ };
+
+ HydrationOpcodeCompiler.prototype.INTEGER = function(integer) {
+ this.opcode('literal', integer.stringModeValue);
+ };
+
+ function processParams(compiler, params) {
+ forEach(params, function(param) {
+ if (param.type === 'text') {
+ compiler.STRING({ stringModeValue: param.chars });
+ } else if (param.type) {
+ compiler[param.type](param);
+ } else {
+ compiler.STRING({ stringModeValue: param });
+ }
+ });
+ }
+
+ function processHash(compiler, hash) {
+ if (hash) {
+ forEach(hash.pairs, function(pair) {
+ var name = pair[0], param = pair[1];
+ compiler[param.type](param);
+ compiler.opcode('stackLiteral', name);
+ });
+ compiler.opcode('stackLiteral', hash.pairs.length);
+ } else {
+ compiler.opcode('stackLiteral', 0);
+ }
+ }
+
+ function distributeMorphs(morphs, opcodes) {
+ if (morphs.length === 0) {
+ return;
+ }
+
+ // Splice morphs after the most recent shareParent/consumeParent.
+ var o;
+ for (o = opcodes.length - 1; o >= 0; --o) {
+ var opcode = opcodes[o][0];
+ if (opcode === 'element' || opcode === 'consumeParent' || opcode === 'popParent') {
+ break;
+ }
+ }
+
+ var spliceArgs = [o + 1, 0];
+ for (var i = 0; i < morphs.length; ++i) {
+ var p = morphs[i];
+ spliceArgs.push(['morph', [p[0], p[1], p[2], p[3]]]);
+ }
+ opcodes.splice.apply(opcodes, spliceArgs);
+ morphs.length = 0;
+ }
+
+ __exports__.HydrationOpcodeCompiler = HydrationOpcodeCompiler;
+ });
+define("htmlbars-compiler/compiler/quoting",
+ ["exports"],
+ function(__exports__) {
+ "use strict";
+ function escapeString(str) {
+ return str.replace(/"/g, '\\"').replace(/\n/g, "\\n");
+ }
+
+ __exports__.escapeString = escapeString;
+
+ function string(str) {
+ return '"' + escapeString(str) + '"';
+ }
+
+ __exports__.string = string;
+
+ function array(a) {
+ return "[" + a + "]";
+ }
+
+ __exports__.array = array;
+
+ function quotedArray(list) {
+ return array(list.map(string).join(", "));
+ }
+
+ __exports__.quotedArray = quotedArray;function hash(pairs) {
+ return "{" + pairs.join(",") + "}";
+ }
+
+ __exports__.hash = hash;function repeat(chars, times) {
+ var str = "";
+ while (times--) {
+ str += chars;
+ }
+ return str;
+ }
+
+ __exports__.repeat = repeat;
+ });
+define("htmlbars-compiler/compiler/template",
+ ["./fragment_opcode","./fragment","./hydration_opcode","./hydration","./template_visitor","./utils","./quoting","exports"],
+ function(__dependency1__, __dependency2__, __dependency3__, __dependency4__, __dependency5__, __dependency6__, __dependency7__, __exports__) {
+ "use strict";
+ var FragmentOpcodeCompiler = __dependency1__.FragmentOpcodeCompiler;
+ var FragmentCompiler = __dependency2__.FragmentCompiler;
+ var HydrationOpcodeCompiler = __dependency3__.HydrationOpcodeCompiler;
+ var HydrationCompiler = __dependency4__.HydrationCompiler;
+ var TemplateVisitor = __dependency5__["default"];
+ var processOpcodes = __dependency6__.processOpcodes;
+ var string = __dependency7__.string;
+ var repeat = __dependency7__.repeat;
+
+ function TemplateCompiler() {
+ this.fragmentOpcodeCompiler = new FragmentOpcodeCompiler();
+ this.fragmentCompiler = new FragmentCompiler();
+ this.hydrationOpcodeCompiler = new HydrationOpcodeCompiler();
+ this.hydrationCompiler = new HydrationCompiler();
+ this.templates = [];
+ this.childTemplates = [];
+ }
+
+ __exports__.TemplateCompiler = TemplateCompiler;TemplateCompiler.prototype.compile = function(ast) {
+ var templateVisitor = new TemplateVisitor();
+ templateVisitor.visit(ast);
+
+ processOpcodes(this, templateVisitor.actions);
+
+ return this.templates.pop();
+ };
+
+ TemplateCompiler.prototype.startProgram = function(program, childTemplateCount, blankChildTextNodes) {
+ this.fragmentOpcodeCompiler.startProgram(program, childTemplateCount, blankChildTextNodes);
+ this.hydrationOpcodeCompiler.startProgram(program, childTemplateCount, blankChildTextNodes);
+
+ this.childTemplates.length = 0;
+ while(childTemplateCount--) {
+ this.childTemplates.push(this.templates.pop());
+ }
+ };
+
+ TemplateCompiler.prototype.endProgram = function(program, programDepth) {
+ this.fragmentOpcodeCompiler.endProgram(program);
+ this.hydrationOpcodeCompiler.endProgram(program);
+
+ var indent = repeat(" ", programDepth);
+ var options = {
+ indent: indent + " "
+ };
+
+ // function build(dom) { return fragment; }
+ var fragmentProgram = this.fragmentCompiler.compile(
+ this.fragmentOpcodeCompiler.opcodes,
+ options
+ );
+
+ // function hydrate(fragment) { return mustaches; }
+ var hydrationProgram = this.hydrationCompiler.compile(
+ this.hydrationOpcodeCompiler.opcodes,
+ options
+ );
+
+ var childTemplateVars = "";
+ for (var i=0, l=this.childTemplates.length; ibaz
+ *
+ * produces the actions
+ *
+ * [['startProgram', [programNode, 0]],
+ * ['text', [textNode, 0, 3]],
+ * ['mustache', [mustacheNode, 1, 3]],
+ * ['openElement', [elementNode, 2, 3, 0]],
+ * ['text', [textNode, 0, 1]],
+ * ['closeElement', [elementNode, 2, 3],
+ * ['endProgram', [programNode]]]
+ *
+ * This visitor walks the AST depth first and backwards. As
+ * a result the bottom-most child template will appear at the
+ * top of the actions list whereas the root template will appear
+ * at the bottom of the list. For example,
+ *
+ *
{{#if}}foo{{else}}bar{{/if}}
+ *
+ * produces the actions
+ *
+ * [['startProgram', [programNode, 0]],
+ * ['text', [textNode, 0, 2, 0]],
+ * ['openElement', [elementNode, 1, 2, 0]],
+ * ['closeElement', [elementNode, 1, 2]],
+ * ['endProgram', [programNode]],
+ * ['startProgram', [programNode, 0]],
+ * ['text', [textNode, 0, 1]],
+ * ['endProgram', [programNode]],
+ * ['startProgram', [programNode, 2]],
+ * ['openElement', [elementNode, 0, 1, 1]],
+ * ['block', [blockNode, 0, 1]],
+ * ['closeElement', [elementNode, 0, 1]],
+ * ['endProgram', [programNode]]]
+ *
+ * The state of the traversal is maintained by a stack of frames.
+ * Whenever a node with children is entered (either a ProgramNode
+ * or an ElementNode) a frame is pushed onto the stack. The frame
+ * contains information about the state of the traversal of that
+ * node. For example,
+ *
+ * - index of the current child node being visited
+ * - the number of mustaches contained within its child nodes
+ * - the list of actions generated by its child nodes
+ */
+
+ function TemplateVisitor() {
+ this.frameStack = [];
+ this.actions = [];
+ this.programDepth = -1;
+ }
+
+ // Traversal methods
+
+ TemplateVisitor.prototype.visit = function(node) {
+ this[node.type](node);
+ };
+
+ TemplateVisitor.prototype.program = function(program) {
+ this.programDepth++;
+
+ var parentFrame = this.getCurrentFrame();
+ var programFrame = this.pushFrame();
+
+ programFrame.parentNode = program;
+ programFrame.children = program.statements;
+ programFrame.childCount = program.statements.length;
+ programFrame.blankChildTextNodes = [];
+ programFrame.actions.push(['endProgram', [program, this.programDepth]]);
+
+ for (var i = program.statements.length - 1; i >= 0; i--) {
+ programFrame.childIndex = i;
+ this.visit(program.statements[i]);
+ }
+
+ programFrame.actions.push(['startProgram', [
+ program, programFrame.childTemplateCount, programFrame.blankChildTextNodes.reverse() ]]);
+ this.popFrame();
+
+ this.programDepth--;
+
+ // Push the completed template into the global actions list
+ if (parentFrame) { parentFrame.childTemplateCount++; }
+ push.apply(this.actions, programFrame.actions.reverse());
+ };
+
+ TemplateVisitor.prototype.element = function(element) {
+ var parentFrame = this.getCurrentFrame();
+ var elementFrame = this.pushFrame();
+ var parentNode = parentFrame.parentNode;
+
+ elementFrame.parentNode = element;
+ elementFrame.children = element.children;
+ elementFrame.childCount = element.children.length;
+ elementFrame.mustacheCount += element.helpers.length;
+ elementFrame.blankChildTextNodes = [];
+
+ var actionArgs = [
+ element,
+ parentFrame.childIndex,
+ parentFrame.childCount,
+ parentNode.type === 'program' && parentFrame.childCount === 1
+ ];
+
+ var lastNode = parentFrame.childIndex === parentFrame.childCount-1,
+ introducesNamespace = elementIntroducesNamespace(element, parentFrame.parentNode);
+ if ( !lastNode && introducesNamespace ) {
+ elementFrame.actions.push(['setNamespace', [parentNode.namespaceURI]]);
+ }
+ elementFrame.actions.push(['closeElement', actionArgs]);
+ if ( !lastNode && element.isHTMLIntergrationPoint ) {
+ elementFrame.actions.push(['setNamespace', []]);
+ }
+
+ for (var i = element.attributes.length - 1; i >= 0; i--) {
+ this.visit(element.attributes[i]);
+ }
+
+ for (i = element.children.length - 1; i >= 0; i--) {
+ elementFrame.childIndex = i;
+ this.visit(element.children[i]);
+ }
+
+ if ( element.isHTMLIntergrationPoint ) {
+ elementFrame.actions.push(['setNamespace', []]);
+ }
+ elementFrame.actions.push(['openElement', actionArgs.concat([
+ elementFrame.mustacheCount, elementFrame.blankChildTextNodes.reverse() ])]);
+ if ( introducesNamespace ) {
+ elementFrame.actions.push(['setNamespace', [element.namespaceURI]]);
+ }
+ this.popFrame();
+
+ // Propagate the element's frame state to the parent frame
+ if (elementFrame.mustacheCount > 0) { parentFrame.mustacheCount++; }
+ parentFrame.childTemplateCount += elementFrame.childTemplateCount;
+ push.apply(parentFrame.actions, elementFrame.actions);
+ };
+
+ TemplateVisitor.prototype.attr = function(attr) {
+ if (attr.value.type === 'mustache') {
+ this.getCurrentFrame().mustacheCount++;
+ }
+ };
+
+ TemplateVisitor.prototype.block = function(node) {
+ var frame = this.getCurrentFrame();
+ var parentNode = frame.parentNode;
+
+ frame.mustacheCount++;
+ frame.actions.push([node.type, [node, frame.childIndex, frame.childCount]]);
+
+ if (node.inverse) { this.visit(node.inverse); }
+ if (node.program) { this.visit(node.program); }
+ };
+
+ TemplateVisitor.prototype.partial = function(node) {
+ var frame = this.getCurrentFrame();
+ frame.mustacheCount++;
+ frame.actions.push(['mustache', [node, frame.childIndex, frame.childCount]]);
+ };
+
+ TemplateVisitor.prototype.component = TemplateVisitor.prototype.block;
+
+ TemplateVisitor.prototype.text = function(text) {
+ var frame = this.getCurrentFrame();
+ var isSingleRoot = frame.parentNode.type === 'program' && frame.childCount === 1;
+ if (text.chars === '') {
+ frame.blankChildTextNodes.push(domIndexOf(frame.children, text));
+ }
+ frame.actions.push(['text', [text, frame.childIndex, frame.childCount, isSingleRoot]]);
+ };
+
+ TemplateVisitor.prototype.mustache = function(mustache) {
+ var frame = this.getCurrentFrame();
+ frame.mustacheCount++;
+ frame.actions.push(['mustache', [mustache, frame.childIndex, frame.childCount]]);
+ };
+
+ // Frame helpers
+
+ TemplateVisitor.prototype.getCurrentFrame = function() {
+ return this.frameStack[this.frameStack.length - 1];
+ };
+
+ TemplateVisitor.prototype.pushFrame = function() {
+ var frame = new Frame();
+ this.frameStack.push(frame);
+ return frame;
+ };
+
+ TemplateVisitor.prototype.popFrame = function() {
+ return this.frameStack.pop();
+ };
+
+ __exports__["default"] = TemplateVisitor;
+
+
+ // Returns the index of `domNode` in the `nodes` array, skipping
+ // over any nodes which do not represent DOM nodes.
+ function domIndexOf(nodes, domNode) {
+ var index = -1;
+
+ for (var i = 0; i < nodes.length; i++) {
+ var node = nodes[i];
+
+ if (node.type !== 'text' && node.type !== 'element') {
+ continue;
+ } else {
+ index++;
+ }
+
+ if (node === domNode) {
+ return index;
+ }
+ }
+
+ return -1;
+ }
+ });
+define("htmlbars-compiler/compiler/utils",
+ ["exports"],
+ function(__exports__) {
+ "use strict";
+ function processOpcodes(compiler, opcodes) {
+ for (var i=0, l=opcodes.length; i 0 && statements[i-1].strip && statements[i-1].strip.right) ||
+ (i === 0 && program.strip.left)) {
+ statement.chars = statement.chars.replace(/^\s+/, '');
+ }
+
+ if ((i < l-1 && statements[i+1].strip && statements[i+1].strip.left) ||
+ (i === l-1 && program.strip.right)) {
+ statement.chars = statement.chars.replace(/\s+$/, '');
+ }
+
+ // Remove unnecessary text nodes
+ if (statement.chars.length === 0) {
+ if ((i > 0 && statements[i-1].type === 'element') ||
+ (i < l-1 && statements[i+1].type === 'element')) {
+ statements.splice(i, 1);
+ i--;
+ l--;
+ }
+ }
+ }
+ }
+
+ __exports__.postprocessProgram = postprocessProgram;
+ });
+define("htmlbars-compiler/html-parser/node-handlers",
+ ["../ast","../html-parser/helpers","../html-parser/tokens","../utils","exports"],
+ function(__dependency1__, __dependency2__, __dependency3__, __dependency4__, __exports__) {
+ "use strict";
+ var BlockNode = __dependency1__.BlockNode;
+ var ProgramNode = __dependency1__.ProgramNode;
+ var TextNode = __dependency1__.TextNode;
+ var PartialNode = __dependency1__.PartialNode;
+ var appendChild = __dependency1__.appendChild;
+ var usesMorph = __dependency1__.usesMorph;
+ var postprocessProgram = __dependency2__.postprocessProgram;
+ var Chars = __dependency3__.Chars;
+ var forEach = __dependency4__.forEach;
+
+ var nodeHandlers = {
+
+ program: function(program) {
+ var statements = [];
+ var node = new ProgramNode(statements, program.strip);
+ var i, l = program.statements.length;
+
+ this.elementStack.push(node);
+
+ if (l === 0) { return this.elementStack.pop(); }
+
+ for (i = 0; i < l; i++) {
+ this.acceptNode(program.statements[i]);
+ }
+
+ this.acceptToken(this.tokenizer.tokenizeEOF());
+
+ postprocessProgram(node);
+
+ // Ensure that that the element stack is balanced properly.
+ var poppedNode = this.elementStack.pop();
+ if (poppedNode !== node) {
+ throw new Error("Unclosed element: " + poppedNode.tag);
+ }
+
+ return node;
+ },
+
+ block: function(block) {
+ switchToHandlebars(this);
+ this.acceptToken(block);
+
+ var mustache = block.mustache;
+ var program = this.acceptNode(block.program);
+ var inverse = block.inverse ? this.acceptNode(block.inverse) : null;
+ var strip = block.strip;
+
+ // Normalize inverse's strip
+ if (inverse && !inverse.strip.left) {
+ inverse.strip.left = false;
+ }
+
+ var node = new BlockNode(mustache, program, inverse, strip);
+ var parentProgram = this.currentElement();
+ appendChild(parentProgram, node);
+ },
+
+ content: function(content) {
+ var tokens = this.tokenizer.tokenizePart(content.string);
+
+ return forEach(tokens, this.acceptToken, this);
+ },
+
+ mustache: function(mustache) {
+ switchToHandlebars(this);
+ this.acceptToken(mustache);
+ },
+
+ comment: function(comment) {
+ return;
+ },
+
+ partial: function(partial) {
+ var node = new PartialNode(partial.partialName.name);
+ appendChild(this.currentElement(), node);
+ return;
+ }
+ };
+
+ function switchToHandlebars(processor) {
+ var token = processor.tokenizer.token;
+
+ // TODO: Monkey patch Chars.addChar like attributes
+ if (token instanceof Chars) {
+ processor.acceptToken(token);
+ processor.tokenizer.token = null;
+ }
+ }
+
+ __exports__["default"] = nodeHandlers;
+ });
+define("htmlbars-compiler/html-parser/token-handlers",
+ ["../ast","./helpers","../utils","exports"],
+ function(__dependency1__, __dependency2__, __dependency3__, __exports__) {
+ "use strict";
+ var ProgramNode = __dependency1__.ProgramNode;
+ var ComponentNode = __dependency1__.ComponentNode;
+ var ElementNode = __dependency1__.ElementNode;
+ var TextNode = __dependency1__.TextNode;
+ var appendChild = __dependency1__.appendChild;
+ var postprocessProgram = __dependency2__.postprocessProgram;
+ var forEach = __dependency3__.forEach;
+
+ // This table maps from the state names in the tokenizer to a smaller
+ // number of states that control how mustaches are handled
+ var states = {
+ "beforeAttributeValue": "before-attr",
+ "attributeValueDoubleQuoted": "attr",
+ "attributeValueSingleQuoted": "attr",
+ "attributeValueUnquoted": "attr",
+ "beforeAttributeName": "in-tag"
+ };
+
+ // The HTML elements in this list are speced by
+ // http://www.w3.org/TR/html-markup/syntax.html#syntax-elements,
+ // and will be forced to close regardless of if they have a
+ // self-closing /> at the end.
+ var voidTagNames = "area base br col command embed hr img input keygen link meta param source track wbr";
+ var voidMap = {};
+
+ forEach(voidTagNames.split(" "), function(tagName) {
+ voidMap[tagName] = true;
+ });
+
+ var svgNamespace = "http://www.w3.org/2000/svg",
+ // http://www.w3.org/html/wg/drafts/html/master/syntax.html#html-integration-point
+ svgHTMLIntegrationPoints = {'foreignObject':true, 'desc':true, 'title':true};
+
+ function applyNamespace(tag, element, currentElement){
+ if (tag.tagName === 'svg') {
+ element.namespaceURI = svgNamespace;
+ } else if (
+ currentElement.type === 'element' &&
+ currentElement.namespaceURI &&
+ !currentElement.isHTMLIntegrationPoint
+ ) {
+ element.namespaceURI = currentElement.namespaceURI;
+ }
+ }
+
+ function applyHTMLIntegrationPoint(tag, element){
+ if (svgHTMLIntegrationPoints[tag.tagName]) {
+ element.isHTMLIntegrationPoint = true;
+ }
+ }
+
+
+ // Except for `mustache`, all tokens are only allowed outside of
+ // a start or end tag.
+ var tokenHandlers = {
+
+ Chars: function(token) {
+ var current = this.currentElement();
+ var text = new TextNode(token.chars);
+ appendChild(current, text);
+ },
+
+ StartTag: function(tag) {
+ var element = new ElementNode(tag.tagName, tag.attributes, tag.helpers || [], []);
+ applyNamespace(tag, element, this.currentElement());
+ applyHTMLIntegrationPoint(tag, element);
+ this.elementStack.push(element);
+ if (voidMap.hasOwnProperty(tag.tagName) || tag.selfClosing) {
+ tokenHandlers.EndTag.call(this, tag);
+ }
+ },
+
+ block: function(block) {
+ if (this.tokenizer.state !== 'data') {
+ throw new Error("A block may only be used inside an HTML element or another block.");
+ }
+ },
+
+ mustache: function(mustache) {
+ var state = this.tokenizer.state;
+ var token = this.tokenizer.token;
+
+ switch(states[state]) {
+ case "before-attr":
+ this.tokenizer.state = 'attributeValueUnquoted';
+ token.addToAttributeValue(mustache);
+ return;
+ case "attr":
+ token.addToAttributeValue(mustache);
+ return;
+ case "in-tag":
+ token.addTagHelper(mustache);
+ return;
+ default:
+ appendChild(this.currentElement(), mustache);
+ }
+ },
+
+ EndTag: function(tag) {
+ var element = this.elementStack.pop();
+ var parent = this.currentElement();
+
+ if (element.tag !== tag.tagName) {
+ throw new Error("Closing tag " + tag.tagName + " did not match last open tag " + element.tag);
+ }
+
+ if (element.tag.indexOf("-") === -1) {
+ appendChild(parent, element);
+ } else {
+ var program = new ProgramNode(element.children, { left: false, right: false });
+ postprocessProgram(program);
+ var component = new ComponentNode(element.tag, element.attributes, program);
+ appendChild(parent, component);
+ }
+
+ }
+
+ };
+
+ __exports__["default"] = tokenHandlers;
+ });
+define("htmlbars-compiler/html-parser/tokens",
+ ["../../simple-html-tokenizer","../ast","exports"],
+ function(__dependency1__, __dependency2__, __exports__) {
+ "use strict";
+ var Chars = __dependency1__.Chars;
+ var StartTag = __dependency1__.StartTag;
+ var EndTag = __dependency1__.EndTag;
+ var AttrNode = __dependency2__.AttrNode;
+ var TextNode = __dependency2__.TextNode;
+ var MustacheNode = __dependency2__.MustacheNode;
+ var StringNode = __dependency2__.StringNode;
+ var IdNode = __dependency2__.IdNode;
+
+ StartTag.prototype.startAttribute = function(char) {
+ this.finalizeAttributeValue();
+ this.currentAttribute = new AttrNode(char.toLowerCase(), []);
+ this.attributes.push(this.currentAttribute);
+ };
+
+ StartTag.prototype.addToAttributeName = function(char) {
+ this.currentAttribute.name += char;
+ };
+
+ StartTag.prototype.addToAttributeValue = function(char) {
+ var value = this.currentAttribute.value;
+
+ if (char.type === 'mustache') {
+ value.push(char);
+ } else {
+ if (value.length > 0 && value[value.length - 1].type === 'text') {
+ value[value.length - 1].chars += char;
+ } else {
+ value.push(new TextNode(char));
+ }
+ }
+ };
+
+ StartTag.prototype.finalize = function() {
+ this.finalizeAttributeValue();
+ delete this.currentAttribute;
+ return this;
+ };
+
+ StartTag.prototype.finalizeAttributeValue = function() {
+ var attr = this.currentAttribute;
+
+ if (!attr) return;
+
+ if (attr.value.length === 1) {
+ // Unwrap a single TextNode or MustacheNode
+ attr.value = attr.value[0];
+ } else {
+ // If the attr value has multiple parts combine them into
+ // a single MustacheNode with the concat helper
+ var params = [ new IdNode([{ part: 'concat' }]) ];
+
+ for (var i = 0; i < attr.value.length; i++) {
+ var part = attr.value[i];
+ if (part.type === 'text') {
+ params.push(new StringNode(part.chars));
+ } else if (part.type === 'mustache') {
+ var sexpr = part.sexpr;
+ delete sexpr.isRoot;
+
+ if (sexpr.isHelper) {
+ sexpr.isHelper = true;
+ }
+
+ params.push(sexpr);
+ }
+ }
+
+ attr.value = new MustacheNode(params, undefined, true, { left: false, right: false });
+ }
+ };
+
+ StartTag.prototype.addTagHelper = function(helper) {
+ var helpers = this.helpers = this.helpers || [];
+ helpers.push(helper);
+ };
+
+ __exports__.Chars = Chars;
+ __exports__.StartTag = StartTag;
+ __exports__.EndTag = EndTag;
+ });
+define("htmlbars-compiler/parser",
+ ["../handlebars/compiler/base","../simple-html-tokenizer","./html-parser/node-handlers","./html-parser/token-handlers","exports"],
+ function(__dependency1__, __dependency2__, __dependency3__, __dependency4__, __exports__) {
+ "use strict";
+ var parse = __dependency1__.parse;
+ var Tokenizer = __dependency2__.Tokenizer;
+ var nodeHandlers = __dependency3__["default"];
+ var tokenHandlers = __dependency4__["default"];
+
+ function preprocess(html, options) {
+ var ast = parse(html);
+ var combined = new HTMLProcessor().acceptNode(ast);
+ return combined;
+ }
+
+ __exports__.preprocess = preprocess;function HTMLProcessor() {
+ this.elementStack = [];
+ this.tokenizer = new Tokenizer('');
+ this.nodeHandlers = nodeHandlers;
+ this.tokenHandlers = tokenHandlers;
+ }
+
+ HTMLProcessor.prototype.acceptNode = function(node) {
+ return this.nodeHandlers[node.type].call(this, node);
+ };
+
+ HTMLProcessor.prototype.acceptToken = function(token) {
+ if (token) {
+ return this.tokenHandlers[token.type].call(this, token);
+ }
+ };
+
+ HTMLProcessor.prototype.currentElement = function() {
+ return this.elementStack[this.elementStack.length - 1];
+ };
+ });
+define("htmlbars-compiler/utils",
+ ["exports"],
+ function(__exports__) {
+ "use strict";
+ function forEach(array, callback, binding) {
+ var i, l;
+ if (binding === undefined) {
+ for (i=0, l=array.length; i
+ //
+ //
+ //
+ // The tbody may be omitted, and the browser will accept and render:
+ //
+ //
+ //
+ //
+ // However, the omitted start tag will still be added to the DOM. Here
+ // we test the string and context to see if the browser is about to
+ // perform this cleanup.
+ //
+ // http://www.whatwg.org/specs/web-apps/current-work/multipage/syntax.html#optional-tags
+ // describes which tags are omittable. The spec for tbody and colgroup
+ // explains this behavior:
+ //
+ // http://www.whatwg.org/specs/web-apps/current-work/multipage/tables.html#the-tbody-element
+ // http://www.whatwg.org/specs/web-apps/current-work/multipage/tables.html#the-colgroup-element
+ //
+
+ var omittedStartTagChildTest = /<([\w:]+)/;
+ function detectOmittedStartTag(string, contextualElement){
+ // Omitted start tags are only inside table tags.
+ if (contextualElement.tagName === 'TABLE') {
+ var omittedStartTagChildMatch = omittedStartTagChildTest.exec(string);
+ if (omittedStartTagChildMatch) {
+ var omittedStartTagChild = omittedStartTagChildMatch[1];
+ // It is already asserted that the contextual element is a table
+ // and not the proper start tag. Just see if a tag was omitted.
+ return omittedStartTagChild === 'tr' ||
+ omittedStartTagChild === 'col';
+ }
+ }
+ }
+
+ function buildSVGDOM(html, dom){
+ var div = dom.document.createElement('div');
+ div.innerHTML = '';
+ return div.firstChild.childNodes;
+ }
+
+ /*
+ * A class wrapping DOM functions to address environment compatibility,
+ * namespaces, contextual elements for morph un-escaped content
+ * insertion.
+ *
+ * When entering a template, a DOMHelper should be passed:
+ *
+ * template(context, { hooks: hooks, dom: new DOMHelper() });
+ *
+ * TODO: support foreignObject as a passed contextual element. It has
+ * a namespace (svg) that does not match its internal namespace
+ * (xhtml).
+ *
+ * @class DOMHelper
+ * @constructor
+ * @param {HTMLDocument} _document The document DOM methods are proxied to
+ */
+ function DOMHelper(_document){
+ this.document = _document || window.document;
+ this.namespace = null;
+ }
+
+ var prototype = DOMHelper.prototype;
+ prototype.constructor = DOMHelper;
+
+ prototype.insertBefore = function(element, childElement, referenceChild) {
+ return element.insertBefore(childElement, referenceChild);
+ };
+
+ prototype.appendChild = function(element, childElement) {
+ return element.appendChild(childElement);
+ };
+
+ prototype.appendText = function(element, text) {
+ return element.appendChild(this.document.createTextNode(text));
+ };
+
+ prototype.setAttribute = function(element, name, value) {
+ element.setAttribute(name, value);
+ };
+
+ if (document.createElementNS) {
+ // Only opt into namespace detection if a contextualElement
+ // is passed.
+ prototype.createElement = function(tagName, contextualElement) {
+ var namespace = this.namespace;
+ if (contextualElement) {
+ if (tagName === 'svg') {
+ namespace = svgNamespace;
+ } else {
+ namespace = interiorNamespace(contextualElement);
+ }
+ }
+ if (namespace) {
+ return this.document.createElementNS(namespace, tagName);
+ } else {
+ return this.document.createElement(tagName);
+ }
+ };
+ } else {
+ prototype.createElement = function(tagName) {
+ return this.document.createElement(tagName);
+ };
+ }
+
+ prototype.setNamespace = function(ns) {
+ this.namespace = ns;
+ };
+
+ prototype.detectNamespace = function(element) {
+ this.namespace = interiorNamespace(element);
+ };
+
+ prototype.createDocumentFragment = function(){
+ return this.document.createDocumentFragment();
+ };
+
+ prototype.createTextNode = function(text){
+ return this.document.createTextNode(text);
+ };
+
+ prototype.repairClonedNode = function(element, blankChildTextNodes, isChecked){
+ if (deletesBlankTextNodes && blankChildTextNodes.length > 0) {
+ for (var i=0, len=blankChildTextNodes.length;i]*)>", 'i'))[0];
+ var endTag = ''+tagName+'>';
+
+ var wrappedHTML = [startTag, html, endTag];
+
+ var i = wrappingTags.length;
+ var wrappedDepth = 1 + i;
+ while(i--) {
+ wrappedHTML.unshift('<'+wrappingTags[i]+'>');
+ wrappedHTML.push(''+wrappingTags[i]+'>');
+ }
+
+ var wrapper = document.createElement('div');
+ scriptSafeInnerHTML(wrapper, wrappedHTML.join(''));
+ var element = wrapper;
+ while (wrappedDepth--) {
+ element = element.firstChild;
+ while (element && element.nodeType !== 1) {
+ element = element.nextSibling;
+ }
+ }
+ while (element && element.tagName !== tagName) {
+ element = element.nextSibling;
+ }
+ return element ? element.childNodes : [];
+ }
+
+ var buildDOM;
+ if (needsShy) {
+ buildDOM = function buildDOM(html, contextualElement, dom){
+ contextualElement = dom.cloneNode(contextualElement, false);
+ scriptSafeInnerHTML(contextualElement, html);
+ return contextualElement.childNodes;
+ };
+ } else {
+ buildDOM = function buildDOM(html, contextualElement, dom){
+ contextualElement = dom.cloneNode(contextualElement, false);
+ contextualElement.innerHTML = html;
+ return contextualElement.childNodes;
+ };
+ }
+
+
+ var buildIESafeDOM;
+ if (tagNamesRequiringInnerHTMLFix.length > 0 || movesWhitespace) {
+ buildIESafeDOM = function buildIESafeDOM(html, contextualElement, dom) {
+ // Make a list of the leading text on script nodes. Include
+ // script tags without any whitespace for easier processing later.
+ var spacesBefore = [];
+ var spacesAfter = [];
+ html = html.replace(/(\s*)(
+ ```
+
+ Take note that `"welcome"` is a string and not an object
+ reference.
+
+ See [Ember.String.loc](/api/classes/Ember.String.html#method_loc) for how to
+ set up localized string references.
+
+ @method loc
+ @for Ember.Handlebars.helpers
+ @param {String} str The string to format
+ @see {Ember.String#loc}
+ */
+ __exports__["default"] = function locHelper(str) {
+ return loc(str);
+ }
+ });
+define("ember-handlebars/helpers/partial",
+ ["ember-metal/core","ember-metal/is_none","ember-handlebars/ext","ember-handlebars/helpers/binding","exports"],
+ function(__dependency1__, __dependency2__, __dependency3__, __dependency4__, __exports__) {
+ "use strict";
+ var Ember = __dependency1__["default"];
+ // Ember.assert
+ // var emberAssert = Ember.assert;
+
+ var isNone = __dependency2__.isNone;
+ var handlebarsGet = __dependency3__.handlebarsGet;
+ var bind = __dependency4__.bind;
+
+ /**
+ @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
+ */
+
+ __exports__["default"] = function partialHelper(name, options) {
+
+ var context = (options.contexts && options.contexts.length) ? options.contexts[0] : this;
+
+ options.helperName = options.helperName || 'partial';
+
+ 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 = handlebarsGet(context, name, fnOptions);
+ renderPartial(context, partialName, fnOptions);
+ };
+
+ return bind.call(context, name, options, true, exists);
+ } else {
+ // Render the partial right into parent template.
+ renderPartial(context, name, options);
+ }
+ }
+
+ function exists(value) {
+ return !isNone(value);
+ }
+
+ function renderPartial(context, name, options) {
+ var nameParts = name.split("/");
+ var lastPart = nameParts[nameParts.length - 1];
+
+ nameParts[nameParts.length - 1] = "_" + lastPart;
+
+ var view = options.data.view;
+ var underscoredName = nameParts.join("/");
+ var template = view.templateForName(underscoredName);
+ var deprecatedTemplate = !template && view.templateForName(name);
+
+ Ember.assert("Unable to find partial with name '"+name+"'.", template || deprecatedTemplate);
+
+ template = template || deprecatedTemplate;
+
+ template(context, { data: options.data });
+ }
+ });
+define("ember-handlebars/helpers/shared",
+ ["ember-handlebars/ext","exports"],
+ function(__dependency1__, __exports__) {
+ "use strict";
+ var handlebarsGet = __dependency1__.handlebarsGet;
+
+ __exports__["default"] = function resolvePaths(options) {
+ var ret = [],
+ contexts = options.contexts,
+ roots = options.roots,
+ data = options.data;
+
+ for (var i=0, l=contexts.length; i
+ {{#with loggedInUser}}
+ Last Login: {{lastLogin}}
+ User Info: {{template "user_info"}}
+ {{/with}}
+
+ ```
+
+ ```html
+
+ ```
+
+ ```handlebars
+ {{#if isUser}}
+ {{template "user_info"}}
+ {{else}}
+ {{template "unlogged_user_info"}}
+ {{/if}}
+ ```
+
+ This helper looks for templates in the global `Ember.TEMPLATES` hash. If you
+ add `";
+ return testEl.firstChild.innerHTML === '';
+ })();
+
+ // IE 8 (and likely earlier) likes to move whitespace preceeding
+ // a script tag to appear after it. This means that we can
+ // accidentally remove whitespace when updating a morph.
+ var movesWhitespace = typeof document !== 'undefined' && (function() {
+ var testEl = document.createElement('div');
+ testEl.innerHTML = "Test: Value";
+ return testEl.childNodes[0].nodeValue === 'Test:' &&
+ testEl.childNodes[2].nodeValue === ' Value';
+ })();
+
+ // Use this to find children by ID instead of using jQuery
+ var findChildById = function(element, id) {
+ if (element.getAttribute('id') === id) { return element; }
+
+ var len = element.childNodes.length, idx, node, found;
+ for (idx=0; idx 0) {
+ var len = matches.length, idx;
+ for (idx=0; idxTest');
+ canSet = el.options.length === 1;
+ }
+
+ innerHTMLTags[tagName] = canSet;
+
+ return canSet;
+ };
+
+ function setInnerHTML(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;
+ }
+
+ __exports__.setInnerHTML = setInnerHTML;function isSimpleClick(event) {
+ var modifier = event.shiftKey || event.metaKey || event.altKey || event.ctrlKey,
+ secondaryClick = event.which > 1; // IE9 may return undefined
+
+ return !modifier && !secondaryClick;
+ }
+
+ __exports__.isSimpleClick = isSimpleClick;
+ });
+define("ember-views/views/collection_view",
+ ["ember-metal/core","ember-metal/platform","ember-metal/binding","ember-metal/merge","ember-metal/property_get","ember-metal/property_set","ember-runtime/system/string","ember-views/views/container_view","ember-views/views/core_view","ember-views/views/view","ember-metal/mixin","ember-runtime/mixins/array","exports"],
+ function(__dependency1__, __dependency2__, __dependency3__, __dependency4__, __dependency5__, __dependency6__, __dependency7__, __dependency8__, __dependency9__, __dependency10__, __dependency11__, __dependency12__, __exports__) {
+ "use strict";
+
+ /**
+ @module ember
+ @submodule ember-views
+ */
+
+ var Ember = __dependency1__["default"];
+ // Ember.assert
+ var create = __dependency2__.create;
+ var isGlobalPath = __dependency3__.isGlobalPath;
+ var merge = __dependency4__["default"];
+ var get = __dependency5__.get;
+ var set = __dependency6__.set;
+ var fmt = __dependency7__.fmt;
+ var ContainerView = __dependency8__["default"];
+ var CoreView = __dependency9__["default"];
+ var View = __dependency10__["default"];
+ var observer = __dependency11__.observer;
+ var beforeObserver = __dependency11__.beforeObserver;
+ var EmberArray = __dependency12__["default"];
+
+ /**
+ `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 `` and the following code:
+
+ ```javascript
+ someItemsView = Ember.CollectionView.create({
+ classNames: ['a-collection'],
+ content: ['A','B','C'],
+ itemViewClass: Ember.View.extend({
+ template: Ember.Handlebars.compile("the letter: {{view.content}}")
+ })
+ });
+
+ someItemsView.appendTo('body');
+ ```
+
+ Will result in the following HTML structure
+
+ ```html
+
+
the letter: A
+
the letter: B
+
the letter: C
+
+ ```
+
+ ## Automatic matching of parent/child tagNames
+
+ Setting the `tagName` property of a `CollectionView` to any of
+ "ul", "ol", "table", "thead", "tbody", "tfoot", "tr", or "select" will result
+ in the item views receiving an appropriately matched `tagName` property.
+
+ Given an empty `` and the following code:
+
+ ```javascript
+ anUnorderedListView = Ember.CollectionView.create({
+ tagName: 'ul',
+ content: ['A','B','C'],
+ itemViewClass: Ember.View.extend({
+ template: Ember.Handlebars.compile("the letter: {{view.content}}")
+ })
+ });
+
+ anUnorderedListView.appendTo('body');
+ ```
+
+ Will result in the following HTML structure
+
+ ```html
+
+
the letter: A
+
the letter: B
+
the letter: C
+
+ ```
+
+ Additional `tagName` pairs can be provided by adding to
+ `Ember.CollectionView.CONTAINER_MAP `
+
+ ```javascript
+ Ember.CollectionView.CONTAINER_MAP['article'] = 'section'
+ ```
+
+ ## Programmatic creation of child views
+
+ For cases where additional customization beyond the use of a single
+ `itemViewClass` or `tagName` matching is required CollectionView's
+ `createChildView` method can be overidden:
+
+ ```javascript
+ CustomCollectionView = Ember.CollectionView.extend({
+ createChildView: function(viewClass, attrs) {
+ if (attrs.content.kind == 'album') {
+ viewClass = App.AlbumView;
+ } else {
+ viewClass = App.SongView;
+ }
+ return this._super(viewClass, attrs);
+ }
+ });
+ ```
+
+ ## Empty View
+
+ You can provide an `Ember.View` subclass to the `Ember.CollectionView`
+ instance as its `emptyView` property. If the `content` property of a
+ `CollectionView` is set to `null` or an empty array, an instance of this view
+ will be the `CollectionView`s only child.
+
+ ```javascript
+ aListWithNothing = Ember.CollectionView.create({
+ classNames: ['nothing']
+ content: null,
+ emptyView: Ember.View.extend({
+ template: Ember.Handlebars.compile("The collection is empty")
+ })
+ });
+
+ aListWithNothing.appendTo('body');
+ ```
+
+ Will result in the following HTML structure
+
+ ```html
+
+
+ The collection is empty
+
+
+ ```
+
+ ## Adding and Removing items
+
+ The `childViews` property of a `CollectionView` should not be directly
+ manipulated. Instead, add, remove, replace items from its `content` property.
+ This will trigger appropriate changes to its rendered HTML.
+
+
+ @class CollectionView
+ @namespace Ember
+ @extends Ember.ContainerView
+ @since Ember 0.9
+ */
+ var CollectionView = 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: 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: 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: 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: 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]), EmberArray.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 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 && isGlobalPath(itemViewClass)) {
+ itemViewClass = get(itemViewClass) || itemViewClass;
+ }
+
+ Ember.assert(fmt("itemViewClass must be a subclass of Ember.View, not %@",
+ [itemViewClass]),
+ 'string' === typeof itemViewClass || 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 && isGlobalPath(emptyView)) {
+ emptyView = get(emptyView) || emptyView;
+ }
+
+ emptyView = this.createChildView(emptyView);
+ addedViews.push(emptyView);
+ set(this, 'emptyView', emptyView);
+
+ if (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 = 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
+ */
+ CollectionView.CONTAINER_MAP = {
+ ul: 'li',
+ ol: 'li',
+ table: 'tr',
+ thead: 'tr',
+ tbody: 'tr',
+ tfoot: 'tr',
+ tr: 'td',
+ select: 'option'
+ };
+
+ __exports__["default"] = CollectionView;
+ });
+define("ember-views/views/component",
+ ["ember-metal/core","ember-views/mixins/component_template_deprecation","ember-runtime/mixins/target_action_support","ember-views/views/view","ember-metal/property_get","ember-metal/property_set","ember-metal/is_none","ember-metal/computed","exports"],
+ function(__dependency1__, __dependency2__, __dependency3__, __dependency4__, __dependency5__, __dependency6__, __dependency7__, __dependency8__, __exports__) {
+ "use strict";
+ var Ember = __dependency1__["default"];
+ // Ember.assert, Ember.Handlebars
+
+ var ComponentTemplateDeprecation = __dependency2__["default"];
+ var TargetActionSupport = __dependency3__["default"];
+ var View = __dependency4__["default"];
+
+ var get = __dependency5__.get;
+ var set = __dependency6__.set;
+ var isNone = __dependency7__.isNone;
+
+ var computed = __dependency8__.computed;
+
+ var a_slice = Array.prototype.slice;
+
+ /**
+ @module ember
+ @submodule ember-views
+ */
+
+ /**
+ An `Ember.Component` is a view that is completely
+ isolated. Property access in its templates go
+ to the view object and actions are targeted at
+ the view object. There is no access to the
+ surrounding context or outer controller; all
+ contextual information must be passed in.
+
+ The easiest way to create an `Ember.Component` is via
+ a template. If you name a template
+ `components/my-foo`, you will be able to use
+ `{{my-foo}}` in other templates, which will make
+ an instance of the isolated component.
+
+ ```handlebars
+ {{app-profile person=currentUser}}
+ ```
+
+ ```handlebars
+
+
{{person.title}}
+
+
{{person.signature}}
+ ```
+
+ You can use `yield` inside a template to
+ include the **contents** of any block attached to
+ the component. The block will be executed in the
+ context of the surrounding context or outer controller:
+
+ ```handlebars
+ {{#app-profile person=currentUser}}
+
Admin mode
+ {{! Executed in the controller's context. }}
+ {{/app-profile}}
+ ```
+
+ ```handlebars
+
+
{{person.title}}
+ {{! Executed in the components context. }}
+ {{yield}} {{! block contents }}
+ ```
+
+ If you want to customize the component, in order to
+ handle events or actions, you implement a subclass
+ of `Ember.Component` named after the name of the
+ component. Note that `Component` needs to be appended to the name of
+ your subclass like `AppProfileComponent`.
+
+ For example, you could implement the action
+ `hello` for the `app-profile` component:
+
+ ```javascript
+ App.AppProfileComponent = Ember.Component.extend({
+ actions: {
+ hello: function(name) {
+ console.log("Hello", name);
+ }
+ }
+ });
+ ```
+
+ And then use it in the component's template:
+
+ ```handlebars
+
+
+
{{person.title}}
+ {{yield}}
+
+
+ ```
+
+ Components must have a `-` in their name to avoid
+ conflicts with built-in controls that wrap HTML
+ elements. This is consistent with the same
+ requirement in web components.
+
+ @class Component
+ @namespace Ember
+ @extends Ember.View
+ */
+ var Component = View.extend(TargetActionSupport, ComponentTemplateDeprecation, {
+ instrumentName: 'component',
+ instrumentDisplay: computed(function() {
+ if (this._debugContainerKey) {
+ return '{{' + this._debugContainerKey.split(':')[1] + '}}';
+ }
+ }),
+
+ init: function() {
+ this._super();
+ set(this, 'origContext', get(this, 'context'));
+ 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: 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(View, {
+ isVirtual: true,
+ tagName: '',
+ _contextView: parentView,
+ template: template,
+ context: options.data.insideGroup ? get(this, 'origContext') : get(parentView, 'context'),
+ controller: get(parentView, 'controller'),
+ templateData: { keywords: parentView.cloneKeywords(), insideGroup: options.data.insideGroup }
+ });
+ }
+ },
+
+ /**
+ 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: 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
+ });
+ }
+ });
+
+ __exports__["default"] = Component;
+ });
+define("ember-views/views/container_view",
+ ["ember-metal/core","ember-metal/merge","ember-runtime/mixins/mutable_array","ember-metal/property_get","ember-metal/property_set","ember-views/views/view","ember-views/views/view_collection","ember-views/views/states","ember-metal/error","ember-metal/enumerable_utils","ember-metal/computed","ember-metal/run_loop","ember-metal/properties","ember-views/system/render_buffer","ember-metal/mixin","ember-runtime/system/native_array","exports"],
+ function(__dependency1__, __dependency2__, __dependency3__, __dependency4__, __dependency5__, __dependency6__, __dependency7__, __dependency8__, __dependency9__, __dependency10__, __dependency11__, __dependency12__, __dependency13__, __dependency14__, __dependency15__, __dependency16__, __exports__) {
+ "use strict";
+ var Ember = __dependency1__["default"];
+ // Ember.assert, Ember.K
+
+ var merge = __dependency2__["default"];
+ var MutableArray = __dependency3__["default"];
+ var get = __dependency4__.get;
+ var set = __dependency5__.set;
+
+ var View = __dependency6__["default"];
+ var ViewCollection = __dependency7__["default"];
+
+ var cloneStates = __dependency8__.cloneStates;
+ var EmberViewStates = __dependency8__.states;
+
+ var EmberError = __dependency9__["default"];
+
+ var forEach = __dependency10__.forEach;
+
+ var computed = __dependency11__.computed;
+ var run = __dependency12__["default"];
+ var defineProperty = __dependency13__.defineProperty;
+ var renderBuffer = __dependency14__["default"];
+ var observer = __dependency15__.observer;
+ var beforeObserver = __dependency15__.beforeObserver;
+ var emberA = __dependency16__.A;
+
+ /**
+ @module ember
+ @submodule ember-views
+ */
+
+ var states = cloneStates(EmberViewStates);
+
+ /**
+ A `ContainerView` is an `Ember.View` subclass that implements `Ember.MutableArray`
+ allowing programmatic management of its child views.
+
+ ## Setting Initial Child Views
+
+ The initial array of child views can be set in one of two ways. You can
+ provide a `childViews` property at creation time that contains instance of
+ `Ember.View`:
+
+ ```javascript
+ aContainer = Ember.ContainerView.create({
+ childViews: [Ember.View.create(), Ember.View.create()]
+ });
+ ```
+
+ You can also provide a list of property names whose values are instances of
+ `Ember.View`:
+
+ ```javascript
+ aContainer = Ember.ContainerView.create({
+ childViews: ['aView', 'bView', 'cView'],
+ aView: Ember.View.create(),
+ bView: Ember.View.create(),
+ cView: Ember.View.create()
+ });
+ ```
+
+ The two strategies can be combined:
+
+ ```javascript
+ aContainer = Ember.ContainerView.create({
+ childViews: ['aView', Ember.View.create()],
+ aView: Ember.View.create()
+ });
+ ```
+
+ Each child view's rendering will be inserted into the container's rendered
+ HTML in the same order as its position in the `childViews` property.
+
+ ## Adding and Removing Child Views
+
+ The container view implements `Ember.MutableArray` allowing programmatic management of its child views.
+
+ To remove a view, pass that view into a `removeObject` call on the container view.
+
+ Given an empty `` the following code
+
+ ```javascript
+ aContainer = Ember.ContainerView.create({
+ classNames: ['the-container'],
+ childViews: ['aView', 'bView'],
+ aView: Ember.View.create({
+ template: Ember.Handlebars.compile("A")
+ }),
+ bView: Ember.View.create({
+ template: Ember.Handlebars.compile("B")
+ })
+ });
+
+ aContainer.appendTo('body');
+ ```
+
+ Results in the HTML
+
+ ```html
+
+
A
+
B
+
+ ```
+
+ Removing a view
+
+ ```javascript
+ aContainer.toArray(); // [aContainer.aView, aContainer.bView]
+ aContainer.removeObject(aContainer.get('bView'));
+ aContainer.toArray(); // [aContainer.aView]
+ ```
+
+ Will result in the following HTML
+
+ ```html
+
+
A
+
+ ```
+
+ Similarly, adding a child view is accomplished by adding `Ember.View` instances to the
+ container view.
+
+ Given an empty `` the following code
+
+ ```javascript
+ aContainer = Ember.ContainerView.create({
+ classNames: ['the-container'],
+ childViews: ['aView', 'bView'],
+ aView: Ember.View.create({
+ template: Ember.Handlebars.compile("A")
+ }),
+ bView: Ember.View.create({
+ template: Ember.Handlebars.compile("B")
+ })
+ });
+
+ aContainer.appendTo('body');
+ ```
+
+ Results in the HTML
+
+ ```html
+
+
A
+
B
+
+ ```
+
+ Adding a view
+
+ ```javascript
+ AnotherViewClass = Ember.View.extend({
+ template: Ember.Handlebars.compile("Another view")
+ });
+
+ aContainer.toArray(); // [aContainer.aView, aContainer.bView]
+ aContainer.pushObject(AnotherViewClass.create());
+ aContainer.toArray(); // [aContainer.aView, aContainer.bView, ]
+ ```
+
+ Will result in the following HTML
+
+ ```html
+
+
A
+
B
+
Another view
+
+ ```
+
+ ## Templates and Layout
+
+ A `template`, `templateName`, `defaultTemplate`, `layout`, `layoutName` or
+ `defaultLayout` property on a container view will not result in the template
+ or layout being rendered. The HTML contents of a `Ember.ContainerView`'s DOM
+ representation will only be the rendered HTML of its child views.
+
+ @class ContainerView
+ @namespace Ember
+ @extends Ember.View
+ */
+ var ContainerView = View.extend(MutableArray, {
+ _states: states,
+
+ willWatchProperty: function(prop){
+ Ember.deprecate(
+ "ContainerViews should not be observed as arrays. This behavior will change in future implementations of ContainerView.",
+ !prop.match(/\[]/) && prop.indexOf('@') !== 0
+ );
+ },
+
+ init: function() {
+ this._super();
+
+ var childViews = get(this, 'childViews');
+
+ // redefine view's childViews property that was obliterated
+ defineProperty(this, 'childViews', 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 - the child is already a child of another view", emberA(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: 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 after the mutation has occurred
+ @param {Number} start the start position of the mutation
+ @param {Number} removed the number of child views removed
+ @param {Number} added 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: beforeObserver('currentView', function() {
+ var currentView = get(this, 'currentView');
+ if (currentView) {
+ currentView.destroy();
+ }
+ }),
+
+ _currentViewDidChange: 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);
+ }
+ });
+
+ merge(states._default, {
+ childViewsWillChange: Ember.K,
+ childViewsDidChange: Ember.K,
+ ensureChildrenAreInDOM: Ember.K
+ });
+
+ merge(states.inBuffer, {
+ childViewsDidChange: function(parentView, views, start, added) {
+ throw new EmberError('You cannot modify child views while in the inBuffer state');
+ }
+ });
+
+ merge(states.hasElement, {
+ childViewsWillChange: function(view, views, start, removed) {
+ for (var i=start; i
+ ```
+
+ ## HTML `class` Attribute
+
+ The HTML `class` attribute of a view's tag can be set by providing a
+ `classNames` property that is set to an array of strings:
+
+ ```javascript
+ MyView = Ember.View.extend({
+ classNames: ['my-class', 'my-other-class']
+ });
+ ```
+
+ Will result in view instances with an HTML representation of:
+
+ ```html
+
+ ```
+
+ `class` attribute values can also be set by providing a `classNameBindings`
+ property set to an array of properties names for the view. The return value
+ of these properties will be added as part of the value for the view's `class`
+ attribute. These properties can be computed properties:
+
+ ```javascript
+ MyView = Ember.View.extend({
+ classNameBindings: ['propertyA', 'propertyB'],
+ propertyA: 'from-a',
+ propertyB: function() {
+ if (someLogic) { return 'from-b'; }
+ }.property()
+ });
+ ```
+
+ Will result in view instances with an HTML representation of:
+
+ ```html
+
+ ```
+
+ If the value of a class name binding returns a boolean the property name
+ itself will be used as the class name if the property is true. The class name
+ will not be added if the value is `false` or `undefined`.
+
+ ```javascript
+ MyView = Ember.View.extend({
+ classNameBindings: ['hovered'],
+ hovered: true
+ });
+ ```
+
+ Will result in view instances with an HTML representation of:
+
+ ```html
+
+ ```
+
+ When using boolean class name bindings you can supply a string value other
+ than the property name for use as the `class` HTML attribute by appending the
+ preferred value after a ":" character when defining the binding:
+
+ ```javascript
+ MyView = Ember.View.extend({
+ classNameBindings: ['awesome:so-very-cool'],
+ awesome: true
+ });
+ ```
+
+ Will result in view instances with an HTML representation of:
+
+ ```html
+
+ ```
+
+ Boolean value class name bindings whose property names are in a
+ camelCase-style format will be converted to a dasherized format:
+
+ ```javascript
+ MyView = Ember.View.extend({
+ classNameBindings: ['isUrgent'],
+ isUrgent: true
+ });
+ ```
+
+ Will result in view instances with an HTML representation of:
+
+ ```html
+
+ ```
+
+ Class name bindings can also refer to object values that are found by
+ traversing a path relative to the view itself:
+
+ ```javascript
+ MyView = Ember.View.extend({
+ classNameBindings: ['messages.empty']
+ messages: Ember.Object.create({
+ empty: true
+ })
+ });
+ ```
+
+ Will result in view instances with an HTML representation of:
+
+ ```html
+
+ ```
+
+ If you want to add a class name for a property which evaluates to true and
+ and a different class name if it evaluates to false, you can pass a binding
+ like this:
+
+ ```javascript
+ // Applies 'enabled' class when isEnabled is true and 'disabled' when isEnabled is false
+ Ember.View.extend({
+ classNameBindings: ['isEnabled:enabled:disabled']
+ isEnabled: true
+ });
+ ```
+
+ Will result in view instances with an HTML representation of:
+
+ ```html
+
+ ```
+
+ When isEnabled is `false`, the resulting HTML reprensentation looks like
+ this:
+
+ ```html
+
+ ```
+
+ This syntax offers the convenience to add a class if a property is `false`:
+
+ ```javascript
+ // Applies no class when isEnabled is true and class 'disabled' when isEnabled is false
+ Ember.View.extend({
+ classNameBindings: ['isEnabled::disabled']
+ isEnabled: true
+ });
+ ```
+
+ Will result in view instances with an HTML representation of:
+
+ ```html
+
+ ```
+
+ When the `isEnabled` property on the view is set to `false`, it will result
+ in view instances with an HTML representation of:
+
+ ```html
+
+ ```
+
+ Updates to the the value of a class name binding will result in automatic
+ update of the HTML `class` attribute in the view's rendered HTML
+ representation. If the value becomes `false` or `undefined` the class name
+ will be removed.
+
+ Both `classNames` and `classNameBindings` are concatenated properties. See
+ [Ember.Object](/api/classes/Ember.Object.html) documentation for more
+ information about concatenated properties.
+
+ ## HTML Attributes
+
+ The HTML attribute section of a view's tag can be set by providing an
+ `attributeBindings` property set to an array of property names on the view.
+ The return value of these properties will be used as the value of the view's
+ HTML associated attribute:
+
+ ```javascript
+ AnchorView = Ember.View.extend({
+ tagName: 'a',
+ attributeBindings: ['href'],
+ href: 'http://google.com'
+ });
+ ```
+
+ Will result in view instances with an HTML representation of:
+
+ ```html
+
+ ```
+
+ One property can be mapped on to another by placing a ":" between
+ the source property and the destination property:
+
+ ```javascript
+ AnchorView = Ember.View.extend({
+ tagName: 'a',
+ attributeBindings: ['url:href'],
+ url: 'http://google.com'
+ });
+ ```
+
+ Will result in view instances with an HTML representation of:
+
+ ```html
+
+ ```
+
+ If the return value of an `attributeBindings` monitored property is a boolean
+ the property will follow HTML's pattern of repeating the attribute's name as
+ its value:
+
+ ```javascript
+ MyTextInput = Ember.View.extend({
+ tagName: 'input',
+ attributeBindings: ['disabled'],
+ disabled: true
+ });
+ ```
+
+ Will result in view instances with an HTML representation of:
+
+ ```html
+
+ ```
+
+ `attributeBindings` can refer to computed properties:
+
+ ```javascript
+ MyTextInput = Ember.View.extend({
+ tagName: 'input',
+ attributeBindings: ['disabled'],
+ disabled: function() {
+ if (someLogic) {
+ return true;
+ } else {
+ return false;
+ }
+ }.property()
+ });
+ ```
+
+ Updates to the the property of an attribute binding will result in automatic
+ update of the HTML attribute in the view's rendered HTML representation.
+
+ `attributeBindings` is a concatenated property. See [Ember.Object](/api/classes/Ember.Object.html)
+ documentation for more information about concatenated properties.
+
+ ## Templates
+
+ The HTML contents of a view's rendered representation are determined by its
+ template. Templates can be any function that accepts an optional context
+ parameter and returns a string of HTML that will be inserted within the
+ view's tag. Most typically in Ember this function will be a compiled
+ `Ember.Handlebars` template.
+
+ ```javascript
+ AView = Ember.View.extend({
+ template: Ember.Handlebars.compile('I am the template')
+ });
+ ```
+
+ Will result in view instances with an HTML representation of:
+
+ ```html
+
I am the template
+ ```
+
+ Within an Ember application is more common to define a Handlebars templates as
+ part of a page:
+
+ ```html
+
+ ```
+
+ And associate it by name using a view's `templateName` property:
+
+ ```javascript
+ AView = Ember.View.extend({
+ templateName: 'some-template'
+ });
+ ```
+
+ If you have nested resources, your Handlebars template will look like this:
+
+ ```html
+
+ ```
+
+ And `templateName` property:
+
+ ```javascript
+ AView = Ember.View.extend({
+ templateName: 'posts/new'
+ });
+ ```
+
+ Using a value for `templateName` that does not have a Handlebars template
+ with a matching `data-template-name` attribute will throw an error.
+
+ For views classes that may have a template later defined (e.g. as the block
+ portion of a `{{view}}` Handlebars helper call in another template or in
+ a subclass), you can provide a `defaultTemplate` property set to compiled
+ template function. If a template is not later provided for the view instance
+ the `defaultTemplate` value will be used:
+
+ ```javascript
+ AView = Ember.View.extend({
+ defaultTemplate: Ember.Handlebars.compile('I was the default'),
+ template: null,
+ templateName: null
+ });
+ ```
+
+ Will result in instances with an HTML representation of:
+
+ ```html
+
I was the default
+ ```
+
+ If a `template` or `templateName` is provided it will take precedence over
+ `defaultTemplate`:
+
+ ```javascript
+ AView = Ember.View.extend({
+ defaultTemplate: Ember.Handlebars.compile('I was the default')
+ });
+
+ aView = AView.create({
+ template: Ember.Handlebars.compile('I was the template, not default')
+ });
+ ```
+
+ Will result in the following HTML representation when rendered:
+
+ ```html
+
I was the template, not default
+ ```
+
+ ## View Context
+
+ The default context of the compiled template is the view's controller:
+
+ ```javascript
+ AView = Ember.View.extend({
+ template: Ember.Handlebars.compile('Hello {{excitedGreeting}}')
+ });
+
+ aController = Ember.Object.create({
+ firstName: 'Barry',
+ excitedGreeting: function() {
+ return this.get("content.firstName") + "!!!"
+ }.property()
+ });
+
+ aView = AView.create({
+ controller: aController
+ });
+ ```
+
+ Will result in an HTML representation of:
+
+ ```html
+
Hello Barry!!!
+ ```
+
+ A context can also be explicitly supplied through the view's `context`
+ property. If the view has neither `context` nor `controller` properties, the
+ `parentView`'s context will be used.
+
+ ## Layouts
+
+ Views can have a secondary template that wraps their main template. Like
+ primary templates, layouts can be any function that accepts an optional
+ context parameter and returns a string of HTML that will be inserted inside
+ view's tag. Views whose HTML element is self closing (e.g. ``)
+ cannot have a layout and this property will be ignored.
+
+ Most typically in Ember a layout will be a compiled `Ember.Handlebars`
+ template.
+
+ A view's layout can be set directly with the `layout` property or reference
+ an existing Handlebars template by name with the `layoutName` property.
+
+ A template used as a layout must contain a single use of the Handlebars
+ `{{yield}}` helper. The HTML contents of a view's rendered `template` will be
+ inserted at this location:
+
+ ```javascript
+ AViewWithLayout = Ember.View.extend({
+ layout: Ember.Handlebars.compile("
{{yield}}
"),
+ template: Ember.Handlebars.compile("I got wrapped")
+ });
+ ```
+
+ Will result in view instances with an HTML representation of:
+
+ ```html
+
+
+ I got wrapped
+
+
+ ```
+
+ See [Ember.Handlebars.helpers.yield](/api/classes/Ember.Handlebars.helpers.html#method_yield)
+ for more information.
+
+ ## Responding to Browser Events
+
+ Views can respond to user-initiated events in one of three ways: method
+ implementation, through an event manager, and through `{{action}}` helper use
+ in their template or layout.
+
+ ### Method Implementation
+
+ Views can respond to user-initiated events by implementing a method that
+ matches the event name. A `jQuery.Event` object will be passed as the
+ argument to this method.
+
+ ```javascript
+ AView = Ember.View.extend({
+ click: function(event) {
+ // will be called when when an instance's
+ // rendered element is clicked
+ }
+ });
+ ```
+
+ ### Event Managers
+
+ Views can define an object as their `eventManager` property. This object can
+ then implement methods that match the desired event names. Matching events
+ that occur on the view's rendered HTML or the rendered HTML of any of its DOM
+ descendants will trigger this method. A `jQuery.Event` object will be passed
+ as the first argument to the method and an `Ember.View` object as the
+ second. The `Ember.View` will be the view whose rendered HTML was interacted
+ with. This may be the view with the `eventManager` property or one of its
+ descendent views.
+
+ ```javascript
+ AView = Ember.View.extend({
+ eventManager: Ember.Object.create({
+ doubleClick: function(event, view) {
+ // will be called when when an instance's
+ // rendered element or any rendering
+ // of this views's descendent
+ // elements is clicked
+ }
+ })
+ });
+ ```
+
+ An event defined for an event manager takes precedence over events of the
+ same name handled through methods on the view.
+
+ ```javascript
+ AView = Ember.View.extend({
+ mouseEnter: function(event) {
+ // will never trigger.
+ },
+ eventManager: Ember.Object.create({
+ mouseEnter: function(event, view) {
+ // takes precedence over AView#mouseEnter
+ }
+ })
+ });
+ ```
+
+ Similarly a view's event manager will take precedence for events of any views
+ rendered as a descendent. A method name that matches an event name will not
+ be called if the view instance was rendered inside the HTML representation of
+ a view that has an `eventManager` property defined that handles events of the
+ name. Events not handled by the event manager will still trigger method calls
+ on the descendent.
+
+ ```javascript
+ OuterView = Ember.View.extend({
+ template: Ember.Handlebars.compile("outer {{#view InnerView}}inner{{/view}} outer"),
+ eventManager: Ember.Object.create({
+ mouseEnter: function(event, view) {
+ // view might be instance of either
+ // OuterView or InnerView depending on
+ // where on the page the user interaction occured
+ }
+ })
+ });
+
+ InnerView = Ember.View.extend({
+ click: function(event) {
+ // will be called if rendered inside
+ // an OuterView because OuterView's
+ // eventManager doesn't handle click events
+ },
+ mouseEnter: function(event) {
+ // will never be called if rendered inside
+ // an OuterView.
+ }
+ });
+ ```
+
+ ### Handlebars `{{action}}` Helper
+
+ See [Handlebars.helpers.action](/api/classes/Ember.Handlebars.helpers.html#method_action).
+
+ ### Event Names
+
+ All of the event handling approaches described above respond to the same set
+ of events. The names of the built-in events are listed below. (The hash of
+ built-in events exists in `Ember.EventDispatcher`.) Additional, custom events
+ can be registered by using `Ember.Application.customEvents`.
+
+ Touch events:
+
+ * `touchStart`
+ * `touchMove`
+ * `touchEnd`
+ * `touchCancel`
+
+ Keyboard events
+
+ * `keyDown`
+ * `keyUp`
+ * `keyPress`
+
+ Mouse events
+
+ * `mouseDown`
+ * `mouseUp`
+ * `contextMenu`
+ * `click`
+ * `doubleClick`
+ * `mouseMove`
+ * `focusIn`
+ * `focusOut`
+ * `mouseEnter`
+ * `mouseLeave`
+
+ Form events:
+
+ * `submit`
+ * `change`
+ * `focusIn`
+ * `focusOut`
+ * `input`
+
+ HTML5 drag and drop events:
+
+ * `dragStart`
+ * `drag`
+ * `dragEnter`
+ * `dragLeave`
+ * `dragOver`
+ * `dragEnd`
+ * `drop`
+
+ ## 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
+ */
+ var View = 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,
+
+ /**
+ Used to identify this view during debugging
+
+ @property instrumentDisplay
+ @type String
+ */
+ instrumentDisplay: computed(function() {
+ if (this.helperName) {
+ return '{{' + this.helperName + '}}';
+ }
+ }),
+
+ /**
+ 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: 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: 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: 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);
+
+ if (!this.container) {
+ throw new EmberError('Container was not found when looking up a views template. ' +
+ 'This is most likely due to manually instantiating an Ember.View. ' +
+ 'See: http://git.io/EKPpnA');
+ }
+
+ return this.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: 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: 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: 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: beforeObserver('childViews', function() {
+ if (this.isVirtual) {
+ var parentView = get(this, 'parentView');
+ if (parentView) { propertyWillChange(parentView, 'childViews'); }
+ }
+ }),
+
+ // When it's a virtual view, we need to notify the parent that their
+ // childViews did change.
+ _childViewsDidChange: observer('childViews', function() {
+ if (this.isVirtual) {
+ var parentView = get(this, 'parentView');
+ if (parentView) { 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 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.
+
+ @method 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: observer('_parentView', function() {
+ if (this.isDestroying) { return; }
+
+ this.trigger('parentViewDidChange');
+
+ if (get(this, 'parentView.controller') && !get(this, 'controller')) {
+ this.notifyPropertyChange('controller');
+ }
+ }),
+
+ _controllerDidChange: 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 ? copy(templateData.keywords) : {};
+ set(keywords, 'view', this.isVirtual ? keywords.view : this);
+ 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')
+ 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 = 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.
+ 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 || {};
+
+ 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);
+ 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);
+
+ 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 = View._parsePropertyPath(property);
+ var path = parsedPath.path;
+
+ var val = get(this, path);
+ if (val === undefined && isGlobalPath(path)) {
+ val = get(Ember.lookup, path);
+ }
+
+ return View._classStringForValue(path, val, parsedPath.className, parsedPath.falsyClassName);
+ },
+
+ // ..........................................................
+ // ELEMENT SUPPORT
+ //
+
+ /**
+ Returns the current DOM element for the view.
+
+ @property element
+ @type DOMElement
+ */
+ element: 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", jQuery(target).length > 0);
+ Ember.assert("You cannot append to an existing Ember.View. Consider using Ember.ContainerView instead.", !jQuery(target).is('.ember-view') && !jQuery(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", jQuery(target).length > 0);
+ Ember.assert("You cannot replace an existing Ember.View. Consider using Ember.ContainerView instead.", !jQuery(target).is('.ember-view') && !jQuery(target).parents().is('.ember-view'));
+
+ this._insertElementLater(function() {
+ jQuery(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 = 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 jQuery(id)[0] || jQuery(id, parentElem)[0];
+ },
+
+ /**
+ Creates a DOM representation of the view and all of its
+ child views by recursively calling the `render()` method.
+
+ After the element has been created, `didInsertElement` will
+ be called on this view and all of its child views.
+
+ @method createElement
+ @return {Ember.View} receiver
+ */
+ createElement: function() {
+ if (get(this, 'element')) { return this; }
+
+ var buffer = this.renderToBuffer();
+ set(this, 'element', buffer.element());
+
+ return this;
+ },
+
+ /**
+ Called when a view is going to insert an element into the DOM.
+
+ @event willInsertElement
+ */
+ willInsertElement: Ember.K,
+
+ /**
+ Called when the element of the view has been inserted into the DOM
+ or after the view was re-rendered. Override this function to do any
+ set up that requires an element in the document body.
+
+ @event didInsertElement
+ */
+ didInsertElement: Ember.K,
+
+ /**
+ Called when the view is about to rerender, but before anything has
+ been torn down. This is a good opportunity to tear down any manual
+ observers you have installed based on the DOM state
+
+ @event willClearRender
+ */
+ willClearRender: Ember.K,
+
+ /**
+ Run this callback on the current view (unless includeSelf is false) and recursively on child views.
+
+ @method invokeRecursively
+ @param fn {Function}
+ @param includeSelf {Boolean} Includes itself if true.
+ @private
+ */
+ invokeRecursively: function(fn, includeSelf) {
+ var childViews = (includeSelf === false) ? this._childViews : [this];
+ var currentViews, view, currentChildViews;
+
+ while (childViews.length) {
+ currentViews = childViews.slice();
+ childViews = [];
+
+ for (var i=0, l=currentViews.length; i` tag for views.
+
+ @property tagName
+ @type String
+ @default null
+ */
+
+ // We leave this null by default so we can tell the difference between
+ // the default case and a user-specified tag.
+ tagName: null,
+
+ /**
+ The WAI-ARIA role of the control represented by this view. For example, a
+ button may have a role of type 'button', or a pane may have a role of
+ type 'alertdialog'. This property is used by assistive software to help
+ visually challenged users navigate rich web applications.
+
+ The full list of valid WAI-ARIA roles is available at:
+ [http://www.w3.org/TR/wai-aria/roles#roles_categorization](http://www.w3.org/TR/wai-aria/roles#roles_categorization)
+
+ @property ariaRole
+ @type String
+ @default null
+ */
+ ariaRole: null,
+
+ /**
+ Standard CSS class names to apply to the view's outer element. This
+ property automatically inherits any class names defined by the view's
+ superclasses as well.
+
+ @property classNames
+ @type Array
+ @default ['ember-view']
+ */
+ classNames: ['ember-view'],
+
+ /**
+ A list of properties of the view to apply as class names. If the property
+ is a string value, the value of that string will be applied as a class
+ name.
+
+ ```javascript
+ // Applies the 'high' class to the view element
+ Ember.View.extend({
+ classNameBindings: ['priority']
+ priority: 'high'
+ });
+ ```
+
+ If the value of the property is a Boolean, the name of that property is
+ added as a dasherized class name.
+
+ ```javascript
+ // Applies the 'is-urgent' class to the view element
+ Ember.View.extend({
+ classNameBindings: ['isUrgent']
+ isUrgent: true
+ });
+ ```
+
+ If you would prefer to use a custom value instead of the dasherized
+ property name, you can pass a binding like this:
+
+ ```javascript
+ // Applies the 'urgent' class to the view element
+ Ember.View.extend({
+ classNameBindings: ['isUrgent:urgent']
+ isUrgent: true
+ });
+ ```
+
+ This list of properties is inherited from the view's superclasses as well.
+
+ @property classNameBindings
+ @type Array
+ @default []
+ */
+ classNameBindings: EMPTY_ARRAY,
+
+ /**
+ A list of properties of the view to apply as attributes. If the property is
+ a string value, the value of that string will be applied as the attribute.
+
+ ```javascript
+ // Applies the type attribute to the element
+ // with the value "button", like
+ Ember.View.extend({
+ attributeBindings: ['type'],
+ type: 'button'
+ });
+ ```
+
+ If the value of the property is a Boolean, the name of that property is
+ added as an attribute.
+
+ ```javascript
+ // Renders something like
";
+ * div.firstChild.firstChild.tagName //=> ""
+ *
+ * If our script markers are inside such a node, we need to find that
+ * node and use *it* as the marker.
+ */
+ var realNode = function(start) {
+ while (start.parentNode.tagName === "") {
+ start = start.parentNode;
+ }
+
+ return start;
+ };
+
+ /*
+ * When automatically adding a tbody, Internet Explorer inserts the
+ * tbody immediately before the first
. Other browsers create it
+ * before the first node, no matter what.
+ *
+ * This means the the following code:
+ *
+ * div = document.createElement("div");
+ * div.innerHTML = "