diff --git a/.eslintignore b/.eslintignore
new file mode 100644
index 0000000..111408a
--- /dev/null
+++ b/.eslintignore
@@ -0,0 +1,4 @@
+coverage/
+dist/
+tmp/
+src/scripts/vendor/
diff --git a/.eslintrc b/.eslintrc
new file mode 100755
index 0000000..fa3171c
--- /dev/null
+++ b/.eslintrc
@@ -0,0 +1,27 @@
+{
+ "extends": "pebble",
+ "env": {
+ "node": true,
+ "browser": true
+ },
+ "globals": {
+ "Pebble": true
+ },
+ "rules": {
+ "new-cap": [0],
+ "max-params": [2, 4],
+ "require-jsdoc": [2, {
+ "require": {
+ "FunctionDeclaration": true,
+ "MethodDefinition": true,
+ "ClassDeclaration": false
+ }
+ }],
+ "valid-jsdoc": [2, {
+ "requireParamDescription": false,
+ "requireReturnDescription": false,
+ "requireReturnType": true
+ }]
+ }
+}
+
diff --git a/.gitignore b/.gitignore
old mode 100644
new mode 100755
index 123ae94..a16810e
--- a/.gitignore
+++ b/.gitignore
@@ -25,3 +25,5 @@ build/Release
# Dependency directory
# https://www.npmjs.org/doc/misc/npm-faq.html#should-i-check-my-node_modules-folder-into-git
node_modules
+
+tmp
diff --git a/.travis.yml b/.travis.yml
new file mode 100644
index 0000000..203d78e
--- /dev/null
+++ b/.travis.yml
@@ -0,0 +1,12 @@
+language: node_js
+sudo: false
+node_js:
+ - '4'
+
+before_script:
+ # Needed to include Chrome in Travis for karma tests
+ - export CHROME_BIN=chromium-browser
+ - export DISPLAY=:99.0
+ - sh -e /etc/init.d/xvfb start
+
+script: 'npm run test-travis'
diff --git a/LICENSE b/LICENSE
old mode 100644
new mode 100755
index 8cdb845..40ebd8e
--- a/LICENSE
+++ b/LICENSE
@@ -1,340 +1,21 @@
- GNU GENERAL PUBLIC LICENSE
- Version 2, June 1991
+The MIT License (MIT)
- Copyright (C) 1989, 1991 Free Software Foundation, Inc.,
- 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
- Everyone is permitted to copy and distribute verbatim copies
- of this license document, but changing it is not allowed.
+Copyright (c) 2016 Pebble Technology
- Preamble
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
- The licenses for most software are designed to take away your
-freedom to share and change it. By contrast, the GNU General Public
-License is intended to guarantee your freedom to share and change free
-software--to make sure the software is free for all its users. This
-General Public License applies to most of the Free Software
-Foundation's software and to any other program whose authors commit to
-using it. (Some other Free Software Foundation software is covered by
-the GNU Lesser General Public License instead.) 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
-this service 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 make restrictions that forbid
-anyone to deny you these rights or to ask you to surrender the rights.
-These restrictions translate to certain responsibilities for you if you
-distribute copies of the software, or if you modify it.
-
- For example, if you distribute copies of such a program, whether
-gratis or for a fee, you must give the recipients all the rights that
-you have. 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.
-
- We protect your rights with two steps: (1) copyright the software, and
-(2) offer you this license which gives you legal permission to copy,
-distribute and/or modify the software.
-
- Also, for each author's protection and ours, we want to make certain
-that everyone understands that there is no warranty for this free
-software. If the software is modified by someone else and passed on, we
-want its recipients to know that what they have is not the original, so
-that any problems introduced by others will not reflect on the original
-authors' reputations.
-
- Finally, any free program is threatened constantly by software
-patents. We wish to avoid the danger that redistributors of a free
-program will individually obtain patent licenses, in effect making the
-program proprietary. To prevent this, we have made it clear that any
-patent must be licensed for everyone's free use or not licensed at all.
-
- The precise terms and conditions for copying, distribution and
-modification follow.
-
- GNU GENERAL PUBLIC LICENSE
- TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
-
- 0. This License applies to any program or other work which contains
-a notice placed by the copyright holder saying it may be distributed
-under the terms of this General Public License. The "Program", below,
-refers to any such program or work, and a "work based on the Program"
-means either the Program or any derivative work under copyright law:
-that is to say, a work containing the Program or a portion of it,
-either verbatim or with modifications and/or translated into another
-language. (Hereinafter, translation is included without limitation in
-the term "modification".) Each licensee is addressed as "you".
-
-Activities other than copying, distribution and modification are not
-covered by this License; they are outside its scope. The act of
-running the Program is not restricted, and the output from the Program
-is covered only if its contents constitute a work based on the
-Program (independent of having been made by running the Program).
-Whether that is true depends on what the Program does.
-
- 1. You may copy and distribute 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 and disclaimer of warranty; keep intact all the
-notices that refer to this License and to the absence of any warranty;
-and give any other recipients of the Program a copy of this License
-along with the Program.
-
-You may charge a fee for the physical act of transferring a copy, and
-you may at your option offer warranty protection in exchange for a fee.
-
- 2. You may modify your copy or copies of the Program or any portion
-of it, thus forming a work based on the Program, and copy and
-distribute such modifications or work under the terms of Section 1
-above, provided that you also meet all of these conditions:
-
- a) You must cause the modified files to carry prominent notices
- stating that you changed the files and the date of any change.
-
- b) You must cause any work that you distribute or publish, that in
- whole or in part contains or is derived from the Program or any
- part thereof, to be licensed as a whole at no charge to all third
- parties under the terms of this License.
-
- c) If the modified program normally reads commands interactively
- when run, you must cause it, when started running for such
- interactive use in the most ordinary way, to print or display an
- announcement including an appropriate copyright notice and a
- notice that there is no warranty (or else, saying that you provide
- a warranty) and that users may redistribute the program under
- these conditions, and telling the user how to view a copy of this
- License. (Exception: if the Program itself is interactive but
- does not normally print such an announcement, your work based on
- the Program is not required to print an announcement.)
-
-These requirements apply to the modified work as a whole. If
-identifiable sections of that work are not derived from the Program,
-and can be reasonably considered independent and separate works in
-themselves, then this License, and its terms, do not apply to those
-sections when you distribute them as separate works. But when you
-distribute the same sections as part of a whole which is a work based
-on the Program, the distribution of the whole must be on the terms of
-this License, whose permissions for other licensees extend to the
-entire whole, and thus to each and every part regardless of who wrote it.
-
-Thus, it is not the intent of this section to claim rights or contest
-your rights to work written entirely by you; rather, the intent is to
-exercise the right to control the distribution of derivative or
-collective works based on the Program.
-
-In addition, mere aggregation of another work not based on the Program
-with the Program (or with a work based on the Program) on a volume of
-a storage or distribution medium does not bring the other work under
-the scope of this License.
-
- 3. You may copy and distribute the Program (or a work based on it,
-under Section 2) in object code or executable form under the terms of
-Sections 1 and 2 above provided that you also do one of the following:
-
- a) Accompany it with the complete corresponding machine-readable
- source code, which must be distributed under the terms of Sections
- 1 and 2 above on a medium customarily used for software interchange; or,
-
- b) Accompany it with a written offer, valid for at least three
- years, to give any third party, for a charge no more than your
- cost of physically performing source distribution, a complete
- machine-readable copy of the corresponding source code, to be
- distributed under the terms of Sections 1 and 2 above on a medium
- customarily used for software interchange; or,
-
- c) Accompany it with the information you received as to the offer
- to distribute corresponding source code. (This alternative is
- allowed only for noncommercial distribution and only if you
- received the program in object code or executable form with such
- an offer, in accord with Subsection b above.)
-
-The source code for a work means the preferred form of the work for
-making modifications to it. For an executable work, complete source
-code means all the source code for all modules it contains, plus any
-associated interface definition files, plus the scripts used to
-control compilation and installation of the executable. However, as a
-special exception, the source code distributed need not include
-anything that is normally distributed (in either source or binary
-form) with the major components (compiler, kernel, and so on) of the
-operating system on which the executable runs, unless that component
-itself accompanies the executable.
-
-If distribution of executable or object code is made by offering
-access to copy from a designated place, then offering equivalent
-access to copy the source code from the same place counts as
-distribution of the source code, even though third parties are not
-compelled to copy the source along with the object code.
-
- 4. You may not copy, modify, sublicense, or distribute the Program
-except as expressly provided under this License. Any attempt
-otherwise to copy, modify, sublicense or distribute the Program is
-void, and will automatically terminate your rights under this License.
-However, parties who have received copies, or rights, from you under
-this License will not have their licenses terminated so long as such
-parties remain in full compliance.
-
- 5. You are not required to accept this License, since you have not
-signed it. However, nothing else grants you permission to modify or
-distribute the Program or its derivative works. These actions are
-prohibited by law if you do not accept this License. Therefore, by
-modifying or distributing the Program (or any work based on the
-Program), you indicate your acceptance of this License to do so, and
-all its terms and conditions for copying, distributing or modifying
-the Program or works based on it.
-
- 6. Each time you redistribute the Program (or any work based on the
-Program), the recipient automatically receives a license from the
-original licensor to copy, distribute or modify the Program subject to
-these terms and conditions. You may not impose any further
-restrictions on the recipients' exercise of the rights granted herein.
-You are not responsible for enforcing compliance by third parties to
-this License.
-
- 7. If, as a consequence of a court judgment or allegation of patent
-infringement or for any other reason (not limited to patent issues),
-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
-distribute so as to satisfy simultaneously your obligations under this
-License and any other pertinent obligations, then as a consequence you
-may not distribute the Program at all. For example, if a patent
-license would not permit royalty-free redistribution of the Program by
-all those who receive copies directly or indirectly through you, then
-the only way you could satisfy both it and this License would be to
-refrain entirely from distribution of the Program.
-
-If any portion of this section is held invalid or unenforceable under
-any particular circumstance, the balance of the section is intended to
-apply and the section as a whole is intended to apply in other
-circumstances.
-
-It is not the purpose of this section to induce you to infringe any
-patents or other property right claims or to contest validity of any
-such claims; this section has the sole purpose of protecting the
-integrity of the free software distribution system, which is
-implemented by public license practices. Many people have made
-generous contributions to the wide range of software distributed
-through that system in reliance on consistent application of that
-system; it is up to the author/donor to decide if he or she is willing
-to distribute software through any other system and a licensee cannot
-impose that choice.
-
-This section is intended to make thoroughly clear what is believed to
-be a consequence of the rest of this License.
-
- 8. If the distribution and/or use of the Program is restricted in
-certain countries either by patents or by copyrighted interfaces, the
-original copyright holder who places the Program under this License
-may add an explicit geographical distribution limitation excluding
-those countries, so that distribution is permitted only in or among
-countries not thus excluded. In such case, this License incorporates
-the limitation as if written in the body of this License.
-
- 9. The Free Software Foundation may publish revised and/or new versions
-of the 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 a version number of this License which applies to it and "any
-later version", you have the option of following the terms and conditions
-either of that version or of any later version published by the Free
-Software Foundation. If the Program does not specify a version number of
-this License, you may choose any version ever published by the Free Software
-Foundation.
-
- 10. If you wish to incorporate parts of the Program into other free
-programs whose distribution conditions are different, write to the author
-to ask for permission. For software which is copyrighted by the Free
-Software Foundation, write to the Free Software Foundation; we sometimes
-make exceptions for this. Our decision will be guided by the two goals
-of preserving the free status of all derivatives of our free software and
-of promoting the sharing and reuse of software generally.
-
- NO WARRANTY
-
- 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, 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.
-
- 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
-WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
-REDISTRIBUTE 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.
-
- 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
-convey the exclusion of warranty; and each file should have at least
-the "copyright" line and a pointer to where the full notice is found.
-
- {description}
- Copyright (C) {year} {fullname}
-
- 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 2 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, write to the Free Software Foundation, Inc.,
- 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
-
-Also add information on how to contact you by electronic and paper mail.
-
-If the program is interactive, make it output a short notice like this
-when it starts in an interactive mode:
-
- Gnomovision version 69, Copyright (C) year name of author
- Gnomovision 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, the commands you use may
-be called something other than `show w' and `show c'; they could even be
-mouse-clicks or menu items--whatever suits your program.
-
-You should also get your employer (if you work as a programmer) or your
-school, if any, to sign a "copyright disclaimer" for the program, if
-necessary. Here is a sample; alter the names:
-
- Yoyodyne, Inc., hereby disclaims all copyright interest in the program
- `Gnomovision' (which makes passes at compilers) written by James Hacker.
-
- {signature of Ty Coon}, 1 April 1989
- Ty Coon, President of Vice
-
-This 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.
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
diff --git a/README.md b/README.md
new file mode 100755
index 0000000..d612f9b
--- /dev/null
+++ b/README.md
@@ -0,0 +1,682 @@
+# Clay
+Clay is a JavaScript library that makes it super easy to add offline configuration pages to your Pebble apps. All you need to get started is a couple lines of JavaScript and a JSON file, no servers or HTML required.
+
+**Clay is still in early development and missing some features. We would love your feedback so please submit any ideas or features you would like via GitHub issues.**
+
+# Getting Started
+
+Clay will eventually be built into the Pebble SDK. However while it is still in beta, you will need to follow some steps
+
+1. Download the Clay distribution file from: [dist/clay.js](dist/clay.js)
+2. Drop `clay.js` in your project's `src/js` directory.
+3. Create a JSON file called `config.json` and place it in your `src/js` directory.
+4. in order for JSON files to work you may need to change the line in your `wscript` from `ctx.pbl_bundle(binaries=binaries, js=ctx.path.ant_glob('src/js/**/*.js'))` to `ctx.pbl_bundle(binaries=binaries, js=ctx.path.ant_glob('src/js/**/*.js*'))`
+5. Your `app.js` needs to `require` clay and your config file, then be initialized. Clay will by default, automatically handle the 'showConfiguration' and 'webviewclosed' events:
+```javascript
+var Clay = require('clay');
+var clayConfig = require('config.json');
+var clay = new Clay(clayConfig);
+```
+
+8. Next is the fun part. Creating your config page. Edit your `config.json` file using the instructions below
+
+# Creating Your Config File
+
+Clay uses JavaScript objects (or JSON) to generate the config page for you. The structure of the page is totally up to you, but you do need to follow some basic rules.
+
+## Basic JSON structure
+
+Your root element should be an array. This represents the entire page. Inside this array you place your config items. Each config item is an object with some properties that configure how each item should be displayed.
+
+#### Example:
+```javascript
+[
+ { "type": "heading", "defaultValue": "Example Config Page" },
+ { "type": "text", "defaultValue": "Clay makes things easy." }
+ //... etc etc
+]
+```
+
+## Components
+
+
+
+### Section
+
+Sections help divide up the page into logical separations. It is recommended that you place all your input based items in a section for maximum prettiness.
+
+##### Properties
+
+| Property | Type | Description |
+|----------|------|-------------|
+| type | string | set to "section"
+| items | array | array of items to include in the section
+
+##### Example
+```javascript
+{
+ "type": "section",
+ "items": [
+ {
+ "type": "heading",
+ "defaultValue": "This is a section"
+ },
+ {
+ "type": "input",
+ "appKey": "email",
+ "label": "Email"
+ },
+ {
+ "type": "toggle",
+ "appKey": "enableThings",
+ "label": "Enable things"
+ }
+ ]
+}
+```
+
+---
+
+### Heading
+
+**Manipulator:** `html`
+
+Headings can be used in anywhere and can have their size adjusted to suit the context. If you place a heading item at the first position of a **section's** `items` array then it will automatically be styled as a header for that section.
+
+##### Properties
+
+| Property | Type | Description
+|----------|------|-------------
+| type | string | set to "heading"
+| id | string (unique) | Set this to a unique string to allow this item to be looked up using `Clay.getItemsById()` in your custom function.
+| appKey | string (unique) | The appKey matching what is defined in your `appinfo.json`. Set this to a unique string to allow this item to be looked up using `Clay.getItemsByAppKey()` in your custom function. You must set this if you wish for the value of this item to be saved after the user closes the config page.
+| defaultValue | string/HTML | The heading text.
+| size | int | Defaults to `4`. An integer from 1 to 6 where 1 is the largest size and 6 is the smallest. (represents HTML `
`, `
`, `
`, etc)
+
+
+##### Example
+```javascript
+{
+ "type": "heading",
+ "id": "main-heading",
+ "defaultValue": "My Cool Watchface",
+ "size": 1
+}
+```
+
+---
+
+### Text
+
+**Manipulator:** `html`
+
+Text is used to provide descriptions of sections or to explain complex parts of your page. Feel free to add any extra HTML you require to the `defaultValue`
+
+##### Properties
+
+| Property | Type | Description
+|----------|------|-------------
+| type | string | set to "text"
+| id | string (unique) | Set this to a unique string to allow this item to be looked up using `Clay.getItemsById()` in your custom function.
+| appKey | string (unique) | The appKey matching what is defined in your `appinfo.json`. Set this to a unique string to allow this item to be looked up using `Clay.getItemsByAppKey()` in your custom function. You must set this if you wish for the value of this item to be saved after the user closes the config page.
+| defaultValue | string/HTML | The content of the text.
+
+
+##### Example
+```javascript
+{
+ "type": "text",
+ "defaultValue": "An explanation of how things work",
+}
+```
+
+---
+
+### Input
+
+**Manipulator:** `val`
+
+Standard text input field.
+
+##### Properties
+
+| Property | Type | Description
+|----------|------|-------------
+| type | string | set to "input"
+| id | string (unique) | Set this to a unique string to allow this item to be looked up using `Clay.getItemsById()` in your custom function.
+| appKey | string (unique) | The appKey matching what is defined in your `appinfo.json`. Set this to a unique string to allow this item to be looked up using `Clay.getItemsByAppKey()` in your custom function. You must set this if you wish for the value of this item to be saved after the user closes the config page.
+| label | string | The label that should appear next to the item
+| defaultValue | string | The default value of the text field
+| attributes | object | A hash of HTML attributes to set on the input field. You can add basic HTML5 validation this way by setting attributes such as `required` or `type` .
+
+
+##### Example
+```javascript
+{
+ "type": "input",
+ "appKey": "email",
+ "defaultValue": "",
+ "label": "Email",
+ "attributes": {
+ "placeholder": "eg: name@domain.com",
+ "limit": 10,
+ "required": "required",
+ "type": "email"
+ }
+}
+```
+
+---
+
+#### Toggle
+
+**Manipulator:** `checked`
+
+Switch for a single item.
+
+##### Properties
+
+| Property | Type | Description
+|----------|------|-------------
+| type | string | set to "toggle"
+| id | string (unique) | Set this to a unique string to allow this item to be looked up using `Clay.getItemsById()` in your custom function.
+| appKey | string (unique) | The appKey matching what is defined in your `appinfo.json`. Set this to a unique string to allow this item to be looked up using `Clay.getItemsByAppKey()` in your custom function. You must set this if you wish for the value of this item to be saved after the user closes the config page.
+| label | string | The label that should appear next to the item
+| defaultValue | boolean | The default value of the toggle. Defaults to `false`
+| attributes | object | A hash of HTML attributes to set on the input field. You can add basic HTML5 validation this way by setting attribute such as `required`.
+
+
+##### Example
+```javascript
+{
+ "type": "toggle",
+ "appKey": "invert",
+ "label": "Invert Colors",
+ "defaultValue": true,
+ "attributes": {
+ "required": "required"
+ }
+}
+```
+
+---
+
+#### Select
+
+**Manipulator:** `val`
+
+A dropdown menu
+
+##### Properties
+
+| Property | Type | Description
+|----------|------|-------------
+| type | string | set to "select"
+| id | string (unique) | Set this to a unique string to allow this item to be looked up using `Clay.getItemsById()` in your custom function.
+| appKey | string (unique) | The appKey matching what is defined in your `appinfo.json`. Set this to a unique string to allow this item to be looked up using `Clay.getItemsByAppKey()` in your custom function. You must set this if you wish for the value of this item to be saved after the user closes the config page.
+| label | string | The label that should appear next to the item
+| defaultValue | string | The default value of dropdown. Must match a value in the `options` array
+| attributes | object | A hash of HTML attributes to set on the input field. You can add basic HTML5 validation this way by setting attribute such as `required`.
+| options | array of objects | The options you want to appear in the dropdown. Each option is an object with a `label` and `value` property.
+
+##### Example
+```javascript
+{
+ "type": "select",
+ "appKey": "flavor",
+ "defaultValue": "grape",
+ "label": "Favorite Flavor",
+ "options": [
+ { "label": "", "value": "" },
+ { "label": "Berry", "value": "berry" },
+ { "label": "Grape", "value": "grape" },
+ { "label": "Banana", "value": "banana" }
+ ],
+ "attributes": {
+ "required": "required"
+ }
+}
+```
+
+---
+
+#### Color
+
+**Manipulator:** `color`
+
+A color picker.
+
+##### Properties
+
+| Property | Type | Description
+|----------|------|-------------
+| type | string | set to "color"
+| id | string (unique) | Set this to a unique string to allow this item to be looked up using `Clay.getItemsById()` in your custom function.
+| appKey | string (unique) | The appKey matching what is defined in your `appinfo.json`. Set this to a unique string to allow this item to be looked up using `Clay.getItemsByAppKey()` in your custom function. You must set this if you wish for the value of this item to be saved after the user closes the config page.
+| label | string | The label that should appear next to the item
+| defaultValue | string OR int | The default color. Always use the uncorrected value even if `sunlight` is true. The component will do the conversion internally.
+| sunlight | boolean | Switch between uncorrected and sunlight color palette. Defaults to `true`
+
+##### Example
+```javascript
+{
+ "type": "color",
+ "appKey": "background",
+ "defaultValue": "FF0000",
+ "label": "Background Color",
+ "sunlight": true
+},
+```
+
+---
+
+#### RadioGroup
+
+**Manipulator:** `radiogroup`
+
+A list of options where a user can only choose one
+
+##### Properties
+
+| Property | Type | Description
+|----------|------|-------------
+| type | string | set to "radiogroup"
+| id | string (unique) | Set this to a unique string to allow this item to be looked up using `Clay.getItemsById()` in your custom function.
+| appKey | string (unique) | The appKey matching what is defined in your `appinfo.json`. Set this to a unique string to allow this item to be looked up using `Clay.getItemsByAppKey()` in your custom function. You must set this if you wish for the value of this item to be saved after the user closes the config page.
+| label | string | The label that should appear next to the item
+| defaultValue | string | The default selected item. Must match a value in the `options` array
+| attributes | object | A hash of HTML attributes to set on the input field. You can add basic HTML5 validation this way by setting attribute such as `required`.
+| options | array of objects | The options you want to appear in the dropdown. Each option is an object with a `label` and `value` property.
+
+##### Example
+```javascript
+{
+ "type": "radiogroup",
+ "appKey": "favorite_food",
+ "label": "Favorite Food",
+ "options": [
+ { "label": "Sushi", "value": "sushi" },
+ { "label": "Pizza", "value": "pizza" },
+ { "label": "Burgers", "value": "burgers" }
+ ]
+},
+```
+
+---
+
+#### CheckboxGroup
+
+**Manipulator:** `checkboxgroup`
+
+A list of options where a user may choose multiple
+
+##### Properties
+
+| Property | Type | Description
+|----------|------|-------------
+| type | string | set to "checkboxgroup"
+| id | string (unique) | Set this to a unique string to allow this item to be looked up using `Clay.getItemsById()` in your custom function.
+| appKey | string (unique) | The appKey matching what is defined in your `appinfo.json`. Set this to a unique string to allow this item to be looked up using `Clay.getItemsByAppKey()` in your custom function. You must set this if you wish for the value of this item to be saved after the user closes the config page.
+| label | string | The label that should appear next to the item
+| defaultValue | array of strings | The default selected items. Must match the values in the `options` array
+| attributes | object | A hash of HTML attributes to set on the input field. You can add basic HTML5 validation this way by setting attribute such as `required`.
+| options | array of objects | The options you want to appear in the dropdown. Each option is an object with a `label` and `value` property.
+
+##### Example
+```javascript
+{
+ "type": "checkboxgroup",
+ "appKey": "favorite_food",
+ "label": "Favorite Food",
+ "defaultValue": ["sushi", "burgers"],
+ "options": [
+ { "label": "Sushi", "value": "sushi" },
+ { "label": "Pizza", "value": "pizza" },
+ { "label": "Burgers", "value": "burgers" }
+ ]
+},
+```
+
+---
+
+### Submit
+
+**Manipulator:** `html`
+
+The submit button for the page. You **MUST** include this component somewhere or users will not be able to save the form.
+
+##### Properties
+
+| Property | Type | Description
+|----------|------|-------------
+| type | string | set to "submit"
+| defaultValue | string | The text displayed in the button
+| attributes | object | A hash of HTML attributes to set on the input field.
+
+##### Example
+```javascript
+{
+ "type": "submit",
+ "label": "Save"
+}
+```
+
+---
+
+### Coming Soon...
+
+- Range Slider
+- Generic Button
+- Tabs
+- Footer
+- dynamic + draggable list
+
+---
+
+## Manipulators
+
+Each component has a **manipulator.** This is a set of methods used to talk to the item on the page. At a minimum, manipulators must have a `.get()` and `.set(value)` method. When the config page is closed, the `.get()` method is run on all components registered with an `appKey`. Many of these methods fire an event when the method is called. You can listen for these events with `ClayItem.on()`
+
+#### html
+| Method | Returns | Event Fired | Description
+|--------|---------|-------------| ----------
+| `.set( [string/HTML] value)` | `ClayItem` | `change` | sets the content of the item
+| `.get()` | `string` | | gets the content of the item
+
+#### val
+| Method | Returns | Event Fired | Description
+|--------|---------|-------------| ----------
+| `.set( [string] value)` | `ClayItem` | `change` | sets the value of the item
+| `.get()` | `string` | | gets the content of the item
+| `.disable()` | `ClayItem` | `disabled` | Prevents the item from being edited by the user
+| `.enable()` | `ClayItem` | `enabled` | Allows the item to be edited by the user
+
+#### checked
+| Method | Returns | Event Fired | Description
+|--------|---------|-------------| ----------
+| `.set( [boolean] value)` | `ClayItem` | `change` | check/uncheck the state of the item
+| `.get()` | `string` | | gets the content of the item
+| `.disable()` | `ClayItem` | `disabled` | Prevents the item from being edited by the user
+| `.enable()` | `ClayItem` | `enabled` | Allows the item to be edited by the user
+
+#### color
+| Method | Returns | Event Fired | Description
+|--------|---------|-------------| ----------
+| `.set( [string \| int] value)` | `ClayItem` | `change` | sets the color picker to the provided color. If the value is a string, it must be provided in hex notation eg `'FF0000'`.
+| `.get()` | `int` | | The color is returned as an int in order to make it easy to be used on the watch side using `GColorFromHEX()`
+| `.disable()` | `ClayItem` | `disabled` | Prevents the item from being edited by the user
+| `.enable()` | `ClayItem` | `enabled` | Allows the item to be edited by the user
+
+#### radiogroup
+| Method | Returns | Event Fired | Description
+|--------|---------|-------------| ----------
+| `.set( [string] value)` | `ClayItem` | `change` | checks the radio button that corresponds to the provided value
+| `.get()` | `string` | | gets the value of the checked radio button in the list
+| `.disable()` | `ClayItem` | `disabled` | Prevents the item from being edited by the user
+| `.enable()` | `ClayItem` | `enabled` | Allows the item to be edited by the user
+
+#### checkboxgroup
+| Method | Returns | Event Fired | Description
+|--------|---------|-------------| ----------
+| `.set( [Array] value)` | `ClayItem` | `change` | checks the checkboxes that corresponds to the provided list of values
+| `.get()` | `string` | | gets the value of the checked radio button in the list
+| `.disable()` | `ClayItem` | `disabled` | Prevents the item from being edited by the user
+| `.enable()` | `ClayItem` | `enabled` | Allows the item to be edited by the user
+
+
+# Extending Clay
+
+Clay is built to allow developers to add their own basic interactivity to the config page. This is done in a number of ways:
+
+## Handling The 'showConfiguration' and 'webviewclosed' Events Manually
+
+Clay will by default, automatically handle the 'showConfiguration' and 'webviewclosed' events. If you wish to override this behavior and handle the events yourself, pass an object as the 3rd parameter of the Clay constructor with `autoHandleEvents` set to `false`
+
+Example:
+
+```javascript
+var Clay = require('./clay');
+var clayConfig = require('./config');
+var clay = new Clay(clayConfig, null, {autoHandleEvents: false});
+
+Pebble.addEventListener('showConfiguration', function(e) {
+ Pebble.openURL(clay.generateUrl());
+});
+
+Pebble.addEventListener('webviewclosed', function(e) {
+
+ if (e && !e.response) { return; }
+
+ // Send settings to Pebble watchapp
+ Pebble.sendAppMessage(clay.getSettings(e.response), function(e) {
+ console.log('Sent config data to Pebble');
+ }, function() {
+ console.log('Failed to send config data!');
+ console.log(JSON.stringify(e));
+ });
+});
+```
+
+### `Clay([Array] config, [function] customFn, [object] options)`
+
+#### Methods
+
+| Method | Returns
+| ---
+| `Clay(Array] config, [function] customFn=null, [object] options={autoHandleEvents: true})` `config` - an Array representing your config `customFn` - function to be run in the context of the generated page `options.autoHandleEvents` - set to `false` to prevent Clay from automatically handling the "showConfiguration" and "webviewclosed" events | `Clay` - a new instance of Clay
+| `.registerComponent( [ClayComponent] component )` Registers a custom component. | `void`.
+| `.generateUrl()` | `string` - The URL to open with `Pebble.openURL()`
+| `.getSettings(response)` `response` - the response object provided to the "webviewclosed" event | `Object` - hash where the key is the `appKey` and the value is the result from the config page.
+---
+
+
+## Custom Function
+
+When initializing Clay in your `app.js`, you can optionally provide a function that will be copied and run on the generated config page. **IMPORTANT:** This function is injected by running `.toString()` on it. If you are making use of `require` or any other dynamic features, they will not work. You must make sure that everything the function needs to execute is available in the function body itself.
+
+This function, when injected into the config page will be run with `ClayConfig` as its context (`this`), and **Minified** as its first parameter. Read below for more information on **Minified**
+
+#### Example
+
+##### app.js
+
+```javascript
+var Clay = require('./clay');
+var clayConfig = require('./config');
+var customClay = require('./custom-clay');
+var clay = new Clay(clayConfig, customClay);
+```
+
+##### custom-clay.js
+
+```javascript
+module.exports = function(minified) {
+ var Clay = this;
+ var _ = minified._;
+ var $ = minified.$;
+ var HTML = minified.HTML;
+
+ function toggleBackground() {
+ if (this.get()) {
+ Clay.getItemByAppKey('background').enable();
+ } else {
+ Clay.getItemByAppKey('background').disable();
+ }
+ }
+
+ Clay.on(Clay.EVENTS.AFTER_BUILD, function() {
+ var coolStuffToggle = Clay.getItemByAppKey('cool_stuff');
+ toggleBackground.call(coolStuffToggle);
+ coolStuffToggle.on('change', toggleBackground);
+ });
+};
+```
+
+## Clay API
+
+### `ClayConfig([Object] settings, [Array] config, [$Minified] $rootContainer)`
+
+This is the main way of talking to your generated config page.
+
+#### Properties
+
+| Property | Type | Description
+|----------|------|------------
+| `.EVENTS.BEFORE_BUILD` | String | Dispatched prior to building the page.
+| `.EVENTS.AFTER_BUILD` | String | Dispatched after building the page.
+| `.config` | Array | Reference to the config passed to the constructer and used for generating the page.
+
+
+#### Methods
+
+| Method | Returns
+|--------|------
+| `.getAllItems()` | `Array.` - an array of all config items
+| `.getItemByAppKey( [string] appKey )` | `ConfigItem\|undefined` - a single `ConfigItem` that has the provided `appKey` otherwise `undefined`
+| `.getItemById( [string] id )` | `ConfigItem\|undefined` - a single `ConfigItem` that has the provided `id` otherwise `undefined`
+| `.getItemsByType( [string] type )` | `Array.` - an array of config items that match the provided `type`
+| `.getSettings()` | `Object` - a hash representing all items with an `appKey` where the key is the `appKey` and the value is the result of running `.get()` on the Clay item.
+| `.build()` Builds the config page. Will dispatch the `BEFORE_BUILD` event prior to building the page, then the `AFTER_BUILD` event once it is complete. | `ClayConfig`
+| `.on( [string] events, [function] handler )` Register an event to the provided handler. The handler will be called with this instance of `ClayConfig` as the context. If you wish to register multiple events to the same handler, then separate the events with a space | `ClayConfig`
+| `.off( [function] handler )` Remove the given event handler. **NOTE:** This will remove the handler from all registered events | `ClayConfig`
+| `.trigger( [string] name, [object] eventObj={} )` Trigger the provided event and optionally pass extra data to the handler. | `ClayConfig`
+| `.registerComponent( [ClayComponent] component )` Registers a component. You must register all components prior to calling `.build()`. This method is available statically as well | `Boolean` - `true` if the component was registered successfully, otherwise `false`.
+
+---
+
+### `ClayItem( [Object] config )`
+
+#### Properties
+
+| Property | Type | Description
+|----------|------|------------
+| `.id` | String | The ID of the item if provided in the config.
+| `.appKey` | String | The ID of the item if provided in the config.
+| `.config` | Object | Reference to the config passed to the constructer.
+| `$element` | $Minified | A Minified list representing the root HTML element of the config item
+| `$manipulatorTarget` | A Minified list representing the HTML element with **data-manipulator-target** set. This is generally pointing to the main `` element and will be used for binding events.
+
+
+#### Methods
+
+| Method | Returns
+|--------|----
+| `.initialize( [ClayConfig] clay)` You shouldn't ever need to run this method manually as it will automatically be called when the config is built | `ConfigItem`
+| `.on( [string] events, [function] handler )` Register an event to the provided handler. The handler will be called with this instance of `ClayItem` as the context. If you wish to register multiple events to the same handler, then separate the events with a space. Events will be registered against the `$manipulatorTarget` so most DOM events such as **"change"** or **"click"** can be listened for. | `ClayItem`
+| `.off( [function] handler )` Remove the given event handler. **NOTE:** This will remove the handler from all registered events | `ClayItem`
+| `.trigger( [string] name, [object] eventObj={} )` Trigger the provided event and optionally pass extra data to the handler. | `ClayItem`
+
+In addition to the methods above, all the methods from the item's manipulator will be attached to the `CLayItem`. This includes `.set()` and `.get()`
+
+---
+
+## Custom Components
+
+Clay is also able to be extended using custom components. This allows developers to share components with each other.
+
+### Component Structure
+
+Components are simple objects with the following properties:
+
+#### `ClayComponent`
+
+| Property | Type | Required | Description
+|----------|------|----------|---
+| name | string | yes | This is the unique way to identify the component and what will be used by the config item's `type`
+| template | string (HTML) | yes | This is the actual HTML content of the component. Make sure there is only **one** root node in the HTML. This HTML will be passed to minified's `HTML()` method. Any properties provided by the config item will be made available to the template, eg: `label`. The template will also be provided with `clayId` as a unique way to set input `name` attributes.
+| style | string | no | Any extra css styles you want to inject into the page make sure to namespace your CSS with a class that is unique to your component in order to avoid conflicts with other components
+| manipulator | string / manipulator | yes | Provide a string here to use one of the built-in manipulators. eg `val`. If an object is provided, it must have both a `.set(value)` and `.get()` method.
+| defaults | object | Only if your template requires it | An object of all the defaults your template requires.
+| initialize | function | no | Method which will be called after the item has been added to the page. It will be called with the `ClayItem` as the context (`this`) and with `minified` as the first parameter.
+
+### Registering a custom component.
+
+Components must be registered before the config page is built. The easiest way to do this is in your `app.js` after you have initialized Clay
+
+```javascript
+var Clay = require('clay');
+var clayConfig = require('config.json');
+var clay = new Clay(clayConfig);
+
+clay.registerComponent(require('./my-custom-component'));
+```
+
+## Minified
+
+Minified is a super light JQuery-like library. We only bundle in a small subset of its functionality. Visit the [Minified Docs](http://minifiedjs.com/api/) for more info on how to use minified. Below is the subset of methods available.
+
+ - `$()`
+ - `$$()`
+ - `.get()`
+ - `.select()`
+ - `.set()`
+ - `.add()`
+ - `.ht()`
+ - `HTML()`
+ - `$.request()`
+ - `promise.always()`
+ - `promise.error()`
+ - `$.off()`
+ - `$.ready()`
+ - `$.wait()`
+ - `.on()`
+ - `.each()`
+ - `.find()`
+ - `_()`
+ - `_.copyObj()`
+ - `_.eachObj()`
+ - `_.extend()`
+ - `_.format()`
+ - `_.formatHtml()`
+ - `_.template()`
+ - `_.isObject()`
+
+# Project Structure and Development
+
+There are two main entry points for Clay. `index.js` and `src/scripts/config-page.js`.
+
+#### index.js
+
+This is the main entry point for the code that will run in the Pebble app's `src/js/app.js`. It is responsible for serializing the provided config into a data URI that will be opened using `Pebble.openURL()`. It also persists data to local storage.
+
+#### src/scripts/config-page.js
+
+This is the main entry point for the code that runs on the generated config page. It is its responsibility to pass the injected config and other components to the `ClayConfig` class.
+
+
+### Building
+
+There are two ways to build Clay, production mode and development mode.
+
+#### Production Mode
+
+`$ npm run build` packages up the entire Clay project into `dist/clay.js` to be required in the developer's `app.js`
+
+#### Development Mode
+
+`$ npm run dev` packages up `src/scripts/config-page.js` and `dev/dev.js` into the `tmp/` directory so `dev/dev.html` can include them as script tags. This will also watch for changes in the project.
+
+While developing components and other functionality for Clay, it is much easier to work with the files in the `dev/` directory than on a phone or emulator. Below is an explanation of the files and their purpose
+
+| File | Purpose
+|----------|------
+| `dev.html` | open this page in a browser after running `$ npm run dev`
+| `dev.js` | injects the components and dependancies things into the window the same way `index.js` would.
+| `config.js` | Clay config to use as a sandbox for testing components.
+| `custom-fn.js` | Clay custom function to be injected by `dev.js`
+| `emulator.html` | Copy of the html page that is used to make the Pebble SDK emulator play nice with Clay
+| `uri-test.html` | Used to stress test URI creation for older browser versions.
+
+## Functionality
+
+Most of the magic happens in the `src/scripts/lib` directory. `config-page.js` initializes a new instance of `ClayConfig` and calls the injected custom function (`window.customFn`) with the ClayConfig as its context. This allows developers to add extra functionality to the config page, such as setting values of items dynamically or registering small custom components.
+
+Once the `ClayConfig` is initialized, we run the `.build()` method. This iterates over the config and injects each item into the page. Each item is an instance of ClayItem. It also indexes the items to later be retrieved with `.getAllItems()`, `.getItemByAppKey()`, `.getItemById()`, `.getItemsByType()`
+
+
+
+
+
+
+
+
diff --git a/dev/config.js b/dev/config.js
new file mode 100644
index 0000000..76aa57a
--- /dev/null
+++ b/dev/config.js
@@ -0,0 +1,119 @@
+'use strict';
+/* eslint-disable quotes */
+
+module.exports = [
+ {
+ "type": "heading",
+ "id": "main-heading",
+ "defaultValue": "Clay Test Page",
+ "size": 1
+ },
+ {
+ "type": "text",
+ "defaultValue": "Some arbitrary text explaining how this all works. " +
+ "It's cool if this wraps across multiple lines"
+ },
+ {
+ "type": "section",
+ "items": [
+ {
+ "type": "heading",
+ "defaultValue": "This is a section"
+ },
+ {
+ "type": "input",
+ "appKey": "email",
+ "defaultValue": "",
+ "label": "Email",
+ "attributes": {
+ "placeholder": "eg: name@domain.com",
+ "limit": 10,
+ "required": "required",
+ type: "email"
+ }
+ },
+ {
+ "type": "toggle",
+ "appKey": "cool_stuff",
+ "label": "Enable Cool Stuff",
+ "defaultValue": false
+ },
+ {
+ "type": "color",
+ "appKey": "colorTest",
+ "defaultValue": "FF0000",
+ "label": "Background Color",
+ "sunlight": false
+ },
+ {
+ "type": "color",
+ "appKey": "sunnyColorTest",
+ "defaultValue": "00FF00",
+ "label": "Sunny Color",
+ "sunlight": true
+ }
+ ]
+ },
+ {
+ "type": "section",
+ "items": [
+ {
+ "type": "heading",
+ "defaultValue": "More Settings"
+ },
+ {
+ "type": "radiogroup",
+ "appKey": "radiogroup-test",
+ "label": "Radio test",
+ "options": [
+ { "label": "Test thing one", "value": "one" },
+ { "label": "Another thing", "value": "two" },
+ { "label": "One really long thing that would not fit", "value": "three" },
+ { "label": "Small", "value": "quote' \"test" },
+ { "label": "Final thing", "value": "three" }
+ ]
+ },
+ {
+ "type": "checkboxgroup",
+ "appKey": "checkboxgroup-test",
+ "defaultValue": ["quote' \"test", "two"],
+ "label": "Checkbox test",
+ "options": [
+ { "label": "Test thing one", "value": "one" },
+ { "label": "Another thing", "value": "two" },
+ { "label": "One really long thing that would not fit", "value": "three" },
+ { "label": "Small", "value": "quote' \"test" },
+ { "label": "Final thing", "value": "three" }
+ ]
+ },
+ {
+ "type": "input",
+ "appKey": "date",
+ "defaultValue": "",
+ "label": "Range",
+ "attributes": {
+ type: "range"
+ }
+ },
+ {
+ "type": "select",
+ "appKey": "flavor",
+ "defaultValue": "grape",
+ "label": "Favorite Flavor",
+ "options": [
+ { "label": "", "value": "" },
+ { "label": "Berry", "value": "berry" },
+ { "label": "Grape", "value": "grape" },
+ { "label": "Banana", "value": "banana" }
+ ],
+ "attributes": {
+ "required": "required"
+ }
+ }
+ ]
+ },
+ {
+ "type": "submit",
+ "defaultValue": "Save"
+ }
+];
diff --git a/dev/custom-fn.js b/dev/custom-fn.js
new file mode 100644
index 0000000..4688283
--- /dev/null
+++ b/dev/custom-fn.js
@@ -0,0 +1,24 @@
+'use strict';
+
+module.exports = function() {
+
+ /** @type {ClayConfig} */
+ var Clay = window.Clay = this;
+
+ /**
+ * @returns {void}
+ */
+ function toggleBackground() {
+ if (this.get()) {
+ Clay.getItemByAppKey('background').enable();
+ } else {
+ Clay.getItemByAppKey('background').disable();
+ }
+ }
+
+ Clay.on(Clay.EVENTS.AFTER_BUILD, function() {
+ var coolStuffToggle = Clay.getItemByAppKey('cool_stuff');
+ toggleBackground.call(coolStuffToggle);
+ coolStuffToggle.on('change', toggleBackground);
+ });
+};
diff --git a/dev/dev.html b/dev/dev.html
new file mode 100644
index 0000000..cea3ed0
--- /dev/null
+++ b/dev/dev.html
@@ -0,0 +1,14 @@
+
+
+
+ Pebble Clay Development Page
+
+
+
+
+
+
+
+
+
+
diff --git a/dev/dev.js b/dev/dev.js
new file mode 100644
index 0000000..bee8be2
--- /dev/null
+++ b/dev/dev.js
@@ -0,0 +1,10 @@
+'use strict';
+
+window.returnTo = '#';
+window.clayConfig = require('./config.js');
+window.claySettings = {};
+window.customFn = require('./custom-fn.js');
+window.clayComponents = require('../src/scripts/components');
+
+var platform = window.navigator.userAgent.match(/Android/) ? 'android' : 'ios';
+document.documentElement.classList.add('platform-' + platform);
diff --git a/dev/emulator.html b/dev/emulator.html
new file mode 100644
index 0000000..0e05135
--- /dev/null
+++ b/dev/emulator.html
@@ -0,0 +1,30 @@
+
+
+
+
+ Config Page Emulator
+
+
+
+
+
diff --git a/dev/uri-test.html b/dev/uri-test.html
new file mode 100644
index 0000000..6e2e3f2
--- /dev/null
+++ b/dev/uri-test.html
@@ -0,0 +1,57 @@
+
+
+
+ Pebble Clay Development Page
+
+
+
+
+
+
+
diff --git a/dist/clay.js b/dist/clay.js
new file mode 100644
index 0000000..7cd0232
--- /dev/null
+++ b/dist/clay.js
@@ -0,0 +1,3 @@
+/* Clay - https://github.com/pebble/clay - Version: 0.1.0 - Build Date: 2016-02-17T01:04:02.060Z */
+!function(t){if("object"==typeof exports&&"undefined"!=typeof module)module.exports=t();else if("function"==typeof define&&define.amd)define([],t);else{var e;e="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof self?self:this,e.clay=t()}}(function(){return function t(e,n,o){function r(a,c){if(!n[a]){if(!e[a]){var s="function"==typeof require&&require;if(!c&&s)return s(a,!0);if(i)return i(a,!0);var l=new Error("Cannot find module '"+a+"'");throw l.code="MODULE_NOT_FOUND",l}var u=n[a]={exports:{}};e[a][0].call(u.exports,function(t){var n=e[a][1][t];return r(n?n:t)},u,u.exports,t,e,n,o)}return n[a].exports}for(var i="function"==typeof require&&require,a=0;a0)return"{$circularReference:"+u+"}";if(c.push(t),Array.isArray(t))return"["+s(t.map(function(t){return a(t,e,n,l,c.slice())}))+"]";var f=Object.keys(t);return f.length?"{"+s(f.map(function(r){return(o(r)?r:JSON.stringify(r))+":"+a(t[r],e,n,l,c.slice())}))+"}":"{}"}var c=[];return a(t,e,void 0===n?" ":n||"",i||"",c)};var i=/^(abstract|boolean|break|byte|case|catch|char|class|const|continue|debugger|default|delete|do|double|else|enum|export|extends|false|final|finally|float|for|function|goto|if|implements|import|in|instanceof|int|interface|long|native|new|null|package|private|protected|public|return|short|static|super|switch|synchronized|this|throw|throws|transient|true|try|typeof|undefined|var|void|volatile|while|with)$/,a="\\/"===new RegExp("/").source},{}],2:[function(t,e,n){"use strict";e.exports={name:"checkboxgroup",template:t("../../templates/components/checkboxgroup.tpl"),style:t("../../styles/clay/components/checkboxgroup.scss"),manipulator:"checkboxgroup",defaults:{label:"",options:[],attributes:{}}}},{"../../styles/clay/components/checkboxgroup.scss":13,"../../templates/components/checkboxgroup.tpl":19}],3:[function(t,e,n){"use strict";e.exports={name:"color",template:t("../../templates/components/color.tpl"),style:t("../../styles/clay/components/color.scss"),manipulator:"color",defaults:{label:""},initialize:function(t){function e(t){if(t===!1)return"transparent";for("number"==typeof t&&(t=t.toString(16));t.length<6;)t="0"+t;return"#"+(r?i[t]:t)}for(var n=t.HTML,o=this,r=o.config.sunlight!==!1,i={"000000":"000000","000055":"001e41","0000aa":"004387","0000ff":"0068ca","005500":"2b4a2c","005555":"27514f","0055aa":"16638d","0055ff":"007dce","00aa00":"5e9860","00aa55":"5c9b72","00aaaa":"57a5a2","00aaff":"4cb4db","00ff00":"8ee391","00ff55":"8ee69e","00ffaa":"8aebc0","00ffff":"84f5f1",550000:"4a161b",550055:"482748","5500aa":"40488a","5500ff":"2f6bcc",555500:"564e36",555555:"545454","5555aa":"4f6790","5555ff":"4180d0","55aa00":"759a64","55aa55":"759d76","55aaaa":"71a6a4","55aaff":"69b5dd","55ff00":"9ee594","55ff55":"9de7a0","55ffaa":"9becc2","55ffff":"95f6f2",aa0000:"99353f",aa0055:"983e5a",aa00aa:"955694",aa00ff:"8f74d2",aa5500:"9d5b4d",aa5555:"9d6064",aa55aa:"9a7099",aa55ff:"9587d5",aaaa00:"afa072",aaaa55:"aea382",aaaaaa:"ababab",ffffff:"ffffff",aaaaff:"a7bae2",aaff00:"c9e89d",aaff55:"c9eaa7",aaffaa:"c7f0c8",aaffff:"c3f9f7",ff0000:"e35462",ff0055:"e25874",ff00aa:"e16aa3",ff00ff:"de83dc",ff5500:"e66e6b",ff5555:"e6727c",ff55aa:"e37fa7",ff55ff:"e194df",ffaa00:"f1aa86",ffaa55:"f1ad93",ffaaaa:"efb5b8",ffaaff:"ecc3eb",ffff00:"ffeeab",ffff55:"fff1b5",ffffaa:"fff6d3"},a=o.config.layout||[[!1,!1,"55ff00","aaff55",!1,"ffff55","ffffaa",!1,!1],[!1,"aaffaa","55ff55","00ff00","aaff00","ffff00","ffaa55","ffaaaa",!1],["55ffaa","00ff55","00aa00","55aa00","aaaa55","aaaa00","ffaa00","ff5500","ff5555"],["aaffff","00ffaa","00aa55","55aa55","005500","555500","aa5500","ff0000","ff0055"],[!1,"55aaaa","00aaaa","005555","ffffff","000000","aa5555","aa0000",!1],["55ffff","00ffff","00aaff","0055aa","aaaaaa","555555","550000","aa0055","ff55aa"],["55aaff","0055ff","0000ff","0000aa","000055","550055","aa00aa","ff00aa","ffaaff"],[!1,"5555aa","5555ff","5500ff","5500aa","aa00ff","ff00ff","ff55ff",!1],[!1,!1,!1,"aaaaff","aa55ff","aa55aa",!1,!1,!1]],c="",s=100/a[0].length,l=100/a.length,u=o.$element,f=0;f'}u.select(".color-box-container").add(n(c));var y=u.select(".value"),v=u.select(".picker-wrap"),x=o.$manipulatorTarget.get("disabled");u.select("label").on("click",function(t){x||v.set("show")}),o.on("change",function(){var t=o.get();y.set("$background-color",e(t)),u.select(".color-box").set("-selected"),u.select('.color-box[data-value="'+t+'"]').set("+selected")}),u.select(".color-box.selectable").on("click",function(t){o.set(parseInt(t.target.dataset.value,10)),v.set("-show")}),v.on("click",function(){v.set("-show")}),o.on("disabled",function(){x=!0}),o.on("enabled",function(){x=!1})}}},{"../../styles/clay/components/color.scss":14,"../../templates/components/color.tpl":20}],4:[function(t,e,n){"use strict";e.exports={name:"footer",template:t("../../templates/components/footer.tpl"),manipulator:"html"}},{"../../templates/components/footer.tpl":21}],5:[function(t,e,n){"use strict";e.exports={name:"heading",template:t("../../templates/components/heading.tpl"),manipulator:"html",defaults:{size:4}}},{"../../templates/components/heading.tpl":22}],6:[function(t,e,n){"use strict";e.exports={color:t("./color"),footer:t("./footer"),heading:t("./heading"),input:t("./input"),select:t("./select"),submit:t("./submit"),text:t("./text"),toggle:t("./toggle"),radiogroup:t("./radiogroup"),checkboxgroup:t("./checkboxgroup")}},{"./checkboxgroup":2,"./color":3,"./footer":4,"./heading":5,"./input":7,"./radiogroup":8,"./select":9,"./submit":10,"./text":11,"./toggle":12}],7:[function(t,e,n){"use strict";e.exports={name:"input",template:t("../../templates/components/input.tpl"),style:t("../../styles/clay/components/input.scss"),manipulator:"val",defaults:{label:"",attributes:{}}}},{"../../styles/clay/components/input.scss":15,"../../templates/components/input.tpl":23}],8:[function(t,e,n){"use strict";e.exports={name:"radiogroup",template:t("../../templates/components/radiogroup.tpl"),style:t("../../styles/clay/components/radiogroup.scss"),manipulator:"radiogroup",defaults:{label:"",options:[],attributes:{}}}},{"../../styles/clay/components/radiogroup.scss":16,"../../templates/components/radiogroup.tpl":24}],9:[function(t,e,n){"use strict";e.exports={name:"select",template:t("../../templates/components/select.tpl"),style:t("../../styles/clay/components/select.scss"),manipulator:"val",defaults:{label:"",options:[],attributes:{}},initialize:function(){var t=this,e=t.$element.select(".value");t.on("change",function(n){var o=t.$manipulatorTarget.select("option:checked").get("innerHTML");e.set("innerHTML",o)})}}},{"../../styles/clay/components/select.scss":17,"../../templates/components/select.tpl":25}],10:[function(t,e,n){"use strict";e.exports={name:"submit",template:t("../../templates/components/submit.tpl"),manipulator:"html",defaults:{attributes:{}}}},{"../../templates/components/submit.tpl":26}],11:[function(t,e,n){"use strict";e.exports={name:"text",template:t("../../templates/components/text.tpl"),manipulator:"html"}},{"../../templates/components/text.tpl":27}],12:[function(t,e,n){"use strict";e.exports={name:"toggle",template:t("../../templates/components/toggle.tpl"),style:t("../../styles/clay/components/toggle.scss"),manipulator:"checked",defaults:{label:"",attributes:{}}}},{"../../styles/clay/components/toggle.scss":18,"../../templates/components/toggle.tpl":28}],13:[function(t,e,n){e.exports=".component-checkbox { -webkit-tap-highlight-color: transparent; display: block; }\n\n.component-checkbox:active { background-color: transparent; }\n\n.component-checkbox > .label { display: block; padding-bottom: 0.35rem; }\n\n.component-checkbox .checkbox-group label { padding: 0.35rem 0; -webkit-tap-highlight-color: rgba(255, 255, 255, 0.1); }\n\n.component-checkbox .checkbox-group label:active { background-color: rgba(255, 255, 255, 0.1); }\n\n.component-checkbox .checkbox-group .label { font-size: 0.9em; padding: 0 0.375rem; }\n\n.component-checkbox .checkbox-group input { opacity: 0; position: absolute; }\n\n.component-checkbox .checkbox-group i { display: block; position: relative; border-radius: 0.25rem; width: 1.4rem; height: 1.4rem; border: 0.11765rem solid #767676; -webkit-flex-shrink: 0; flex-shrink: 0; }\n\n.component-checkbox .checkbox-group input:checked ~ i { border-color: #ff4700; background: #ff4700; }\n\n.component-checkbox .checkbox-group input:checked ~ i:after { content: ''; box-sizing: border-box; -webkit-transform: rotate(45deg); transform: rotate(45deg); position: absolute; left: 0.35rem; top: -0.05rem; display: block; width: 0.5rem; height: 1rem; border: 0 solid #ffffff; border-right-width: 0.11765rem; border-bottom-width: 0.11765rem; }\n"},{}],14:[function(t,e,n){e.exports=".component-color .value { width: 2.2652rem; height: 1.4rem; border-radius: 0.7rem; box-shadow: #2f2f2f 0 0.1rem 0.1rem; }\n\n.component-color .picker-wrap { left: 0; top: 0; right: 0; bottom: 0; position: fixed; padding: 1rem; background: rgba(0, 0, 0, 0.7); opacity: 0; -webkit-transition: opacity 70ms ease-in 175ms; transition: opacity 70ms ease-in 175ms; pointer-events: none; z-index: 100; }\n\n.component-color .picker-wrap .picker { padding: 1rem; background: #f2f2f2; border-radius: 0.25rem; }\n\n.component-color .picker-wrap.show { -webkit-transition-delay: 0ms; transition-delay: 0ms; pointer-events: auto; opacity: 1; }\n\n.component-color .color-box-wrap { box-sizing: border-box; position: relative; height: 0; width: 100%; padding: 0 0 100% 0; margin: 0.6em 0 0; }\n\n.component-color .color-box-wrap .color-box-container { position: absolute; height: 99.97%; width: 100%; left: 0; top: 0; }\n\n.component-color .color-box-wrap .color-box-container .color-box { float: left; cursor: pointer; -webkit-tap-highlight-color: transparent; }\n\n.component-color .color-box-wrap .color-box-container .color-box.rounded-tl { border-top-left-radius: 0.25rem; }\n\n.component-color .color-box-wrap .color-box-container .color-box.rounded-tr { border-top-right-radius: 0.25rem; }\n\n.component-color .color-box-wrap .color-box-container .color-box.rounded-bl { border-bottom-left-radius: 0.25rem; }\n\n.component-color .color-box-wrap .color-box-container .color-box.rounded-br { border-bottom-right-radius: 0.25rem; }\n\n.component-color .color-box-wrap .color-box-container .color-box.selected { -webkit-transform: scale(1.15); transform: scale(1.15); border-radius: 0.25rem; box-shadow: #111 0 0 0.24rem; position: relative; z-index: 100; }\n"},{}],15:[function(t,e,n){e.exports=".component-input { display: block; }\n\n.component-input .label { padding-bottom: 0.7rem; }\n\n.component-input .input { position: relative; min-width: 100%; margin-top: 0.7rem; margin-left: 0; }\n\n.component-input input { display: block; width: 100%; background: #333333; border-radius: 0.25rem; padding: 0.35rem 0.375rem; border: none; vertical-align: baseline; color: #ffffff; font-size: inherit; }\n\n.component-input input::-webkit-input-placeholder { color: #858585; }\n\n.component-input input::-moz-placeholder { color: #858585; }\n\n.component-input input:-moz-placeholder { color: #858585; }\n\n.component-input input:-ms-input-placeholder { color: #858585; }\n\n.component-input input:focus { border: none; box-shadow: none; }\n\n.component-input input:focus::-webkit-input-placeholder { color: #666666; }\n\n.component-input input:focus::-moz-placeholder { color: #666666; }\n\n.component-input input:focus:-moz-placeholder { color: #666666; }\n\n.component-input input:focus:-ms-input-placeholder { color: #666666; }\n"},{}],16:[function(t,e,n){e.exports=".component-radio { -webkit-tap-highlight-color: transparent; display: block; }\n\n.component-radio:active { background-color: transparent; }\n\n.component-radio > .label { display: block; padding-bottom: 0.35rem; }\n\n.component-radio .radio-group label { padding: 0.35rem 0; -webkit-tap-highlight-color: rgba(255, 255, 255, 0.1); }\n\n.component-radio .radio-group label:active { background-color: rgba(255, 255, 255, 0.1); }\n\n.component-radio .radio-group .label { font-size: 0.9em; padding: 0 0.375rem; }\n\n.component-radio .radio-group input { opacity: 0; position: absolute; }\n\n.component-radio .radio-group i { display: block; position: relative; border-radius: 1.4rem; width: 1.4rem; height: 1.4rem; border: 2px solid #767676; -webkit-flex-shrink: 0; flex-shrink: 0; }\n\n.component-radio .radio-group input:checked ~ i { border-color: #ff4700; }\n\n.component-radio .radio-group input:checked ~ i:after { content: ''; display: block; position: absolute; left: 15%; right: 15%; top: 15%; bottom: 15%; border-radius: 1.4rem; background: #ff4700; }\n"},{}],17:[function(t,e,n){e.exports='.component-select { position: relative; }\n\n.component-select .value { position: relative; padding-right: 1.1rem; }\n\n.component-select .value:after { content: ""; position: absolute; right: 0; top: 50%; margin-top: -0.1rem; height: 0; width: 0; border-left: 0.425rem solid transparent; border-right: 0.425rem solid transparent; border-top: 0.425rem solid #ececec; }\n\n.component-select select { opacity: 0; position: absolute; display: block; left: 0; right: 0; top: 0; bottom: 0; background: #000; width: 100%; border: none; margin: 0; padding: 0; }\n'},{}],18:[function(t,e,n){e.exports=".component-toggle input { display: none; }\n\n.component-toggle .graphic { display: inline-block; position: relative; }\n\n.component-toggle .graphic .slide { display: block; border-radius: 1.05rem; height: 1.05rem; width: 2.2652rem; background: #2f2f2f; -webkit-transition: background-color 150ms linear; transition: background-color 150ms linear; }\n\n.component-toggle .graphic .marker { background: #ececec; width: 1.4rem; height: 1.4rem; border-radius: 1.4rem; position: absolute; left: 0; display: block; top: -0.175rem; -webkit-transition: -webkit-transform 150ms linear; transition: -webkit-transform 150ms linear; transition: transform 150ms linear; transition: transform 150ms linear, -webkit-transform 150ms linear; box-shadow: #2f2f2f 0 0.1rem 0.1rem; }\n\n.component-toggle input:checked + .graphic .slide { background: #993d19; }\n\n.component-toggle input:checked + .graphic .marker { background: #ff4700; -webkit-transform: translateX(0.8652rem); transform: translateX(0.8652rem); }\n"},{}],19:[function(t,e,n){e.exports='
');
+ $container.add($wrapper);
+ _addItems(item.items, $wrapper);
+ } else {
+ var _item = _.copyObj(item);
+ _item.clayId = _items.length;
+
+ var clayItem = new ClayItem(_item).initialize(self);
+
+ if (_item.id) {
+ _itemsById[_item.id] = clayItem;
+ }
+
+ if (_item.appKey) {
+ _itemsByAppKey[_item.appKey] = clayItem;
+ }
+
+ _items.push(clayItem);
+
+ // set the value of the item via the manipulator to ensure consistency
+ var value = typeof _settings[_item.appKey] !== 'undefined' ?
+ _settings[_item.appKey] :
+ (_item.defaultValue || '');
+
+ clayItem.set(value);
+
+ $container.add(clayItem.$element);
+ }
+ }
+
+ /**
+ * Throws if the config has not been built yet.
+ * @param {string} fnName
+ * @returns {boolean}
+ * @private
+ */
+ function _checkBuilt(fnName) {
+ if (!_isBuilt) {
+ throw new Error(
+ 'ClayConfig not built. build() must be run before ' +
+ 'you can run ' + fnName + '()'
+ );
+ }
+ return true;
+ }
+
+ self.EVENTS = {
+ /**
+ * Called before framework has initialized. This is when you would attach your
+ * custom components.
+ * @const
+ */
+ BEFORE_BUILD: 'BEFORE_BUILD',
+
+ /**
+ * Called after the config has been parsed and all components have their initial
+ * value set
+ * @const
+ */
+ AFTER_BUILD: 'AFTER_BUILD'
+ };
+ utils.updateProperties(self.EVENTS, {writable: false});
+
+ /**
+ * @returns {Array.}
+ */
+ self.getAllItems = function() {
+ _checkBuilt('getAllItems');
+ return _items;
+ };
+
+ /**
+ * @param {string} appKey
+ * @returns {ClayItem}
+ */
+ self.getItemByAppKey = function(appKey) {
+ _checkBuilt('getItemByAppKey');
+ return _itemsByAppKey[appKey];
+ };
+
+ /**
+ * @param {string} id
+ * @returns {ClayItem}
+ */
+ self.getItemById = function(id) {
+ _checkBuilt('getItemById');
+ return _itemsById[id];
+ };
+
+ /**
+ * @param {string} type
+ * @returns {Array.}
+ */
+ self.getItemsByType = function(type) {
+ _checkBuilt('getItemsByType');
+ return _items.filter(function(item) {
+ return item.config.type === type;
+ });
+ };
+
+ /**
+ * @returns {Object}
+ */
+ self.getSettings = function() {
+ _checkBuilt('getSettings');
+ _.eachObj(_itemsByAppKey, function(appKey, item) {
+ _settings[appKey] = item.get();
+ });
+ return _settings;
+ };
+
+ // @todo maybe don't do this and force the static method
+ self.registerComponent = ClayConfig.registerComponent;
+
+ /**
+ * Build the config page. This must be run before any of the get methods can be run
+ * @returns {ClayConfig}
+ */
+ self.build = function() {
+ self.trigger(self.EVENTS.BEFORE_BUILD);
+ _addItems(config, $rootContainer);
+ _isBuilt = true;
+ self.trigger(self.EVENTS.AFTER_BUILD);
+ return self;
+ };
+
+ // attach event methods
+ ClayEvents.call(self, $rootContainer);
+
+ // prevent external modifications of properties
+ utils.updateProperties(self, { writable: false, configurable: false });
+
+ // expose the config to allow developers to update it before the build is run
+ self.config = config;
+
+}
+
+/**
+ * Register a component to Clay. This must be called prior to .build();
+ * @param {Object} component - the clay component to register
+ * @param {string} component.name - the name of the component
+ * @param {string} component.template - HTML template to use for the component
+ * @param {string|Object} component.manipulator - methods to attach to the component
+ * @param {function} component.manipulator.set - set manipulator method
+ * @param {function} component.manipulator.get - get manipulator method
+ * @param {Object} [component.defaults] - template defaults
+ * @param {function} [component.initialize] - method to scaffold the component
+ * @return {boolean} - Returns true if component was registered correctly
+ */
+ClayConfig.registerComponent = function(component) {
+ var _component = _.copyObj(component);
+
+ if (componentStore[_component.name]) {
+ console.warn('Component: ' + _component.name +
+ ' is already registered. If you wish to override the existing' +
+ ' functionality, you must provide a new name');
+ return false;
+ }
+
+ if (typeof _component.manipulator === 'string') {
+ _component.manipulator = manipulators[component.manipulator];
+
+ if (!_component.manipulator) {
+ throw new Error('The manipulator: ' + component.manipulator +
+ ' does not exist in the built-in manipulators.');
+ }
+ }
+
+ if (!_component.manipulator) {
+ throw new Error('The manipulator must be defined');
+ }
+
+ if (typeof _component.manipulator.set !== 'function' ||
+ typeof _component.manipulator.get !== 'function') {
+ throw new Error('The manipulator must have both a `get` and `set` method');
+ }
+
+ if (_component.style) {
+ var style = document.createElement('style');
+ style.type = 'text/css';
+ style.appendChild(document.createTextNode(_component.style));
+ document.head.appendChild(style);
+ }
+
+ componentStore[_component.name] = _component;
+ return true;
+};
+
+module.exports = ClayConfig;
diff --git a/src/scripts/lib/clay-events.js b/src/scripts/lib/clay-events.js
new file mode 100644
index 0000000..6eacdac
--- /dev/null
+++ b/src/scripts/lib/clay-events.js
@@ -0,0 +1,101 @@
+'use strict';
+
+var $ = require('../vendor/minified').$;
+var _ = require('../vendor/minified')._;
+
+/**
+ * Attaches event methods to the context.
+ * Call with ClayEvents.call(yourObject, $eventTarget)
+ * @param {EventEmitter|M} $eventTarget - An object that will be used as the event
+ * target. Must implement EventEmitter
+ * @constructor
+ */
+function ClayEvents($eventTarget) {
+ var self = this;
+ var _eventProxies = [];
+
+ /**
+ * prefixes events with "|"
+ * @param {string} events
+ * @returns {string}
+ * @private
+ */
+ function _transformEventNames(events) {
+ return events.split(' ').map(function(event) {
+ return '|' + event.replace(/^\|/, '');
+ }).join(' ');
+ }
+
+ /**
+ * @param {function} handler
+ * @param {function} proxy
+ * @returns {function}
+ * @private
+ */
+ function _registerEventProxy(handler, proxy) {
+ var eventProxy = _.find(_eventProxies, function(item) {
+ return item.handler === handler ? item : null;
+ });
+
+ if (!eventProxy) {
+ eventProxy = { handler: handler, proxy: proxy };
+ _eventProxies.push(eventProxy);
+ }
+ return eventProxy.proxy;
+ }
+
+ /**
+ * @param {function} handler
+ * @returns {function}
+ * @private
+ */
+ function _getEventProxy(handler) {
+ return _.find(_eventProxies, function(item) {
+ return item.handler === handler ? item.proxy : null;
+ });
+ }
+
+ /**
+ * Attach an event listener to the item.
+ * @param {string} events - a space separated list of events
+ * @param {function} handler
+ * @returns {ClayEvents}
+ */
+ self.on = function(events, handler) {
+ var _events = _transformEventNames(events);
+ var self = this;
+ var _proxy = _registerEventProxy(handler, function() {
+ handler.apply(self, arguments);
+ });
+ $eventTarget.on(_events, _proxy);
+ return self;
+ };
+
+ /**
+ * Remove the given event handler. NOTE: This will remove the handler from all
+ * registered events
+ * @param {function} handler
+ * @returns {ClayEvents}
+ */
+ self.off = function(handler) {
+ var _proxy = _getEventProxy(handler);
+ if (_proxy) {
+ $.off(_proxy);
+ }
+ return self;
+ };
+
+ /**
+ * Trigger an event.
+ * @param {string} name - a single event name to trigger
+ * @param {Object} [eventObj] - an object to pass to the event handler,
+ * provided the handler does not have custom arguments.
+ * @returns {ClayEvents}
+ */
+ self.trigger = function(name, eventObj) {
+ $eventTarget.trigger(name, eventObj);
+ return self;
+ };
+}
+
+module.exports = ClayEvents;
diff --git a/src/scripts/lib/clay-item.js b/src/scripts/lib/clay-item.js
new file mode 100644
index 0000000..3d38138
--- /dev/null
+++ b/src/scripts/lib/clay-item.js
@@ -0,0 +1,73 @@
+'use strict';
+
+var componentRegistry = require('./component-registry');
+var minified = require('../vendor/minified');
+var utils = require('../lib/utils');
+var ClayEvents = require('./clay-events');
+
+var _ = minified._;
+var HTML = minified.HTML;
+
+/**
+ * @extends ClayEvents
+ * @param {Clay~ConfigItem} config
+ * @constructor
+ */
+function ClayItem(config) {
+ var self = this;
+
+ var _component = componentRegistry[config.type];
+
+ if (!_component) {
+ throw new Error('The component: ' + config.type + ' is not registered. ' +
+ 'Make sure to register it with ClayConfig.registerComponent()');
+ }
+
+ var _templateData = _.extend({}, _component.defaults || {}, config);
+
+ /** @type {string|null} */
+ self.id = config.id || null;
+
+ /** @type {string|null} */
+ self.appKey = config.appKey || null;
+
+ /** @type {Object} */
+ self.config = config;
+
+ /** @type {M} */
+ self.$element = HTML(_component.template.trim(), _templateData);
+
+ /** @type {M} */
+ self.$manipulatorTarget = self.$element.select('[data-manipulator-target]');
+
+ // this caters for situations where the manipulator target is the root element
+ if (!self.$manipulatorTarget.length) {
+ self.$manipulatorTarget = self.$element;
+ }
+
+ /**
+ * Run the initializer if it exists and attaches the css to the head.
+ * Passes minified as the first param
+ * @param {ClayConfig} clay
+ * @returns {ClayItem}
+ */
+ self.initialize = function(clay) {
+ if (typeof _component.initialize === 'function') {
+ _component.initialize.call(self, minified, clay);
+ }
+ return self;
+ };
+
+ // attach event methods
+ ClayEvents.call(self, self.$element);
+
+ // attach the manipulator methods to the clayItem
+ _.eachObj(_component.manipulator, function(methodName, method) {
+ self[methodName] = method.bind(self);
+ });
+
+ // prevent external modifications of properties
+ utils.updateProperties(self, { writable: false, configurable: false });
+}
+
+module.exports = ClayItem;
diff --git a/src/scripts/lib/component-registry.js b/src/scripts/lib/component-registry.js
new file mode 100755
index 0000000..4a50721
--- /dev/null
+++ b/src/scripts/lib/component-registry.js
@@ -0,0 +1,4 @@
+'use strict';
+
+// module is blank because we dynamically add components
+module.exports = {};
diff --git a/src/scripts/lib/manipulators.js b/src/scripts/lib/manipulators.js
new file mode 100755
index 0000000..e117b2c
--- /dev/null
+++ b/src/scripts/lib/manipulators.js
@@ -0,0 +1,104 @@
+'use strict';
+
+/**
+ * @returns {ClayEvents}
+ */
+function disable() {
+ this.$element.set('+disabled');
+ this.$manipulatorTarget.set('disabled', true);
+ return this.trigger('disabled');
+}
+
+/**
+ * @returns {ClayEvents}
+ */
+function enable() {
+ this.$element.set('-disabled');
+ this.$manipulatorTarget.set('disabled', false);
+ return this.trigger('enabled');
+}
+
+module.exports = {
+ html: {
+ get: function() {
+ return this.$manipulatorTarget.get('innerHTML');
+ },
+ set: function(value) {
+ this.$manipulatorTarget.set('innerHTML', value);
+ return this.trigger('change');
+ }
+ },
+ val: {
+ get: function() {
+ return this.$manipulatorTarget.get('value');
+ },
+ set: function(value) {
+ this.$manipulatorTarget.set('value', value);
+ return this.trigger('change');
+ },
+ disable: disable,
+ enable: enable
+ },
+ checked: {
+ get: function() {
+ return this.$manipulatorTarget.get('checked');
+ },
+ set: function(value) {
+ this.$manipulatorTarget.set('checked', value);
+ return this.trigger('change');
+ },
+ disable: disable,
+ enable: enable
+ },
+ radiogroup: {
+ get: function() {
+ return this.$element.select('input:checked').get('value');
+ },
+ set: function(value) {
+ this.$element
+ .select('input[value="' + value.replace('"', '\\"') + '"]')
+ .set('checked', true);
+ return this.trigger('change');
+ },
+ disable: disable,
+ enable: enable
+ },
+ checkboxgroup: {
+ get: function() {
+ var result = [];
+ this.$element.select('input:checked').each(function(item) {
+ result.push(item.value);
+ });
+ return result;
+ },
+ set: function(values) {
+ var self = this;
+ self.$element.select('input').set('checked', false);
+ values = values || [];
+ values.map(function(value) {
+ self.$element
+ .select('input[value="' + value.replace('"', '\\"') + '"]')
+ .set('checked', true);
+ });
+ return self.trigger('change');
+ },
+ disable: disable,
+ enable: enable
+ },
+ color: {
+ get: function() {
+ return parseInt(this.$manipulatorTarget.get('value'), 16);
+ },
+ set: function(value) {
+ switch (typeof value) {
+ case 'number': value = value.toString(16); break;
+ case 'string': value = value.replace(/^#|^0x/, ''); break;
+ }
+
+ this.$manipulatorTarget.set('value', value || '000000');
+ return this.trigger('change');
+ },
+ disable: disable,
+ enable: enable
+ }
+};
diff --git a/src/scripts/lib/utils.js b/src/scripts/lib/utils.js
new file mode 100644
index 0000000..02079f3
--- /dev/null
+++ b/src/scripts/lib/utils.js
@@ -0,0 +1,19 @@
+'use strict';
+
+/**
+ * Batch update all the properties of an object.
+ * @param {Object} obj
+ * @param {Object} descriptor
+ * @param {boolean} [descriptor.configurable]
+ * @param {boolean} [descriptor.enumerable]
+ * @param {*} [descriptor.value]
+ * @param {boolean} [descriptor.writable]
+ * @param {function} [descriptor.get]
+ * @param {function} [descriptor.set]
+ * @return {void}
+ */
+module.exports.updateProperties = function(obj, descriptor) {
+ Object.getOwnPropertyNames(obj).forEach(function(prop) {
+ Object.defineProperty(obj, prop, descriptor);
+ });
+};
diff --git a/src/scripts/vendor/autoprefixify.js b/src/scripts/vendor/autoprefixify.js
new file mode 100644
index 0000000..3ab60b4
--- /dev/null
+++ b/src/scripts/vendor/autoprefixify.js
@@ -0,0 +1,51 @@
+var path = require('path');
+var through = require('through');
+var postcss = require('postcss');
+var autoprefixer = require('autoprefixer');
+var requireFromString = require('require-from-string');
+
+/**
+ * Stringifies the content
+ * @param {string} content
+ * @returns {string}
+ */
+function stringify (content) {
+ return 'module.exports = ' + JSON.stringify(content) + ';\n';
+}
+
+module.exports = function (file, options) {
+
+ /**
+ * The function Browserify will use to transform the input.
+ * @param {string} file
+ * @returns {stream}
+ */
+ function browserifyTransform (file) {
+ var extensions = ['.css', '.sass', '.scss', '.less'];
+ var chunks = [];
+
+ if (extensions.indexOf(path.extname(file)) === -1) {
+ return through();
+ }
+
+ var write = function (buffer) {
+ chunks.push(buffer);
+ };
+
+ var end = function () {
+ var contents = requireFromString(Buffer.concat(chunks).toString('utf8'));
+ contents = postcss([autoprefixer(options)]).process(contents).css;
+ this.queue(stringify(contents));
+ this.queue(null);
+ };
+
+ return through(write, end);
+ }
+
+ if (typeof file !== 'string') {
+ options = file;
+ return browserifyTransform;
+ } else {
+ return browserifyTransform(file);
+ }
+};
diff --git a/src/scripts/vendor/minified.js b/src/scripts/vendor/minified.js
new file mode 100644
index 0000000..05b4061
--- /dev/null
+++ b/src/scripts/vendor/minified.js
@@ -0,0 +1,3552 @@
+// minified.js config start -- use this comment to re-create a configuration in the Builder
+// - Only sections add, always, amdsupport, copyobj, dollardollar,
+// - each, eachobj, error, extend, find, format, formathtml, get, ht, html,
+// - isobject, off, on, ready, request, select, set, template, trigger, underscore,
+// - wait.
+
+
+// WARNING! This file is autogenerated from minified-master.js and others.
+
+/*
+ * Minified.js - Lightweight Client-Side JavaScript Library (full package)
+ * Version: Version 2014 beta 5 b2
+ *
+ * Public Domain. Use, modify and distribute it any way you like. No attribution required.
+ * To the extent possible under law, Tim Jansen has waived all copyright and related or neighboring rights to Minified.
+ * Please see http://creativecommons.org/publicdomain/zero/1.0/.
+ * NO WARRANTY EXPRESSED OR IMPLIED. USE AT YOUR OWN RISK.
+ *
+ * Contains code based on https://github.com/douglascrockford/JSON-js (also Public Domain).
+ *
+ * https://github.com/timjansen/minified.js
+ */
+// ==ClosureCompiler==
+// @output_file_name minified.js
+// @compilation_level ADVANCED_OPTIMIZATIONS
+// ==/ClosureCompiler==
+
+/*$
+ * @id ALL
+ * @doc no
+ * @required
+ * This id allows identifying whether both Web and Util are available.
+ */
+
+///#snippet commonAmdStart
+
+/*$
+ * @id require
+ * @name require()
+ * @syntax require(name)
+ * @group OPTIONS
+ * @module WEB, UTIL
+ * Returns a reference to a module. If you do not use an AMD loader to load Minified, just call require() with the
+ * argument 'minified' to get a reference to Minified. You can also access all modules defined using ##define().
+ *
+ * If you do use an AMD loader, Minified will not define this function and you can use the AMD loader to obtain the
+ * reference to Minified.
+ * Minified's version of require is very simple and will only support Minified and other libraries designed
+ * for Minfied, but no real AMD libraries. If you need to work with libraries requiring AMD, you need a real AMD loader.
+ *
+ * @param name the name of the module to request. Minified is available as 'minified'.
+ * @return the reference to the module. Use the name 'minified' to get Minified. You can also access any modules defined using
+ * ##define(). If the name is unknown, it returns undefined.
+ *
+ * @see ##define() allows you to define modules that can be obtained using require().
+ */
+
+/*$
+ * @id define
+ * @name define()
+ * @syntax define(name, factoryFunction)
+ * @group OPTIONS
+ * @module WEB, UTIL
+ * Defines a module that can be returned by ##require(), in case you don't have a AMD loader. If you have a AMD loader before you include Minified,
+ * define() will not be set and you can use the AMD loader's (more powerful) variant.
+ *
+ * Minified's versions of require() and define() are very simple and can not resolve things like circular references.
+ * Also, they are not AMD-compatible and only useful for simple modules. If you need to work with real AMD libraries that are not written
+ * for Minified, you need a real AMD loader.
+ *
+ * @example Creates a simple module and uses it:
+ *
+ * define('makeGreen', function(require) {
+ * var MINI = require('minified'), $ = MINI.$; // obtain own ref to Minified
+ * return function(list) {
+ * $(list).set({$color: '#0f0', $backgroundColor: '#050'});
+ * });
+ * });
+ *
+ * var makeGreen = require('makeGreen');
+ * makeGreen('.notGreenEnough');
+ *
+ *
+ * @param name the name of the module to request. In Minified's implementation, only 'minified' is supported.
+ * @param factoryFunction is a function(require) will be called the first time the name is defined to obtain the module
+ * reference. It received a reference to ##require() (which is required for AMD backward-compatibility) and
+ * must return the value that is returned by ##require(). The function will only be called once, its result will
+ * be cached.
+ *
require
A reference to ##require(). While you could use require() from the global
+ * context, this would prevent backward compatibility with AMD.
+ *
(callback return value)
The reference to be returned by ##require().
+ *
+ * @see ##require() can be used to obtain references defined with ##define().
+ */
+
+/*$
+ * @id amdsupport
+ * @name AMD stubs
+ * @configurable default
+ * @group OPTIONS
+ * @doc no
+ * @module WEB, UTIL
+ * If enabled, Minified will create stubs so you can use it without an AMD framework.
+ * It requires AMD's define() function.
+ */
+if (/^u/.test(typeof define)) { // no AMD support available ? define a minimal version
+ (function(def){
+ var require = this['require'] = function(name) { return def[name]; };
+ this['define'] = function(name, f) { def[name] = def[name] || f(require); };
+ })({});
+}
+/*$
+ * @stop
+ */
+
+define('minified', function() {
+
+ ///#/snippet commonAmdStart
+ ///#snippet webVars
+ /*$
+ * @id WEB
+ * @doc no
+ * @required
+ * This id allows identifying whether the Web module is available.
+ */
+
+ /**
+ * @const
+ */
+ var _window = window;
+
+ /**
+ * @const
+ * @type {!string}
+ */
+ var MINIFIED_MAGIC_NODEID = 'Nia';
+
+ /**
+ * @const
+ * @type {!string}
+ */
+ var MINIFIED_MAGIC_PREV = 'NiaP';
+
+ var setter = {}, getter = {};
+
+ var idSequence = 1; // used as node id to identify nodes, and as general id for other maps
+
+
+ /*$
+ * @id ready_vars
+ * @dependency
+ */
+ /** @type {!Array.} */
+ var DOMREADY_HANDLER = /^[ic]/.test(document['readyState']) ? _null : []; // check for 'interactive' and 'complete'
+ /*$
+ * @stop
+ */
+
+ ///#/snippet webVars
+ ///#snippet utilVars
+ /*$
+ * @id UTIL
+ * @doc no
+ * @required
+ * This id allows identifying whether the Util module is available.
+ */
+
+ var _null = null;
+
+ /** @const */
+ var undef;
+
+ /*$
+ * @id date_constants
+ * @dependency
+ */
+ function val3(v) {return v.substr(0,3);}
+ var MONTH_LONG_NAMES = split('January,February,March,April,May,June,July,August,September,October,November,December', /,/g);
+ var MONTH_SHORT_NAMES = map(MONTH_LONG_NAMES, val3); // ['Jan','Feb','Mar','Apr','May','Jun','Jul','Aug','Sep','Oct','Nov','Dec'];
+ var WEEK_LONG_NAMES = split('Sunday,Monday,Tuesday,Wednesday,Thursday,Friday,Saturday', /,/g);
+ var WEEK_SHORT_NAMES = map(WEEK_LONG_NAMES, val3);
+ var MERIDIAN_NAMES = split('am,pm', /,/g);
+ var MERIDIAN_NAMES_FULL = split('am,am,am,am,am,am,am,am,am,am,am,am,pm,pm,pm,pm,pm,pm,pm,pm,pm,pm,pm,pm', /,/g);
+
+ var FORMAT_DATE_MAP = {
+ 'y': ['FullYear', nonOp],
+ 'Y': ['FullYear', function(d) { return d % 100; }],
+ 'M': ['Month', plusOne],
+ 'n': ['Month', MONTH_SHORT_NAMES],
+ 'N': ['Month', MONTH_LONG_NAMES],
+ 'd': ['Date', nonOp],
+ 'm': ['Minutes', nonOp],
+ 'H': ['Hours', nonOp],
+ 'h': ['Hours', function(d) { return (d % 12) || 12; }],
+ 'k': ['Hours', plusOne],
+ 'K': ['Hours', function(d) { return d % 12; }],
+ 's': ['Seconds', nonOp],
+ 'S': ['Milliseconds', nonOp],
+ 'a': ['Hours', MERIDIAN_NAMES_FULL],
+ 'w': ['Day', WEEK_SHORT_NAMES],
+ 'W': ['Day', WEEK_LONG_NAMES],
+ 'z': ['TimezoneOffset', function(d, dummy, timezone) {
+ if (timezone)
+ return timezone;
+
+ var sign = d > 0 ? '-' : '+';
+ var off = d < 0 ? -d : d;
+ return sign + pad(2, Math.floor(off/60)) + pad(2, off%60);
+ }]
+ };
+
+ var PARSE_DATE_MAP = {
+ 'y': 0, // placeholder -> ctorIndex
+ 'Y': [0, -2000],
+ 'M': [1,1], // placeholder -> [ctorIndex, offset|value array]
+ 'n': [1, MONTH_SHORT_NAMES],
+ 'N': [1, MONTH_LONG_NAMES],
+ 'd': 2,
+ 'm': 4,
+ 'H': 3,
+ 'h': 3,
+ 'K': [3,1],
+ 'k': [3,1],
+ 's': 5,
+ 'S': 6,
+ 'a': [3, MERIDIAN_NAMES]
+ };
+
+ /*$
+ * @stop
+ */
+
+ /** @const */
+ var MAX_CACHED_TEMPLATES = 99;
+ var templateCache={}; // template -> function
+ var templates = []; // list of MAX_CACHED_TEMPLATES templates
+
+ ///#/snippet utilVars
+ ///#snippet commonFunctions
+
+ /** @param s {?} */
+ function toString(s) {
+ return s!=_null ? ''+s : '';
+ }
+ /**
+ * @param s {?}
+ * @param o {string}
+ */
+ function isType(s,o) {
+ return typeof s == o;
+ }
+ /** @param s {?} */
+ function isString(s) {
+ return isType(s, 'string');
+ }
+ function isObject(f) {
+ return !!f && isType(f, 'object');
+ }
+ function isNode(n) {
+ return n && n['nodeType'];
+ }
+ function isNumber(n) {
+ return isType(n, 'number');
+ }
+ function isDate(n) {
+ return isObject(n) && !!n['getDay'];
+ }
+ function isBool(n) {
+ return n === true || n === false;
+ }
+ function isValue(n) {
+ var type = typeof n;
+ return type == 'object' ? !!(n && n['getDay']) : (type == 'string' || type == 'number' || isBool(n));
+ }
+ function nonOp(v) {
+ return v;
+ }
+ function plusOne(d) {
+ return d+1;
+ }
+ function replace(s, regexp, sub) {
+ return toString(s).replace(regexp, sub != _null ? sub : '');
+ }
+ function escapeRegExp(s) {
+ return replace(s, /[\\\[\]\/{}()*+?.$|^-]/g, "\\$&");
+ }
+ function trim(s) {
+ return replace(s, /^\s+|\s+$/g);
+ }
+ function eachObj(obj, cb, ctx) {
+ for (var n in obj)
+ if (obj.hasOwnProperty(n))
+ cb.call(ctx || obj, n, obj[n]);
+ return obj;
+ }
+ function each(list, cb, ctx) {
+ if (list)
+ for (var i = 0; i < list.length; i++)
+ cb.call(ctx || list, list[i], i);
+ return list;
+ }
+ function filter(list, filterFuncOrObject, ctx) {
+ var r = [];
+ var f = isFunction(filterFuncOrObject) ? filterFuncOrObject : function(value) { return filterFuncOrObject != value; };
+ each(list, function(value, index) {
+ if (f.call(ctx || list, value, index))
+ r.push(value);
+ });
+ return r;
+ }
+ function collector(iterator, obj, collectFunc, ctx) {
+ var result = [];
+ iterator(obj, function (a, b) {
+ if (isList(a = collectFunc.call(ctx || obj, a, b))) // extreme variable reusing: a is now the callback result
+ each(a, function(rr) { result.push(rr); });
+ else if (a != _null)
+ result.push(a);
+ });
+ return result;
+ }
+ function collectObj(obj, collectFunc, ctx) {
+ return collector(eachObj, obj, collectFunc, ctx);
+ }
+ function collect(list, collectFunc, ctx) {
+ return collector(each, list, collectFunc, ctx);
+ }
+ function keyCount(obj) {
+ var c = 0;
+ eachObj(obj, function(key) { c++; });
+ return c;
+ }
+ function keys(obj) { // use Object.keys? in IE>=9
+ var list = [];
+ eachObj(obj, function(key) { list.push(key); });
+ return list;
+ }
+ function map(list, mapFunc, ctx) {
+ var result = [];
+ each(list, function(item, index) {
+ result.push(mapFunc.call(ctx || list, item, index));
+ });
+ return result;
+ }
+ function startsWith(base, start) {
+ if (isList(base)) {
+ var s2 = _(start); // convert start as we don't know whether it is a list yet
+ return equals(sub(base, 0, s2.length), s2);
+ }
+ else
+ return start != _null && base.substr(0, start.length) == start;
+ }
+ function endsWith(base, end) {
+ if (isList(base)) {
+ var e2 = _(end);
+ return equals(sub(base, -e2.length), e2) || !e2.length;
+ }
+ else
+ return end != _null && base.substr(base.length - end.length) == end;
+ }
+ function reverse(list) {
+ var len = list.length;
+ if (isList(list))
+ return new M(map(list, function() { return list[--len]; }));
+ else
+ return replace(list, /[\s\S]/g, function() { return list.charAt(--len); });
+ }
+ function toObject(list, value) {
+ var obj = {};
+ each(list, function(item, index) {
+ obj[item] = value;
+ });
+ return obj;
+ }
+ function copyObj(from, to) {
+ var dest = to || {};
+ for (var name in from)
+ dest[name] = from[name];
+ return dest;
+ }
+ function merge(list, target) {
+ var o = target;
+ for (var i = 0; i < list.length; i++)
+ o = copyObj(list[i], o);
+ return o;
+ }
+ function getFindFunc(findFunc) {
+ return isFunction(findFunc) ? findFunc : function(obj, index) { if (findFunc === obj) return index; };
+ }
+ function getFindIndex(list, index, defaultIndex) {
+ return index == _null ? defaultIndex : index < 0 ? Math.max(list.length+index, 0) : Math.min(list.length, index);
+ }
+ function find(list, findFunc, startIndex, endIndex) {
+ var f = getFindFunc(findFunc);
+ var e = getFindIndex(list, endIndex, list.length);
+ var r;
+ for (var i = getFindIndex(list, startIndex, 0); i < e; i++)
+ if ((r = f.call(list, list[i], i)) != _null)
+ return r;
+ }
+ function findLast(list, findFunc, startIndex, endIndex) {
+ var f = getFindFunc(findFunc);
+ var e = getFindIndex(list, endIndex, -1);
+ var r;
+ for (var i = getFindIndex(list, startIndex, list.length-1); i > e; i--)
+ if ((r = f.call(list, list[i], i)) != _null)
+ return r;
+ }
+ function sub(list, startIndex, endIndex) {
+ var r = [];
+ if (list) {
+ var e = getFindIndex(list, endIndex, list.length);
+ for (var i = getFindIndex(list, startIndex, 0); i < e; i++)
+ r.push(list[i]);
+ }
+ return r;
+ }
+ function array(list) {
+ return map(list, nonOp);
+ }
+ function unite(list) {
+ return function() {
+ return new M(callList(list, arguments));
+ };
+ }
+ function uniq(list) {
+ var found = {};
+ return filter(list, function(item) {
+ if (found[item])
+ return false;
+ else
+ return found[item] = 1;
+ });
+ }
+ function intersection(list, otherList) {
+ var keys = toObject(otherList, 1);
+ return filter(list, function(item) {
+ var r = keys[item];
+ keys[item] = 0;
+ return r;
+ });
+ }
+ function contains(list, value) { // TODO: can Array.indexOf be used in >IE8?
+ for (var i = 0; i < list.length; i++)
+ if (list[i] == value)
+ return true;
+ return false;
+ }
+ // equals if a and b have the same elements and all are equal. Supports getters.
+ function equals(x, y) {
+ var a = isFunction(x) ? x() : x;
+ var b = isFunction(y) ? y() : y;
+ var aKeys;
+ if (a == b)
+ return true;
+ else if (a == _null || b == _null)
+ return false;
+ else if (isValue(a) || isValue(b))
+ return isDate(a) && isDate(b) && +a==+b;
+ else if (isList(a)) {
+ return (a.length == b.length) &&
+ !find(a, function(val, index) {
+ if (!equals(val, b[index]))
+ return true;
+ });
+ }
+ else {
+ return !isList(b) &&
+ ((aKeys = keys(a)).length == keyCount(b)) &&
+ !find(aKeys, function(key) {
+ if (!equals(a[key],b[key]))
+ return true;
+ });
+ }
+ }
+
+ function call(f, fThisOrArgs, args) {
+ if (isFunction(f))
+ return f.apply(args && fThisOrArgs, map(args || fThisOrArgs, nonOp));
+ }
+ function callList(list, fThisOrArgs, args) {
+ return map(list, function(f) { return call(f, fThisOrArgs, args);});
+ }
+ function bind(f, fThis, beforeArgs, afterArgs) {
+ return function() {
+ return call(f, fThis, collect([beforeArgs, arguments, afterArgs], nonOp));
+ };
+ }
+ function partial(f, beforeArgs, afterArgs) {
+ return bind(f, this, beforeArgs, afterArgs);
+ }
+ function pad(digits, number) {
+ var signed = number < 0 ? '-' : '';
+ var preDecimal = (signed?-number:number).toFixed(0);
+ while (preDecimal.length < digits)
+ preDecimal = '0' + preDecimal;
+ return signed + preDecimal;
+ }
+
+ function processNumCharTemplate(tpl, input, fwd) {
+ var inHash;
+ var inputPos = 0;
+ var rInput = fwd ? input : reverse(input);
+ var s = (fwd ? tpl : reverse(tpl)).replace(/./g, function(tplChar) {
+ if (tplChar == '0') {
+ inHash = false;
+ return rInput.charAt(inputPos++) || '0';
+ }
+ else if (tplChar == '#') {
+ inHash = true;
+ return rInput.charAt(inputPos++) || '';
+ }
+ else
+ return inHash && !rInput.charAt(inputPos) ? '' : tplChar;
+ });
+ return fwd ? s : (input.substr(0, input.length - inputPos) + reverse(s));
+ }
+
+ function getTimezone(match, idx, refDate) { // internal helper, see below
+ if (idx == _null || !match)
+ return 0;
+ return parseFloat(match[idx]+match[idx+1])*60 + parseFloat(match[idx]+match[idx+2]) + refDate.getTimezoneOffset();
+ }
+
+ // formats number with format string (e.g. "#.000", "#,#", "00000", "000.00", "000.000.000,00", "000,000,000.##")
+ // choice syntax: :|:|...
+ // e.g. 0:no item|1:one item|>=2:# items
+ // ="null" used to compare with nulls.
+ // choice also works with strings or bools, e.g. ERR:error|WAR:warning|FAT:fatal|ok
+ function formatValue(fmt, value) {
+ var format = replace(fmt, /^\?/);
+ if (isDate(value)) {
+ var timezone, match;
+
+ if (match = /^\[(([+-])(\d\d)(\d\d))\]\s*(.*)/.exec(format)) {
+ timezone = match[1];
+ value = dateAdd(value, 'minutes', getTimezone(match, 2, value));
+ format = match[5];
+ }
+
+ return replace(format, /(\w)(\1*)(?:\[([^\]]+)\])?/g, function(s, placeholderChar, placeholderDigits, params) {
+ var val = FORMAT_DATE_MAP[placeholderChar];
+ if (val) {
+ var d = value['get' + val[0]]();
+ var optionArray = (params && params.split(','));
+
+ if (isList(val[1]))
+ d = (optionArray || val[1])[d];
+ else
+ d = val[1](d, optionArray, timezone);
+ if (d != _null && !isString(d))
+ d = pad(placeholderDigits.length+1, d);
+ return d;
+ }
+ else
+ return s;
+ });
+
+ }
+ else
+ return find(format.split(/\s*\|\s*/), function(fmtPart) {
+ var match, numFmtOrResult;
+ if (match = /^([<>]?)(=?)([^:]*?)\s*:\s*(.*)$/.exec(fmtPart)) {
+ var cmpVal1 = value, cmpVal2 = +(match[3]);
+ if (isNaN(cmpVal2) || !isNumber(cmpVal1)) {
+ cmpVal1 = (cmpVal1==_null) ? "null" : toString(cmpVal1); // not ""+value, because undefined is treated as null here
+ cmpVal2 = match[3];
+ }
+ if (match[1]) {
+ if ((!match[2] && cmpVal1 == cmpVal2 ) ||
+ (match[1] == '<' && cmpVal1 > cmpVal2) ||
+ (match[1] == '>' && cmpVal1 < cmpVal2))
+ return _null;
+ }
+ else if (cmpVal1 != cmpVal2)
+ return _null;
+ numFmtOrResult = match[4];
+ }
+ else
+ numFmtOrResult = fmtPart;
+
+ if (isNumber(value))
+ return numFmtOrResult.replace(/[0#](.*[0#])?/, function(numFmt) {
+ var decimalFmt = /^([^.]+)(\.)([^.]+)$/.exec(numFmt) || /^([^,]+)(,)([^,]+)$/.exec(numFmt);
+ var signed = value < 0 ? '-' : '';
+ var numData = /(\d+)(\.(\d+))?/.exec((signed?-value:value).toFixed(decimalFmt ? decimalFmt[3].length:0));
+ var preDecimalFmt = decimalFmt ? decimalFmt[1] : numFmt;
+ var postDecimal = decimalFmt ? processNumCharTemplate(decimalFmt[3], replace(numData[3], /0+$/), true) : '';
+
+ return (signed ? '-' : '') +
+ (preDecimalFmt == '#' ? numData[1] : processNumCharTemplate(preDecimalFmt, numData[1])) +
+ (postDecimal.length ? decimalFmt[2] : '') +
+ postDecimal;
+ });
+ else
+ return numFmtOrResult;
+ });
+ }
+ // returns date; null if optional and not set; undefined if parsing failed
+ function parseDate(fmt, date) {
+ var indexMap = {}; // contains reGroupPosition -> typeLetter or [typeLetter, value array]
+ var reIndex = 1;
+ var timezoneOffsetMatch;
+ var timezoneIndex;
+ var match;
+
+ var format = replace(fmt, /^\?/);
+ if (format!=fmt && !trim(date))
+ return _null;
+
+ if (match = /^\[([+-])(\d\d)(\d\d)\]\s*(.*)/.exec(format)) {
+ timezoneOffsetMatch = match;
+ format = match[4];
+ }
+
+ var parser = new RegExp(format.replace(/(.)(\1*)(?:\[([^\]]*)\])?/g, function(wholeMatch, placeholderChar, placeholderDigits, param) {
+ if (/[dmhkyhs]/i.test(placeholderChar)) {
+ indexMap[reIndex++] = placeholderChar;
+ var plen = placeholderDigits.length+1;
+ return "(\\d"+(plen<2?"+":("{1,"+plen+"}"))+")";
+ }
+ else if (placeholderChar == 'z') {
+ timezoneIndex = reIndex;
+ reIndex += 3;
+ return "([+-])(\\d\\d)(\\d\\d)";
+ }
+ else if (/[Nna]/.test(placeholderChar)) {
+ indexMap[reIndex++] = [placeholderChar, param && param.split(',')];
+ return "([a-zA-Z\\u0080-\\u1fff]+)";
+ }
+ else if (/w/i.test(placeholderChar))
+ return "[a-zA-Z\\u0080-\\u1fff]+";
+ else if (/\s/.test(placeholderChar))
+ return "\\s+";
+ else
+ return escapeRegExp(wholeMatch);
+ }));
+
+ if (!(match = parser.exec(date)))
+ return undef;
+
+ var ctorArgs = [0, 0, 0, 0, 0, 0, 0];
+ for (var i = 1; i < reIndex; i++) {
+ var matchVal = match[i];
+ var indexEntry = indexMap[i];
+ if (isList(indexEntry)) { // for a, n or N
+ var placeholderChar = indexEntry[0];
+ var mapEntry = PARSE_DATE_MAP[placeholderChar];
+ var ctorIndex = mapEntry[0];
+ var valList = indexEntry[1] || mapEntry[1];
+ var listValue = find(valList, function(v, index) { if (startsWith(matchVal.toLowerCase(), v.toLowerCase())) return index; });
+ if (listValue == _null)
+ return undef;
+ if (placeholderChar == 'a')
+ ctorArgs[ctorIndex] += listValue * 12;
+ else
+ ctorArgs[ctorIndex] = listValue;
+ }
+ else if (indexEntry) { // for numeric values (yHmMs)
+ var value = parseFloat(matchVal);
+ var mapEntry = PARSE_DATE_MAP[indexEntry];
+ if (isList(mapEntry))
+ ctorArgs[mapEntry[0]] += value - mapEntry[1];
+ else
+ ctorArgs[mapEntry] += value;
+ }
+ }
+ var d = new Date(ctorArgs[0], ctorArgs[1], ctorArgs[2], ctorArgs[3], ctorArgs[4], ctorArgs[5], ctorArgs[6]);
+ return dateAdd(d, 'minutes', -getTimezone(timezoneOffsetMatch, 1, d) - getTimezone(match, timezoneIndex, d));
+ }
+ // format ?##00,00##
+ // returns number; null if optional and not set; undefined if parsing failed
+ function parseNumber(fmt, value) {
+ var format = replace(fmt, /^\?/);
+ if (format!=fmt && !trim(value))
+ return _null;
+ var decSep = (/(^|[^0#.,])(,|[0#.]*,[0#]+|[0#]+\.[0#]+\.[0#.,]*)($|[^0#.,])/.test(format)) ? ',' : '.';
+ var r = parseFloat(replace(replace(replace(value, decSep == ',' ? /\./g : /,/g), decSep, '.'), /^[^\d-]*(-?\d)/, '$1'));
+ return isNaN(r) ? undef : r;
+ }
+ function now() {
+ return new Date();
+ }
+ function dateClone(date) {
+ return new Date(+date);
+ }
+ function capWord(w) {
+ return w.charAt(0).toUpperCase() + w.substr(1);
+ }
+ function dateAddInline(d, cProp, value) {
+ d['set'+cProp](d['get'+cProp]() + value);
+ return d;
+ }
+ function dateAdd(date, property, value) {
+ if (value == _null)
+ return dateAdd(now(), date, property);
+ return dateAddInline(dateClone(date), capWord(property), value);
+ }
+ function dateMidnight(date) {
+ var od = date || now();
+ return new Date(od.getFullYear(), od.getMonth(), od.getDate());
+ }
+ function dateDiff(property, date1, date2) {
+ var d1t = +date1;
+ var d2t = +date2;
+ var dt = d2t - d1t;
+ if (dt < 0)
+ return -dateDiff(property, date2, date1);
+
+ var propValues = {'milliseconds': 1, 'seconds': 1000, 'minutes': 60000, 'hours': 3600000};
+ var ft = propValues[property];
+ if (ft)
+ return dt / ft;
+
+ var cProp = capWord(property);
+ var calApproxValues = {'fullYear': 8.64e7*365, 'month': 8.64e7*365/12, 'date': 8.64e7}; // minimum values, a little bit below avg values
+ var minimumResult = Math.floor((dt / calApproxValues[property])-2); // -2 to remove the imperfections caused by the values above
+
+ var d = dateAddInline(new Date(d1t), cProp, minimumResult);
+ for (var i = minimumResult; i < minimumResult*1.2+4; i++) { // try out 20% more than needed, just to be sure
+ if (+dateAddInline(d, cProp, 1) > d2t)
+ return i;
+ }
+ // should never ever be reached
+ }
+
+ function ucode(a) {
+ return '\\u' + ('0000' + a.charCodeAt(0).toString(16)).slice(-4);
+ }
+
+ function escapeJavaScriptString(s) {
+ return replace(s, /[\x00-\x1f'"\u2028\u2029]/g, ucode);
+ }
+
+ // reimplemented split for IE8
+ function split(str, regexp) {
+
+ return str.split(regexp);
+ }
+
+ function template(template, escapeFunction) {
+ if (templateCache[template])
+ return templateCache[template];
+ else {
+ var funcBody = 'with(_.isObject(obj)?obj:{}){'+
+ map(split(template, /{{|}}}?/g), function(chunk, index) {
+ var match, c1 = trim(chunk), c2 = replace(c1, /^{/), escapeSnippet = (c1==c2) ? 'esc(' : '';
+ if (index%2) { // odd means JS code
+ if (match = /^each\b(\s+([\w_]+(\s*,\s*[\w_]+)?)\s*:)?(.*)/.exec(c2))
+ return 'each('+(trim(match[4])?match[4]:'this')+', function('+match[2]+'){';
+ else if (match = /^if\b(.*)/.exec(c2))
+ return 'if('+match[1]+'){';
+ else if (match = /^else\b\s*(if\b(.*))?/.exec(c2))
+ return '}else ' + (match[1] ? 'if('+match[2] +')' : '')+'{';
+ else if (match = /^\/(if)?/.exec(c2))
+ return match[1] ? '}\n' : '});\n';
+ else if (match = /^(var\s.*)/.exec(c2))
+ return match[1]+';';
+ else if (match = /^#(.*)/.exec(c2))
+ return match[1];
+ else if (match = /(.*)::\s*(.*)/.exec(c2))
+ return 'print('+escapeSnippet+'_.formatValue("'+escapeJavaScriptString(match[2])+'",'+(trim(match[1])?match[1]:'this')+(escapeSnippet&&')')+'));\n';
+ else
+ return 'print('+escapeSnippet+(trim(c2)?c2:'this')+(escapeSnippet&&')')+');\n';
+ }
+ else if (chunk){
+ return 'print("'+escapeJavaScriptString(chunk)+'");\n';
+ }
+ }).join('')+'}';
+ var f = (new Function('obj', 'each', 'esc', 'print', '_', funcBody));
+ var t = function(obj, thisContext) {
+ var result = [];
+ f.call(thisContext || obj, obj, function(obj, func) {
+ if (isList(obj))
+ each(obj, function(value, index) { func.call(value, value, index); });
+ else
+ eachObj(obj, function(key, value) { func.call(value, key, value); });
+ }, escapeFunction || nonOp, function() {call(result['push'], result, arguments);}, _);
+ return result.join('');
+ };
+ if (templates.push(t) > MAX_CACHED_TEMPLATES)
+ delete templateCache[templates.shift()];
+ return templateCache[template] = t;
+ }
+ }
+
+ function escapeHtml(s) {
+ return replace(s, /[<>'"&]/g, function(s) {
+ return ''+s.charCodeAt(0)+';';
+ });
+ }
+
+ function formatHtml(tpl, obj) {
+ return template(tpl, escapeHtml)(obj);
+ }
+
+ function listBindArray(func) {
+ return function(arg1, arg2) {
+ return new M(func(this, arg1, arg2));
+ };
+ }
+ function listBind(func) {
+ return function(arg1, arg2, arg3) {
+ return func(this, arg1, arg2, arg3);
+ };
+ }
+ function funcArrayBind(func) {
+ return function(arg1, arg2, arg3) {
+ return new M(func(arg1, arg2, arg3));
+ };
+ }
+
+ ///#/snippet commonFunctions
+ ///#snippet webFunctions
+
+ // note: only the web version has the f.item check
+ function isFunction(f) {
+ return typeof f == 'function' && !f['item']; // item check as work-around for webkit bug 14547
+ }
+
+ function isList(v) {
+ return v && v.length != _null && !isString(v) && !isNode(v) && !isFunction(v) && v !== _window;
+ }
+
+ // used by IE impl of on() only
+ function push(obj, prop, value) {
+ (obj[prop] = (obj[prop] || [])).push(value);
+ }
+ // used by IE impl of on()/off() only
+ function removeFromArray(array, value) {
+ for (var i = 0; array && i < array.length; i++)
+ if (array[i] === value)
+ array['splice'](i--, 1);
+ }
+
+ function extractNumber(v) {
+ return parseFloat(replace(v, /^[^\d-]+/));
+ }
+
+ // retrieves the node id of the element, create one if needed.
+ function getNodeId(el) {
+ return (el[MINIFIED_MAGIC_NODEID] = (el[MINIFIED_MAGIC_NODEID] || ++idSequence));
+ }
+
+ // collect variant that filters out duplicate nodes from the given list, returns a new array
+ function collectUniqNodes(list, func) {
+ var result = [];
+ var nodeIds = {};
+ var currentNodeId;
+
+ flexiEach(list, function(value) {
+ flexiEach(func(value), function(node) {
+ if (!nodeIds[currentNodeId = getNodeId(node)]) {
+ result.push(node);
+ nodeIds[currentNodeId] = true;
+ }
+ });
+ });
+ return result;
+ }
+
+ // finds out the 'natural' height of the first element, the one if $$slide=1
+ function getNaturalHeight(elementList, factor) {
+ var q = {'$position': 'absolute', '$visibility': 'hidden', '$display': 'block', '$height': _null};
+ var oldStyles = elementList['get'](q);
+ var h = elementList['set'](q)['get']('clientHeight');
+ elementList['set'](oldStyles);
+ return h*factor + 'px';
+ }
+
+
+
+
+ // @condblock !ie8compatibility
+ function on(subSelector, eventSpec, handler, args, bubbleSelector) {
+ if (isFunction(eventSpec))
+ return this['on'](_null, subSelector, eventSpec, handler, args);
+ else if (isString(args))
+ return this['on'](subSelector, eventSpec, handler, _null, args);
+ else
+ return this['each'](function(baseElement, index) {
+ flexiEach(subSelector ? dollarRaw(subSelector, baseElement) : baseElement, function(registeredOn) {
+ flexiEach(toString(eventSpec).split(/\s/), function(namePrefixed) {
+ var name = replace(namePrefixed, /[?|]/g);
+ var prefix = replace(namePrefixed, /[^?|]/g);
+ var capture = (name == 'blur' || name == 'focus') && !!bubbleSelector; // bubble selectors for 'blur' and 'focus' registered as capuring!
+ var triggerId = idSequence++;
+
+ // returns true if processing should be continued
+ function triggerHandler(eventName, event, target) {
+ var match = !bubbleSelector;
+ var el = bubbleSelector ? target : registeredOn;
+ if (bubbleSelector) {
+ var selectorFilter = getFilterFunc(bubbleSelector, registeredOn);
+ while (el && el != registeredOn && !(match = selectorFilter(el)))
+ el = el['parentNode'];
+ }
+ return (!match) || (name != eventName) || ((handler.apply($(el), args || [event, index]) && prefix=='?') || prefix == '|');
+ };
+
+ function eventHandler(event) {
+ if (!triggerHandler(name, event, event['target'])) {
+ event['preventDefault']();
+ event['stopPropagation']();
+ }
+ };
+
+ registeredOn.addEventListener(name, eventHandler, capture);
+
+ if (!registeredOn['M'])
+ registeredOn['M'] = {};
+ registeredOn['M'][triggerId] = triggerHandler; // to be called by trigger()
+
+ handler['M'] = collector(flexiEach, [handler['M'], function () { // this function will be called by off()
+ registeredOn.removeEventListener(name, eventHandler, capture);
+ delete registeredOn['M'][triggerId];
+ }], nonOp);
+
+ });
+ });
+ });
+ }
+ // @condend !ie8compatibility
+
+
+ // @condblock !ie8compatibility
+ function off(handler) {
+ callList(handler['M']);
+ handler['M'] = _null;
+ }
+ // @condend !ie8compatibility
+
+ // for remove & window.unload, IE only
+ function detachHandlerList(dummy, handlerList) {
+ flexiEach(handlerList, function(h) {
+ h.element.detachEvent('on'+h.eventType, h.handlerFunc);
+ });
+ }
+
+ function ready(handler) {
+ if (DOMREADY_HANDLER)
+ DOMREADY_HANDLER.push(handler);
+ else
+ setTimeout(handler, 0);
+ }
+
+ function $$(selector, context, childrenOnly) {
+ return dollarRaw(selector, context, childrenOnly)[0];
+ }
+
+ function EE(elementName, attributes, children) {
+ var e = $(document.createElement(elementName));
+ // @condblock UTIL
+ // this attributes != null check is only required with Util's isObject() implementation. Web's isObject() is simpler.
+ return (isList(attributes) || (attributes != _null && !isObject(attributes)) ) ? e['add'](attributes) : e['set'](attributes)['add'](children);
+ // @condend UTIL
+ // @cond !UTIL return (isList(attributes) || (!isObject(attributes)) ) ? e['add'](attributes) : e['set'](attributes)['add'](children);
+ }
+
+ function clone(listOrNode) {
+ return collector(flexiEach, listOrNode, function(e) {
+ var c;
+ if (isList(e))
+ return clone(e);
+ else if (isNode(e)) {
+ c = e['cloneNode'](true);
+ c['removeAttribute'] && c['removeAttribute']('id');
+ return c;
+ }
+ else
+ return e;
+ });
+ }
+
+ /*$
+ * @stop
+ */
+
+ function $(selector, context, childOnly) {
+ // @condblock ready
+ return isFunction(selector) ? ready(selector) : new M(dollarRaw(selector, context, childOnly));
+ // @condend
+ // @cond !ready return new M(dollarRaw(selector, context));
+ }
+
+ // implementation of $ that does not produce a Minified list, but just an array
+
+
+
+
+
+
+
+
+
+
+ // @condblock !ie7compatibility
+ function dollarRaw(selector, context, childOnly) {
+ function flatten(a) { // flatten list, keep non-lists, remove nulls
+ return isList(a) ? collector(flexiEach, a, flatten) : a;
+ }
+ function filterElements(list) { // converts into array, makes sure context is respected
+ return filter(collector(flexiEach, list, flatten), function(node) {
+ var a = node;
+ while (a = a['parentNode'])
+ if (a == context[0] || childOnly)
+ return a == context[0];
+ // fall through to return undef
+ });
+ }
+
+ if (context) {
+ if ((context = dollarRaw(context)).length != 1)
+ return collectUniqNodes(context, function(ci) { return dollarRaw(selector, ci, childOnly);});
+ else if (isString(selector)) {
+ if (isNode(context[0]) != 1)
+ return [];
+ else
+ return childOnly ? filterElements(context[0].querySelectorAll(selector)) : context[0].querySelectorAll(selector);
+ }
+ else
+ return filterElements(selector);
+
+ }
+ else if (isString(selector))
+ return document.querySelectorAll(selector);
+ else
+ return collector(flexiEach, selector, flatten);
+ };
+ // @condend !ie7compatibility
+
+ // If context is set, live updates will be possible.
+ // Please note that the context is not evaluated for the '*' and 'tagname.classname' patterns, because context is used only
+ // by on(), and in on() only nodes in the right context will be checked
+ function getFilterFunc(selector, context) {
+ function wordRegExpTester(name, prop) {
+ var re = RegExp('(^|\\s+)' + name + '(?=$|\\s)', 'i');
+ return function(obj) {return name ? re.test(obj[prop]) : true;};
+ }
+
+ var nodeSet = {};
+ var dotPos = nodeSet;
+ if (isFunction(selector))
+ return selector;
+ else if (isNumber(selector))
+ return function(v, index) { return index == selector; };
+ else if (!selector || selector == '*' ||
+ (isString(selector) && (dotPos = /^([\w-]*)\.?([\w-]*)$/.exec(selector)))) {
+ var nodeNameFilter = wordRegExpTester(dotPos[1], 'tagName');
+ var classNameFilter = wordRegExpTester(dotPos[2], 'className');
+ return function(v) {
+ return isNode(v) == 1 && nodeNameFilter(v) && classNameFilter(v);
+ };
+ }
+ else if (context)
+ return function(v) {
+ return $(selector, context)['find'](v)!=_null; // live search instead of node set, for on()
+ };
+ else {
+ $(selector)['each'](function(node) {
+ nodeSet[getNodeId(node)] = true;
+ });
+ return function(v) {
+ return nodeSet[getNodeId(v)];
+ };
+ }
+ }
+
+ function getInverseFilterFunc(selector) {
+ var f = getFilterFunc(selector);
+ return function(v) {return f(v) ? _null : true;};
+ }
+ ///#/snippet webFunctions
+
+ ///#snippet extrasFunctions
+ function flexiEach(list, cb) {
+ if (isList(list))
+ each(list, cb);
+ else if (list != _null)
+ cb(list, 0);
+ return list;
+ }
+
+ function Promise() {
+ this['state'] = null;
+ this['values'] = [];
+ this['parent'] = null;
+ }
+
+ /*$
+ * @id promise
+ * @name _.promise()
+ * @syntax _.promise()
+ * @syntax _.promise(otherPromises...)
+ * @module WEB+UTIL
+ *
+ * Creates a new ##promiseClass#Promise##, optionally assimilating other promises. If no other promise is given,
+ * a fresh new promise is returned.
+ *
+ * The returned promise provides the methods ##fulfill() and ##reject() that can be called directly to change the promise's state,
+ * as well as the more powerful ##fire().
+ *
+ * If one promise is given as parameter, the new promise assimilates the given promise as-is, and just forwards
+ * fulfillment and rejection with the original values.
+ *
+ * If more than one promise are given, it will assimilate all of them with slightly different rules:
+ *
the new promise is fulfilled if all assimilated promises have been fulfilled. The fulfillment values
+ * of all assimilated promises are given to the handler as arguments. Note that the fulfillment values themselves are always
+ * arrays, as a promise can have several fulfillment values in Minified's implementation.
+ *
when one of the promises is rejected, the new promise is rejected immediately. The rejection handler gets the
+ * promises rejection value (first argument if it got several) as first argument, an array of the result values
+ * of all promises as a second (that means one array of arguments for each promise), and the index of the failed
+ * promise as third.
+ *
+ *
+ * @example A simple promise that is fulfilled after 1 second, using Minified's invocation syntax:
+ *
var p = _.promise();
+ * setTimeout(function() {
+ * p.fire(true);
+ * }, 1000);
+ *
+ *
+ * @example Request three files in parallel. When all three have been downloaded, concatenate them into a single string.
+ *
+ *
+ * @param otherPromises one or more promises to assimilate (varargs). You can also pass lists of promises.
+ * @return the new promise.
+ */
+ function promise() {
+ var deferred = []; // this function calls the functions supplied by then()
+
+ var assimilatedPromises = arguments;
+ var assimilatedNum = assimilatedPromises.length;
+ var numCompleted = 0; // number of completed, assimilated promises
+ var rejectionHandlerNum = 0;
+
+ var obj = new Promise();
+
+ obj['errHandled'] = function() {
+ rejectionHandlerNum++;
+ if (obj['parent'])
+ obj['parent']['errHandled']();
+ };
+
+ /*$
+ * @id fire
+ * @name promise.fire()
+ * @syntax _.fire(newState)
+ * @syntax _.fire(newState, values)
+ * @module WEB+UTIL
+ *
+ * Changes the state of the promise into either fulfilled or rejected. This will also notify all ##then() handlers. If the promise
+ * already has a state, the call will be ignored.
+ *
+ * fire() can be invoked as a function without context ('this'). Every promise has its own instance.
+ *
+ * @example A simple promise that is fulfilled after 1 second, using Minified's invocation syntax:
+ *
var p = _.promise();
+ * setTimeout(function() {
+ * p.fire(true, []);
+ * }, 1000);
+ *
+ *
+ * @example Call fire() without a context:
+ *
var p = _.promise(function(resolve, reject) {
+ * setTimeout(resolve.fire, 1000);
+ * });
+ *
+ *
+ * @param newState true to set the Promise to fulfilled, false to set the state as rejected. If you pass null or
+ * undefined, the promise's state does not change.
+ * @param values optional an array of values to pass to ##then() handlers as arguments. You can also pass a non-list argument, which will then
+ * be passed as only argument.
+ * @return the promise
+ */
+ var fire = obj['fire'] = function(newState, newValues) {
+ if (obj['state'] == null && newState != null) {
+ obj['state'] = !!newState;
+ obj['values'] = isList(newValues) ? newValues : [newValues];
+ setTimeout(function() {
+ each(deferred, function(f) {f();});
+ }, 0);
+ }
+ return obj;
+ };
+
+ // use promise varargs
+ each(assimilatedPromises, function assimilate(promise, index) {
+ try {
+ if (promise['then'])
+ promise['then'](function(v) {
+ var then;
+ if ((isObject(v) || isFunction(v)) && isFunction(then = v['then']))
+ assimilate(v, index);
+ else {
+ obj['values'][index] = array(arguments);
+ if (++numCompleted == assimilatedNum)
+ fire(true, assimilatedNum < 2 ? obj['values'][index] : obj['values']);
+ }
+ },
+ function(e) {
+ obj['values'][index] = array(arguments);
+ fire(false, assimilatedNum < 2 ? obj['values'][index] : [obj['values'][index][0], obj['values'], index]);
+ });
+ else
+ promise(function() {fire(true, array(arguments));}, function() {fire(false, array(arguments)); });
+ }
+ catch (e) {
+ fire(false, [e, obj['values'], index]);
+ }
+ });
+
+ /*$
+ * @id stop
+ * @name promise.stop()
+ * @syntax promise.stop()
+ * @module WEB+UTIL
+ * Stops an ongoing operation, if supported. Currently the only promises supporting this are those returned by ##request(), ##animate(), ##wait() and
+ * ##asyncEach().
+ * stop() invocation will be propagated over promises returned by ##then() and promises assimilated by ##promise(). You only need to invoke stop
+ * with the last promise, and all dependent promises will automatically stop as well.
+ *
+ * stop() can be invoked as a function without context ('this'). Every promise has its own instance.
+ *
+ * @return In some cases, the stop() can return a value. This is currently only done by ##animate() and ##wait(), which will return the actual duration.
+ * ##asyncEach()'s promise will also return any value it got from the promise that it stopped.
+ *
+ * @example Animation chain that can be stopped.
+ *
+ */
+ obj['stop'] = function() {
+ each(assimilatedPromises, function(promise) {
+ if (promise['stop'])
+ promise['stop']();
+ });
+
+ return obj['stop0'] && call(obj['stop0']);
+ };
+
+ /*$
+ * @id then
+ * @name promise.then()
+ * @syntax promise.then()
+ * @syntax promise.then(onSuccess)
+ * @syntax promise.then(onSuccess, onError)
+ *
+ * @module WEB
+ * Registers two callbacks that will be invoked when the ##promise#Promise##'s asynchronous operation finished
+ * successfully (onSuccess) or an error occurred (onError). The callbacks will be called after
+ * then() returned, from the browser's event loop.
+ * You can chain then() invocations, as then() returns another Promise object that you can attach to.
+ *
+ * The full distribution of Minified implements the Promises/A+ specification, allowing interoperability with other Promises frameworks.
+ *
+ * Note: If you use the Web module, you will get a simplified Promises implementation that cuts some corners. The most notable
+ * difference is that when a then() handler throws an exception, this will not be caught and the promise returned by
+ * then will not be automatically rejected.
+ *
+ * @example Simple handler for an HTTP request. Handles only success and ignores errors.
+ *
+ *
+ * @param onSuccess optional a callback function to be called when the operation has been completed successfully. The exact arguments it receives depend on the operation.
+ * If the function returns a ##promise#Promise##, that Promise will be evaluated to determine the state of the promise returned by then(). If it returns any other value, the
+ * returned Promise will also succeed. If the function throws an error, the returned Promise will be in error state.
+ * Pass null or undefined if you do not need the success handler.
+ * @param onError optional a callback function to be called when the operation failed. The exact arguments it receives depend on the operation. If the function returns a ##promise#Promise##, that promise will
+ * be evaluated to determine the state of the Promise returned by then(). If it returns anything else, the returned Promise will
+ * have success status. If the function throws an error, the returned Promise will be in the error state.
+ * You can pass null or undefined if you do not need the error handler.
+ * @return a new ##promise#Promise## object. If you specified a callback for success or error, the new Promises's state will be determined by that callback if it is called.
+ * If no callback has been provided and the original Promise changes to that state, the new Promise will change to that state as well.
+ */
+ var then = obj['then'] = function (onFulfilled, onRejected) {
+ var promise2 = promise();
+ var callCallbacks = function() {
+ try {
+ var f = (obj['state'] ? onFulfilled : onRejected);
+ if (isFunction(f)) {
+ (function resolve(x) {
+ try {
+ var then, cbCalled = 0;
+ if ((isObject(x) || isFunction(x)) && isFunction(then = x['then'])) {
+ if (x === promise2)
+ throw new TypeError();
+ then.call(x, function(x) { if (!cbCalled++) resolve(x); }, function(value) { if (!cbCalled++) promise2['fire'](false, [value]);});
+ promise2['stop0'] = x['stop'];
+ }
+ else
+ promise2['fire'](true, [x]);
+ }
+ catch(e) {
+ if (!(cbCalled++)) {
+ promise2['fire'](false, [e]);
+ if (!rejectionHandlerNum)
+ throw e;
+ }
+ }
+ })(call(f, undef, obj['values']));
+ }
+ else
+ promise2['fire'](obj['state'], obj['values']);
+ }
+ catch (e) {
+ promise2['fire'](false, [e]);
+ if (!rejectionHandlerNum)
+ throw e;
+ }
+ };
+ if (isFunction(onRejected))
+ obj['errHandled']();
+ promise2['stop0'] = obj['stop'];
+ promise2['parent'] = obj;
+ if (obj['state'] != null)
+ setTimeout(callCallbacks, 0);
+ else
+ deferred.push(callCallbacks);
+ return promise2;
+ };
+
+ /*$
+ * @id always
+ * @group REQUEST
+ * @name promise.always()
+ * @syntax promise.always(callback)
+ * @configurable default
+ * @module WEB+UTIL
+ * Registers a callback that will always be called when the ##promise#Promise##'s operation ended, no matter whether the operation succeeded or not.
+ * This is a convenience function that will call ##then() with the same function for both arguments. It shares all of its semantics.
+ *
+ * @example Simple handler for a HTTP request.
+ *
+ *
+ * @param callback a function to be called when the operation has been finished, no matter what its result was. The exact arguments depend on the operation and may
+ * vary depending on whether it succeeded or not. If the function returns a ##promise#Promise##, that Promise will
+ * be evaluated to determine the state of the returned Promise. If provided and it returns regularly, the returned promise will
+ * have success status. If it throws an error, the returned Promise will be in the error state.
+ * @return a new ##promise#Promise## object. Its state is determined by the callback.
+ */
+ obj['always'] = function(func) { return then(func, func); };
+
+ /*$
+ * @id error
+ * @group REQUEST
+ * @name promise.error()
+ * @syntax promise.error(callback)
+ * @configurable default
+ * @module WEB, UTIL
+ * Registers a callback that will be called when the operation failed.
+ * This is a convenience function that will invoke ##then() with only the second argument set. It shares all of its semantics.
+ *
+ * @example Simple handler for a HTTP request.
+ *
+ *
+ * @param callback a function to be called when the operation has failed. The exact arguments depend on the operation. If the function returns a ##promise#Promise##, that Promise will
+ * be evaluated to determine the state of the returned Promise. If it returns regularly, the returned Promise will
+ * have success status. If it throws an error, the returned Promise will be in error state.
+ * @return a new ##promise#Promise## object. Its state is determined by the callback.
+ */
+ obj['error'] = function(func) { return then(0, func); };
+
+ return obj;
+ }
+
+ ///#/snippet extrasFunctions
+ ///#snippet extrasDocs
+ /*$
+ * @id length
+ * @group SELECTORS
+ * @requires dollar
+ * @name list.length
+ * @syntax length
+ * @module WEB, UTIL
+ *
+ * Contains the number of elements in the ##list#Minified list##.
+ *
+ * @example With Web module:
+ *
+ * var list = $('input');
+ * var myValues = {};
+ * for (var i = 0; i < list.length; i++)
+ * myValues[list[i].name] = list[i].value;
+ *
+ *
+ * @example With Util module:
+ *
+ * var list = _(1, 2, 3);
+ * var sum = 0;
+ * for (var i = 0; i < list.length; i++)
+ * sum += list[i];
+ *
+ */
+ /*$
+ * @stop
+ */
+ ///#/snippet extrasDocs
+
+ ///#snippet utilM
+
+ /*
+ * syntax: M(list, assimilateSublists)
+ * M(null, singleElement)
+ *
+ *
+ */
+ /** @constructor */
+ function M(list, assimilateSublists) {
+ var self = this, idx = 0;
+ if (list)
+ for (var i = 0, len = list.length; i < len; i++) {
+ var item = list[i];
+ if (assimilateSublists && isList(item))
+ for (var j = 0, len2 = item.length; j < len2; j++)
+ self[idx++] = item[j];
+ else
+ self[idx++] = item;
+ }
+ else
+ self[idx++] = assimilateSublists;
+
+ self['length'] = idx;
+ self['_'] = true;
+ }
+
+ function _() {
+ return new M(arguments, true);
+ }
+
+ ///#/snippet utilM
+
+ //// LIST FUNCTIONS ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+ copyObj({
+ ///#snippet utilListFuncs
+ /*$
+ * @id each
+ * @group LIST
+ * @requires
+ * @configurable default
+ * @name .each()
+ * @altname _.each()
+ * @syntax list.each(callback)
+ * @syntax list.each(callback, ctx)
+ * @syntax _.each(list, callback)
+ * @syntax _.each(list, callback, ctx)
+ * @module UTIL, WEB
+ * Invokes the given function once for each item in the list. The function will be called with the item as first parameter and
+ * the zero-based index as second. Unlike JavaScript's built-in forEach() it will be invoked for each item in the list,
+ * even if it is undefined.
+ *
+ * @example Creates the sum of all list entries.
+ *
+ * var sum = 0;
+ * _(17, 4, 22).each(function(item, index) {
+ * sum += item;
+ * });
+ *
+ *
+ * @example The previous example with a native array:
+ *
+ * var sum = 0;
+ * _.each([17, 4, 22], function(item, index) {
+ * sum += item;
+ * });
+ *
+ *
+ * @example This goes through all h2 elements of the class 'section' on a web page and changes their content:
+ *
+ *
+ * @param list a list to iterate. Can be an array, a ##list#Minified list## or any other array-like structure with
+ * length property.
+ * @param callback The callback function(item, index) to invoke for each list element.
+ *
item
The current list element.
+ *
index
The second the zero-based index of the current element.
+ *
this
The given context if not null. Otherwise the list.
+ * The callback's return value will be ignored.
+ * @param ctx optional a context to pass to the callback as 'this'. Only supported in UTIL module.
+ * @return the list
+ *
+ * @see ##per() works like each(), but wraps the list elements in a list.
+ * @see ##find() can be used instead of each() if you need to abort the loop.
+ * @see ##eachObj() iterates through the properties of an object.
+ */
+ 'each': listBind(each),
+
+ /*$
+ * @id find
+ * @group LIST
+ * @requires
+ * @configurable default
+ * @name .find()
+ * @altname _.find()
+ * @syntax list.find(findFunc)
+ * @syntax list.find(element)
+ * @syntax list.find(findFunc, startIndex)
+ * @syntax list.find(element, startIndex)
+ * @syntax _.find(list, findFunc)
+ * @syntax _.find(list, element)
+ * @syntax _.find(list, findFunc, startIndex)
+ * @syntax _.find(list, element, startIndex)
+ * @module WEB, UTIL
+ * Finds a specific value in the list. There are two ways of calling find():
+ *
+ *
With a value as argument. Then find() will search for the first occurrence of an identical value in the list,
+ * using the '===' operator for comparisons, and return the index. If it is not found,
+ * find() returns undefined.
+ *
With a callback function. find() will then call the given function for each list element until the function
+ * returns a value that is not null or undefined. This value will be returned.
+ *
+ *
+ * find() can also be used as an alternative to ##each() if you need to abort the loop.
+ *
+ * @example Finds the first negative number in the list:
+ *
+ * var i = _(1, 2, -4, 5, 2, -1).find(function(value, index) { if (value < 0) return index; }); // returns 2
+ *
+
+ * @example Finds the index of the first 5 in the array:
+ *
+ * var i = _.find([3, 6, 7, 6, 5, 4, 5], 5); // returns 4 (index of first 5)
+ *
+ *
+ * @example Determines the position of the element with the id '#wanted' among all li elements:
+ *
+ * var elementIndex = $('li').find($$('#wanted'));
+ *
+ *
+ * @example Goes through the elements to find the first div that has the class 'myClass', and returns this element:
+ *
+ * var myClassElement = $('div').find(function(e) { if ($(e).is('.myClass')) return e; });
+ *
+ *
+ * @param list A list to use as input. Can be an array, a ##list#Minified list## or any other array-like structure with
+ * length property.
+ * @param findFunc The callback function(item, index) that will be invoked for every list item until it returns a non-null value:
+ *
item
The current list element.
index
The second the zero-based index of the current element.
+ *
this
This list.
+ *
(callback return value)
If the callback returns something other than null or
+ * undefined, find() will return it directly. Otherwise it will continue.
+ * @param element the element to search for
+ * @param startIndex optional the 0-based index of the first element to search.
+ * @return if called with an element, either the element's index in the list or undefined if not found. If called with a callback function,
+ * it returns either the value returned by the callback or undefined.
+ *
+ * @see ##findLast() is the equivalent to find() for the list's end.
+ */
+ 'find': listBind(find),
+
+ /*$
+ * @stop
+ */
+ dummySort:0
+ ,
+ ///#/snippet utilListFuncs
+ ///#snippet webListFuncs
+
+ /*$
+ * @id select
+ * @group SELECTORS
+ * @requires dollar
+ * @configurable default
+ * @name .select()
+ * @syntax list.select(selector)
+ * @syntax list.select(selector, childrenOnly)
+ * @module WEB
+ * Executes a selector with the list as context. list.select(selector, childrenOnly) is equivalent
+ * to $(selector, list, childrenOnly).
+ *
+ * @example Returns a list of all list elements:
+ *
+ * var parents = $('ol.myList').select('li', true);
+ *
+ *
+ * @example Returns a list of all child elements:
+ *
+ * var children = $('.myElements').select('*', true);
+ *
+ *
+ * @param selector a selector or any other valid first argument for #dollar#$().
+ * @param childrenOnly optional if set, only direct children of the context nodes are included in the list. Children of children will be filtered out. If omitted or not
+ * true, all descendants of the context will be included.
+ * @return the new list containing the selected descendants.
+ *
+ * @see ##only() executes a selector on the list elements, instead of their descendants.
+ */
+ 'select': function(selector, childOnly) {
+ return $(selector, this, childOnly);
+ },
+
+ /*$
+ * @id get
+ * @group SELECTORS
+ * @requires dollar
+ * @configurable default
+ * @name .get()
+ * @syntax list.get(name)
+ * @syntax list.get(name, toNumber)
+ * @syntax list.get(list)
+ * @syntax list.get(list, toNumber)
+ * @syntax list.get(map)
+ * @syntax list.get(map, toNumber)
+ * @module WEB
+ * Retrieves properties, attributes and styles from the list's first element. The syntax to request those values is mostly identical with ##set(). You can either
+ * get a single value if you specify only one name, or get an object map when you specify several names using an array or an object map.
+ *
+ * The name parameter defines what kind of data you are reading. The following name schemes are supported:
+ *
+ *
Name Schema
Example
Sets what?
Description
+ *
name
innerHTML
Property
A name without prefix of '$' or '@' gets a property of the object.
+ *
@name
@href
Attribute
Gets the HTML attribute using getAttribute().
+ *
%name
%phone
Data-Attribute
Gets a data attribute using getAttribute(). Data attributes are
+ * attributes whose names start with 'data-'. '%myattr' and '@data-myattr' are equivalent.
+ *
$name
$fontSize
CSS Property
Gets a style using the element's style object.
+ * The syntax for the CSS styles is camel-case (e.g. "$backgroundColor", not "$background-color"). Shorthand properties like "border" or "margin" are
+ * not supported. You must use the full name, e.g. "$marginTop". Minified will try to determine the effective style
+ * and thus will return the value set in style sheets if not overwritten using a regular style.
+ *
$
$
CSS Classes
A simple $ returns the CSS classes of the element and is identical with "className".
+ *
$$
$$
Style
Reads the element's style attribute in a browser-independent way. On legacy IEs it uses
+ * style.cssText, and on everything else just the "style" attribute.
+ *
$$show
$$show
Show/Hide
Returns 1 if the element is visible and 0 if it is not visible. An element counts as
+ * visible if '$visibility' is not 'hidden' and '$display' is not 'none'. Other properties will be ignored, even if they can also be used to hide the element.
+ *
$$fade
$$fade
Fade Effect
The name '$$fade' returns the opacity of the element as a value between 0 and 1.
+ * '$$fade' will also automatically evaluate the element's 'visibility' and 'display' styles to find out whether the element is actually visible.
+ *
$$slide
$$slide
Slide Effect
'$$slide' returns the height of the element in pixels with a 'px' suffix and is equivalent to '$height'.
+ * Please note that you can pass that 'px' value to '$$slide' in ##set(), which will then set the according '$height'.
+ *
$$scrollX, $$scrollY
$$scrollY
Scroll Coordinates
The names '$$scrollX' and
+ * '$$scrollY' can be used on $(window) to retrieve the scroll coordinates of the document.
+ * The coordinates are specified in pixels without a 'px' unit postfix.
+ *
+ *
+ * @example Retrieves the id, title attribute and the background color of the element '#myElement':
+ *
+ * var id = $('#myElement).get('id');
+ * var title = $('#myElement).get('@title');
+ * var bgColor = $('#myElement).get('$backgroundColor');
+ *
+ *
+ * @example Retrieves the id, title attribute and the background color of the element '#myElement' as a map:
+ *
+ * var m = $('#myElement).get(['id', '@title', '$backgroundColor']);
+ * var id = m.id;
+ * var title = m['@title'];
+ * var bgColor = m.$backgroundColor;
+ *
+ *
+ * @example Uses ##get() and ##set() to reposition an element:
+ *
+ * Please note that the values of $top and $left in the get() invocation do not matter and will be ignored!
+ *
+ * @param name the name of a single property or attribute to modify. Unprefixed names set properties, a '$' prefix sets CSS styles and
+ * '@' sets attributes. Please see the table above for special properties and other options.
+ * @param list in order to retrieve more than one value, you can specify several names in an array or list. get() will then return an object map
+ * containing the values.
+ * @param map if you specify an object that is neither list nor string, get() will use it as a map of property names. Each property name will be requested.
+ * The values of the properties in the map will be ignored. get() will then return a new object map containing of results.
+ * @param toNumber if 'true', get() converts all returned values into numbers. If they are strings,
+ * get() removes any non-numeric characters before the conversion. This is useful when you request
+ * a CSS property such as '$marginTop' that returns a value with a unit suffix, like "21px". get() will convert it
+ * into a number and return 21. If the returned value is not parsable as a number, NaN will be returned.
+ * @return if get() was called with a single name, it returns the corresponding value.
+ * If a list or map was given, get() returns a new object map with the names as keys and the values as values.
+ * It returns undefined if the list is empty.
+ *
+ * @see ##set() sets values using the same property syntax.
+ */
+ 'get': function(spec, toNumber) {
+ var self = this;
+ var element = self[0];
+
+ if (element) {
+ if (isString(spec)) {
+ var match = /^(\W*)(.*)/.exec(replace(spec, /^%/,'@data-'));
+ var prefix = match[1];
+ var s;
+
+ if (getter[prefix])
+ s = getter[prefix](this, match[2]);
+ else if (spec == '$')
+ s = self['get']('className');
+ else if (spec == '$$') {
+ s = self['get']('@style');
+ }
+ else if (spec == '$$slide')
+ s = self['get']('$height');
+ else if (spec == '$$fade' || spec == '$$show') {
+ if (self['get']('$visibility') == 'hidden' || self['get']('$display') == 'none')
+ s = 0;
+ else if (spec == '$$fade') {
+ s =
+ isNaN(self['get']('$opacity', true)) ? 1 : self['get']('$opacity', true);
+ }
+ else // $$show
+ s = 1;
+ }
+ else if (prefix == '$') {
+ s = _window['getComputedStyle'](element, _null)['getPropertyValue'](replace(match[2], /[A-Z]/g, function (match2) { return '-' + match2.toLowerCase(); }));
+ }
+ else if (prefix == '@')
+ s = element.getAttribute(match[2]);
+ else
+ s = element[match[2]];
+ return toNumber ? extractNumber(s) : s;
+ }
+ else {
+ var r = {};
+ (isList(spec) ? flexiEach : eachObj)(spec, function(name) {
+ r[name] = self['get'](name, toNumber);
+ });
+ return r;
+ }
+ }
+ },
+
+ /*$
+ * @id set
+ * @group SELECTORS
+ * @requires dollar get
+ * @configurable default
+ * @name .set()
+ * @syntax list.set(name, value)
+ * @syntax list.set(properties)
+ * @syntax list.set(cssClasses)
+ * @module WEB
+ *
+ * Modifies the list's elements by setting their properties, attributes, CSS styles and/or CSS classes. You can either supply a
+ * single name and value to set only one property, or you can provide an object that contains name/value pairs to describe more than one property.
+ * More complex operations can be accomplished by supplying functions as values. They will then be called for each element that will
+ * be set.
+ *
+ * The name parameter defines what kind of data you are setting. The following name schemes are supported:
+ *
+ *
+ *
Name Schema
Example
Sets what?
Description
+ *
name
innerHTML
Property
A name without prefix of '$' or '@' sets a property of the object.
+ *
@name
@href
Attribute
Sets the HTML attribute using setAttribute(). In order to stay compatible with Internet Explorer 7 and earlier,
+ * you should not set the attributes '@class' and '@style'. Instead use '$' and '$$' as shown below.
+ *
%name
%phone
Data-Attribute
Sets a data attribute using setAttribute(). Data attributes are
+ * attributes whose names start with 'data-'. '%myattr' and '@data-myattr' are equivalent.
+ *
$name
$fontSize
CSS Property
Sets a style using the element's style object.
+ * The syntax for the CSS styles is camel-case (e.g. "$backgroundColor", not "$background-color").
+ *
$
$
CSS Classes
A simple $ modifies the element's CSS classes using the object's className property. The value is a
+ * space-separated list of class names. If prefixed with '-' the class is removed, a '+' prefix adds the class and a class name without prefix toggles the class.
+ * The name '$' can also be omitted if set is called with class names as only argument.
+ *
$$
$$
Style
Sets the element's style attribute in a browser-independent way.
+ *
$$show
$$show
Show/Hide
If true or a number not 0, it will make sure the element is visible by
+ * making sure '$display' is not 'none' and by setting '$visibility' to 'visible'. Please see ##show() for details. If the value is false or 0, it
+ * will be hidden by setting '$display' to 'none'.
+ *
$$fade
$$fade
Fade Effect
The name '$$fade' sets the opacity of the element in a browser-independent way. The value must be a number
+ * between 0 and 1. '$$fade' will also automatically control the element's 'visibility' style. If the value is 0,
+ * the element's visibility will automatically be set to 'hidden'. If the value is larger, the visibility will be set to
+ * 'visible'. '$$fade' only works with block elements.
+ *
$$slide
$$slide
Slide Effect
The name '$$slide' allows a vertical slide-out or slide-in effect. The value must be a number
+ * between 0 and 1 and will be used to set the element's '$height'. '$$slide' will also automatically control the element's 'visibility'
+ * style. If the value is 0, the element's visibility will automatically be set to 'hidden'. If the value is larger,
+ * the visibility will be set to 'visible'. '$$slide' only works with block elements and will not set the
+ * element's margin or padding. If you need a margin or padding, you should wrap the elements in a simple <div>.
+ *
$$scrollX, $$scrollY
$$scrollY
Scroll Coordinates
The names '$$scrollX' and
+ * '$$scrollY' can be used on $(window) to set the scroll coordinates of the document.
+ * The coordinates are specified in pixels, but must not use a 'px' unit postfix.
+ *
+ * @param name the name of a single property or attribute to modify. Unprefixed names set properties, a '$' prefix sets CSS styles and
+ * '@' sets attributes. Please see the table above for special properties and other options.
+ * @param value the value to set. If value is null and name specified an attribute, the attribute will be removed.
+ * If dollar ('$') has been passed as name, the value can contain space-separated CSS class names. If prefixed with a '+' the class will be added,
+ * with a '-' prefix the class will be removed. Without prefix, the class will be toggled.
+ * If value is a function, the function(oldValue, index, obj) will be invoked for each list element
+ * to evaluate the new value:
+ *
oldValue
The old value of the property to be changed, as returned by ##get().
+ * For the CSS style names, this is the computed style of the property
+ *
index
The list index of the object owning the property
+ *
obj
The list element owning the property.
+ *
(callback return value)
The value to be set.
+ * Functions are not supported by '$'.
+ * @param properties a Object as map containing names as keys and the values to set as map values. See above for the name and value syntax.
+ * @param cssClasses if set() is invoked with a string as single argument, the name "$" (CSS classes) is assumed and the argument is the
+ * value. See above for CSS syntax.
+ * Instead of a string, you can also specify a function(oldValue, index, obj) to modify the existing classes.
+ * @return the list
+ *
+ * @see ##get() retrieves values using the same property syntax.
+ * @see ##animate() animates values using the same property syntax.
+ * @see ##toggle() can toggle between two sets of values.
+ * @see ##dial() allows smooth transitions between two sets of values.
+ */
+ 'set': function (name, value) {
+ var self = this;
+ if (value !== undef) {
+ var match = /^(\W*)(.*)/.exec(replace(replace(name, /^\$float$/, 'cssFloat'), /^%/,'@data-'));
+ var prefix = match[1];
+
+ if (setter[prefix])
+ setter[prefix](this, match[2], value);
+ else if (name == '$$fade') {
+ this['set']({'$visibility': value ? 'visible' : 'hidden', '$opacity': value});
+ }
+ else if (name == '$$slide') {
+ self['set']({'$visibility': value ? 'visible' : 'hidden', '$overflow': 'hidden',
+ '$height': /px/.test(value) ? value : function(oldValue, idx, element) { return getNaturalHeight($(element), value);}
+ });
+ }
+ else if (name == '$$show') {
+ if (value)
+ self['set']({'$visibility': value ? 'visible' : 'hidden', '$display': ''}) // that value? part is only for gzip
+ ['set']({'$display': function(oldVal) { // set for 2nd time: now we get the stylesheet's $display
+ return oldVal == 'none' ? 'block' : oldVal;
+ }});
+ else
+ self['set']({'$display': 'none'});
+ }
+ else if (name == '$$') {
+ self['set']('@style', value);
+ }
+ else
+ flexiEach(this, function(obj, c) {
+ var newValue = isFunction(value) ? value($(obj)['get'](name), c, obj) : value;
+ if (prefix == '$') {
+ if (match[2])
+ obj['style'][match[2]] = newValue;
+ else {
+ flexiEach(newValue && newValue.split(/\s+/), function(clzz) {
+ var cName = replace(clzz, /^[+-]/);
+
+ if (/^\+/.test(clzz))
+ obj['classList'].add(cName);
+ else if (/^-/.test(clzz))
+ obj['classList'].remove(cName);
+ else
+ obj['classList'].toggle(cName);
+ });
+ }
+ }
+ else if (name == '$$scrollX')
+ obj['scroll'](newValue, $(obj)['get']('$$scrollY'));
+ else if (name == '$$scrollY')
+ obj['scroll']($(obj)['get']('$$scrollX'), newValue);
+ else if (prefix == '@') {
+ if (newValue == _null)
+ obj.removeAttribute(match[2]);
+ else
+ obj.setAttribute(match[2], newValue);
+ }
+ else
+ obj[match[2]] = newValue;
+ });
+ }
+ else if (isString(name) || isFunction(name))
+ self['set']('$', name);
+ else
+ eachObj(name, function(n,v) { self['set'](n, v); });
+ return self;
+ },
+
+ /*$
+ * @id add
+ * @group ELEMENT
+ * @requires dollar each
+ * @configurable default
+ * @name .add()
+ * @syntax list.add(text)
+ * @syntax list.add(node)
+ * @syntax list.add(list)
+ * @syntax list.add(factoryFunction)
+ * @module WEB
+ * Adds the given node(s) as children to the list's HTML elements. If a string has been given, it will be added as text node.
+ * DOM nodes will be added directly. If you pass a list, all its elements will be added using the rules above.
+ *
+ * When you pass a DOM node and the target list has more than one element, the original node will be added to the first list element,
+ * and ##clone#clones## to all following list elements.
+ *
+ * ##EE(), ##HTML() and ##clone() are compatible with add() and can help you create new HTML nodes.
+ *
+ * @example Using the following HTML:
+ *
+ * <div id="comments">Here is some text.<br/></div>
+ *
+ * The next line appends a text node to the div:
+ *
+ *
+ * @param text a string or number to add as text node
+ * @param node a DOM node to add to the list. If the list has more than one element, the given node will be added to the first element.
+ * For all additional elements, the node will be cloned using ##clone().
+ * @param list a list containing text and/or nodes. May also contain nested lists with nodes or text..
+ * @param factoryFunction a function(listItem, listIndex) that will be invoked for each list element to create the nodes:
+ *
listItem
The list element that will receive the new children.
+ *
listIndex
The index of the list element that will receive the new children.
+ *
(callback return value)
The node(s) to be added to the list element.
+ * Can be either a string for a text node, an HTML element or a list containing strings and/or DOM nodes.
+ * If a function is returned, it will be invoked recursively with the same arguments.
+ * @return the current list
+ *
+ * @see ##fill() works like add(), but deletes all children before adding the new nodes.
+ * @see ##addFront() adds nodes as first child, not as last.
+ * @see ##addAfter() adds nodes not as children but as siblings.
+ * @see ##addBefore() also adds nodes not as children but as siblings.
+ * @see ##replace() replaces existing nodes.
+ */
+ 'add': function (children, addFunction) {
+ return this['each'](function(e, index) {
+ var lastAdded;
+ function appendChildren(c) {
+ if (isList(c))
+ flexiEach(c, appendChildren);
+ else if (isFunction(c))
+ appendChildren(c(e, index));
+ else if (c != _null) { // must check null, as 0 is a valid parameter
+ var n = isNode(c) ? c : document.createTextNode(c);
+ if (lastAdded)
+ lastAdded['parentNode']['insertBefore'](n, lastAdded['nextSibling']);
+ else if (addFunction)
+ addFunction(n, e, e['parentNode']);
+ else
+ e.appendChild(n);
+ lastAdded = n;
+ }
+ }
+ appendChildren(index &&!isFunction(children) ? clone(children) : children);
+ });
+ },
+
+ /*$
+ * @id on
+ * @group EVENTS
+ * @requires dollar each
+ * @configurable default
+ * @name .on()
+ * @syntax list.on(names, eventHandler)
+ * @syntax list.on(selector, names, eventHandler)
+ * @syntax list.on(names, customFunc, args)
+ * @syntax list.on(selector, names, customFunc, args)
+ * @syntax list.on(names, eventHandler, bubbleSelector)
+ * @syntax list.on(names, customFunc, args, bubbleSelector)
+ * @module WEB
+ * Registers the function as event handler for all items in the list.
+ *
+ * By default, Minified cancels event propagation and disables element's default behavior for all elements that have an event handler.
+ * You can override this, either by prefixing the event name with a '|', or by prefixing them with '?' and returning a true
+ * in the handler. Both will reinstate the original JavaScript behavior.
+ *
+ * Handlers are called with the original event object as first argument, the index of the source element in the
+ * list as second argument and 'this' set to the source element of the event (e.g. the button that has been clicked).
+ *
+ * Instead of the event objects, you can also pass an array of arguments that will be passed instead of event object and index.
+ *
+ * Optionally you can specify two a selector strings to qualify only certain events. The first one is a selector
+ * that allows you to select only specific children of the list elements. This is mostly useful for adding events to DOM trees
+ * generated using ##HTML() or ##EE().
+ *
+ * The second type of selector is the bubble selector that allows you to receive only events that bubbled up from
+ * elements matching the selector. The selector is executed in the context of the element you registered on to identify whether the
+ * original target of the event qualifies. If not, the handler is not called.
+ *
+ * Minified always registers event handlers with event bubbling enabled. Event capture is not supported.
+ *
+ * Event handlers can be unregistered using #off#$.off().
+ *
+ * @example Adds a handler to all divs which paints the div background color to red when clicked.
+ *
+ * $('div').on('click', function() {
+ * this.style.backgroundColor = 'red'; // 'this' contains the element that caused the event
+ * });
+ *
+ *
+ * @example Registers a handler to call a method setStatus('running') using an inline function:
+ *
+ * Please note that bubble selectors will even listen to events for
+ * table rows that have been added after you registered for the events.
+ *
+ * @param selector optional a selector string for ##dollar#$()## to register the event only on those children of the list elements that
+ * match the selector.
+ * Supports all valid parameters for $() except functions.
+ * @param names the space-separated names of the events to register for, e.g. 'click'. Case-sensitive. The 'on' prefix in front of
+ * the name must not used. You can register the handler for more than one event by specifying several
+ * space-separated event names. If the name is prefixed
+ * with '|' (pipe), the event will be passed through and the event's default actions will be executed by the browser.
+ * If the name is prefixed with '?', the event will only be passed through if the handler returns true.
+ * @param eventHandler the callback function(event, index) to invoke when the event has been triggered:
+ *
+ *
event
The original DOM event object.
+ *
index
The index of the target object in the ##list#Minified list## .
+ *
this
A ##list#Minified list## containing the target element as only item (same as event.target).
+ *
(callback return value)
The return value will only be used if the event name prefix was '?'.
+ * Then, a return value false will stop all further processing of the event and disable event bubbling.
+ * true will keep the event alive.
+ *
+ * @param customFunc a function to be called instead of a regular event handler with the arguments given in args.
+ * 'this' will be a ##list#Minified list## containing the target element as only item (same element as event.target).
+ * @param args optional an array of arguments to pass to the custom callback function instead of the event objects. If omitted, the function is
+ * called as event handler with the event object as argument.
+ * @param bubbleSelector optional a selector string for ##dollar#$()## to receive only events that bubbled up from an
+ * element that matches this selector.
+ * Supports all valid parameters for $() except functions. Analog to ##is(),
+ * the selector is optimized for the simple patterns '.classname', 'tagname' and 'tagname.classname'.
+ * @return the list
+ * @see ##off() allows you to unregister an event handler.
+ * @see ##onClick() as a shortcut for 'click' events.
+ * @see ##onOver() to simplify mouseover/mouseout events.
+ * @see ##onFocus() as convenient way to register for focus events.
+ * @see ##onChange() to get notified when an input's content changes.
+ */
+ 'on': on,
+
+ /*$
+ * @id trigger
+ * @group EVENTS
+ * @requires on each
+ * @configurable default
+ * @name .trigger()
+ * @syntax list.trigger(name)
+ * @syntax list.trigger(name, eventObject)
+ * @module WEB
+ *
+ * Triggers event handlers registered with ##on().
+ * Any event that has been previously registered using ##on() can be invoked with trigger(). Please note that
+ * it will not simulate the default behavior on the elements, such as a form submit when you click on a submit button. Event bubbling
+ * is supported, thus unless there's an event handler that cancels the event, the event will be triggered on all parent elements.
+ *
+ *
+ * @example Simulates a 'click' event on the button.
+ *
+ * $('#myButton').trigger('click');
+ *
+ *
+ * @param name a single event name to trigger
+ * @param eventObj optional an object to pass to the event handler, provided the handler does not have custom arguments.
+ * Anything you pass here will be directly given to event handlers as event object, so you need to know what
+ * they expect.
+ * @return the list
+ * @see ##on() registers events that can be triggered.
+ */
+ 'trigger': function (eventName, eventObj) {
+ return this['each'](function(element, index) {
+ var bubbleOn = true, el = element;
+ while(el && bubbleOn) {
+ eachObj(el['M'], function(id, f) {
+ bubbleOn = bubbleOn && f(eventName, eventObj, element);
+ });
+ el = el['parentNode'];
+ }
+ });
+ }
+
+ /*$
+ * @stop
+ */
+ // @cond !trigger dummyTrigger:0
+ ,
+ ///#/snippet webListFuncs
+ ///#snippet extrasListFuncs
+
+ /*$
+ * @id ht
+ * @group ELEMENT
+ * @requires set template
+ * @configurable default
+ * @name .ht()
+ * @syntax list.ht(templateString, object...)
+ * @syntax list.ht(templateFunction, object...)
+ * @syntax list.ht(idSelector, object...)
+ * @module WEB+UTIL
+ * Replaces the content of the list elements with the HTML generated using the given template. The template uses
+ * ##template() syntax and HTML-escaped its output using ##escapeHtml().
+ *
+ * @example When you have a HTML snippet like this:
+ *
+ * <div id="price"></div>
+ *
+ * Then you can format the price value like this:
+ *
+ *
+ * @example You can store templates in <script> tags. First you need to create a <script> tag with a type not
+ * supported by the browser and put your template in there, like this:
+ *
<script id="myTimeTpl" type="minified-template">The time is {{HH:mm:ss}}.</script>
+ * Then you can specify the tag's id directly to access it:
+ *
$('#timeDisplay').ht('#myTimeTpl', new Date());
+ *
+ * @param templateString the template using ##template() syntax. Please note, because this is a template, you should
+ * avoid creating the template itself dynamically, as compiling templates is expensive and
+ * Minified will cache only a limited number of templates. Exception: If the template string does not use
+ * any template functionality (no {{}}), it does not need to be compiled and won't be cached.
+ * The template will use ##escapeHtml() as escape function, so all template substitutions will be HTML-escaped,
+ * unless you use triple curly-braces.
+ * @param templateFunction instead of a HTML template, ht() can also use a template function, e.g. one
+ * created by ##template(). It will be invoked with the object as only argument.
+ * @param idSelector if you pass an ID CSS selector in the form "#myScript", Minified will recognize this and use the content
+ * of the specified <script> element as template. This allows you to put your template into
+ * a <script> tag with a non-JavaScript type (see example). Any string that starts with '#' and does not
+ * contain any spaces is used as selector.
+ * @param object optional one or more objects to pass to the template. If object is not set, the template is called with undefined
+ * as object. If exactly one object is given, it is passed directly to the template. If you specify more than one
+ * object, they are ##merge#merged##.
+ * @return the current list
+ *
+ * @see ##HTML() creates only the nodes and can be used with ##add() and other methods to add the nodes to the DOM, giving you more flexibility than ht().
+ */
+ 'ht': function(htmlTemplate, object) {
+ var o = arguments.length > 2 ? merge(sub(arguments, 1)) : object;
+ return this['set']('innerHTML', isFunction(htmlTemplate) ? htmlTemplate(o) :
+ /{{/.test(htmlTemplate) ? formatHtml(htmlTemplate, o) :
+ /^#\S+$/.test(htmlTemplate) ? formatHtml($$(htmlTemplate)['text'], o) : htmlTemplate);
+ }
+ /*$
+ * @stop
+ */
+ // @cond !ht dummyHt:0
+ ///#/snippet extrasListFuncs
+ }, M.prototype);
+
+ //// DOLLAR FUNCTIONS ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+ copyObj({
+ ///#snippet webDollarFuncs
+ /*$
+ * @id request
+ * @group REQUEST
+ * @requires
+ * @configurable default
+ * @name $.request()
+ * @syntax $.request(method, url)
+ * @syntax $.request(method, url, data)
+ * @syntax $.request(method, url, data, settings)
+ * @module WEB
+ * Initiates a HTTP request to the given URL, using XMLHttpRequest. It returns a ##promiseClass#Promise## object that allows you to obtain the result.
+ *
+ * @example Invokes a REST web service and parses the resulting document using JSON:
+ *
+ * $.request('get', 'http://service.example.com/weather', {zipcode: 90210})
+ * .then(function(txt) {
+ * var json = $.parseJSON(txt);
+ * $('#weatherResult').fill('Today's forecast is is: ' + json.today.forecast);
+ * })
+ * .error(function(status, statusText, responseText) {
+ * $('#weatherResult').fill('The weather service was not available.');
+ * });
+ *
+ *
+ * @example Sending a JSON object to a REST web service:
+ *
+ *
+ *
+ * @param method the HTTP method, e.g. 'get', 'post' or 'head' (rule of thumb: use 'post' for requests that change data
+ * on the server, and 'get' to request data). Not case sensitive.
+ * @param url the server URL to request. May be a relative URL (relative to the document) or an absolute URL. Note that unless you do something
+ * fancy on the server (keyword to google: Access-Control-Allow-Origin), you can only call URLs on the server your script originates from.
+ * @param data optional data to send in the request, either as POST body or as URL parameters. It can be either a plain object as map of
+ * parameters (for all HTTP methods), a string (for all HTTP methods), a DOM document ('post' only) or a FormData object ('post' only).
+ * If the method is 'post', it will be sent as body, otherwise parameters are appended to the URL. In order to send several parameters with the
+ * same name, use an array of values in the map. Use null as value for a parameter without value.
+ * @param settings optional a map of additional parameters. Supports the following properties (all optional):
+ *
headers
a map of HTTP headers to add to the request. Note that you should use the proper capitalization for the
+ * header 'Content-Type', if you set it, because otherwise it may be overwritten.
+ *
xhr
a map of properties to set in the XMLHttpRequest object before the request is sent, for example {withCredentials: true}.
+ *
user
username for HTTP authentication, together with the pass parameter
+ *
pass
password for HTTP authentication, together with the user parameter
+ *
+ * @return a ##promiseClass#Promise## containing the request's status. If the request has successfully completed with a HTTP status 2xx,
+ * the promise's completion handler will be called as function(text, xhr):
+ *
text
The response sent by the server as text.
+ *
xhr
The XMLHttpRequest used for the request. This allows you to retrieve the response in different
+ * formats (e.g. responseXml for an XML document), to retrieve headers and more.
+ * The failure handler will be called as function(statusCode, statusText, text):
+ *
statusCode
The HTTP status (never 200; 0 if no HTTP request took place).
+ *
text
The response's body text, if there was any, or the exception as string if the browser threw one.
+ *
xhr
The XMLHttpRequest used for the request. This allows you to retrieve the response in different
+ * formats (e.g. responseXml for an XML document), to retrieve headers and more..
+ * The returned promise supports ##stop(). Calling stop() will invoke the XHR's abort() method.
+ * The underlying XmlHttpRequest can also be obtained from the promise's xhr property.
+ *
+ * @see ##values() serializes an HTML form in a format ready to be sent by $.request.
+ * @see ##$.parseJSON() can be used to parse JSON responses.
+ * @see ##$.toJSON() can create JSON messages.
+ * @see ##_.format() can be useful for creating REST-like URLs, if you use JavaScript's built-in escape() function.
+ */
+ 'request': function (method, url, data, settings0) {
+ var settings = settings0 || {};
+ var xhr, callbackCalled = 0, prom = promise(), dataIsMap = data && (data['constructor'] == settings['constructor']);
+ try {
+ prom['xhr'] = xhr = new XMLHttpRequest();
+
+ prom['stop0'] = function() { xhr['abort'](); };
+ // @condend
+
+ if (dataIsMap) { // if data is parameter map...
+ data = collector(eachObj, data, function processParam(paramName, paramValue) {
+ return collector(flexiEach, paramValue, function(v) {
+ return encodeURIComponent(paramName) + ((v != _null) ? '=' + encodeURIComponent(v) : '');
+ });
+ }).join('&');
+ }
+
+ if (data != _null && !/post/i.test(method)) {
+ url += '?' + data;
+ data = _null;
+ }
+
+ xhr['open'](method, url, true, settings['user'], settings['pass']);
+ if (dataIsMap && /post/i.test(method))
+ xhr['setRequestHeader']('Content-Type', 'application/x-www-form-urlencoded');
+
+ eachObj(settings['headers'], function(hdrName, hdrValue) {
+ xhr['setRequestHeader'](hdrName, hdrValue);
+ });
+ eachObj(settings['xhr'], function(name, value) {
+ xhr[name] = value;
+ });
+
+ xhr['onreadystatechange'] = function() {
+ if (xhr['readyState'] == 4 && !callbackCalled++) {
+ if (xhr['status'] >= 200 && xhr['status'] < 300)
+ prom['fire'](true, [xhr['responseText'], xhr]);
+ else
+ prom['fire'](false, [xhr['status'], xhr['responseText'], xhr]);
+ }
+ };
+
+ xhr['send'](data);
+ }
+ catch (e) {
+ if (!callbackCalled)
+ prom['fire'](false, [0, _null, toString(e)]);
+ }
+
+ return prom;
+ },
+
+ /*
+ * JSON Module. Uses browser built-ins or json.org implementation if available. Otherwise its own implementation,
+ * originally based on public domain implementation http://www.JSON.org/json2.js / http://www.JSON.org/js.html.
+ * Extremely simplified code, made variables local, removed all side-effects (especially new properties for String, Date and Number).
+ */
+
+ /*$
+ * @id ready
+ * @group EVENTS
+ * @requires ready_vars ready_init
+ * @configurable default
+ * @name $.ready()
+ * @syntax $.ready(handler)
+ * @module WEB
+ * Registers a handler to be called as soon as the HTML has been fully loaded in the browser. Does not necessarily wait for images and other elements,
+ * only the main HTML document needs to be complete. On older browsers it is the same as window.onload.
+ *
+ * If you call ready() after the page is completed, the handler is scheduled for invocation in the event loop as soon as possible.
+ *
+ * A shortcut for ready() is to call ##dollar#$()## with the handler function. It does the same with fewer characters.
+ *
+ * @example Registers a handler that sets some text in an element:
+ *
+ *
+ * @param handler the function() to be called when the HTML is ready.
+ * @see ##dollar#$()## calls ready() when invoked with a function, offering a more convenient syntax.
+ */
+ 'ready': ready,
+
+ /*$
+ * @id off
+ * @group EVENTS
+ * @requires on
+ * @configurable default
+ * @name $.off()
+ * @syntax $.off(handler)
+ * @module WEB
+ * Removes the given event handler. The call will be ignored if the given handler has not been registered using ##on().
+ * If the handler has been registered for more than one element or event, it will be removed from all instances.
+ *
+ * Please note that you can not unregister event handlers registered using ##onOver() or ##onChange().
+ *
+ * @example Adds a handler to an element:
+ *
+ * function myEventHandler() {
+ * this.style.backgroundColor = 'red'; // 'this' contains the element that caused the event
+ * }
+ * $('#myElement').on('click', myEventHandler); // add event handler
+ *
+ * window.setInterval(function() { // after 5s, remove event handler
+ * $.off(myEventHandler);
+ * }, 5000);
+ *
+ *
+ * @param handler the handler to unregister, as given to ##on(). It must be a handler that has previously been registered using ##on().
+ * If the handler is not registered as event handler, the function does nothing.
+ *
+ * @see ##on() registers an event handler.
+ */
+ 'off': off
+
+ /*$
+ * @stop
+ */
+ // @cond !off dummyOff:null
+ ,
+ ///#/snippet webDollarFuncs
+ ///#snippet extrasDollarFuncs
+
+ /*$
+ * @id wait
+ * @group EVENTS
+ * @configurable default
+ * @requires promise
+ * @name $.wait()
+ * @syntax $.wait()
+ * @syntax $.wait(durationMs)
+ * @syntax $.wait(durationMs, args)
+ * @module WEB+UTIL
+ *
+ * Creates a new ##promise#Promise## that will be fulfilled as soon as the specified number of milliseconds have passed. This is mainly useful for animation,
+ * because it allows you to chain delays into your animation chain.
+ *
+ * The operation can be interrupted by calling the promise's ##stop() function.
+ *
+ * @example Chained animation using Promise callbacks. The element is first moved to the position 200/0, then to 200/200, waits for 50ms
+ * and finally moves to 100/100.
+ *
+ *
+ *
+ * @param durationMs optional the number of milliseconds to wait. If omitted, the promise will be fulfilled as soon as the browser can run it
+ * from the event loop.
+ * @param args optional an array or list of arguments to pass to the promise handler
+ * @return a ##promise#Promise## object that will be fulfilled when the time is over, or fail when the promise's ##stop() has been called.
+ * The promise argument of a fulfilled promise is the args parameter as given to wait(). The returned promise supports ##stop()
+ * to interrupt the promise.
+ */
+ 'wait': function(durationMs, args) {
+ var p = promise();
+ var id = setTimeout(function() {
+ p['fire'](true, args);
+ }, durationMs);
+ p['stop0'] = function() { p['fire'](false); clearTimeout(id); };
+ return p;
+ }
+
+ /*$
+ * @stop
+ */
+ // @cond !wait dummyWait:0
+
+ ///#/snippet extrasDollarFuncs
+ }, $);
+
+ //// UNDERSCORE FUNCTIONS ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+ copyObj({
+ ///#snippet utilUnderscoreFuncs
+ // @condblock each
+ 'each': each,
+ // @condend
+ // @condblock each
+ 'toObject': toObject,
+ // @condend
+ // @condblock find
+ 'find': find,
+ // @condend
+
+ /*$
+ * @id copyobj
+ * @group OBJECT
+ * @requires
+ * @configurable default
+ * @name _.copyObj()
+ * @syntax _.copyObj(from)
+ * @syntax _.copyObj(from, to)
+ * @module UTIL
+ * Copies every property of the first object into the second object. The properties are copied as shallow-copies.
+ *
+ * @example Copying properties:
+ *
var target = {a:3, c: 3};
+ * _.copyObj({a: 1, b: 2}, target); // target is now {a: 1, b: 2, c: 3}
+ *
+ * @example Inline property merge:
+ *
var target = _.copyObj({a: 1, b: 2}, {a:3, c: 3}); // target is now {a: 1, b: 2, c: 3}
+ *
+ * @example Duplicating an object:
+ *
var target = _.copyObj({a: 1, b: 2}); // target is now {a: 1, b: 2}
+ *
+ * @param from the object to copy from
+ * @param to optional the object to copy to. If not given, a new object will be created.
+ * @return the object that has been copied to
+ *
+ * @see ##_.extend() is very similar to copyObj(), but with a slightly different syntax.
+ * @see ##_.merge() copies a list of objects into a new object.
+ */
+ 'copyObj': copyObj,
+
+ /*$
+ * @id extend
+ * @group OBJECT
+ * @requires
+ * @configurable default
+ * @name _.extend()
+ * @syntax _.extend(target, src...)
+ * @module UTIL
+ * Copies every property of the source objects into the first object. The source objects are specified using variable arguments.
+ * There can be more than one.
+ * The properties are copied as shallow-copies.
+ *
+ * Please note: Unlike jQuery, extend does not directly add a function to extend Minified, although
+ * you can use it to for this. To add a function to ##list#Minified lists##, add a property to
+ * ##M#MINI.M##. If you want to extend $ or _, just assign the new function(s) as property.
+ *
+ * @example Copying properties:
+ *
var target = {a:3, c: 3};
+ * _.extend(target, {a: 1, b: 2}); // target is now {a: 1, b: 2, c: 3}
+ *
+ * @example Using several source values:
+ *
var extend = _.extend({a: 1, b: 2}, {a:3, c: 3}, {d: 5}); // target is now {a: 1, b: 2, c: 3, d: 5}
+ *
+ * @param target the object to copy to
+ * @param src the object(s) to copy from. Variable argument, there can be any number of sources. Nulls and undefined
+ * parameters will be ignored.
+ * @return the target
+ *
+ * @see ##_.copyObj() is very similar to extend(), but with a slightly different and more straightforward syntax.
+ * @see ##_.merge() copies a list of objects into a new object.
+ */
+ 'extend': function(target) {
+ return merge(sub(arguments, 1), target);
+ },
+
+ /*$
+ * @id eachobj
+ * @group OBJECT
+ * @requires
+ * @configurable default
+ * @name _.eachObj()
+ * @syntax _.eachObj(obj, callback)
+ * @syntax _.eachObj(obj, callback, ctx)
+ * @module UTIL
+ * Invokes the given function once for each property of the given object. The callback is not invoked for inherited properties.
+ *
+ * @example Dumps all properties of an object.
+ *
+ * var s = '';
+ * _.eachObj({a: 1, b: 5, c: 2}, function(key, value) {
+ * s += 'key=' + key + ' value=' + value + '\n';
+ * });
+ *
+ *
+ * @param obj the object to use
+ * @param callback The callback function(key, value) to invoke for each property.
+ *
key
The name of the current property.
+ *
value
The value of the current property.
+ *
this
The given context. If not set, the object itself.
+ * The callback's return value will be ignored.
+ * @param ctx optional a context to pass to the callback as 'this'.
+ * @return the object
+ *
+ * @see ##_.each() iterates through a list.
+ */
+ 'eachObj': eachObj,
+
+ /*$
+ * @id isobject
+ * @group TYPE
+ * @requires
+ * @configurable default
+ * @name _.isObject()
+ * @syntax _.isObject(obj)
+ * @module UTIL
+ * Checks whether the given reference is an object as defined by typeof.
+ *
+ * @param obj the object to test
+ * @return true if the object is an object, false otherwise.
+ */
+ 'isObject': isObject,
+
+ /*$
+ * @id format
+ * @group FORMAT
+ * @requires template
+ * @configurable default
+ * @name _.format()
+ * @syntax _.format()
+ * @syntax _.format(template, object)
+ * @syntax _.format(template, object, escapeFunction)
+ * @module UTIL
+ * Formats an object using a ##template#template##. The template syntax is shared with ##_.template(). The only difference is that
+ * format() frees you from the extra step of creating the template. In any case, whether you use
+ * format() or ##_.template(), the template will be cached. Be careful when you create templates dynamically, as
+ * every template is cached and consumes memory.
+ * If you only want to format a single value, use ##_.formatValue().
+ *
+ * @example Format a name:
+ *
var s = _.formatHtml("{{first}} {{last}}", {first: 'Tim', last: 'Taylor'});
+ *
+ * @example Format a list of dates:
+ *
var s = _.format("{{each}}{{this :: yyyy-MM-dd}}{{/each}}", dateList);
+ *
+ * @param template The ##template#template## as a string. The template, once created, will be cached.
+ * @param object the object to format
+ * @param escapeFunction optional The callback function(inputString) that will be used
+ * to escape all output:
+ *
inputString
The string to escape.
+ *
(callback return value)
The escaped string.
+ * If no escapeFunction has been given, the output will not be escaped.
+ * ##_.escapeHtml() can be used as an escape function for HTML, and ##_.escapeRegExp() for regular expressions.
+ * JavaScript's built-in escape() function can escape URL components.
+ * See ##_.htmlFormat() for a version of format() that already includes HTML escaping.
+ * @return the string created by the template
+ *
+ * @see ##_.template() creates a template function, using the same syntax.
+ * @see ##_.formatHtml() is a variant of format() with HTML-escpaping built it.
+ * @see ##_.formatValue() formats a single number or date.
+ * @see ##_.escapeRegExp() can be used by format() to escape regular expressions.
+ */
+ 'format': function(tpl, object, escapeFunction) {
+ return template(tpl, escapeFunction)(object);
+ },
+
+ /*$
+ * @id template
+ * @group FORMAT
+ * @requires date_constants
+ * @configurable default
+ * @name _.template()
+ * @syntax _.template(template)
+ * @syntax _.template(template, escapeFunction)
+ * @module UTIL
+ * Parses a Handlebars-like template to create a reusable template function.
+ *
+ * The syntax of the template uses a syntax that superficially looks like
+ * Handlebars. Unlike Handlebars, it is based on raw JavaScript expressions and thus gives you
+ * complete freedom, but also offers you shortcuts for formatting, iteration and conditionals.
+ *
+ * Every template can receive exactly one object as input. If you need more than one value as input, put all required values
+ * into an object.
+ *
+ * Use double curly braces to embed a JavaScript expression and insert its result:
+ *
{{a}} plus {{b}} is {{a+b}}
+ *
+ * To use such a template, create it with template() and then execute the resulting function:
+ *
var myTemplate = _.template('{{a}} plus {{b}} is {{a+b}}');
+ * var result = myTemplate({a: 5, b: 7});
+ * If you pass an object as input, its properties will be mapped using JavaScript's with
+ * statement and are available as variables throughout the template.
+ *
+ * If you have only a simple value to render, you can pass it directly and access it through the pre-defined
+ * variable obj:
+ *
var myTemplate = _.template('The result is {{obj}}.');
+ * var result = myTemplate(17);
+ * Alternatively, you could also access the input as this, but be aware that JavaScript wraps simples types
+ * such as Number and Boolean. this is the default, so you can omit it to get the same result:
+ *
var myTemplate = _.template('The result is {{ }}.');
+ * var result = myTemplate(17);
+ *
+ * Minified templates can use ##_.formatValue() formats directly. Just separate them from the expression by
+ * a double-colon:
+ *
The price is {{obj::#.00}}.
+ *
+ * Conditions can be expressed using if and else:
+ *
Hello {{if visits==0}}New{{else if visits<10}}Returning{{else}}Regular{{/if}} Customer.
+ * You can use any JavaScript expression as condition.
+ *
+ * Use each to iterate through a list:
+ *
var myTemplate = _.template(
+ * '{{each names}}{{this.firstName}} {{this.lastName}}{{/each}}');
+ * var result = myTemplate({names: [{firstName: 'Joe', lastName: 'Jones'},
+ * {firstName: 'Marc', lastName: 'Meyer'}]});
+ * each will iterate through the members of the given object. It
+ * calls its body for each item and put a reference to the item into this.
+ * Optionally, you can specify up to two variables to store the value in and
+ * the zero-based index of the current item:
+ *
+ *
+ * If you do not pass an expression to each, it will take the list from this:
+ *
var myTemplate = _.template('{{each value:}}{{value}};{{/each}}');
+ * var result = myTemplate([1, 2, 3]);
+ *
+ * Beside lists, you can also iterate through the properties of an object. The property name will be stored
+ * in the first given parameter and the value in this and the second parameter:
+ *
var myTemplate = _.template('{{each key, value: nicknames}}{{key}}: {{value}}{{/each}}');
+ * var result = myTemplate({nicknames: {Matt: 'Matthew', John: 'Jonathan'} });
+ *
+ * Shorter version of the previous example that uses this for the value:
+ *
var myTemplate = _.template('{{each key: nicknames}}{{key}}: {{this}}{{/each}}');
+ *
+ * If you do not need the key, you can omit the variable specification:
+ *
var myTemplate = _.template('{{each nicknames}}{{this}}{{/each}}');
+ *
+ * You can define your own variables, using the regular JavaScript syntax, with 'var':
+ *
var myTemplate = _.template('{{var s=very.long.name, sum=a+b;}}{{s.desc}}, {{sum}}');
+ *
+ * In some situations, it may be inevitable to embed raw JavaScript in the template.
+ * To embed JavaScript code, prefix the code with a '#':
+ *
var myTemplate = _.template(
+ * '{{each}}{{#var sum = 0; for (var i = 0; i < 3; i++) sum += this.numbers[i]; }}{{sum}}{{/each}}');
+ * var result = myTemplate([['Foreword', 'Intro'], ['Something', 'Something else']]);
+ *
+ *
+ * By default, all output will be escaped. You can prevent this by using triple-curly-braces:
+ *
Here's the original: {{{rawText}}}
.
+ *
+ * The template's JavaScript code is executed in a sandbox without access to global variables. Minified defines the
+ * following variables for you:
+ *
+ *
Name
Desciption
+ *
this
The template object outside of each. Inside eachs, the current value.
+ *
obj
The parameter given to the template function.
+ *
_
A reference to Minified Util.
+ *
esc
The escape function given when the template has been defined. If no function has been given,
+ * a default function that returns the input unmodified.
+ *
print
A function(text,...) that appends one or more strings to the template result.
+ *
each
A function(listOrObject, eachCallback) that can iterate over lists or object properties.
+ * The eachCallback is a function(key, value) for objects or function(value, index)
+ * for arrays that will be invoked for each item.
+ *
+ *
+ * Every template you create is already cached, so it not an expensive operation to call ##_.template() a second
+ * time with the same template. However, because of caching, you should be careful when creating templates
+ * dynamically, as this will fill the cache up quickly.
+ *
+ * @param template The template as a string using the syntax described below.
+ * @param escapeFunction optional The callback function(inputString) that will be used
+ * to escape all output:
+ *
inputString
The string to escape.
+ *
(callback return value)
The escaped string.
+ * If no escapeFunction has been given, the output will not be escaped.
+ * ##_.escapeHtml() can be used as an escape function for HTML, and ##_.escapeRegExp() for regular expressions.
+ * JavaScript's built-in escape() function can escape URL components.
+ * @return the value returned by the last invocation of func
+ *
+ * @see ##_.format() shares template()'s syntax but returns the result directly.
+ * @see ##_.formatHtml() is a variant of format() with HTML escaping.
+ * @see ##_.escapeHtml() can be used by template() to escape HTML.
+ * @see ##_.escapeRegExp() can be used by template() to escape regular expressions.
+ * @see ##HTML() creates a HTML element tree from a template.
+ */
+ 'template': template,
+
+ /*$
+ * @id formathtml
+ * @group FORMAT
+ * @requires template
+ * @configurable default
+ * @name _.formatHtml()
+ * @syntax _.formatHtml()
+ * @syntax _.formatHtml(template, object)
+ * @module UTIL
+ * Formats an object using a ##template#template## with HTML escaping for the output.
+ * The template syntax is shared with ##_.template(). Output in double curly braces is automatically escaped using ##_.escapeHtml().
+ * formatHtml() just creates a new template with HTML escaping and invokes it immediately.
+ * The template will be cached. Be careful when you create templates dynamically, as
+ * every template is cached and consumes memory.
+ * If you only want to format a single value, use ##_.formatValue().
+ *
+ * @example Format a name:
+ *
var s = _.formatHtml("{{first}} {{last}}", {first: 'Tim', last: 'Taylor'});
+ *
+ * @example Format a list of dates:
+ *
var s = _.formatHtml("{{each}}{{::yyyy-MM-dd}}{{/each}}", dateList);
+ *
+ * @param template The ##template#template## as a string. The template, once created, will be cached.
+ * @param object the object to format
+ * @return the string created by the template
+ *
+ * @see ##ht() works uses formatHtml to set element's innerHTML.
+ * @see ##HTML() create HTML nodes using formatHtml.
+ * @see ##_.template() creates a template function, using the same syntax.
+ * @see ##_.format() allows you to specify alternative escape mechanisms.
+ */
+ 'formatHtml': formatHtml
+ /*$
+ * @stop
+ */
+
+ // @cond !format dummyFormatHtml:0
+ ,
+
+ ///#/snippet utilUnderscoreFuncs
+ ///#snippet extrasUnderscoreFuncs
+ // @condblock promise
+ 'promise': promise
+ // @condend promise
+
+ /*$
+ * @stop
+ */
+ // @cond !promise dummyPromise:0
+
+ ///#/snippet extrasUnderscoreFuncs
+ }, _);
+
+ ////INITIALIZATION ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+ ///#snippet webInit
+ /*$
+ * @id ready_init
+ * @dependency
+ */
+ document.addEventListener("DOMContentLoaded", function() {
+ callList(DOMREADY_HANDLER);
+ DOMREADY_HANDLER = _null;
+ }, false);
+ /*$
+ @stop
+ */
+
+
+ ///#/snippet webInit
+
+ return {
+ ///#snippet extrasExports
+
+ /*$
+ * @id html
+ * @group ELEMENT
+ * @requires template ht
+ * @configurable default
+ * @name HTML()
+ * @syntax HTML(templateString, object...)
+ * @syntax HTML(templateFunction, object...)
+ * @syntax HTML(idSelector, object...)
+ * @module WEB
+ * Creates a ##list#list## of HTML nodes from the given HTML template. The list is compatible with ##add(), ##fill() and related methods.
+ * The template uses the ##template() syntax with ##escapeHtml() escaping for values.
+ *
+ * Please note that the function HTML will not be automatically exported by Minified. You should always import it
+ * using the recommended import statement:
+ *
+ * var MINI = require('minified'), $ = MINI.$, $$ = MINI.$$, EE = MINI.EE, HTML = MINI.HTML;
+ *
+ *
+ * @example Creating a HTML element showing a number:
+ *
+ *
+ * @example You can store templates in <script> tags. First you need to create a <script> tag with a type not
+ * supported by the browser and put your template in there, like this:
+ *
<script id="myTimeTpl" type="minified-template">The time is {{HH:mm:ss}}.</script>
+ * Then you can specify the tag's id directly to access it:
+ *
$('#timeDisplay').fill(HTML('#myTimeTpl', new Date()));
+ *
+ * @param templateString the template using ##template() syntax. Please note, because this is a template, you should
+ * avoid creating the template itself dynamically, as compiling templates is expensive and
+ * Minified will cache only a limited number of templates. Exception: If the template string does not use
+ * any template functionality (no {{}}), it does not need to be compiled and won't be cached.
+ * The template will use ##escapeHtml() as escape function, so all template substitutions will be HTML-escaped,
+ * unless you use triple curly-braces.
+ * @param templateFunction instead of a HTML template HTML() also accepts a template function, e.g. one
+ * created by ##template(). It will be invoked with the object as only argument.
+ * @param idSelector if you pass an ID CSS selector in the form "#myScript", Minified will recognize this and use the content
+ * of the specified <script> element as template. This allows you to put your template into
+ * a <script> tag with a non-JavaScript type (see example). Any string that starts with '#' and does not
+ * contain any spaces is used as selector.
+ * @param object optional one or more objects to pass to the template. If object is not set, the template is called with undefined
+ * as object. If exactly one object is given, it is passed directly to the template. If you specify more than one
+ * object, they are ##merge#merged##.
+ * @return the list containing the new HTML nodes
+ *
+ * @see ##ht() is a shortcut for fill(HTML()).
+ * @see ##EE() is a different way of creating HTML nodes.
+ */
+ 'HTML': function () {
+ var div = EE('div');
+ return _(call(div['ht'], div, arguments)[0].childNodes);
+ },
+ /*$
+ * @stop
+ */
+
+ ///#/snippet extrasExports
+ ///#snippet utilExports
+ /*$
+ * @id underscore
+ * @group LIST
+ * @name _()
+ * @syntax _(item...)
+ * @configurable default
+ * @module UTIL
+ * Creates a new Minified list. Supports variable arguments so you can add items directly to the list. For arguments that are lists
+ * (as defined by ##_.isList()), the list content will be added to the new list. Unlike #dollar#$()#, this is not done recursively
+ * and thus you can create a list of lists by wrapping arguments in a list. Another difference between _() and $()
+ * is that $() will automatically remove null values while _() will keep them.
+ *
+ * @example Creating an empty list:
+ *
_()
+ *
+ * @example Creating a list with three items:
+ *
_(1, 2, 3)
+ *
+ * @example Creating the same list, but by passing an array. One array level will be flattened:
+ *
_([1, 2, 3])
+ *
+ * @example Creating a list containing the arrays [1, 2] and [3, 4].
+ *
_([[1, 2], [3, 4]])
+ *
+ * @example Merging two lists:
+ *
var a = _("a", "b", "c");
+ * var b = _("x", "y", "z");
+ * var merged = _(a, b); // contains _("a", "b", "c", "x", "y", "z")
+ *
+ *
+ * @example Adding two elements to a list:
+ *
var a = _(1, 2, 3);
+ * var a4 = _(a, 4); // contains _(1, 2, 3, 4)
+ *
+ *
+ * @example Mixing different list types and single elements:
+ *
_(1, [], [2, 3], _(), _(4, 5)); // same content as _(1, 2, 3, 4, 5)
+ *
+ * @param item an item to add to the new list. If it is a list (as defined by ##_.isList()), its content will be to the new
+ * ##Minified list#list## (but NOT recursively).
+ */
+ '_': _,
+ /*$
+ * @stop
+ */
+ ///#/snippet utilExports
+ ///#snippet webExports
+
+ /*$
+ * @id dollar
+ * @group SELECTORS
+ * @requires
+ * @dependency yes
+ * @name $()
+ * @syntax $()
+ * @syntax $(selector)
+ * @syntax $(selector, context)
+ * @syntax $(selector, context, childOnly)
+ * @syntax $(list)
+ * @syntax $(list, context)
+ * @syntax $(list, context, childOnly)
+ * @syntax $(object)
+ * @syntax $(object, context)
+ * @syntax $(object, context, childOnly)
+ * @syntax $(domreadyFunction)
+ * @module WEB
+ * Creates a new ##list#Minified list##, or register a DOMReady-handler.
+ * The most common usage is with a CSS-like selector. $() will then create a list containing all elements of the current HTML
+ * document that fulfill the filter conditions. Alternatively you can also specify a list of objects or a single object.
+ * Nested lists will automatically be flattened, and nulls will automatically be removed from the resulting list.
+ * If you call $() without any arguments, it will return an empty list.
+ *
+ * Additionally, you can specify a second argument to provide a context. Contexts only make sense if you selected
+ * HTML nodes with the first parameter. Then the context limits the resulting list to include only those nodes
+ * that are descendants of the context nodes. The context can be either a selector, a list or a single HTML node, and will be
+ * processed like the first argument. A third arguments allows you to limit the list to
+ * only those elements that are direct children of the context nodes (so a child of a child would be filtered out).
+ *
+ * The lists created by $() are the same type as the ##list#Minified lists## created by Util's #underscore#_() constructor and other
+ * Util methods. All Util methods work on lists created by $(). If you want to add your own methods to those lists,
+ * use ##M#MINI.M##.
+ *
+ * As a special shortcut, if you pass a function to $(), it will be registered using #ready#$.ready() to be executed
+ * when the DOM model is complete.
+ *
+ * @example A simple selector to find an element by id.
+ *
+ * var l0 = $('#myElementId');
+ *
+ *
+ * @example You can pass an object reference to create a list containing only this element:
+ *
+ * var l1 = $(document.getElementById('myElementId'));
+ *
+ *
+ * @example Lists and arrays will be copied:
+ *
+ * var l2 = $([elementA, elementB, elementC]);
+ *
+ *
+ * @example Lists will be automatically flattened and nulls removed. So this list l3 has the same content as l2:
+ *
+ *
+ * @example This is a simple selector to find all elements with the given class.
+ *
+ * var l4 = $('.myClass');
+ *
+ *
+ * @example A selector to find all elements of the given type.
+ *
+ * var l5 = $('input'); // finds all input elements
+ *
+ *
+ * @example A selector to find all elements with the given type and class.
+ *
+ * var l6 = $('input.myRadio'); // finds all input elements with class 'myRadio'
+ *
+ *
+ * @example A selector to find all elements that are descendants of the given element.
+ *
+ * var l7 = $('#myForm input'); // finds all input elements contained in the element myForm
+ *
+ *
+ * @example A selector to find all elements that have either a CSS class 'a' or class 'b':
+ *
+ * var l8 = $('.a, .b'); // finds all elements that have class a or class b
+ *
+ *
+ * @example A selector that finds all elements that are descendants of the element myDivision, are inside an element with the
+ * class .myForm and are input elements:
+ *
+ * var l9 = $('#myDivision .myForm input');
+ *
+ *
+ * @example Contexts can make it easier to specify ancestors:
+ *
+ *
+ * @example Using one of the list functions, ##set(), on the list, and setting the element's text color. '$' at the beginning of the property name sets a CSS value.
+ *
+ * $('#myElementId').set('$color', 'red');
+ *
+ *
+ * @example Most list methods return the list you invoked them on, allowing you to chain them:
+ *
+ *
+ * @example Using $() as a #ready#$.ready() shortcut:
+ *
+ * $(function() {
+ * // in here you can safely work with the HTML document
+ * });
+ *
+ *
+ * @param selector a simple, CSS-like selector for HTML elements. It supports '#id' (lookup by id), '.class' (lookup by class),
+ * 'element' (lookup by elements) and 'element.class' (combined class and element). Use commas to combine several selectors.
+ * You can also join two or more selectors by space to find elements which are descendants of the previous selectors.
+ * For example, use 'div' to find all div elements, '.header' to find all elements containing a class name called 'header', and
+ * 'a.popup' for all a elements with the class 'popup'. To find all elements with 'header' or 'footer' class names,
+ * write '.header, .footer'. To find all divs elements below the element with the id 'main', use '#main div'.
+ * The selector "*" will return all elements.
+ * @param list a list to copy. It can be an array, another Minified list, a DOM nodelist or anything else that has a length property and
+ * allows read access by index. A shallow copy of the list will be returned. Nulls will be automatically removed from the copy. Nested lists
+ * will be flattened, so the result only contains nodes.
+ * @param object an object to create a single-element list containing only the object. If the argument is null, an empty list will be returned.
+ * @param domreadyFunction a function to be registered using #ready#$.ready().
+ * @param context optional an optional selector, node or list of nodes which specifies one or more common ancestor nodes for the selection. The context can be specified as
+ * a selector, a list or using a single object, just like the first argument.
+ * The returned list will contain only descendants of the context nodes. All others will be filtered out.
+ * @param childOnly optional if set, only direct children of the context nodes are included in the list. Children of children will be filtered out. If omitted or not
+ * true, all descendants of the context will be included.
+ * @return the array-like ##list#Minified list## object containing the content specified by the selector.
+ * Please note that if the first argument was a list, the existing order will be kept. If the first argument was a simple selector, the nodes are in document order.
+ * If you combined several selectors using commas, only the individual results of the selectors will keep the document order,
+ * but will then be joined to form a single list. This list will
+ * not be in document order anymore, unless you use a build without legacy IE support.
+ * Duplicate nodes will be removed from selectors, but not from lists.
+ *
+ * @see #underscore#_() is Util's alternative constructor for ##list#Minified lists##
+ * @see ##dollardollar#$$()## works like $(), but returns the resulting list's first element.
+ */
+ '$': $,
+
+ /*$
+ * @id dollardollar
+ * @group SELECTORS
+ * @requires
+ * @configurable default
+ * @name $$()
+ * @syntax $(selector)
+ * @syntax $(selector, context)
+ * @syntax $(selector, context, childOnly)
+ * @shortcut $$() - It is recommended that you assign MINI.$$ to a variable $$.
+ * @module WEB
+ * Returns a DOM object containing the first match of the given selector, or undefined if no match was found.
+ * $$ allows you to easily access an element directly. It is the equivalent to writing $(selector)[0].
+ *
+ * Please note that the function $$ will not be automatically exported by Minified. You should always import it
+ * using the recommended import statement:
+ *
+ * var MINI = require('minified'), $ = MINI.$, $$ = MINI.$$, EE = MINI.EE;
+ *
+ *
+ * @param selector a simple, CSS-like selector for the element. Uses the same syntax as #dollar#$(). The most common
+ * parameter for this function is the id selector with the syntax "#id".
+ * @param context optional an optional selector, node or list of nodes which specifies one or more common ancestor nodes for the selection. The context can be specified as
+ * a selector, a list or using a single object, just like the first argument.
+ * The returned list will contain only descendants of the context nodes. All others will be filtered out.
+ * @param childOnly optional if set, only direct children of the context nodes are included in the list. Children of children will be filtered out. If omitted or not
+ * true, all descendants of the context will be included.
+ * @return a DOM object of the first match, or undefined if the selector did not return at least one match
+ *
+ * @see ##dollar#$()## creates a list using the selector, instead of returning only the first result.
+ */
+ '$$': $$,
+
+ /*$
+ * @id M
+ * @name M
+ * @syntax MINI.M
+ * @module WEB, UTIL
+ *
+ * Exposes the internal class used by all ##list#Minified lists##. This is mainly intended to allow you adding your
+ * own functions.
+ *
+ * @example Adding a function printLength() to M:
+ *
+ */
+ 'M': M,
+
+ /*$
+ * @id getter
+ * @requires get
+ * @name MINI.getter
+ * @syntax MINI.getter
+ * @module WEB
+ *
+ * Exposes a map of prefix handlers used by ##get(). You can add support for a new prefix in get()
+ * by adding a function to this map. The prefix can be any string consisting solely of non-alphanumeric characters
+ * that's not already used by Minified.
+ *
+ * You must not replace getters by a new map, but must always modify the existing map.
+ *
+ * The function's signature is function(list, name) where
+ *
list
Is the Minified list to get the value from. By convention you should always use only the first element. The list is
+ * non-empty and the first elememt can't be null or undefined (get() automatically returns undefined in
+ * all other case).
+ *
name
The name of the property. That's the part AFTER the prefix.
+ *
(callback return value)
The value to return to the user.
+ *
+ * @example Adding a shortcut '||' for accessing border style properties:
+ *
+ * MINI.getter['||'] = function(list, name) {
+ * return list.get('$border' + name.replace(/^[a-z]/, function(a) { return a.toUpperCase()});
+ * };
+ *
+ * var borderColor = $('#box').get('||color'); // same as '$borderColor'
+ * var borderLeftRadius = $('#box').get('||leftRadius'); // same as '$borderLeftRadius'
+ *
+ *
+ * @example Adding XLink attribute support to get(). This is useful if you work with SVG. The prefix is '>'.
+ *
+ */
+ 'getter': getter,
+
+ /*$
+ * @id setter
+ * @requires set
+ * @name MINI.setter
+ * @syntax MINI.setter
+ * @module WEB
+ *
+ * Exposes a map of prefix handlers used by ##set(). You can add support for a new prefix in set()
+ * by adding a function to this map. The prefix can be any string consisting solely of non-alphanumeric characters
+ * that's not already used by Minified.
+ *
+ * You must not replace setters by a new map, but must always modify the existing map.
+ *
+ * The function's signature is function(list, name, value) where
+ *
list
Is the Minified list to use.
+ *
name
The name of the property. That's the part AFTER the prefix.
+ *
value
Either the value to set, or a callback function to create the value that you must call for each
+ * value (see ##set() ).
+ *
+ *
+ * If you provide complete ##get() and ##set() support for a prefix, you are also able to use it in other Minified
+ * function such as ##animate() and ##toggle().
+ *
+ * @example Adding a shortcut '||' for accessing border style properties. As it's just calling ##set() for an existing
+ * property, it is not required to extra code for the callback.
+ *
+ */
+ 'setter': setter
+ /*$
+ * @stop
+ */
+ ///#/snippet webExports
+ };
+
+ ///#snippet commonAmdEnd
+});
+///#/snippet commonAmdEnd
+///#snippet webDocs
+
+/*$
+ * @id list
+ * @name Minified Lists
+ * @module WEB, UTIL
+ *
+ * Minified lists are Array-like objects provided by Minified. Like a regular JavaScript array,
+ * they provide a length property and you can access their content using the index operator (a[5]).
+ * However, they do not provide the same methods as JavaScript's native array and are designed to be immutable, so
+ * there is no direct way to add something to a Minified list. Instead Minified provides a number of functions and methods
+ * that take a list and create a modified copy which, for example, may contain additional elements.
+ *
+ * Minified lists are typically created either using the Web module's #dollar#$() function or with the Util module's
+ * #underscore#_() function, but many functions in the Util module also return a Minified list.
+ *
+ * The Util module provides a function ##_.array() that converts a Minified list to a regular JavaScript array.
+ */
+
+/*$
+ * @id promiseClass
+ * @name Promise
+ * @module WEB, UTIL
+ *
+ * Promises are objects that represent the future result of an asynchronous operation. When you start such an operation, using #request#$.request(),
+ * ##animate(), or ##wait(), you will get a Promise object that allows you to get the result as soon as the operation is finished.
+ *
+ * Minified's full distribution ships with a Promises/A+-compliant implementation of Promises that should
+ * be able to interoperate with most other Promises implementations. Minified's Web module in stand-alone distribution comes with a limited implementation.
+ * See below for details.
+ *
+ * What may be somewhat surprising about this Promises specification is that the only standard-compliant way to access the result is to
+ * register callbacks. They will be invoked as soon as the operation is finished.
+ * If the operation already ended when you register the callbacks, the callback will then just be called from the event loop as soon
+ * as possible (but never while the ##then() you register them with is still running).
+ * This design forces you to handle the operation result asynchronously and disencourages 'bad' techniques such as polling.
+ *
+ * The central method of a Promise, and indeed the only required function in Promises/A+, is ##then(). It allows you to register
+ * two callback methods, one for success (called 'fulfillment' in Promises/A+ terminology) and one for failures (called 'rejection' in Promises/A+).
+ *
+ * This example shows you how to use then():
+ *
+ *
+ * What makes Promises so special is that ##then() itself returns a new Promise, which is based on the Promise then() was called on, but can be
+ * modified by the outcome of callbacks. Both arguments to then() are optional, and you can also write the code like this:
+ *
+ *
+ * Because the first ##then() returns a new Promise based on the original Promise, the second then() will handle errors of the request just like
+ * the first one did. There is only one subtle difference in the second example: the error handler will not only be called if the request failed,
+ * but also when the request succeded but the success handler threw an exception. That's one of the two differences between the original Promise and
+ * the Promise returned by then(). Any exception thrown in a callback causes the new Promise to be in error state.
+ *
+ * Before I show you the second difference between the original Promise and the new Promise, let me make the example a bit more readable
+ * by using ##error(), which is not part of Promises/A+, but a simple extension by Minified. It just registers the failure callback without
+ * forcing you to specify null as first argument:
+ *
+ *
+ * A very powerful capability of Promises is that you can easily chain them. If a ##then() callback returns a value, the new Promise returned
+ * by then() will be marked as success (fulfilled) and this value is the result of the operation. If a callback returns a Promise,
+ * the new Promise will assume the state of the returned Promise. You can use the latter to create chains of asynchronous operations,
+ * but you still need only a single error handler for all of them and you do not need to nest functions to achieve this:
+ *
+ *
+ * Only the full Minified distribution allows you to create promises yourself, using the ##promise() function. The Promises/A+
+ * specification does not specify how to fulfill a promise, but in Minified's implementation every Promise object has a function fire()
+ * that needs to be called when the promise result is ready. It requires two arguments.
+ * The first is a boolean, true for a successful operation and false for a failure. The second is an array or list containing the
+ * arguments to call the corresponding ##then() handler with.
+ *
+ * The following example is a function, similar to ##wait(), that returns a Promise which succeeds after the given amount
+ * of milliseconds has passed.
+ * It then fulfills the promise with the number of milliseconds as argument.
+ *
+ *
+ * function timeout(durationMs) {
+ * var p = _.promise();
+ * setTimeout(function() { p.fire(true, [durationMs]); }, durationMs);
+ * return p;
+ * }
+ *
+ * If you use only the Web module, instead of the full implementation, the promises implementation is not fully Promises/A+ compliant.
+ * One major difference is that it does not allow you create promises yourself. The only way to get a promise in the Web module
+ * is from functions like ##animate() and ##request(). The other difference is that the interoperability with other promises frameworks
+ * is limited, even though it should be good enough most of the time.
+ *
+ * There are two things you may run into when you use Web's simplified implementation with a complete implementation:
+ *
The simplified implementation does not support recursive thenables. So when you register callbacks with ##then(),
+ * you can return a promise or a thenable, but only if that promise is not also returning a promise.
+ *
Many corner cases required by the Promises/A+ specification are not handled. When interoperating using
+ * reasonable implementations, you may never run into this, but Promises/A+ has detailed rules for things like ##then()
+ * methods implemented as dynamic getter and returning a new value on each invocation or throwing exceptions. If you need
+ * a water-proof implementation, you need to use the complete implementation in Minified's full package.