diff --git a/.npmignore b/.npmignore
new file mode 100644
index 0000000..ee2f8fe
--- /dev/null
+++ b/.npmignore
@@ -0,0 +1,4 @@
+dist/
+npm-debug.log
+.lock-wscript
+build/node_modules/grunt/node_modules/glob/build
diff --git a/app/config.js b/app/config.js
new file mode 100644
index 0000000..8412b8c
--- /dev/null
+++ b/app/config.js
@@ -0,0 +1,30 @@
+// Set the require.js configuration for your application.
+require.config({
+ // Initialize the application with the main application file
+ deps: ["main"],
+
+ paths: {
+ // JavaScript folders
+ libs: "../assets/js/libs",
+ plugins: "../assets/js/plugins",
+
+ // Libraries
+ jquery: "../assets/js/libs/jquery",
+ underscore: "../assets/js/libs/underscore",
+ backbone: "../assets/js/libs/backbone",
+
+ // Shim Plugin
+ use: "../assets/js/plugins/use"
+ },
+
+ use: {
+ backbone: {
+ deps: ["use!underscore", "jquery"],
+ attach: "Backbone"
+ },
+
+ underscore: {
+ attach: "_"
+ }
+ }
+});
diff --git a/app/main.js b/app/main.js
new file mode 100644
index 0000000..4c1c2ea
--- /dev/null
+++ b/app/main.js
@@ -0,0 +1,81 @@
+require([
+ "namespace",
+
+ // Libs
+ "jquery",
+ "use!backbone",
+
+ // Modules
+ "modules/example"
+],
+
+function(namespace, $, Backbone, Example) {
+
+ // Defining the application router, you can attach sub routers here.
+ var Router = Backbone.Router.extend({
+ routes: {
+ "": "index",
+ ":hash": "index"
+ },
+
+ index: function(hash) {
+ var route = this;
+ var tutorial = new Example.Views.Tutorial();
+
+ // Attach the tutorial to the DOM
+ tutorial.render(function(el) {
+ $("#main").html(el);
+
+ // Fix for hashes in pushState and hash fragment
+ if (hash && !route._alreadyTriggered) {
+ // Reset to home, pushState support automatically converts hashes
+ Backbone.history.navigate("", false);
+
+ // Trigger the default browser behavior
+ location.hash = hash;
+
+ // Set an internal flag to stop recursive looping
+ route._alreadyTriggered = true;
+ }
+ });
+ }
+ });
+
+ // Shorthand the application namespace
+ var app = namespace.app;
+
+ // Treat the jQuery ready function as the entry point to the application.
+ // Inside this function, kick-off all initialization, everything up to this
+ // point should be definitions.
+ $(function() {
+ // Define your master router on the application namespace and trigger all
+ // navigation from this instance.
+ app.router = new Router();
+
+ // Trigger the initial route and enable HTML5 History API support
+ Backbone.history.start({ pushState: true });
+ });
+
+ // All navigation that is relative should be passed through the navigate
+ // method, to be processed by the router. If the link has a data-bypass
+ // attribute, bypass the delegation completely.
+ $(document).on("click", "a:not([data-bypass])", function(evt) {
+ // Get the anchor href and protcol
+ var href = $(this).attr("href");
+ var protocol = this.protocol + "//";
+
+ // Ensure the protocol is not part of URL, meaning its relative.
+ if (href && href.slice(0, protocol.length) !== protocol &&
+ href.indexOf("javascript:") !== 0) {
+ // Stop the default event to ensure the link will not cause a page
+ // refresh.
+ evt.preventDefault();
+
+ // `Backbone.history.navigate` is sufficient for all Routers and will
+ // trigger the correct events. The Router's internal `navigate` method
+ // calls this anyways.
+ Backbone.history.navigate(href, true);
+ }
+ });
+
+});
diff --git a/app/modules/example.js b/app/modules/example.js
new file mode 100644
index 0000000..9a42a81
--- /dev/null
+++ b/app/modules/example.js
@@ -0,0 +1,44 @@
+define([
+ "namespace",
+
+ // Libs
+ "use!backbone"
+
+ // Modules
+
+ // Plugins
+],
+
+function(namespace, Backbone) {
+
+ // Create a new module
+ var Example = namespace.module();
+
+ // Example extendings
+ Example.Model = Backbone.Model.extend({ /* ... */ });
+ Example.Collection = Backbone.Collection.extend({ /* ... */ });
+ Example.Router = Backbone.Router.extend({ /* ... */ });
+
+ // This will fetch the tutorial template and render it.
+ Example.Views.Tutorial = Backbone.View.extend({
+ template: "app/templates/example.html",
+
+ render: function(done) {
+ var view = this;
+
+ // Fetch the template, render it to the View element and call done.
+ namespace.fetchTemplate(this.template, function(tmpl) {
+ view.el.innerHTML = tmpl();
+
+ // If a done function is passed, call it with the element
+ if (_.isFunction(done)) {
+ done(view.el);
+ }
+ });
+ }
+ });
+
+ // Required, return the module for AMD compliance
+ return Example;
+
+});
diff --git a/app/namespace.js b/app/namespace.js
new file mode 100644
index 0000000..17e380d
--- /dev/null
+++ b/app/namespace.js
@@ -0,0 +1,65 @@
+define([
+ // Libs
+ "jquery",
+ "use!underscore",
+ "use!backbone"
+],
+
+function($, _, Backbone) {
+ // Put application wide code here
+
+ return {
+ // This is useful when developing if you don't want to use a
+ // build process every time you change a template.
+ //
+ // Delete if you are using a different template loading method.
+ fetchTemplate: function(path, done) {
+ var JST = window.JST = window.JST || {};
+ var def = new $.Deferred();
+
+ // Should be an instant synchronous way of getting the template, if it
+ // exists in the JST object.
+ if (JST[path]) {
+ if (_.isFunction(done)) {
+ done(JST[path]);
+ }
+
+ return def.resolve(JST[path]);
+ }
+
+ // Fetch it asynchronously if not available from JST, ensure that
+ // template requests are never cached and prevent global ajax event
+ // handlers from firing.
+ $.ajax({
+ url: path,
+ type: "get",
+ dataType: "text",
+ cache: false,
+ global: false,
+
+ success: function(contents) {
+ JST[path] = _.template(contents);
+
+ // Set the global JST cache and return the template
+ if (_.isFunction(done)) {
+ done(JST[path]);
+ }
+
+ // Resolve the template deferred
+ def.resolve(JST[path]);
+ }
+ });
+
+ // Ensure a normalized return value (Promise)
+ return def.promise();
+ },
+
+ // Create a custom object with a nested Views object
+ module: function(additionalProps) {
+ return _.extend({ Views: {} }, additionalProps);
+ },
+
+ // Keep active application instances namespaced under an app object.
+ app: _.extend({}, Backbone.Events)
+ };
+});
diff --git a/app/templates/example.html b/app/templates/example.html
new file mode 100644
index 0000000..e547fff
--- /dev/null
+++ b/app/templates/example.html
@@ -0,0 +1,355 @@
+
+
+
+
+
+
Congratulations!
+
Seeing this means you have installed Backbone Boilerplate correctly.
+
+
Now that you have the easiest and most-powerful Backbone boilerplate available,
+ you're probably wondering how to use it to start building applications...
Backbone Boilerplate is the product of much research and frustration. While
+ existing boilerplates for Backbone exist, they will often modify the Backbone
+ core, don't have an integrated build system, or impose too much on your
+ application's structure. This boilerplate attempts to improve that.
+
+ Organize your application in a logical filesystem, and develop Models,
+ Collections, Views, and Routers inside modules. Build your application knowing
+ you have efficient, compact code. Backbone Boilerplate extends on the
+ versatile Backbone core, and helps developers manage their application.
Managed filesystem structure for application code, assets, tests, and distribution.
+
Snippets to make common tasks easier: modules, HTML5 History API/Hash navigation, template loading and application events.
+
Flexible and extendable build system.
+
+
Concatenate and minify all your libraries, application code, templates and CSS down to reduce transmission time.
+
Compile underscore templates to prevent pre-processing on the client.
+
+
+
+
+
+
Getting help
+
+
If you're encountering issues, need assistance, or have a question that hasn't been answered in this
+ tutorial or the GitHub project page
+ you may find help in one of these places:
+
+
+
IRC - #backbone-boilerplate on irc.freenode.net
+
GitHub Issues - Please report if you've found an issue,
+ bug, or controversial request.
+
+
+
I want this project to be the best it possibly can and represent the interests of the community, please
+ submit issues with features you find useful and anything that you question.
+
+
+
+
Writing your application
+
Your application may be made up of third-party libraries, plugins, application code, templates, and lots of logic. All of this will need
+ to be well structured to keep it maintainable and it also needs to be compiled if deployed into production. Before you can get started you
+ will need to clean out all the existing defaults that are in the boilerplate are necessary to display this tutorial.
+
+
+
Strongly recommend you read through this tutorial before cleaning out any files that may hold clues on how to use the Boilerplate.
+
+
Cleaning out default files and code
+
There are several places where customization may be required.
+
+
+
Removing the Git history
+
If you cloned the Backbone Boilerplate with Git, you should delete the git directory and then initialize your own Git history:
+
+
+ $ rm -rf .git
+ $ git init
+
+
+
+
Removing the test directory
+
If you are not planning on testing your application with QUnit you should delete this directory.
+
+
Removing the build process
+
If you are not planning on using the build tool, delete the
+ grunt.js file. It contains configuration you will not need.
+
+
+
Changing the Favicon
+
At the root level of the project simply change the favicon.ico file to point to your own branded icon.
+
+
Removing default application code
+
This tutorial is rendered in the app/modules/example.js file and written in app/templates/example.html.
+ Both of these files are safe to remove.
+
+
Removing the default routes
+
Routes are defined in the app/main.js file. Familiarize yourself with it's contents. You'll notice the default router has two existing routes and callback defined, reset it to:
+
+
+ // Defining the application router, you can attach sub routers here.
+ var Router = Backbone.Router.extend({
+ routes: {
+ "": "index",
+ },
+
+ index: function() {
+ // Put your homepage route logic here
+ }
+ });
+
+
+
+
+
Above the Router definition you'll see a reference to the example module,
+ this is safe to delete as well.
+
+
+ // Include the example module
+ var Example = namespace.module("example");
+
+
+
+
Removing default assets
+
The default styles for this tutorial are stored in assets/css/style.css. You will probably want to remove these since they only make sense for this specific page. They start on Line 209. With the following H5BP header:
+
+
You may also want to change the name to yours, if you're planning on putting your custom CSS here as well.
+
+
You should delete the assets/img/backbone.png file if you are not planning on using it in your app.
+
+
+
Namespace
+
The namespace.js file is very important since it contains logic that should
+ exist for every module in your application. This also contains the module
+ shortcut function and fetchTemplate function.
+
+
+
Creating a module
+
Following the Bocoup post on Organizing Your Backbone.js Application With Modules this boilerplate provides the same module definition structure.
+
+ Modules are placed in the app/modules/ directory. There is an example module
+ there named: example.js. The actual module definition function is located
+ inside the app/index.js file. You create and reference modules with the same
+ function call: namespace.module("<module_name>").
+
+ Typically a module contains a single Model/Collection/Router and many Views.
+ Therefore the returned module object is empty except for a Views object
+ property that can be used to attach many Views to, like:
+
+
Templates are a super useful way to separate concerns in your application. Instead of generating markup from inside your JavaScript
+ application, you instead create it in a separate file and load it into your application. There are numerous ways of loading in a
+ template, but this boilerplate has chosen the most performant way to build all your templates into a single file.
+
+
This tutorial itself is a template that exists in app/templates/example.html. You can edit this file and hit refresh
+ in here to see the changes. The boilerplate comes with a built in function to handle the loading of templates. It's called:
+
+
+ namespace.fetchTemplate("app/templates/name.html", function(template) {
+ // Template here is a function, that accepts an object. Identical to _.template.
+ console.log(template({ ... }));
+ });
+
+
+
+
By defining a custom function this will ensure that if you use the build tool or AJAX, that your templates will load consistently.
+ You can see it in action inside the app/modules/example.js module.
+
+
If you use the build process to compile your templates, it will automatically find all the HTML files inside the templates
+ directory and compile them into a templates.js file. These are actual JavaScript template functions being compiled on the server, which
+ is different from Jammit and most other server-side builders that just invoke functions on page load.
+
+
You can access a compiled template like so:
+
+
+ var template = window.JST["app/modules/example.html"];
+ template({ ... });
+
+
+
+
Working with Application Wide Events
+
Application wide events provide a convenient way for modules to communicate with each other. namespace.app references a copy of the Backbone.Events object. More information on Backbone Events
+
+
+ To bind a callback function to an event:
+
+
+ //binding an annonymous function to the event "all" with a context of this.
+ namespace.app.on("all", function(){...}, this);
+
+
+
+
+ To remove a callback function (or many) from an event:
+
+
+ // Removes just the doSomething callback.
+ namespace.app.off("change", doSomething);
+
+ // Removes all "change" events.
+ namespace.app.off("change");
+
+ // Removes all events from the namespace.app object.
+ namespace.app.off();
+
+
+
+
+ To trigger the "change" event:
+
+
+ namespace.app.trigger("change", [*args]);
+
+
+
+
Working with libraries and plugins
+
Libraries and plugins are easily added to the application, by placing them inside the assets/js/libs/ directory.
+ If you have many plugins in your application, it may make sense to create a separate folder such as assets/js/plugins/
+ for them.
+
+
+
+
Using the build tool
+
The Backbone Boilerplate build process is a state-of-the-art task driven
+ Node.js application that utilizes @cowboy's grunt project.
+
+ To run the defaults, execute the following command from the project root,
+ and *not from inside the build folder*.
+
+
Running with the defaults
+
To run the defaults, execute the following command from the project root,
+ and *not from inside the build folder*.
+
+
+ node build
+
+
+
+
+ This will do a number of things for you. First it will concatenate all your
+ libs, app code, and templates into separate files inside the `dist/debug`
+ folder. It will then minify those files and your CSS into production ready
+ files inside the dist/release folder.
+
+
Customizing the build configuration
+
To customize and configure the build tool, open `build/config.js` and tweak
+the settings.
+
+
Using the development server
+
+ While writing an application that leverages pushState you can run the
+ following command to run a server that will always resolve to the index.html
+
+
+ node build/server
+
+
+
+
+ This will spawn up an HTTP server on port 8000. This server is intended
+ for development and not production. You should use url rewriting or forwarding
+ all requests in your production server to achieve this same effect.
+
+
Serving the built assets
+
+
If you are using the build tool in conjunction with this development server
+ you can optionally update the index.html file to remove the existing script
+ tags and uncomment out the scripts tag at the bottom to load the dist/debug
+ or dist/release assets. You can achieve this by specifying either debug
+ or release after the server command, like so:
+
+
+ node build/server release
+
+
+
+
Adding new tasks
+
To add a new task into the build system, you simply copy and paste the task JavaScript folder/file into the build/tasks folder
+ or extract the task archive into the same directory. At the very least in order to run this task, you'll need to add it to the build/config.js
+ file. The last line should look something like:
+
+
+ task.registerTask("default", "clean lint:files concat jst min mincss new_module_here");
+
+
+
+ It's possible the custom task will have additional setup instructions, so make
+ sure you read the README for any task.
+
+
+
+
+
diff --git a/assets/css/index.css b/assets/css/index.css
new file mode 100644
index 0000000..a70db9f
--- /dev/null
+++ b/assets/css/index.css
@@ -0,0 +1 @@
+@import "style.css";
diff --git a/assets/css/style.css b/assets/css/style.css
new file mode 100644
index 0000000..c9726da
--- /dev/null
+++ b/assets/css/style.css
@@ -0,0 +1,342 @@
+/*
+ * HTML5 ✰ Boilerplate
+ *
+ * What follows is the result of much research on cross-browser styling.
+ * Credit left inline and big thanks to Nicolas Gallagher, Jonathan Neal,
+ * Kroc Camen, and the H5BP dev community and team.
+ *
+ * Detailed information about this CSS: h5bp.com/css
+ *
+ * ==|== normalize ==========================================================
+ */
+
+
+/* =============================================================================
+ HTML5 display definitions
+ ========================================================================== */
+
+article, aside, details, figcaption, figure, footer, header, hgroup, nav, section { display: block; }
+audio, canvas, video { display: inline-block; *display: inline; *zoom: 1; }
+audio:not([controls]) { display: none; }
+[hidden] { display: none; }
+
+
+/* =============================================================================
+ Base
+ ========================================================================== */
+
+/*
+ * 1. Correct text resizing oddly in IE6/7 when body font-size is set using em units
+ * 2. Force vertical scrollbar in non-IE
+ * 3. Prevent iOS text size adjust on device orientation change, without disabling user zoom: h5bp.com/g
+ */
+
+html { font-size: 100%; overflow-y: scroll; -webkit-text-size-adjust: 100%; -ms-text-size-adjust: 100%; }
+
+body { margin: 0; font-size: 1em; line-height: 1.4; }
+
+body, button, input, select, textarea { font-family: sans-serif; color: #222; }
+
+/*
+ * Remove text-shadow in selection highlight: h5bp.com/i
+ * These selection declarations have to be separate
+ * Also: hot pink! (or customize the background color to match your design)
+ */
+
+::-moz-selection { background: #fe57a1; color: #fff; text-shadow: none; }
+::selection { background: #fe57a1; color: #fff; text-shadow: none; }
+
+
+/* =============================================================================
+ Links
+ ========================================================================== */
+
+a { color: #00e; }
+a:visited { color: #551a8b; }
+a:hover { color: #06e; }
+a:focus { outline: thin dotted; }
+
+/* Improve readability when focused and hovered in all browsers: h5bp.com/h */
+a:hover, a:active { outline: 0; }
+
+
+/* =============================================================================
+ Typography
+ ========================================================================== */
+
+abbr[title] { border-bottom: 1px dotted; }
+
+b, strong { font-weight: bold; }
+
+blockquote { margin: 1em 40px; }
+
+dfn { font-style: italic; }
+
+hr { display: block; height: 1px; border: 0; border-top: 1px solid #ccc; margin: 1em 0; padding: 0; }
+
+ins { background: #ff9; color: #000; text-decoration: none; }
+
+mark { background: #ff0; color: #000; font-style: italic; font-weight: bold; }
+
+/* Redeclare monospace font family: h5bp.com/j */
+pre, code, kbd, samp { font-family: monospace, serif; _font-family: 'courier new', monospace; font-size: 1em; }
+
+/* Improve readability of pre-formatted text in all browsers */
+pre { white-space: pre; white-space: pre-wrap; word-wrap: break-word; }
+
+q { quotes: none; }
+q:before, q:after { content: ""; content: none; }
+
+small { font-size: 85%; }
+
+/* Position subscript and superscript content without affecting line-height: h5bp.com/k */
+sub, sup { font-size: 75%; line-height: 0; position: relative; vertical-align: baseline; }
+sup { top: -0.5em; }
+sub { bottom: -0.25em; }
+
+
+/* =============================================================================
+ Lists
+ ========================================================================== */
+
+ul, ol { margin: 1em 0; padding: 0 0 0 40px; }
+dd { margin: 0 0 0 40px; }
+nav ul, nav ol { list-style: none; list-style-image: none; margin: 0; padding: 0; }
+
+
+/* =============================================================================
+ Embedded content
+ ========================================================================== */
+
+/*
+ * 1. Improve image quality when scaled in IE7: h5bp.com/d
+ * 2. Remove the gap between images and borders on image containers: h5bp.com/e
+ */
+
+img { border: 0; -ms-interpolation-mode: bicubic; vertical-align: middle; }
+
+/*
+ * Correct overflow not hidden in IE9
+ */
+
+svg:not(:root) { overflow: hidden; }
+
+
+/* =============================================================================
+ Figures
+ ========================================================================== */
+
+figure { margin: 0; }
+
+
+/* =============================================================================
+ Forms
+ ========================================================================== */
+
+form { margin: 0; }
+fieldset { border: 0; margin: 0; padding: 0; }
+
+/* Indicate that 'label' will shift focus to the associated form element */
+label { cursor: pointer; }
+
+/*
+ * 1. Correct color not inheriting in IE6/7/8/9
+ * 2. Correct alignment displayed oddly in IE6/7
+ */
+
+legend { border: 0; *margin-left: -7px; padding: 0; }
+
+/*
+ * 1. Correct font-size not inheriting in all browsers
+ * 2. Remove margins in FF3/4 S5 Chrome
+ * 3. Define consistent vertical alignment display in all browsers
+ */
+
+button, input, select, textarea { font-size: 100%; margin: 0; vertical-align: baseline; *vertical-align: middle; }
+
+/*
+ * 1. Define line-height as normal to match FF3/4 (set using !important in the UA stylesheet)
+ */
+
+button, input { line-height: normal; }
+
+/*
+ * 1. Display hand cursor for clickable form elements
+ * 2. Allow styling of clickable form elements in iOS
+ * 3. Correct inner spacing displayed oddly in IE7 (doesn't effect IE6)
+ */
+
+button, input[type="button"], input[type="reset"], input[type="submit"] { cursor: pointer; -webkit-appearance: button; *overflow: visible; }
+
+/*
+ * Consistent box sizing and appearance
+ */
+
+input[type="checkbox"], input[type="radio"] { box-sizing: border-box; padding: 0; }
+input[type="search"] { -webkit-appearance: textfield; -moz-box-sizing: content-box; -webkit-box-sizing: content-box; box-sizing: content-box; }
+input[type="search"]::-webkit-search-decoration { -webkit-appearance: none; }
+
+/*
+ * Remove inner padding and border in FF3/4: h5bp.com/l
+ */
+
+button::-moz-focus-inner, input::-moz-focus-inner { border: 0; padding: 0; }
+
+/*
+ * 1. Remove default vertical scrollbar in IE6/7/8/9
+ * 2. Allow only vertical resizing
+ */
+
+textarea { overflow: auto; vertical-align: top; resize: vertical; }
+
+/* Colors for form validity */
+input:valid, textarea:valid { }
+input:invalid, textarea:invalid { background-color: #f0dddd; }
+
+
+/* =============================================================================
+ Tables
+ ========================================================================== */
+
+table { border-collapse: collapse; border-spacing: 0; }
+td { vertical-align: top; }
+
+
+/* ==|== primary styles =====================================================
+ Author: Backbone Boilerplate
+ ========================================================================== */
+
+#main {
+ margin: 40px;
+ font-size: 14px;
+}
+
+#main pre {
+ min-width: 320px;
+ border-radius: 10px;
+ color: #333;
+ font-weight: normal;
+ font-size: 12px;
+ display: inline-block;
+ padding-right: 40px;
+ background: #F8FAFC;
+}
+
+#main a {
+ color: #00CCFF;
+}
+
+#main a:hover {
+ color: #FE57A1;
+}
+
+#main img {
+ width: 320px;
+ height: auto;
+}
+
+#main p {
+ line-height: 28px;
+}
+
+#main ul.nested {
+ margin-top: 0;
+ margin-bottom: 0;
+}
+
+#main ul li {
+ line-height: 28px;
+}
+
+#main header h1 {
+ margin: 15px;
+ display: inline-block;
+ color: #333;
+}
+
+#main header h2:not(.congrats) {
+ color: #666;
+ margin: 0; padding: 0;
+}
+
+#main header .congrats {
+ padding-bottom: 0;
+ margin-bottom: 0;
+ color: #00CCFF;
+ display: inline-block;
+ font-size: 28px;
+}
+
+#main .topic {
+ color: #CCC;
+}
+
+#main .clues {
+ color: #FE57A1;
+}
+
+
+/* ==|== media queries ======================================================
+ PLACEHOLDER Media Queries for Responsive Design.
+ These override the primary ('mobile first') styles
+ Modify as content requires.
+ ========================================================================== */
+
+@media only screen and (min-width: 480px) {
+ /* Style adjustments for viewports 480px and over go here */
+
+}
+
+@media only screen and (min-width: 768px) {
+ /* Style adjustments for viewports 768px and over go here */
+
+}
+
+
+
+/* ==|== non-semantic helper classes ========================================
+ Please define your styles before this section.
+ ========================================================================== */
+
+/* For image replacement */
+.ir { display: block; border: 0; text-indent: -999em; overflow: hidden; background-color: transparent; background-repeat: no-repeat; text-align: left; direction: ltr; *line-height: 0; }
+.ir br { display: none; }
+
+/* Hide from both screenreaders and browsers: h5bp.com/u */
+.hidden { display: none !important; visibility: hidden; }
+
+/* Hide only visually, but have it available for screenreaders: h5bp.com/v */
+.visuallyhidden { border: 0; clip: rect(0 0 0 0); height: 1px; margin: -1px; overflow: hidden; padding: 0; position: absolute; width: 1px; }
+
+/* Extends the .visuallyhidden class to allow the element to be focusable when navigated to via the keyboard: h5bp.com/p */
+.visuallyhidden.focusable:active, .visuallyhidden.focusable:focus { clip: auto; height: auto; margin: 0; overflow: visible; position: static; width: auto; }
+
+/* Hide visually and from screenreaders, but maintain layout */
+.invisible { visibility: hidden; }
+
+/* Contain floats: h5bp.com/q */
+.clearfix:before, .clearfix:after { content: ""; display: table; }
+.clearfix:after { clear: both; }
+.clearfix { *zoom: 1; }
+
+
+
+/* ==|== print styles =======================================================
+ Print styles.
+ Inlined to avoid required HTTP connection: h5bp.com/r
+ ========================================================================== */
+
+@media print {
+ * { background: transparent !important; color: black !important; box-shadow:none !important; text-shadow: none !important; filter:none !important; -ms-filter: none !important; } /* Black prints faster: h5bp.com/s */
+ a, a:visited { text-decoration: underline; }
+ a[href]:after { content: " (" attr(href) ")"; }
+ abbr[title]:after { content: " (" attr(title) ")"; }
+ .ir a:after, a[href^="javascript:"]:after, a[href^="#"]:after { content: ""; } /* Don't show links for images, or javascript/internal links */
+ pre, blockquote { border: 1px solid #999; page-break-inside: avoid; }
+ thead { display: table-header-group; } /* h5bp.com/t */
+ tr, img { page-break-inside: avoid; }
+ img { max-width: 100% !important; }
+ @page { margin: 0.5cm; }
+ p, h2, h3 { orphans: 3; widows: 3; }
+ h2, h3 { page-break-after: avoid; }
+}
diff --git a/assets/img/backbone.png b/assets/img/backbone.png
new file mode 100644
index 0000000..e12c5b6
Binary files /dev/null and b/assets/img/backbone.png differ
diff --git a/assets/js/libs/almond.js b/assets/js/libs/almond.js
new file mode 100644
index 0000000..613854a
--- /dev/null
+++ b/assets/js/libs/almond.js
@@ -0,0 +1,277 @@
+/**
+ * almond 0.0.3 Copyright (c) 2011, The Dojo Foundation All Rights Reserved.
+ * Available via the MIT or new BSD license.
+ * see: http://github.com/jrburke/almond for details
+ */
+/*jslint strict: false, plusplus: false */
+/*global setTimeout: false */
+
+var requirejs, require, define;
+(function (undef) {
+
+ var defined = {},
+ waiting = {},
+ aps = [].slice,
+ main, req;
+
+ if (typeof define === "function") {
+ //If a define is already in play via another AMD loader,
+ //do not overwrite.
+ return;
+ }
+
+ /**
+ * Given a relative module name, like ./something, normalize it to
+ * a real name that can be mapped to a path.
+ * @param {String} name the relative name
+ * @param {String} baseName a real name that the name arg is relative
+ * to.
+ * @returns {String} normalized name
+ */
+ function normalize(name, baseName) {
+ //Adjust any relative paths.
+ if (name && name.charAt(0) === ".") {
+ //If have a base name, try to normalize against it,
+ //otherwise, assume it is a top-level require that will
+ //be relative to baseUrl in the end.
+ if (baseName) {
+ //Convert baseName to array, and lop off the last part,
+ //so that . matches that "directory" and not name of the baseName's
+ //module. For instance, baseName of "one/two/three", maps to
+ //"one/two/three.js", but we want the directory, "one/two" for
+ //this normalization.
+ baseName = baseName.split("/");
+ baseName = baseName.slice(0, baseName.length - 1);
+
+ name = baseName.concat(name.split("/"));
+
+ //start trimDots
+ var i, part;
+ for (i = 0; (part = name[i]); i++) {
+ if (part === ".") {
+ name.splice(i, 1);
+ i -= 1;
+ } else if (part === "..") {
+ if (i === 1 && (name[2] === '..' || name[0] === '..')) {
+ //End of the line. Keep at least one non-dot
+ //path segment at the front so it can be mapped
+ //correctly to disk. Otherwise, there is likely
+ //no path mapping for a path starting with '..'.
+ //This can still fail, but catches the most reasonable
+ //uses of ..
+ break;
+ } else if (i > 0) {
+ name.splice(i - 1, 2);
+ i -= 2;
+ }
+ }
+ }
+ //end trimDots
+
+ name = name.join("/");
+ }
+ }
+ return name;
+ }
+
+ function makeRequire(relName, forceSync) {
+ return function () {
+ //A version of a require function that passes a moduleName
+ //value for items that may need to
+ //look up paths relative to the moduleName
+ return req.apply(undef, aps.call(arguments, 0).concat([relName, forceSync]));
+ };
+ }
+
+ function makeNormalize(relName) {
+ return function (name) {
+ return normalize(name, relName);
+ };
+ }
+
+ function makeLoad(depName) {
+ return function (value) {
+ defined[depName] = value;
+ };
+ }
+
+ function callDep(name) {
+ if (waiting.hasOwnProperty(name)) {
+ var args = waiting[name];
+ delete waiting[name];
+ main.apply(undef, args);
+ }
+ return defined[name];
+ }
+
+ /**
+ * Makes a name map, normalizing the name, and using a plugin
+ * for normalization if necessary. Grabs a ref to plugin
+ * too, as an optimization.
+ */
+ function makeMap(name, relName) {
+ var prefix, plugin,
+ index = name.indexOf('!');
+
+ if (index !== -1) {
+ prefix = normalize(name.slice(0, index), relName);
+ name = name.slice(index + 1);
+ plugin = callDep(prefix);
+
+ //Normalize according
+ if (plugin && plugin.normalize) {
+ name = plugin.normalize(name, makeNormalize(relName));
+ } else {
+ name = normalize(name, relName);
+ }
+ } else {
+ name = normalize(name, relName);
+ }
+
+ //Using ridiculous property names for space reasons
+ return {
+ f: prefix ? prefix + '!' + name : name, //fullName
+ n: name,
+ p: plugin
+ };
+ }
+
+ main = function (name, deps, callback, relName) {
+ var args = [],
+ usingExports,
+ cjsModule, depName, i, ret, map;
+
+ //Use name if no relName
+ if (!relName) {
+ relName = name;
+ }
+
+ //Call the callback to define the module, if necessary.
+ if (typeof callback === 'function') {
+
+ //Default to require, exports, module if no deps if
+ //the factory arg has any arguments specified.
+ if (!deps.length && callback.length) {
+ deps = ['require', 'exports', 'module'];
+ }
+
+ //Pull out the defined dependencies and pass the ordered
+ //values to the callback.
+ for (i = 0; i < deps.length; i++) {
+ map = makeMap(deps[i], relName);
+ depName = map.f;
+
+ //Fast path CommonJS standard dependencies.
+ if (depName === "require") {
+ args[i] = makeRequire(name);
+ } else if (depName === "exports") {
+ //CommonJS module spec 1.1
+ args[i] = defined[name] = {};
+ usingExports = true;
+ } else if (depName === "module") {
+ //CommonJS module spec 1.1
+ cjsModule = args[i] = {
+ id: name,
+ uri: '',
+ exports: defined[name]
+ };
+ } else if (defined.hasOwnProperty(depName) || waiting.hasOwnProperty(depName)) {
+ args[i] = callDep(depName);
+ } else if (map.p) {
+ map.p.load(map.n, makeRequire(relName, true), makeLoad(depName), {});
+ args[i] = defined[depName];
+ } else {
+ throw name + ' missing ' + depName;
+ }
+ }
+
+ ret = callback.apply(defined[name], args);
+
+ if (name) {
+ //If setting exports via "module" is in play,
+ //favor that over return value and exports. After that,
+ //favor a non-undefined return value over exports use.
+ if (cjsModule && cjsModule.exports !== undef) {
+ defined[name] = cjsModule.exports;
+ } else if (!usingExports) {
+ //Use the return value from the function.
+ defined[name] = ret;
+ }
+ }
+ } else if (name) {
+ //May just be an object definition for the module. Only
+ //worry about defining if have a module name.
+ defined[name] = callback;
+ }
+ };
+
+ requirejs = req = function (deps, callback, relName, forceSync) {
+ if (typeof deps === "string") {
+
+ //Just return the module wanted. In this scenario, the
+ //deps arg is the module name, and second arg (if passed)
+ //is just the relName.
+ //Normalize module name, if it contains . or ..
+ return callDep(makeMap(deps, callback).f);
+ } else if (!deps.splice) {
+ //deps is a config object, not an array.
+ //Drop the config stuff on the ground.
+ if (callback.splice) {
+ //callback is an array, which means it is a dependency list.
+ //Adjust args if there are dependencies
+ deps = callback;
+ callback = arguments[2];
+ } else {
+ deps = [];
+ }
+ }
+
+ //Simulate async callback;
+ if (forceSync) {
+ main(undef, deps, callback, relName);
+ } else {
+ setTimeout(function () {
+ main(undef, deps, callback, relName);
+ }, 15);
+ }
+
+ return req;
+ };
+
+ /**
+ * Just drops the config on the floor, but returns req in case
+ * the config return value is used.
+ */
+ req.config = function () {
+ return req;
+ };
+
+ /**
+ * Export require as a global, but only if it does not already exist.
+ */
+ if (!require) {
+ require = req;
+ }
+
+ define = function (name, deps, callback) {
+
+ //This module may not have dependencies
+ if (!deps.splice) {
+ //deps is not an array, so probably means
+ //an object literal or factory function for
+ //the value. Adjust args.
+ callback = deps;
+ deps = [];
+ }
+
+ if (define.unordered) {
+ waiting[name] = [name, deps, callback];
+ } else {
+ main(name, deps, callback);
+ }
+ };
+
+ define.amd = {
+ jQuery: true
+ };
+}());
diff --git a/assets/js/libs/backbone.js b/assets/js/libs/backbone.js
new file mode 100644
index 0000000..3373c95
--- /dev/null
+++ b/assets/js/libs/backbone.js
@@ -0,0 +1,1431 @@
+// Backbone.js 0.9.2
+
+// (c) 2010-2012 Jeremy Ashkenas, DocumentCloud Inc.
+// Backbone may be freely distributed under the MIT license.
+// For all details and documentation:
+// http://backbonejs.org
+
+(function(){
+
+ // Initial Setup
+ // -------------
+
+ // Save a reference to the global object (`window` in the browser, `global`
+ // on the server).
+ var root = this;
+
+ // Save the previous value of the `Backbone` variable, so that it can be
+ // restored later on, if `noConflict` is used.
+ var previousBackbone = root.Backbone;
+
+ // Create a local reference to slice/splice.
+ var slice = Array.prototype.slice;
+ var splice = Array.prototype.splice;
+
+ // The top-level namespace. All public Backbone classes and modules will
+ // be attached to this. Exported for both CommonJS and the browser.
+ var Backbone;
+ if (typeof exports !== 'undefined') {
+ Backbone = exports;
+ } else {
+ Backbone = root.Backbone = {};
+ }
+
+ // Current version of the library. Keep in sync with `package.json`.
+ Backbone.VERSION = '0.9.2';
+
+ // Require Underscore, if we're on the server, and it's not already present.
+ var _ = root._;
+ if (!_ && (typeof require !== 'undefined')) _ = require('underscore');
+
+ // For Backbone's purposes, jQuery, Zepto, or Ender owns the `$` variable.
+ var $ = root.jQuery || root.Zepto || root.ender;
+
+ // Set the JavaScript library that will be used for DOM manipulation and
+ // Ajax calls (a.k.a. the `$` variable). By default Backbone will use: jQuery,
+ // Zepto, or Ender; but the `setDomLibrary()` method lets you inject an
+ // alternate JavaScript library (or a mock library for testing your views
+ // outside of a browser).
+ Backbone.setDomLibrary = function(lib) {
+ $ = lib;
+ };
+
+ // Runs Backbone.js in *noConflict* mode, returning the `Backbone` variable
+ // to its previous owner. Returns a reference to this Backbone object.
+ Backbone.noConflict = function() {
+ root.Backbone = previousBackbone;
+ return this;
+ };
+
+ // Turn on `emulateHTTP` to support legacy HTTP servers. Setting this option
+ // will fake `"PUT"` and `"DELETE"` requests via the `_method` parameter and
+ // set a `X-Http-Method-Override` header.
+ Backbone.emulateHTTP = false;
+
+ // Turn on `emulateJSON` to support legacy servers that can't deal with direct
+ // `application/json` requests ... will encode the body as
+ // `application/x-www-form-urlencoded` instead and will send the model in a
+ // form param named `model`.
+ Backbone.emulateJSON = false;
+
+ // Backbone.Events
+ // -----------------
+
+ // Regular expression used to split event strings
+ var eventSplitter = /\s+/;
+
+ // A module that can be mixed in to *any object* in order to provide it with
+ // custom events. You may bind with `on` or remove with `off` callback functions
+ // to an event; trigger`-ing an event fires all callbacks in succession.
+ //
+ // var object = {};
+ // _.extend(object, Backbone.Events);
+ // object.on('expand', function(){ alert('expanded'); });
+ // object.trigger('expand');
+ //
+ var Events = Backbone.Events = {
+
+ // Bind one or more space separated events, `events`, to a `callback`
+ // function. Passing `"all"` will bind the callback to all events fired.
+ on: function(events, callback, context) {
+
+ var calls, event, node, tail, list;
+ if (!callback) return this;
+ events = events.split(eventSplitter);
+ calls = this._callbacks || (this._callbacks = {});
+
+ // Create an immutable callback list, allowing traversal during
+ // modification. The tail is an empty object that will always be used
+ // as the next node.
+ while (event = events.shift()) {
+ list = calls[event];
+ node = list ? list.tail : {};
+ node.next = tail = {};
+ node.context = context;
+ node.callback = callback;
+ calls[event] = {tail: tail, next: list ? list.next : node};
+ }
+
+ return this;
+ },
+
+ // Remove one or many callbacks. If `context` is null, removes all callbacks
+ // with that function. If `callback` is null, removes all callbacks for the
+ // event. If `events` is null, removes all bound callbacks for all events.
+ off: function(events, callback, context) {
+ var event, calls, node, tail, cb, ctx;
+
+ // No events, or removing *all* events.
+ if (!(calls = this._callbacks)) return;
+ if (!(events || callback || context)) {
+ delete this._callbacks;
+ return this;
+ }
+
+ // Loop through the listed events and contexts, splicing them out of the
+ // linked list of callbacks if appropriate.
+ events = events ? events.split(eventSplitter) : _.keys(calls);
+ while (event = events.shift()) {
+ node = calls[event];
+ delete calls[event];
+ if (!node || !(callback || context)) continue;
+ // Create a new list, omitting the indicated callbacks.
+ tail = node.tail;
+ while ((node = node.next) !== tail) {
+ cb = node.callback;
+ ctx = node.context;
+ if ((callback && cb !== callback) || (context && ctx !== context)) {
+ this.on(event, cb, ctx);
+ }
+ }
+ }
+
+ return this;
+ },
+
+ // Trigger one or many events, firing all bound callbacks. Callbacks are
+ // passed the same arguments as `trigger` is, apart from the event name
+ // (unless you're listening on `"all"`, which will cause your callback to
+ // receive the true name of the event as the first argument).
+ trigger: function(events) {
+ var event, node, calls, tail, args, all, rest;
+ if (!(calls = this._callbacks)) return this;
+ all = calls.all;
+ events = events.split(eventSplitter);
+ rest = slice.call(arguments, 1);
+
+ // For each event, walk through the linked list of callbacks twice,
+ // first to trigger the event, then to trigger any `"all"` callbacks.
+ while (event = events.shift()) {
+ if (node = calls[event]) {
+ tail = node.tail;
+ while ((node = node.next) !== tail) {
+ node.callback.apply(node.context || this, rest);
+ }
+ }
+ if (node = all) {
+ tail = node.tail;
+ args = [event].concat(rest);
+ while ((node = node.next) !== tail) {
+ node.callback.apply(node.context || this, args);
+ }
+ }
+ }
+
+ return this;
+ }
+
+ };
+
+ // Aliases for backwards compatibility.
+ Events.bind = Events.on;
+ Events.unbind = Events.off;
+
+ // Backbone.Model
+ // --------------
+
+ // Create a new model, with defined attributes. A client id (`cid`)
+ // is automatically generated and assigned for you.
+ var Model = Backbone.Model = function(attributes, options) {
+ var defaults;
+ attributes || (attributes = {});
+ if (options && options.parse) attributes = this.parse(attributes);
+ if (defaults = getValue(this, 'defaults')) {
+ attributes = _.extend({}, defaults, attributes);
+ }
+ if (options && options.collection) this.collection = options.collection;
+ this.attributes = {};
+ this._escapedAttributes = {};
+ this.cid = _.uniqueId('c');
+ this.changed = {};
+ this._silent = {};
+ this._pending = {};
+ this.set(attributes, {silent: true});
+ // Reset change tracking.
+ this.changed = {};
+ this._silent = {};
+ this._pending = {};
+ this._previousAttributes = _.clone(this.attributes);
+ this.initialize.apply(this, arguments);
+ };
+
+ // Attach all inheritable methods to the Model prototype.
+ _.extend(Model.prototype, Events, {
+
+ // A hash of attributes whose current and previous value differ.
+ changed: null,
+
+ // A hash of attributes that have silently changed since the last time
+ // `change` was called. Will become pending attributes on the next call.
+ _silent: null,
+
+ // A hash of attributes that have changed since the last `'change'` event
+ // began.
+ _pending: null,
+
+ // The default name for the JSON `id` attribute is `"id"`. MongoDB and
+ // CouchDB users may want to set this to `"_id"`.
+ idAttribute: 'id',
+
+ // Initialize is an empty function by default. Override it with your own
+ // initialization logic.
+ initialize: function(){},
+
+ // Return a copy of the model's `attributes` object.
+ toJSON: function(options) {
+ return _.clone(this.attributes);
+ },
+
+ // Get the value of an attribute.
+ get: function(attr) {
+ return this.attributes[attr];
+ },
+
+ // Get the HTML-escaped value of an attribute.
+ escape: function(attr) {
+ var html;
+ if (html = this._escapedAttributes[attr]) return html;
+ var val = this.get(attr);
+ return this._escapedAttributes[attr] = _.escape(val == null ? '' : '' + val);
+ },
+
+ // Returns `true` if the attribute contains a value that is not null
+ // or undefined.
+ has: function(attr) {
+ return this.get(attr) != null;
+ },
+
+ // Set a hash of model attributes on the object, firing `"change"` unless
+ // you choose to silence it.
+ set: function(key, value, options) {
+ var attrs, attr, val;
+
+ // Handle both `"key", value` and `{key: value}` -style arguments.
+ if (_.isObject(key) || key == null) {
+ attrs = key;
+ options = value;
+ } else {
+ attrs = {};
+ attrs[key] = value;
+ }
+
+ // Extract attributes and options.
+ options || (options = {});
+ if (!attrs) return this;
+ if (attrs instanceof Model) attrs = attrs.attributes;
+ if (options.unset) for (attr in attrs) attrs[attr] = void 0;
+
+ // Run validation.
+ if (!this._validate(attrs, options)) return false;
+
+ // Check for changes of `id`.
+ if (this.idAttribute in attrs) this.id = attrs[this.idAttribute];
+
+ var changes = options.changes = {};
+ var now = this.attributes;
+ var escaped = this._escapedAttributes;
+ var prev = this._previousAttributes || {};
+
+ // For each `set` attribute...
+ for (attr in attrs) {
+ val = attrs[attr];
+
+ // If the new and current value differ, record the change.
+ if (!_.isEqual(now[attr], val) || (options.unset && _.has(now, attr))) {
+ delete escaped[attr];
+ (options.silent ? this._silent : changes)[attr] = true;
+ }
+
+ // Update or delete the current value.
+ options.unset ? delete now[attr] : now[attr] = val;
+
+ // If the new and previous value differ, record the change. If not,
+ // then remove changes for this attribute.
+ if (!_.isEqual(prev[attr], val) || (_.has(now, attr) != _.has(prev, attr))) {
+ this.changed[attr] = val;
+ if (!options.silent) this._pending[attr] = true;
+ } else {
+ delete this.changed[attr];
+ delete this._pending[attr];
+ }
+ }
+
+ // Fire the `"change"` events.
+ if (!options.silent) this.change(options);
+ return this;
+ },
+
+ // Remove an attribute from the model, firing `"change"` unless you choose
+ // to silence it. `unset` is a noop if the attribute doesn't exist.
+ unset: function(attr, options) {
+ (options || (options = {})).unset = true;
+ return this.set(attr, null, options);
+ },
+
+ // Clear all attributes on the model, firing `"change"` unless you choose
+ // to silence it.
+ clear: function(options) {
+ (options || (options = {})).unset = true;
+ return this.set(_.clone(this.attributes), options);
+ },
+
+ // Fetch the model from the server. If the server's representation of the
+ // model differs from its current attributes, they will be overriden,
+ // triggering a `"change"` event.
+ fetch: function(options) {
+ options = options ? _.clone(options) : {};
+ var model = this;
+ var success = options.success;
+ options.success = function(resp, status, xhr) {
+ if (!model.set(model.parse(resp, xhr), options)) return false;
+ if (success) success(model, resp);
+ };
+ options.error = Backbone.wrapError(options.error, model, options);
+ return (this.sync || Backbone.sync).call(this, 'read', this, options);
+ },
+
+ // Set a hash of model attributes, and sync the model to the server.
+ // If the server returns an attributes hash that differs, the model's
+ // state will be `set` again.
+ save: function(key, value, options) {
+ var attrs, current;
+
+ // Handle both `("key", value)` and `({key: value})` -style calls.
+ if (_.isObject(key) || key == null) {
+ attrs = key;
+ options = value;
+ } else {
+ attrs = {};
+ attrs[key] = value;
+ }
+ options = options ? _.clone(options) : {};
+
+ // If we're "wait"-ing to set changed attributes, validate early.
+ if (options.wait) {
+ if (!this._validate(attrs, options)) return false;
+ current = _.clone(this.attributes);
+ }
+
+ // Regular saves `set` attributes before persisting to the server.
+ var silentOptions = _.extend({}, options, {silent: true});
+ if (attrs && !this.set(attrs, options.wait ? silentOptions : options)) {
+ return false;
+ }
+
+ // After a successful server-side save, the client is (optionally)
+ // updated with the server-side state.
+ var model = this;
+ var success = options.success;
+ options.success = function(resp, status, xhr) {
+ var serverAttrs = model.parse(resp, xhr);
+ if (options.wait) {
+ delete options.wait;
+ serverAttrs = _.extend(attrs || {}, serverAttrs);
+ }
+ if (!model.set(serverAttrs, options)) return false;
+ if (success) {
+ success(model, resp);
+ } else {
+ model.trigger('sync', model, resp, options);
+ }
+ };
+
+ // Finish configuring and sending the Ajax request.
+ options.error = Backbone.wrapError(options.error, model, options);
+ var method = this.isNew() ? 'create' : 'update';
+ var xhr = (this.sync || Backbone.sync).call(this, method, this, options);
+ if (options.wait) this.set(current, silentOptions);
+ return xhr;
+ },
+
+ // Destroy this model on the server if it was already persisted.
+ // Optimistically removes the model from its collection, if it has one.
+ // If `wait: true` is passed, waits for the server to respond before removal.
+ destroy: function(options) {
+ options = options ? _.clone(options) : {};
+ var model = this;
+ var success = options.success;
+
+ var triggerDestroy = function() {
+ model.trigger('destroy', model, model.collection, options);
+ };
+
+ if (this.isNew()) {
+ triggerDestroy();
+ return false;
+ }
+
+ options.success = function(resp) {
+ if (options.wait) triggerDestroy();
+ if (success) {
+ success(model, resp);
+ } else {
+ model.trigger('sync', model, resp, options);
+ }
+ };
+
+ options.error = Backbone.wrapError(options.error, model, options);
+ var xhr = (this.sync || Backbone.sync).call(this, 'delete', this, options);
+ if (!options.wait) triggerDestroy();
+ return xhr;
+ },
+
+ // Default URL for the model's representation on the server -- if you're
+ // using Backbone's restful methods, override this to change the endpoint
+ // that will be called.
+ url: function() {
+ var base = getValue(this, 'urlRoot') || getValue(this.collection, 'url') || urlError();
+ if (this.isNew()) return base;
+ return base + (base.charAt(base.length - 1) == '/' ? '' : '/') + encodeURIComponent(this.id);
+ },
+
+ // **parse** converts a response into the hash of attributes to be `set` on
+ // the model. The default implementation is just to pass the response along.
+ parse: function(resp, xhr) {
+ return resp;
+ },
+
+ // Create a new model with identical attributes to this one.
+ clone: function() {
+ return new this.constructor(this.attributes);
+ },
+
+ // A model is new if it has never been saved to the server, and lacks an id.
+ isNew: function() {
+ return this.id == null;
+ },
+
+ // Call this method to manually fire a `"change"` event for this model and
+ // a `"change:attribute"` event for each changed attribute.
+ // Calling this will cause all objects observing the model to update.
+ change: function(options) {
+ options || (options = {});
+ var changing = this._changing;
+ this._changing = true;
+
+ // Silent changes become pending changes.
+ for (var attr in this._silent) this._pending[attr] = true;
+
+ // Silent changes are triggered.
+ var changes = _.extend({}, options.changes, this._silent);
+ this._silent = {};
+ for (var attr in changes) {
+ this.trigger('change:' + attr, this, this.get(attr), options);
+ }
+ if (changing) return this;
+
+ // Continue firing `"change"` events while there are pending changes.
+ while (!_.isEmpty(this._pending)) {
+ this._pending = {};
+ this.trigger('change', this, options);
+ // Pending and silent changes still remain.
+ for (var attr in this.changed) {
+ if (this._pending[attr] || this._silent[attr]) continue;
+ delete this.changed[attr];
+ }
+ this._previousAttributes = _.clone(this.attributes);
+ }
+
+ this._changing = false;
+ return this;
+ },
+
+ // Determine if the model has changed since the last `"change"` event.
+ // If you specify an attribute name, determine if that attribute has changed.
+ hasChanged: function(attr) {
+ if (!arguments.length) return !_.isEmpty(this.changed);
+ return _.has(this.changed, attr);
+ },
+
+ // Return an object containing all the attributes that have changed, or
+ // false if there are no changed attributes. Useful for determining what
+ // parts of a view need to be updated and/or what attributes need to be
+ // persisted to the server. Unset attributes will be set to undefined.
+ // You can also pass an attributes object to diff against the model,
+ // determining if there *would be* a change.
+ changedAttributes: function(diff) {
+ if (!diff) return this.hasChanged() ? _.clone(this.changed) : false;
+ var val, changed = false, old = this._previousAttributes;
+ for (var attr in diff) {
+ if (_.isEqual(old[attr], (val = diff[attr]))) continue;
+ (changed || (changed = {}))[attr] = val;
+ }
+ return changed;
+ },
+
+ // Get the previous value of an attribute, recorded at the time the last
+ // `"change"` event was fired.
+ previous: function(attr) {
+ if (!arguments.length || !this._previousAttributes) return null;
+ return this._previousAttributes[attr];
+ },
+
+ // Get all of the attributes of the model at the time of the previous
+ // `"change"` event.
+ previousAttributes: function() {
+ return _.clone(this._previousAttributes);
+ },
+
+ // Check if the model is currently in a valid state. It's only possible to
+ // get into an *invalid* state if you're using silent changes.
+ isValid: function() {
+ return !this.validate(this.attributes);
+ },
+
+ // Run validation against the next complete set of model attributes,
+ // returning `true` if all is well. If a specific `error` callback has
+ // been passed, call that instead of firing the general `"error"` event.
+ _validate: function(attrs, options) {
+ if (options.silent || !this.validate) return true;
+ attrs = _.extend({}, this.attributes, attrs);
+ var error = this.validate(attrs, options);
+ if (!error) return true;
+ if (options && options.error) {
+ options.error(this, error, options);
+ } else {
+ this.trigger('error', this, error, options);
+ }
+ return false;
+ }
+
+ });
+
+ // Backbone.Collection
+ // -------------------
+
+ // Provides a standard collection class for our sets of models, ordered
+ // or unordered. If a `comparator` is specified, the Collection will maintain
+ // its models in sort order, as they're added and removed.
+ var Collection = Backbone.Collection = function(models, options) {
+ options || (options = {});
+ if (options.model) this.model = options.model;
+ if (options.comparator) this.comparator = options.comparator;
+ this._reset();
+ this.initialize.apply(this, arguments);
+ if (models) this.reset(models, {silent: true, parse: options.parse});
+ };
+
+ // Define the Collection's inheritable methods.
+ _.extend(Collection.prototype, Events, {
+
+ // The default model for a collection is just a **Backbone.Model**.
+ // This should be overridden in most cases.
+ model: Model,
+
+ // Initialize is an empty function by default. Override it with your own
+ // initialization logic.
+ initialize: function(){},
+
+ // The JSON representation of a Collection is an array of the
+ // models' attributes.
+ toJSON: function(options) {
+ return this.map(function(model){ return model.toJSON(options); });
+ },
+
+ // Add a model, or list of models to the set. Pass **silent** to avoid
+ // firing the `add` event for every new model.
+ add: function(models, options) {
+ var i, index, length, model, cid, id, cids = {}, ids = {}, dups = [];
+ options || (options = {});
+ models = _.isArray(models) ? models.slice() : [models];
+
+ // Begin by turning bare objects into model references, and preventing
+ // invalid models or duplicate models from being added.
+ for (i = 0, length = models.length; i < length; i++) {
+ if (!(model = models[i] = this._prepareModel(models[i], options))) {
+ throw new Error("Can't add an invalid model to a collection");
+ }
+ cid = model.cid;
+ id = model.id;
+ if (cids[cid] || this._byCid[cid] || ((id != null) && (ids[id] || this._byId[id]))) {
+ dups.push(i);
+ continue;
+ }
+ cids[cid] = ids[id] = model;
+ }
+
+ // Remove duplicates.
+ i = dups.length;
+ while (i--) {
+ models.splice(dups[i], 1);
+ }
+
+ // Listen to added models' events, and index models for lookup by
+ // `id` and by `cid`.
+ for (i = 0, length = models.length; i < length; i++) {
+ (model = models[i]).on('all', this._onModelEvent, this);
+ this._byCid[model.cid] = model;
+ if (model.id != null) this._byId[model.id] = model;
+ }
+
+ // Insert models into the collection, re-sorting if needed, and triggering
+ // `add` events unless silenced.
+ this.length += length;
+ index = options.at != null ? options.at : this.models.length;
+ splice.apply(this.models, [index, 0].concat(models));
+ if (this.comparator) this.sort({silent: true});
+ if (options.silent) return this;
+ for (i = 0, length = this.models.length; i < length; i++) {
+ if (!cids[(model = this.models[i]).cid]) continue;
+ options.index = i;
+ model.trigger('add', model, this, options);
+ }
+ return this;
+ },
+
+ // Remove a model, or a list of models from the set. Pass silent to avoid
+ // firing the `remove` event for every model removed.
+ remove: function(models, options) {
+ var i, l, index, model;
+ options || (options = {});
+ models = _.isArray(models) ? models.slice() : [models];
+ for (i = 0, l = models.length; i < l; i++) {
+ model = this.getByCid(models[i]) || this.get(models[i]);
+ if (!model) continue;
+ delete this._byId[model.id];
+ delete this._byCid[model.cid];
+ index = this.indexOf(model);
+ this.models.splice(index, 1);
+ this.length--;
+ if (!options.silent) {
+ options.index = index;
+ model.trigger('remove', model, this, options);
+ }
+ this._removeReference(model);
+ }
+ return this;
+ },
+
+ // Add a model to the end of the collection.
+ push: function(model, options) {
+ model = this._prepareModel(model, options);
+ this.add(model, options);
+ return model;
+ },
+
+ // Remove a model from the end of the collection.
+ pop: function(options) {
+ var model = this.at(this.length - 1);
+ this.remove(model, options);
+ return model;
+ },
+
+ // Add a model to the beginning of the collection.
+ unshift: function(model, options) {
+ model = this._prepareModel(model, options);
+ this.add(model, _.extend({at: 0}, options));
+ return model;
+ },
+
+ // Remove a model from the beginning of the collection.
+ shift: function(options) {
+ var model = this.at(0);
+ this.remove(model, options);
+ return model;
+ },
+
+ // Get a model from the set by id.
+ get: function(id) {
+ if (id == null) return void 0;
+ return this._byId[id.id != null ? id.id : id];
+ },
+
+ // Get a model from the set by client id.
+ getByCid: function(cid) {
+ return cid && this._byCid[cid.cid || cid];
+ },
+
+ // Get the model at the given index.
+ at: function(index) {
+ return this.models[index];
+ },
+
+ // Return models with matching attributes. Useful for simple cases of `filter`.
+ where: function(attrs) {
+ if (_.isEmpty(attrs)) return [];
+ return this.filter(function(model) {
+ for (var key in attrs) {
+ if (attrs[key] !== model.get(key)) return false;
+ }
+ return true;
+ });
+ },
+
+ // Force the collection to re-sort itself. You don't need to call this under
+ // normal circumstances, as the set will maintain sort order as each item
+ // is added.
+ sort: function(options) {
+ options || (options = {});
+ if (!this.comparator) throw new Error('Cannot sort a set without a comparator');
+ var boundComparator = _.bind(this.comparator, this);
+ if (this.comparator.length == 1) {
+ this.models = this.sortBy(boundComparator);
+ } else {
+ this.models.sort(boundComparator);
+ }
+ if (!options.silent) this.trigger('reset', this, options);
+ return this;
+ },
+
+ // Pluck an attribute from each model in the collection.
+ pluck: function(attr) {
+ return _.map(this.models, function(model){ return model.get(attr); });
+ },
+
+ // When you have more items than you want to add or remove individually,
+ // you can reset the entire set with a new list of models, without firing
+ // any `add` or `remove` events. Fires `reset` when finished.
+ reset: function(models, options) {
+ models || (models = []);
+ options || (options = {});
+ for (var i = 0, l = this.models.length; i < l; i++) {
+ this._removeReference(this.models[i]);
+ }
+ this._reset();
+ this.add(models, _.extend({silent: true}, options));
+ if (!options.silent) this.trigger('reset', this, options);
+ return this;
+ },
+
+ // Fetch the default set of models for this collection, resetting the
+ // collection when they arrive. If `add: true` is passed, appends the
+ // models to the collection instead of resetting.
+ fetch: function(options) {
+ options = options ? _.clone(options) : {};
+ if (options.parse === undefined) options.parse = true;
+ var collection = this;
+ var success = options.success;
+ options.success = function(resp, status, xhr) {
+ collection[options.add ? 'add' : 'reset'](collection.parse(resp, xhr), options);
+ if (success) success(collection, resp);
+ };
+ options.error = Backbone.wrapError(options.error, collection, options);
+ return (this.sync || Backbone.sync).call(this, 'read', this, options);
+ },
+
+ // Create a new instance of a model in this collection. Add the model to the
+ // collection immediately, unless `wait: true` is passed, in which case we
+ // wait for the server to agree.
+ create: function(model, options) {
+ var coll = this;
+ options = options ? _.clone(options) : {};
+ model = this._prepareModel(model, options);
+ if (!model) return false;
+ if (!options.wait) coll.add(model, options);
+ var success = options.success;
+ options.success = function(nextModel, resp, xhr) {
+ if (options.wait) coll.add(nextModel, options);
+ if (success) {
+ success(nextModel, resp);
+ } else {
+ nextModel.trigger('sync', model, resp, options);
+ }
+ };
+ model.save(null, options);
+ return model;
+ },
+
+ // **parse** converts a response into a list of models to be added to the
+ // collection. The default implementation is just to pass it through.
+ parse: function(resp, xhr) {
+ return resp;
+ },
+
+ // Proxy to _'s chain. Can't be proxied the same way the rest of the
+ // underscore methods are proxied because it relies on the underscore
+ // constructor.
+ chain: function () {
+ return _(this.models).chain();
+ },
+
+ // Reset all internal state. Called when the collection is reset.
+ _reset: function(options) {
+ this.length = 0;
+ this.models = [];
+ this._byId = {};
+ this._byCid = {};
+ },
+
+ // Prepare a model or hash of attributes to be added to this collection.
+ _prepareModel: function(model, options) {
+ options || (options = {});
+ if (!(model instanceof Model)) {
+ var attrs = model;
+ options.collection = this;
+ model = new this.model(attrs, options);
+ if (!model._validate(model.attributes, options)) model = false;
+ } else if (!model.collection) {
+ model.collection = this;
+ }
+ return model;
+ },
+
+ // Internal method to remove a model's ties to a collection.
+ _removeReference: function(model) {
+ if (this == model.collection) {
+ delete model.collection;
+ }
+ model.off('all', this._onModelEvent, this);
+ },
+
+ // Internal method called every time a model in the set fires an event.
+ // Sets need to update their indexes when models change ids. All other
+ // events simply proxy through. "add" and "remove" events that originate
+ // in other collections are ignored.
+ _onModelEvent: function(event, model, collection, options) {
+ if ((event == 'add' || event == 'remove') && collection != this) return;
+ if (event == 'destroy') {
+ this.remove(model, options);
+ }
+ if (model && event === 'change:' + model.idAttribute) {
+ delete this._byId[model.previous(model.idAttribute)];
+ this._byId[model.id] = model;
+ }
+ this.trigger.apply(this, arguments);
+ }
+
+ });
+
+ // Underscore methods that we want to implement on the Collection.
+ var methods = ['forEach', 'each', 'map', 'reduce', 'reduceRight', 'find',
+ 'detect', 'filter', 'select', 'reject', 'every', 'all', 'some', 'any',
+ 'include', 'contains', 'invoke', 'max', 'min', 'sortBy', 'sortedIndex',
+ 'toArray', 'size', 'first', 'initial', 'rest', 'last', 'without', 'indexOf',
+ 'shuffle', 'lastIndexOf', 'isEmpty', 'groupBy'];
+
+ // Mix in each Underscore method as a proxy to `Collection#models`.
+ _.each(methods, function(method) {
+ Collection.prototype[method] = function() {
+ return _[method].apply(_, [this.models].concat(_.toArray(arguments)));
+ };
+ });
+
+ // Backbone.Router
+ // -------------------
+
+ // Routers map faux-URLs to actions, and fire events when routes are
+ // matched. Creating a new one sets its `routes` hash, if not set statically.
+ var Router = Backbone.Router = function(options) {
+ options || (options = {});
+ if (options.routes) this.routes = options.routes;
+ this._bindRoutes();
+ this.initialize.apply(this, arguments);
+ };
+
+ // Cached regular expressions for matching named param parts and splatted
+ // parts of route strings.
+ var namedParam = /:\w+/g;
+ var splatParam = /\*\w+/g;
+ var escapeRegExp = /[-[\]{}()+?.,\\^$|#\s]/g;
+
+ // Set up all inheritable **Backbone.Router** properties and methods.
+ _.extend(Router.prototype, Events, {
+
+ // Initialize is an empty function by default. Override it with your own
+ // initialization logic.
+ initialize: function(){},
+
+ // Manually bind a single named route to a callback. For example:
+ //
+ // this.route('search/:query/p:num', 'search', function(query, num) {
+ // ...
+ // });
+ //
+ route: function(route, name, callback) {
+ Backbone.history || (Backbone.history = new History);
+ if (!_.isRegExp(route)) route = this._routeToRegExp(route);
+ if (!callback) callback = this[name];
+ Backbone.history.route(route, _.bind(function(fragment) {
+ var args = this._extractParameters(route, fragment);
+ callback && callback.apply(this, args);
+ this.trigger.apply(this, ['route:' + name].concat(args));
+ Backbone.history.trigger('route', this, name, args);
+ }, this));
+ return this;
+ },
+
+ // Simple proxy to `Backbone.history` to save a fragment into the history.
+ navigate: function(fragment, options) {
+ Backbone.history.navigate(fragment, options);
+ },
+
+ // Bind all defined routes to `Backbone.history`. We have to reverse the
+ // order of the routes here to support behavior where the most general
+ // routes can be defined at the bottom of the route map.
+ _bindRoutes: function() {
+ if (!this.routes) return;
+ var routes = [];
+ for (var route in this.routes) {
+ routes.unshift([route, this.routes[route]]);
+ }
+ for (var i = 0, l = routes.length; i < l; i++) {
+ this.route(routes[i][0], routes[i][1], this[routes[i][1]]);
+ }
+ },
+
+ // Convert a route string into a regular expression, suitable for matching
+ // against the current location hash.
+ _routeToRegExp: function(route) {
+ route = route.replace(escapeRegExp, '\\$&')
+ .replace(namedParam, '([^\/]+)')
+ .replace(splatParam, '(.*?)');
+ return new RegExp('^' + route + '$');
+ },
+
+ // Given a route, and a URL fragment that it matches, return the array of
+ // extracted parameters.
+ _extractParameters: function(route, fragment) {
+ return route.exec(fragment).slice(1);
+ }
+
+ });
+
+ // Backbone.History
+ // ----------------
+
+ // Handles cross-browser history management, based on URL fragments. If the
+ // browser does not support `onhashchange`, falls back to polling.
+ var History = Backbone.History = function() {
+ this.handlers = [];
+ _.bindAll(this, 'checkUrl');
+ };
+
+ // Cached regex for cleaning leading hashes and slashes .
+ var routeStripper = /^[#\/]/;
+
+ // Cached regex for detecting MSIE.
+ var isExplorer = /msie [\w.]+/;
+
+ // Has the history handling already been started?
+ History.started = false;
+
+ // Set up all inheritable **Backbone.History** properties and methods.
+ _.extend(History.prototype, Events, {
+
+ // The default interval to poll for hash changes, if necessary, is
+ // twenty times a second.
+ interval: 50,
+
+ // Gets the true hash value. Cannot use location.hash directly due to bug
+ // in Firefox where location.hash will always be decoded.
+ getHash: function(windowOverride) {
+ var loc = windowOverride ? windowOverride.location : window.location;
+ var match = loc.href.match(/#(.*)$/);
+ return match ? match[1] : '';
+ },
+
+ // Get the cross-browser normalized URL fragment, either from the URL,
+ // the hash, or the override.
+ getFragment: function(fragment, forcePushState) {
+ if (fragment == null) {
+ if (this._hasPushState || forcePushState) {
+ fragment = window.location.pathname;
+ var search = window.location.search;
+ if (search) fragment += search;
+ } else {
+ fragment = this.getHash();
+ }
+ }
+ if (!fragment.indexOf(this.options.root)) fragment = fragment.substr(this.options.root.length);
+ return fragment.replace(routeStripper, '');
+ },
+
+ // Start the hash change handling, returning `true` if the current URL matches
+ // an existing route, and `false` otherwise.
+ start: function(options) {
+ if (History.started) throw new Error("Backbone.history has already been started");
+ History.started = true;
+
+ // Figure out the initial configuration. Do we need an iframe?
+ // Is pushState desired ... is it available?
+ this.options = _.extend({}, {root: '/'}, this.options, options);
+ this._wantsHashChange = this.options.hashChange !== false;
+ this._wantsPushState = !!this.options.pushState;
+ this._hasPushState = !!(this.options.pushState && window.history && window.history.pushState);
+ var fragment = this.getFragment();
+ var docMode = document.documentMode;
+ var oldIE = (isExplorer.exec(navigator.userAgent.toLowerCase()) && (!docMode || docMode <= 7));
+
+ if (oldIE) {
+ this.iframe = $('').hide().appendTo('body')[0].contentWindow;
+ this.navigate(fragment);
+ }
+
+ // Depending on whether we're using pushState or hashes, and whether
+ // 'onhashchange' is supported, determine how we check the URL state.
+ if (this._hasPushState) {
+ $(window).bind('popstate', this.checkUrl);
+ } else if (this._wantsHashChange && ('onhashchange' in window) && !oldIE) {
+ $(window).bind('hashchange', this.checkUrl);
+ } else if (this._wantsHashChange) {
+ this._checkUrlInterval = setInterval(this.checkUrl, this.interval);
+ }
+
+ // Determine if we need to change the base url, for a pushState link
+ // opened by a non-pushState browser.
+ this.fragment = fragment;
+ var loc = window.location;
+ var atRoot = loc.pathname == this.options.root;
+
+ // If we've started off with a route from a `pushState`-enabled browser,
+ // but we're currently in a browser that doesn't support it...
+ if (this._wantsHashChange && this._wantsPushState && !this._hasPushState && !atRoot) {
+ this.fragment = this.getFragment(null, true);
+ window.location.replace(this.options.root + '#' + this.fragment);
+ // Return immediately as browser will do redirect to new url
+ return true;
+
+ // Or if we've started out with a hash-based route, but we're currently
+ // in a browser where it could be `pushState`-based instead...
+ } else if (this._wantsPushState && this._hasPushState && atRoot && loc.hash) {
+ this.fragment = this.getHash().replace(routeStripper, '');
+ window.history.replaceState({}, document.title, loc.protocol + '//' + loc.host + this.options.root + this.fragment);
+ }
+
+ if (!this.options.silent) {
+ return this.loadUrl();
+ }
+ },
+
+ // Disable Backbone.history, perhaps temporarily. Not useful in a real app,
+ // but possibly useful for unit testing Routers.
+ stop: function() {
+ $(window).unbind('popstate', this.checkUrl).unbind('hashchange', this.checkUrl);
+ clearInterval(this._checkUrlInterval);
+ History.started = false;
+ },
+
+ // Add a route to be tested when the fragment changes. Routes added later
+ // may override previous routes.
+ route: function(route, callback) {
+ this.handlers.unshift({route: route, callback: callback});
+ },
+
+ // Checks the current URL to see if it has changed, and if it has,
+ // calls `loadUrl`, normalizing across the hidden iframe.
+ checkUrl: function(e) {
+ var current = this.getFragment();
+ if (current == this.fragment && this.iframe) current = this.getFragment(this.getHash(this.iframe));
+ if (current == this.fragment) return false;
+ if (this.iframe) this.navigate(current);
+ this.loadUrl() || this.loadUrl(this.getHash());
+ },
+
+ // Attempt to load the current URL fragment. If a route succeeds with a
+ // match, returns `true`. If no defined routes matches the fragment,
+ // returns `false`.
+ loadUrl: function(fragmentOverride) {
+ var fragment = this.fragment = this.getFragment(fragmentOverride);
+ var matched = _.any(this.handlers, function(handler) {
+ if (handler.route.test(fragment)) {
+ handler.callback(fragment);
+ return true;
+ }
+ });
+ return matched;
+ },
+
+ // Save a fragment into the hash history, or replace the URL state if the
+ // 'replace' option is passed. You are responsible for properly URL-encoding
+ // the fragment in advance.
+ //
+ // The options object can contain `trigger: true` if you wish to have the
+ // route callback be fired (not usually desirable), or `replace: true`, if
+ // you wish to modify the current URL without adding an entry to the history.
+ navigate: function(fragment, options) {
+ if (!History.started) return false;
+ if (!options || options === true) options = {trigger: options};
+ var frag = (fragment || '').replace(routeStripper, '');
+ if (this.fragment == frag) return;
+
+ // If pushState is available, we use it to set the fragment as a real URL.
+ if (this._hasPushState) {
+ if (frag.indexOf(this.options.root) != 0) frag = this.options.root + frag;
+ this.fragment = frag;
+ window.history[options.replace ? 'replaceState' : 'pushState']({}, document.title, frag);
+
+ // If hash changes haven't been explicitly disabled, update the hash
+ // fragment to store history.
+ } else if (this._wantsHashChange) {
+ this.fragment = frag;
+ this._updateHash(window.location, frag, options.replace);
+ if (this.iframe && (frag != this.getFragment(this.getHash(this.iframe)))) {
+ // Opening and closing the iframe tricks IE7 and earlier to push a history entry on hash-tag change.
+ // When replace is true, we don't want this.
+ if(!options.replace) this.iframe.document.open().close();
+ this._updateHash(this.iframe.location, frag, options.replace);
+ }
+
+ // If you've told us that you explicitly don't want fallback hashchange-
+ // based history, then `navigate` becomes a page refresh.
+ } else {
+ window.location.assign(this.options.root + fragment);
+ }
+ if (options.trigger) this.loadUrl(fragment);
+ },
+
+ // Update the hash location, either replacing the current entry, or adding
+ // a new one to the browser history.
+ _updateHash: function(location, fragment, replace) {
+ if (replace) {
+ location.replace(location.toString().replace(/(javascript:|#).*$/, '') + '#' + fragment);
+ } else {
+ location.hash = fragment;
+ }
+ }
+ });
+
+ // Backbone.View
+ // -------------
+
+ // Creating a Backbone.View creates its initial element outside of the DOM,
+ // if an existing element is not provided...
+ var View = Backbone.View = function(options) {
+ this.cid = _.uniqueId('view');
+ this._configure(options || {});
+ this._ensureElement();
+ this.initialize.apply(this, arguments);
+ this.delegateEvents();
+ };
+
+ // Cached regex to split keys for `delegate`.
+ var delegateEventSplitter = /^(\S+)\s*(.*)$/;
+
+ // List of view options to be merged as properties.
+ var viewOptions = ['model', 'collection', 'el', 'id', 'attributes', 'className', 'tagName'];
+
+ // Set up all inheritable **Backbone.View** properties and methods.
+ _.extend(View.prototype, Events, {
+
+ // The default `tagName` of a View's element is `"div"`.
+ tagName: 'div',
+
+ // jQuery delegate for element lookup, scoped to DOM elements within the
+ // current view. This should be prefered to global lookups where possible.
+ $: function(selector) {
+ return this.$el.find(selector);
+ },
+
+ // Initialize is an empty function by default. Override it with your own
+ // initialization logic.
+ initialize: function(){},
+
+ // **render** is the core function that your view should override, in order
+ // to populate its element (`this.el`), with the appropriate HTML. The
+ // convention is for **render** to always return `this`.
+ render: function() {
+ return this;
+ },
+
+ // Remove this view from the DOM. Note that the view isn't present in the
+ // DOM by default, so calling this method may be a no-op.
+ remove: function() {
+ this.$el.remove();
+ return this;
+ },
+
+ // For small amounts of DOM Elements, where a full-blown template isn't
+ // needed, use **make** to manufacture elements, one at a time.
+ //
+ // var el = this.make('li', {'class': 'row'}, this.model.escape('title'));
+ //
+ make: function(tagName, attributes, content) {
+ var el = document.createElement(tagName);
+ if (attributes) $(el).attr(attributes);
+ if (content) $(el).html(content);
+ return el;
+ },
+
+ // Change the view's element (`this.el` property), including event
+ // re-delegation.
+ setElement: function(element, delegate) {
+ if (this.$el) this.undelegateEvents();
+ this.$el = (element instanceof $) ? element : $(element);
+ this.el = this.$el[0];
+ if (delegate !== false) this.delegateEvents();
+ return this;
+ },
+
+ // Set callbacks, where `this.events` is a hash of
+ //
+ // *{"event selector": "callback"}*
+ //
+ // {
+ // 'mousedown .title': 'edit',
+ // 'click .button': 'save'
+ // 'click .open': function(e) { ... }
+ // }
+ //
+ // pairs. Callbacks will be bound to the view, with `this` set properly.
+ // Uses event delegation for efficiency.
+ // Omitting the selector binds the event to `this.el`.
+ // This only works for delegate-able events: not `focus`, `blur`, and
+ // not `change`, `submit`, and `reset` in Internet Explorer.
+ delegateEvents: function(events) {
+ if (!(events || (events = getValue(this, 'events')))) return;
+ this.undelegateEvents();
+ for (var key in events) {
+ var method = events[key];
+ if (!_.isFunction(method)) method = this[events[key]];
+ if (!method) throw new Error('Method "' + events[key] + '" does not exist');
+ var match = key.match(delegateEventSplitter);
+ var eventName = match[1], selector = match[2];
+ method = _.bind(method, this);
+ eventName += '.delegateEvents' + this.cid;
+ if (selector === '') {
+ this.$el.bind(eventName, method);
+ } else {
+ this.$el.delegate(selector, eventName, method);
+ }
+ }
+ },
+
+ // Clears all callbacks previously bound to the view with `delegateEvents`.
+ // You usually don't need to use this, but may wish to if you have multiple
+ // Backbone views attached to the same DOM element.
+ undelegateEvents: function() {
+ this.$el.unbind('.delegateEvents' + this.cid);
+ },
+
+ // Performs the initial configuration of a View with a set of options.
+ // Keys with special meaning *(model, collection, id, className)*, are
+ // attached directly to the view.
+ _configure: function(options) {
+ if (this.options) options = _.extend({}, this.options, options);
+ for (var i = 0, l = viewOptions.length; i < l; i++) {
+ var attr = viewOptions[i];
+ if (options[attr]) this[attr] = options[attr];
+ }
+ this.options = options;
+ },
+
+ // Ensure that the View has a DOM element to render into.
+ // If `this.el` is a string, pass it through `$()`, take the first
+ // matching element, and re-assign it to `el`. Otherwise, create
+ // an element from the `id`, `className` and `tagName` properties.
+ _ensureElement: function() {
+ if (!this.el) {
+ var attrs = getValue(this, 'attributes') || {};
+ if (this.id) attrs.id = this.id;
+ if (this.className) attrs['class'] = this.className;
+ this.setElement(this.make(this.tagName, attrs), false);
+ } else {
+ this.setElement(this.el, false);
+ }
+ }
+
+ });
+
+ // The self-propagating extend function that Backbone classes use.
+ var extend = function (protoProps, classProps) {
+ var child = inherits(this, protoProps, classProps);
+ child.extend = this.extend;
+ return child;
+ };
+
+ // Set up inheritance for the model, collection, and view.
+ Model.extend = Collection.extend = Router.extend = View.extend = extend;
+
+ // Backbone.sync
+ // -------------
+
+ // Map from CRUD to HTTP for our default `Backbone.sync` implementation.
+ var methodMap = {
+ 'create': 'POST',
+ 'update': 'PUT',
+ 'delete': 'DELETE',
+ 'read': 'GET'
+ };
+
+ // Override this function to change the manner in which Backbone persists
+ // models to the server. You will be passed the type of request, and the
+ // model in question. By default, makes a RESTful Ajax request
+ // to the model's `url()`. Some possible customizations could be:
+ //
+ // * Use `setTimeout` to batch rapid-fire updates into a single request.
+ // * Send up the models as XML instead of JSON.
+ // * Persist models via WebSockets instead of Ajax.
+ //
+ // Turn on `Backbone.emulateHTTP` in order to send `PUT` and `DELETE` requests
+ // as `POST`, with a `_method` parameter containing the true HTTP method,
+ // as well as all requests with the body as `application/x-www-form-urlencoded`
+ // instead of `application/json` with the model in a param named `model`.
+ // Useful when interfacing with server-side languages like **PHP** that make
+ // it difficult to read the body of `PUT` requests.
+ Backbone.sync = function(method, model, options) {
+ var type = methodMap[method];
+
+ // Default options, unless specified.
+ options || (options = {});
+
+ // Default JSON-request options.
+ var params = {type: type, dataType: 'json'};
+
+ // Ensure that we have a URL.
+ if (!options.url) {
+ params.url = getValue(model, 'url') || urlError();
+ }
+
+ // Ensure that we have the appropriate request data.
+ if (!options.data && model && (method == 'create' || method == 'update')) {
+ params.contentType = 'application/json';
+ params.data = JSON.stringify(model.toJSON());
+ }
+
+ // For older servers, emulate JSON by encoding the request into an HTML-form.
+ if (Backbone.emulateJSON) {
+ params.contentType = 'application/x-www-form-urlencoded';
+ params.data = params.data ? {model: params.data} : {};
+ }
+
+ // For older servers, emulate HTTP by mimicking the HTTP method with `_method`
+ // And an `X-HTTP-Method-Override` header.
+ if (Backbone.emulateHTTP) {
+ if (type === 'PUT' || type === 'DELETE') {
+ if (Backbone.emulateJSON) params.data._method = type;
+ params.type = 'POST';
+ params.beforeSend = function(xhr) {
+ xhr.setRequestHeader('X-HTTP-Method-Override', type);
+ };
+ }
+ }
+
+ // Don't process data on a non-GET request.
+ if (params.type !== 'GET' && !Backbone.emulateJSON) {
+ params.processData = false;
+ }
+
+ // Make the request, allowing the user to override any Ajax options.
+ return $.ajax(_.extend(params, options));
+ };
+
+ // Wrap an optional error callback with a fallback error event.
+ Backbone.wrapError = function(onError, originalModel, options) {
+ return function(model, resp) {
+ resp = model === originalModel ? resp : model;
+ if (onError) {
+ onError(originalModel, resp, options);
+ } else {
+ originalModel.trigger('error', originalModel, resp, options);
+ }
+ };
+ };
+
+ // Helpers
+ // -------
+
+ // Shared empty constructor function to aid in prototype-chain creation.
+ var ctor = function(){};
+
+ // Helper function to correctly set up the prototype chain, for subclasses.
+ // Similar to `goog.inherits`, but uses a hash of prototype properties and
+ // class properties to be extended.
+ var inherits = function(parent, protoProps, staticProps) {
+ var child;
+
+ // The constructor function for the new subclass is either defined by you
+ // (the "constructor" property in your `extend` definition), or defaulted
+ // by us to simply call the parent's constructor.
+ if (protoProps && protoProps.hasOwnProperty('constructor')) {
+ child = protoProps.constructor;
+ } else {
+ child = function(){ parent.apply(this, arguments); };
+ }
+
+ // Inherit class (static) properties from parent.
+ _.extend(child, parent);
+
+ // Set the prototype chain to inherit from `parent`, without calling
+ // `parent`'s constructor function.
+ ctor.prototype = parent.prototype;
+ child.prototype = new ctor();
+
+ // Add prototype properties (instance properties) to the subclass,
+ // if supplied.
+ if (protoProps) _.extend(child.prototype, protoProps);
+
+ // Add static properties to the constructor function, if supplied.
+ if (staticProps) _.extend(child, staticProps);
+
+ // Correctly set child's `prototype.constructor`.
+ child.prototype.constructor = child;
+
+ // Set a convenience property in case the parent's prototype is needed later.
+ child.__super__ = parent.prototype;
+
+ return child;
+ };
+
+ // Helper function to get a value from a Backbone object as a property
+ // or as a function.
+ var getValue = function(object, prop) {
+ if (!(object && object[prop])) return null;
+ return _.isFunction(object[prop]) ? object[prop]() : object[prop];
+ };
+
+ // Throw an error when a URL is needed, and none is supplied.
+ var urlError = function() {
+ throw new Error('A "url" property or function must be specified');
+ };
+
+}).call(this);
diff --git a/assets/js/libs/crafty.js b/assets/js/libs/crafty.js
new file mode 100644
index 0000000..d2dc50e
--- /dev/null
+++ b/assets/js/libs/crafty.js
@@ -0,0 +1,8976 @@
+/*!
+* Crafty v0.4.7
+* http://craftyjs.com
+*
+* Copyright 2010, Louis Stowasser
+* Dual licensed under the MIT or GPL licenses.
+*/
+ (function (window, undefined) {
+
+ /**@
+* #Crafty
+* @category Core
+* Select a set of or single entities by components or an entity's ID.
+*
+* Crafty uses syntax similar to jQuery by having a selector engine to select entities by their components.
+*
+* @example
+* ~~~
+* Crafty("MyComponent")
+* Crafty("Hello 2D Component")
+* Crafty("Hello, 2D, Component")
+* ~~~
+* The first selector will return all entities that has the component `MyComponent`. The second will return all entities that has `Hello` and `2D` and `Component` whereas the last will return all entities that has at least one of those components (or).
+* ~~~
+* Crafty(1)
+* ~~~
+* Passing an integer will select the entity with that `ID`.
+*
+* Finding out the `ID` of an entity can be done by returning the property `0`.
+* ~~~
+* var ent = Crafty.e("2D");
+* ent[0]; //ID
+* ~~~
+*/
+ var Crafty = function (selector) {
+ return new Crafty.fn.init(selector);
+ },
+
+ GUID = 1, //GUID for entity IDs
+ FPS = 50,
+ frame = 1,
+
+ components = {}, //map of components and their functions
+ entities = {}, //map of entities and their data
+ handlers = {}, //global event handlers
+ onloads = [], //temporary storage of onload handlers
+ tick,
+
+ /*
+ * `window.requestAnimationFrame` or its variants is called for animation.
+ * `.requestID` keeps a record of the return value previous `window.requestAnimationFrame` call.
+ * This is an internal variable. Used to stop frame.
+ */
+ requestID,
+
+ noSetter,
+
+ loops = 0,
+ milliSecPerFrame = 1000 / FPS,
+ nextGameTick = (new Date).getTime(),
+
+ slice = Array.prototype.slice,
+ rlist = /\s*,\s*/,
+ rspace = /\s+/;
+
+ /**@
+* #Crafty Core
+* @category Core
+* @trigger NewComponent - when a new component is added to the entity - String - Component
+* @trigger RemoveComponent - when a component is removed from the entity - String - Component
+* @trigger Remove - when the entity is removed by calling .destroy()
+* Set of methods added to every single entity.
+*/
+ Crafty.fn = Crafty.prototype = {
+
+ init: function (selector) {
+ //select entities by component
+ if (typeof selector === "string") {
+ var elem = 0, //index elements
+ e, //entity forEach
+ current,
+ and = false, //flags for multiple
+ or = false,
+ del,
+ comps,
+ score,
+ i, l;
+
+ if (selector === '*') {
+ for (e in entities) {
+ this[+e] = entities[e];
+ elem++;
+ }
+ this.length = elem;
+ return this;
+ }
+
+ //multiple components OR
+ if (selector.indexOf(',') !== -1) {
+ or = true;
+ del = rlist;
+ //deal with multiple components AND
+ } else if (selector.indexOf(' ') !== -1) {
+ and = true;
+ del = rspace;
+ }
+
+ //loop over entities
+ for (e in entities) {
+ if (!entities.hasOwnProperty(e)) continue; //skip
+ current = entities[e];
+
+ if (and || or) { //multiple components
+ comps = selector.split(del);
+ i = 0;
+ l = comps.length;
+ score = 0;
+
+ for (; i < l; i++) //loop over components
+ if (current.__c[comps[i]]) score++; //if component exists add to score
+
+ //if anded comps and has all OR ored comps and at least 1
+ if (and && score === l || or && score > 0) this[elem++] = +e;
+
+ } else if (current.__c[selector]) this[elem++] = +e; //convert to int
+ }
+
+ //extend all common components
+ if (elem > 0 && !and && !or) this.extend(components[selector]);
+ if (comps && and) for (i = 0; i < l; i++) this.extend(components[comps[i]]);
+
+ this.length = elem; //length is the last index (already incremented)
+
+ } else { //Select a specific entity
+
+ if (!selector) { //nothin passed creates God entity
+ selector = 0;
+ if (!(selector in entities)) entities[selector] = this;
+ }
+
+ //if not exists, return undefined
+ if (!(selector in entities)) {
+ this.length = 0;
+ return this;
+ }
+
+ this[0] = selector;
+ this.length = 1;
+
+ //update from the cache
+ if (!this.__c) this.__c = {};
+
+ //update to the cache if NULL
+ if (!entities[selector]) entities[selector] = this;
+ return entities[selector]; //return the cached selector
+ }
+
+ return this;
+ },
+
+ /**@
+ * #.addComponent
+ * @comp Crafty Core
+ * @sign public this .addComponent(String componentList)
+ * @param componentList - A string of components to add seperated by a comma `,`
+ * @sign public this .addComponent(String Component1[, .., String ComponentN])
+ * @param Component# - Component ID to add.
+ * Adds a component to the selected entities or entity.
+ *
+ * Components are used to extend the functionality of entities.
+ * This means it will copy properties and assign methods to
+ * augment the functionality of the entity.
+ *
+ * There are multiple methods of adding components. Passing a
+ * string with a list of component names or passing multiple
+ * arguments with the component names.
+ *
+ * If the component has a function named `init` it will be called.
+ *
+ * @example
+ * ~~~
+ * this.addComponent("2D, Canvas");
+ * this.addComponent("2D", "Canvas");
+ * ~~~
+ */
+ addComponent: function (id) {
+ var uninit = [], c = 0, ul, //array of components to init
+ i = 0, l, comps;
+
+ //add multiple arguments
+ if (arguments.length > 1) {
+ l = arguments.length;
+ for (; i < l; i++) {
+ this.__c[arguments[i]] = true;
+ uninit.push(arguments[i]);
+ }
+ //split components if contains comma
+ } else if (id.indexOf(',') !== -1) {
+ comps = id.split(rlist);
+ l = comps.length;
+ for (; i < l; i++) {
+ this.__c[comps[i]] = true;
+ uninit.push(comps[i]);
+ }
+ //single component passed
+ } else {
+ this.__c[id] = true;
+ uninit.push(id);
+ }
+
+ //extend the components
+ ul = uninit.length;
+ for (; c < ul; c++) {
+ comp = components[uninit[c]];
+ this.extend(comp);
+
+ //if constructor, call it
+ if (comp && "init" in comp) {
+ comp.init.call(this);
+ }
+ }
+
+ this.trigger("NewComponent", ul);
+ return this;
+ },
+ /**@
+ * @comp Crafty Core
+ * @sign public this. toggleComponent(String componentID,String componentToggle)
+ * @param componentID - Component ID to add or remove.
+ * @param componentToggle - Component ID to replace instead of remove
+ * Add or Remove Components
+ * @example
+ * ~~~
+ * var e = Crafty.e("2D,DOM,Test");
+ * e.toggleComponent("Test,Test2"); //Remove Test add Test2 and vice versa
+ * ~~~
+ */
+ toggleComponent:function(toggle){
+ var i = 0, l, comps;
+ if (arguments.length > 1) {
+ l = arguments.length;
+
+ for (; i < l; i++) {
+ if(this.has(arguments[i])){
+ this.removeComponent(arguments[i]);
+ }else{
+ this.addComponent(arguments[i]);
+ }
+ }
+ //split components if contains comma
+ } else if (toggle.indexOf(',') !== -1) {
+ comps = toggle.split(rlist);
+ l = comps.length;
+ for (; i < l; i++) {
+ if(this.has(comps[i])){
+ this.removeComponent(comps[i]);
+ }else{
+ this.addComponent(comps[i]);
+ }
+ }
+
+ //single component passed
+ } else {
+ if(this.has(toggle)){
+ this.removeComponent(toggle);
+ }else{
+ this.addComponent(toggle);
+ }
+ }
+
+ return this;
+ },
+ /**@
+ * #.requires
+ * @comp Crafty Core
+ * @sign public this .requires(String componentList)
+ * @param componentList - List of components that must be added
+ * Makes sure the entity has the components listed. If the entity does not
+ * have the component, it will add it.
+ * @see .addComponent
+ */
+ requires: function (list) {
+ var comps = list.split(rlist),
+ i = 0, l = comps.length,
+ comp;
+
+ //loop over the list of components and add if needed
+ for (; i < l; ++i) {
+ comp = comps[i];
+ if (!this.has(comp)) this.addComponent(comp);
+ }
+
+ return this;
+ },
+
+ /**@
+ * #.removeComponent
+ * @comp Crafty Core
+ * @sign public this .removeComponent(String Component[, soft])
+ * @param component - Component to remove
+ * @param soft - Whether to soft remove it (defaults to `true`)
+ *
+ * Removes a component from an entity. A soft remove (the default) will only
+ * refrain `.has()` from returning true. Hard will remove all
+ * associated properties and methods.
+ */
+ removeComponent: function (id, soft) {
+ if (soft === false) {
+ var props = components[id], prop;
+ for (prop in props) {
+ delete this[prop];
+ }
+ }
+ delete this.__c[id];
+
+ this.trigger("RemoveComponent", id);
+ return this;
+ },
+
+ /**@
+ * #.has
+ * @comp Crafty Core
+ * @sign public Boolean .has(String component)
+ * Returns `true` or `false` depending on if the
+ * entity has the given component.
+ *
+ * For better performance, simply use the `.__c` object
+ * which will be `true` if the entity has the component or
+ * will not exist (or be `false`).
+ */
+ has: function (id) {
+ return !!this.__c[id];
+ },
+
+ /**@
+ * #.attr
+ * @comp Crafty Core
+ * @sign public this .attr(String property, * value)
+ * @param property - Property of the entity to modify
+ * @param value - Value to set the property to
+ * @sign public this .attr(Object map)
+ * @param map - Object where the key is the property to modify and the value as the property value
+ * @trigger Change - when properties change - {key: value}
+ * Use this method to set any property of the entity.
+ * @example
+ * ~~~
+ * this.attr({key: "value", prop: 5});
+ * this.key; //value
+ * this.prop; //5
+ *
+ * this.attr("key", "newvalue");
+ * this.key; //newvalue
+ * ~~~
+ */
+ attr: function (key, value) {
+ if (arguments.length === 1) {
+ //if just the key, return the value
+ if (typeof key === "string") {
+ return this[key];
+ }
+
+ //extend if object
+ this.extend(key);
+ this.trigger("Change", key); //trigger change event
+ return this;
+ }
+ //if key value pair
+ this[key] = value;
+
+ var change = {};
+ change[key] = value;
+ this.trigger("Change", change); //trigger change event
+ return this;
+ },
+
+ /**@
+ * #.toArray
+ * @comp Crafty Core
+ * @sign public this .toArray(void)
+ * This method will simply return the found entities as an array.
+ */
+ toArray: function () {
+ return slice.call(this, 0);
+ },
+
+ /**@
+ * #.timeout
+ * @comp Crafty Core
+ * @sign public this .timeout(Function callback, Number delay)
+ * @param callback - Method to execute after given amount of milliseconds
+ * @param delay - Amount of milliseconds to execute the method
+ * The delay method will execute a function after a given amount of time in milliseconds.
+ *
+ * Essentially a wrapper for `setTimeout`.
+ *
+ * @example
+ * Destroy itself after 100 milliseconds
+ * ~~~
+ * this.timeout(function() {
+ this.destroy();
+ * }, 100);
+ * ~~~
+ */
+ timeout: function (callback, duration) {
+ this.each(function () {
+ var self = this;
+ setTimeout(function () {
+ callback.call(self);
+ }, duration);
+ });
+ return this;
+ },
+
+ /**@
+ * #.bind
+ * @comp Crafty Core
+ * @sign public this .bind(String eventName, Function callback)
+ * @param eventName - Name of the event to bind to
+ * @param callback - Method to execute when the event is triggered
+ * Attach the current entity (or entities) to listen for an event.
+ *
+ * Callback will be invoked when an event with the event name passed
+ * is triggered. Depending on the event, some data may be passed
+ * via an argument to the callback function.
+ *
+ * The first argument is the event name (can be anything) whilst the
+ * second argument is the callback. If the event has data, the
+ * callback should have an argument.
+ *
+ * Events are arbitrary and provide communication between components.
+ * You can trigger or bind an event even if it doesn't exist yet.
+ * @example
+ * ~~~
+ * this.attr("triggers", 0); //set a trigger count
+ * this.bind("myevent", function() {
+ * this.triggers++; //whenever myevent is triggered, increment
+ * });
+ * this.bind("EnterFrame", function() {
+ * this.trigger("myevent"); //trigger myevent on every frame
+ * });
+ * ~~~
+ * @see .trigger, .unbind
+ */
+ bind: function (event, callback) {
+ //optimization for 1 entity
+ if (this.length === 1) {
+ if (!handlers[event]) handlers[event] = {};
+ var h = handlers[event];
+
+ if (!h[this[0]]) h[this[0]] = []; //init handler array for entity
+ h[this[0]].push(callback); //add current callback
+ return this;
+ }
+
+ this.each(function () {
+ //init event collection
+ if (!handlers[event]) handlers[event] = {};
+ var h = handlers[event];
+
+ if (!h[this[0]]) h[this[0]] = []; //init handler array for entity
+ h[this[0]].push(callback); //add current callback
+ });
+ return this;
+ },
+
+ /**@
+ * #.unbind
+ * @comp Crafty Core
+ * @sign public this .unbind(String eventName[, Function callback])
+ * @param eventName - Name of the event to unbind
+ * @param callback - Function to unbind
+ * Removes binding with an event from current entity.
+ *
+ * Passing an event name will remove all events binded to
+ * that event. Passing a reference to the callback will
+ * unbind only that callback.
+ * @see .bind, .trigger
+ */
+ unbind: function (event, callback) {
+ this.each(function () {
+ var hdl = handlers[event], i = 0, l, current;
+ //if no events, cancel
+ if (hdl && hdl[this[0]]) l = hdl[this[0]].length;
+ else return this;
+
+ //if no function, delete all
+ if (!callback) {
+ delete hdl[this[0]];
+ return this;
+ }
+ //look for a match if the function is passed
+ for (; i < l; i++) {
+ current = hdl[this[0]];
+ if (current[i] == callback) {
+ current.splice(i, 1);
+ i--;
+ }
+ }
+ });
+
+ return this;
+ },
+
+ /**@
+ * #.trigger
+ * @comp Crafty Core
+ * @sign public this .trigger(String eventName[, Object data])
+ * @param eventName - Event to trigger
+ * @param data - Arbitrary data that will be passed into every callback as an argument
+ * Trigger an event with arbitrary data. Will invoke all callbacks with
+ * the context (value of `this`) of the current entity object.
+ *
+ * *Note: This will only execute callbacks within the current entity, no other entity.*
+ *
+ * The first argument is the event name to trigger and the optional
+ * second argument is the arbitrary event data. This can be absolutely anything.
+ */
+ trigger: function (event, data) {
+ if (this.length === 1) {
+ //find the handlers assigned to the event and entity
+ if (handlers[event] && handlers[event][this[0]]) {
+ var callbacks = handlers[event][this[0]], i = 0, l = callbacks.length;
+ for (; i < l; i++) {
+ callbacks[i].call(this, data);
+ }
+ }
+ return this;
+ }
+
+ this.each(function () {
+ //find the handlers assigned to the event and entity
+ if (handlers[event] && handlers[event][this[0]]) {
+ var callbacks = handlers[event][this[0]], i = 0, l = callbacks.length;
+ for (; i < l; i++) {
+ callbacks[i].call(this, data);
+ }
+ }
+ });
+ return this;
+ },
+
+ /**@
+ * #.each
+ * @sign public this .each(Function method)
+ * @param method - Method to call on each iteration
+ * Iterates over found entities, calling a function for every entity.
+ *
+ * The function will be called for every entity and will pass the index
+ * in the iteration as an argument. The context (value of `this`) of the
+ * function will be the current entity in the iteration.
+ * @example
+ * Destroy every second 2D entity
+ * ~~~
+ * Crafty("2D").each(function(i) {
+ * if(i % 2 === 0) {
+ * this.destroy();
+ * }
+ * });
+ * ~~~
+ */
+ each: function (func) {
+ var i = 0, l = this.length;
+ for (; i < l; i++) {
+ //skip if not exists
+ if (!entities[this[i]]) continue;
+ func.call(entities[this[i]], i);
+ }
+ return this;
+ },
+
+ /**@
+ * #.clone
+ * @comp Crafty Core
+ * @sign public Entity .clone(void)
+ * @returns Cloned entity of the current entity
+ * Method will create another entity with the exact same
+ * properties, components and methods as the current entity.
+ */
+ clone: function () {
+ var comps = this.__c,
+ comp,
+ prop,
+ clone = Crafty.e();
+
+ for (comp in comps) {
+ clone.addComponent(comp);
+ }
+ for (prop in this) {
+ if (prop != "0" && prop != "_global" && prop != "_changed" && typeof this[prop] != "function" && typeof this[prop] != "object") {
+ clone[prop] = this[prop];
+ }
+ }
+
+ return clone;
+ },
+
+ /**@
+ * #.setter
+ * @comp Crafty Core
+ * @sign public this .setter(String property, Function callback)
+ * @param property - Property to watch for modification
+ * @param callback - Method to execute if the property is modified
+ * Will watch a property waiting for modification and will then invoke the
+ * given callback when attempting to modify.
+ *
+ * *Note: Support in IE<9 is slightly different. The method will be executed
+ * after the property has been set*
+ */
+ setter: function (prop, callback) {
+ if (Crafty.support.setter) {
+ this.__defineSetter__(prop, callback);
+ } else if (Crafty.support.defineProperty) {
+ Object.defineProperty(this, prop, {
+ set: callback,
+ configurable: true
+ });
+ } else {
+ noSetter.push({
+ prop: prop,
+ obj: this,
+ fn: callback
+ });
+ }
+ return this;
+ },
+
+ /**@
+ * #.destroy
+ * @comp Crafty Core
+ * @sign public this .destroy(void)
+ * Will remove all event listeners and delete all properties as well as removing from the stage
+ */
+ destroy: function () {
+ //remove all event handlers, delete from entities
+ this.each(function () {
+ this.trigger("Remove");
+ for (var e in handlers) {
+ this.unbind(e);
+ }
+ delete entities[this[0]];
+ });
+ }
+ };
+
+ //give the init instances the Crafty prototype
+ Crafty.fn.init.prototype = Crafty.fn;
+
+ /**
+* Extension method to extend the namespace and
+* selector instances
+*/
+ Crafty.extend = Crafty.fn.extend = function (obj) {
+ var target = this, key;
+
+ //don't bother with nulls
+ if (!obj) return target;
+
+ for (key in obj) {
+ if (target === obj[key]) continue; //handle circular reference
+ target[key] = obj[key];
+ }
+
+ return target;
+ };
+
+ /**@
+* #Crafty.extend
+* @category Core
+* Used to extend the Crafty namespace.
+*/
+ Crafty.extend({
+ /**@
+ * #Crafty.init
+ * @category Core
+ * @trigger EnterFrame - on each frame - { frame: Number }
+ * @trigger Load - Just after the viewport is initialised. Before the EnterFrame loops is started
+ * @sign public this Crafty.init([Number width, Number height])
+ * @param width - Width of the stage
+ * @param height - Height of the stage
+ * Create a div with id `cr-stage`, if there is not already an HTMLElement with id `cr-stage` (by `Crafty.viewport.init`).
+ *
+ * Starts the `EnterFrame` interval. This will call the `EnterFrame` event for every frame.
+ *
+ * Can pass width and height values for the stage otherwise will default to window size (see `Crafty.DOM.window`).
+ *
+ * All `Load` events will be executed.
+ *
+ * Uses `requestAnimationFrame` to sync the drawing with the browser but will default to `setInterval` if the browser does not support it.
+ * @see Crafty.stop
+ * @see Crafty.viewport.init
+ */
+ init: function (w, h) {
+ Crafty.viewport.init(w, h);
+
+ //call all arbitrary functions attached to onload
+ this.trigger("Load");
+ this.timer.init();
+
+ return this;
+ },
+
+ /**@
+ * #Crafty.stop
+ * @category Core
+ * @trigger CraftyStop - when the game is stopped
+ * @sign public this Crafty.stop(void)
+ * Stops the EnterFrame interval and removes the stage element.
+ *
+ * To restart, use `Crafty.init()`.
+ * @see Crafty.init
+ */
+ stop: function () {
+ this.timer.stop();
+ Crafty.stage.elem.parentNode.removeChild(Crafty.stage.elem);
+
+ return this;
+ },
+
+ /**@
+ * #Crafty.pause
+ * @category Core
+ * @trigger Pause - when the game is paused
+ * @trigger Unpause - when the game is unpaused
+ * @sign public this Crafty.pause(void)
+ * Pauses the game by stoping the EnterFrame event from firing. If the game is already paused it is unpaused.
+ * You can pass a boolean parameter if you want to pause or unpause mo matter what the current state is.
+ * Modern browsers pauses the game when the page is not visible to the user. If you want the Pause event
+ * to be triggered when that happens you can enable autoPause in `Crafty.settings`.
+ * @example
+ * Have an entity pause the game when it is clicked.
+ * ~~~
+ * button.bind("click", function() {
+ * Crafty.pause();
+ * });
+ * ~~~
+ */
+ pause: function (toggle) {
+ if (arguments.length == 1 ? toggle : !this._paused) {
+ this.trigger('Pause');
+ this._paused = true;
+
+ Crafty.timer.stop();
+ Crafty.keydown = {};
+ } else {
+ this.trigger('Unpause');
+ this._paused = false;
+
+ Crafty.timer.init();
+ }
+ return this;
+ },
+
+ /**@
+ * #Crafty.isPaused
+ * @category Core
+ * @sign public this Crafty.isPaused()
+ * Check whether the game is already paused or not.
+ * ~~~
+ * Crafty.isPaused();
+ * ~~~
+ */
+ isPaused: function () {
+ return this._paused;
+ },
+ /**@
+ * #Crafty.timer
+ * @category Internal
+ * Handles game ticks
+ */
+ timer: {
+ prev: (+new Date),
+ current: (+new Date),
+ curTime: Date.now(),
+
+ init: function () {
+ var onFrame = window.requestAnimationFrame ||
+ window.webkitRequestAnimationFrame ||
+ window.mozRequestAnimationFrame ||
+ window.oRequestAnimationFrame ||
+ window.msRequestAnimationFrame ||
+ null;
+
+ if (onFrame) {
+ tick = function () {
+ Crafty.timer.step();
+ requestID = onFrame(tick);
+ //console.log(requestID + ', ' + frame)
+ }
+
+ tick();
+ } else {
+ tick = setInterval(Crafty.timer.step, 1000 / FPS);
+ }
+ },
+
+ stop: function () {
+ Crafty.trigger("CraftyStop");
+
+ if (typeof tick === "number") clearInterval(tick);
+
+ var onFrame = window.cancelRequestAnimationFrame ||
+ window.webkitCancelRequestAnimationFrame ||
+ window.mozCancelRequestAnimationFrame ||
+ window.oCancelRequestAnimationFrame ||
+ window.msCancelRequestAnimationFrame ||
+ null;
+
+ if (onFrame) onFrame(requestID);
+ tick = null;
+ },
+
+ /**@
+ * #Crafty.timer.step
+ * @comp Crafty.timer
+ * @sign public void Crafty.timer.step()
+ * Advances the game by triggering `EnterFrame` and calls `Crafty.DrawManager.draw` to update the stage.
+ */
+ step: function () {
+ loops = 0;
+ this.curTime = Date.now();
+ if (this.curTime - nextGameTick > 60 * milliSecPerFrame) {
+ nextGameTick = this.curTime - milliSecPerFrame;
+ }
+ while (this.curTime > nextGameTick) {
+ Crafty.trigger("EnterFrame", { frame: frame++ });
+ nextGameTick += milliSecPerFrame;
+ loops++;
+ }
+ if (loops) {
+ Crafty.DrawManager.draw();
+ }
+ },
+ /**@
+ * #Crafty.timer.getFPS
+ * @comp Crafty.timer
+ * @sign public void Crafty.timer.getFPS()
+ * Returns the target frames per second. This is not an actual frame rate.
+ */
+ getFPS: function () {
+ return FPS;
+ },
+ /**@
+ * #Crafty.timer.simulateFrames
+ * @comp Crafty.timer
+ * Advances the game state by a number of frames and draws the resulting stage at the end. Useful for tests and debugging.
+ * @sign public this Crafty.timer.simulateFrames(Number frames)
+ * @param frames - number of frames to simulate
+ */
+ simulateFrames: function (frames) {
+ while (frames-- > 0) {
+ Crafty.trigger("EnterFrame", { frame: frame++ });
+ }
+ Crafty.DrawManager.draw();
+ }
+
+ },
+
+ /**@
+ * #Crafty.e
+ * @category Core
+ * @trigger NewEntity - When the entity is created and all components are added - { id:Number }
+ * @sign public Entity Crafty.e(String componentList)
+ * @param componentList - List of components to assign to new entity
+ * @sign public Entity Crafty.e(String component1[, .., String componentN])
+ * @param component# - Component to add
+ * Creates an entity. Any arguments will be applied in the same
+ * way `.addComponent()` is applied as a quick way to add components.
+ *
+ * Any component added will augment the functionality of
+ * the created entity by assigning the properties and methods from the component to the entity.
+ * ~~~
+ * var myEntity = Crafty.e("2D, DOM, Color");
+ * ~~~
+ * @see Crafty.c
+ */
+ e: function () {
+ var id = UID(), craft;
+
+ entities[id] = null; //register the space
+ entities[id] = craft = Crafty(id);
+
+ if (arguments.length > 0) {
+ craft.addComponent.apply(craft, arguments);
+ }
+ craft.addComponent("obj"); //every entity automatically assumes obj
+
+ Crafty.trigger("NewEntity", { id: id });
+
+ return craft;
+ },
+
+ /**@
+ * #Crafty.c
+ * @category Core
+ * @sign public void Crafty.c(String name, Object component)
+ * @param name - Name of the component
+ * @param component - Object with the components properties and methods
+ * Creates a component where the first argument is the ID and the second
+ * is the object that will be inherited by entities.
+ *
+ * There is a convention for writing components.
+ *
+ * - Properties or methods that start with an underscore are considered private.
+ * - A method called `init` will automatically be called as soon as the
+ * component is added to an entity.
+ * - A methid called `uninit` will be called when the component is removed from an entity.
+ * A sample use case for this is the native DOM component that removes its div element wehen removed from an entity.
+ * - A method with the same name as the component is considered to be a constructor
+ * and is generally used when you need to pass configuration data to the component on a per entity basis.
+ *
+ * ~~~
+ * Crafty.c("Annoying", {
+ * _message: "HiHi",
+ * init: function() {
+ * this.bind("EnterFrame", function() { alert(this.message); });
+ * },
+ * annoying: function(message) { this.message = message; }
+ * });
+ *
+ * Crafty.e("Annoying").annoying("I'm an orange...");
+ * ~~~
+ * @see Crafty.e
+ */
+ c: function (compName, component) {
+ components[compName] = component;
+ },
+
+ /**@
+ * #Crafty.trigger
+ * @category Core, Events
+ * @sign public void Crafty.trigger(String eventName, * data)
+ * @param eventName - Name of the event to trigger
+ * @param data - Arbitrary data to pass into the callback as an argument
+ * This method will trigger every single callback attached to the event name. This means
+ * every global event and every entity that has a callback.
+ * @see Crafty.bind
+ */
+ trigger: function (event, data) {
+ var hdl = handlers[event], h, i, l;
+ //loop over every object bound
+ for (h in hdl) {
+ if (!hdl.hasOwnProperty(h)) continue;
+
+ //loop over every handler within object
+ for (i = 0, l = hdl[h].length; i < l; i++) {
+ if (hdl[h] && hdl[h][i]) {
+ //if an entity, call with that context
+ if (entities[h]) {
+ hdl[h][i].call(Crafty(+h), data);
+ } else { //else call with Crafty context
+ hdl[h][i].call(Crafty, data);
+ }
+ }
+ }
+ }
+ },
+
+ /**@
+ * #Crafty.bind
+ * @category Core, Events
+ * @sign public Number bind(String eventName, Function callback)
+ * @param eventName - Name of the event to bind to
+ * @param callback - Method to execute upon event triggered
+ * @returns ID of the current callback used to unbind
+ * Binds to a global event. Method will be executed when `Crafty.trigger` is used
+ * with the event name.
+ * @see Crafty.trigger, Crafty.unbind
+ */
+ bind: function (event, callback) {
+ if (!handlers[event]) handlers[event] = {};
+ var hdl = handlers[event];
+
+ if (!hdl.global) hdl.global = [];
+ return hdl.global.push(callback) - 1;
+ },
+
+ /**@
+ * #Crafty.unbind
+ * @category Core, Events
+ * @sign public Boolean Crafty.unbind(String eventName, Function callback)
+ * @param eventName - Name of the event to unbind
+ * @param callback - Function to unbind
+ * @sign public Boolean Crafty.unbind(String eventName, Number callbackID)
+ * @param callbackID - ID of the callback
+ * @returns True or false depending on if a callback was unbound
+ * Unbind any event from any entity or global event.
+ */
+ unbind: function (event, callback) {
+ var hdl = handlers[event], h, i, l;
+
+ //loop over every object bound
+ for (h in hdl) {
+ if (!hdl.hasOwnProperty(h)) continue;
+
+ //if passed the ID
+ if (typeof callback === "number") {
+ delete hdl[h][callback];
+ return true;
+ }
+
+ //loop over every handler within object
+ for (i = 0, l = hdl[h].length; i < l; i++) {
+ if (hdl[h][i] === callback) {
+ delete hdl[h][i];
+ return true;
+ }
+ }
+ }
+
+ return false;
+ },
+
+ /**@
+ * #Crafty.frame
+ * @category Core
+ * @sign public Number Crafty.frame(void)
+ * Returns the current frame number
+ */
+ frame: function () {
+ return frame;
+ },
+
+ components: function () {
+ return components;
+ },
+
+ isComp: function (comp) {
+ return comp in components;
+ },
+
+ debug: function () {
+ return entities;
+ },
+
+ /**@
+ * #Crafty.settings
+ * @category Core
+ * Modify the inner workings of Crafty through the settings.
+ */
+ settings: (function () {
+ var states = {},
+ callbacks = {};
+
+ return {
+ /**@
+ * #Crafty.settings.register
+ * @comp Crafty.settings
+ * @sign public void Crafty.settings.register(String settingName, Function callback)
+ * @param settingName - Name of the setting
+ * @param callback - Function to execute when use modifies setting
+ * Use this to register custom settings. Callback will be executed when `Crafty.settings.modify` is used.
+ * @see Crafty.settings.modify
+ */
+ register: function (setting, callback) {
+ callbacks[setting] = callback;
+ },
+
+ /**@
+ * #Crafty.settings.modify
+ * @comp Crafty.settings
+ * @sign public void Crafty.settings.modify(String settingName, * value)
+ * @param settingName - Name of the setting
+ * @param value - Value to set the setting to
+ * Modify settings through this method.
+ * @see Crafty.settings.register, Crafty.settings.get
+ */
+ modify: function (setting, value) {
+ if (!callbacks[setting]) return;
+ callbacks[setting].call(states[setting], value);
+ states[setting] = value;
+ },
+
+ /**@
+ * #Crafty.settings.get
+ * @comp Crafty.settings
+ * @sign public * Crafty.settings.get(String settingName)
+ * @param settingName - Name of the setting
+ * @returns Current value of the setting
+ * Returns the current value of the setting.
+ * @see Crafty.settings.register, Crafty.settings.get
+ */
+ get: function (setting) {
+ return states[setting];
+ }
+ };
+ })(),
+
+ clone: clone
+ });
+
+ /**
+* Return a unique ID
+*/
+ function UID() {
+ var id = GUID++;
+ //if GUID is not unique
+ if (id in entities) {
+ return UID(); //recurse until it is unique
+ }
+ return id;
+ }
+
+ /**@
+ * #Crafty.clone
+ * @category Core
+ * @sign public Object .clone(Object obj)
+ * @param obj - an object
+ * Deep copy (a.k.a clone) of an object.
+ */
+ function clone(obj) {
+ if (obj === null || typeof(obj) != 'object')
+ return obj;
+
+ var temp = obj.constructor(); // changed
+
+ for (var key in obj)
+ temp[key] = clone(obj[key]);
+ return temp;
+ }
+
+ Crafty.bind("Load", function () {
+ if (!Crafty.support.setter && Crafty.support.defineProperty) {
+ noSetter = [];
+ Crafty.bind("EnterFrame", function () {
+ var i = 0, l = noSetter.length, current;
+ for (; i < l; ++i) {
+ current = noSetter[i];
+ if (current.obj[current.prop] !== current.obj['_' + current.prop]) {
+ current.fn.call(current.obj, current.obj[current.prop]);
+ }
+ }
+ });
+ }
+ });
+
+ //make Crafty global
+ window.Crafty = Crafty;
+})(window);
+ //wrap around components
+(function(Crafty, window, document) {
+ /**
+* Spatial HashMap for broad phase collision
+*
+* @author Louis Stowasser
+*/
+(function (parent) {
+
+
+ /**@
+ * #Crafty.HashMap.constructor
+ * @comp Crafty.HashMap
+ * @sign public void Crafty.HashMap([cellsize])
+ * @param cellsize - the cell size. If omitted, `cellsize` is 64.
+ * Set `cellsize`.
+ * And create `this.map`.
+ */
+ var cellsize,
+ HashMap = function (cell) {
+ cellsize = cell || 64;
+ this.map = {};
+ },
+ SPACE = " ";
+
+ HashMap.prototype = {
+ /**@
+ * #Crafty.map.insert
+ * @comp Crafty.map
+ * @sign public Object Crafty.map.insert(Object obj)
+ * @param obj - An entity to be inserted.
+ * `obj` is instered in '.map' of the corresponding broad phase cells. An object of the following fields is returned.
+ *
+ * - the object that keep track of cells (keys)
+ * - `obj`
+ * - the HashMap object
+ */
+ insert: function (obj) {
+ var keys = HashMap.key(obj),
+ entry = new Entry(keys, obj, this),
+ i = 0,
+ j,
+ hash;
+
+ //insert into all x buckets
+ for (i = keys.x1; i <= keys.x2; i++) {
+ //insert into all y buckets
+ for (j = keys.y1; j <= keys.y2; j++) {
+ hash = i + SPACE + j;
+ if (!this.map[hash]) this.map[hash] = [];
+ this.map[hash].push(obj);
+ }
+ }
+
+ return entry;
+ },
+
+ /**@
+ * #Crafty.map.search
+ * @comp Crafty.map
+ * @sign public Object Crafty.map.search(Object rect[, Boolean filter])
+ * @param rect - the rectangular region to search for entities.
+ * @param filter - Default value is true. Otherwise, must be false.
+ * - If `filter` is `false`, just search for all the entries in the give `rect` region by broad phase collision. Entity may be returned duplicated.
+ * - If `filter` is `true`, filter the above results by checking that they actually overlap `rect`.
+ * The easier usage is with `filter`=`true`. For performance reason, you may use `filter`=`false`, and filter the result youself. See examples in drawing.js and collision.js
+ */
+ search: function (rect, filter) {
+ var keys = HashMap.key(rect),
+ i, j,
+ hash,
+ results = [];
+
+ if (filter === undefined) filter = true; //default filter to true
+
+ //search in all x buckets
+ for (i = keys.x1; i <= keys.x2; i++) {
+ //insert into all y buckets
+ for (j = keys.y1; j <= keys.y2; j++) {
+ hash = i + SPACE + j;
+
+ if (this.map[hash]) {
+ results = results.concat(this.map[hash]);
+ }
+ }
+ }
+
+ if (filter) {
+ var obj, id, finalresult = [], found = {};
+ //add unique elements to lookup table with the entity ID as unique key
+ for (i = 0, l = results.length; i < l; i++) {
+ obj = results[i];
+ if (!obj) continue; //skip if deleted
+ id = obj[0]; //unique ID
+
+ //check if not added to hash and that actually intersects
+ if (!found[id] && obj.x < rect._x + rect._w && obj._x + obj._w > rect._x &&
+ obj.y < rect._y + rect._h && obj._h + obj._y > rect._y)
+ found[id] = results[i];
+ }
+
+ //loop over lookup table and copy to final array
+ for (obj in found) finalresult.push(found[obj]);
+
+ return finalresult;
+ } else {
+ return results;
+ }
+ },
+
+ /**@
+ * #Crafty.map.remove
+ * @comp Crafty.map
+ * @sign public void Crafty.map.remove([Object keys, ]Object obj)
+ * @param keys - key region. If omitted, it will be derived from obj by `Crafty.HashMap.key`.
+ * @param obj - need more document.
+ * Remove an entity in a broad phase map.
+ * - The second form is only used in Crafty.HashMap to save time for computing keys again, where keys were computed previously from obj. End users should not call this form directly.
+ * ~~~
+ * Crafty.map.remove(e);
+ * ~~~
+ */
+ remove: function (keys, obj) {
+ var i = 0, j, hash;
+
+ if (arguments.length == 1) {
+ obj = keys;
+ keys = HashMap.key(obj);
+ }
+
+ //search in all x buckets
+ for (i = keys.x1; i <= keys.x2; i++) {
+ //insert into all y buckets
+ for (j = keys.y1; j <= keys.y2; j++) {
+ hash = i + SPACE + j;
+
+ if (this.map[hash]) {
+ var cell = this.map[hash],
+ m,
+ n = cell.length;
+ //loop over objs in cell and delete
+ for (m = 0; m < n; m++)
+ if (cell[m] && cell[m][0] === obj[0])
+ cell.splice(m, 1);
+ }
+ }
+ }
+ },
+
+ /**@
+ * #Crafty.map.boundaries
+ * @comp Crafty.map
+ * @sign public Object Crafty.map.boundaries()
+ * The return `Object` is of the following format.
+ * {
+ * min: {
+ * x: val_x,
+ * y: val_y
+ * },
+ * max: {
+ * x: val_x,
+ * y: val_y
+ * }
+ * }
+ */
+ boundaries: function () {
+ var k, ent,
+ hash = {
+ max: { x: -Infinity, y: -Infinity },
+ min: { x: Infinity, y: Infinity }
+ },
+ coords = {
+ max: { x: -Infinity, y: -Infinity },
+ min: { x: Infinity, y: Infinity }
+ };
+
+ //Using broad phase hash to speed up the computation of boundaries.
+ for (var h in this.map) {
+ if (!this.map[h].length) continue;
+
+ //broad phase coordinate
+ var map_coord = h.split(SPACE),
+ i=map_coord[0],
+ j=map_coord[0];
+ if (i >= hash.max.x) {
+ hash.max.x = i;
+ for (k in this.map[h]) {
+ ent = this.map[h][k];
+ //make sure that this is a Crafty entity
+ if (typeof ent == 'object' && 'requires' in ent) {
+ coords.max.x = Math.max(coords.max.x, ent.x + ent.w);
+ }
+ }
+ }
+ if (i <= hash.min.x) {
+ hash.min.x = i;
+ for (k in this.map[h]) {
+ ent = this.map[h][k];
+ if (typeof ent == 'object' && 'requires' in ent) {
+ coords.min.x = Math.min(coords.min.x, ent.x);
+ }
+ }
+ }
+ if (j >= hash.max.y) {
+ hash.max.y = j;
+ for (k in this.map[h]) {
+ ent = this.map[h][k];
+ if (typeof ent == 'object' && 'requires' in ent) {
+ coords.max.y = Math.max(coords.max.y, ent.y + ent.h);
+ }
+ }
+ }
+ if (j <= hash.min.y) {
+ hash.min.y = j;
+ for (k in this.map[h]) {
+ ent = this.map[h][k];
+ if (typeof ent == 'object' && 'requires' in ent) {
+ coords.min.y = Math.min(coords.min.y, ent.y);
+ }
+ }
+ }
+ }
+
+ return coords;
+ }
+ };
+
+/**@
+* #Crafty.HashMap
+* @category 2D
+* Broad-phase collision detection engine. See background information at
+*
+* - [N Tutorial B - Broad-Phase Collision](http://www.metanetsoftware.com/technique/tutorialB.html)
+* - [Broad-Phase Collision Detection with CUDA](http.developer.nvidia.com/GPUGems3/gpugems3_ch32.html)
+* @see Crafty.map
+*/
+
+ /**@
+ * #Crafty.HashMap.key
+ * @comp Crafty.HashMap
+ * @sign public Object Crafty.HashMap.key(Object obj)
+ * @param obj - an Object that has .mbr() or _x, _y, _w and _h.
+ * Get the rectangular region (in terms of the grid, with grid size `cellsize`), where the object may fall in. This region is determined by the object's bounding box.
+ * The `cellsize` is 64 by default.
+ * @see Crafty.HashMap.constructor
+ */
+ HashMap.key = function (obj) {
+ if (obj.hasOwnProperty('mbr')) {
+ obj = obj.mbr();
+ }
+ var x1 = Math.floor(obj._x / cellsize),
+ y1 = Math.floor(obj._y / cellsize),
+ x2 = Math.floor((obj._w + obj._x) / cellsize),
+ y2 = Math.floor((obj._h + obj._y) / cellsize);
+ return { x1: x1, y1: y1, x2: x2, y2: y2 };
+ };
+
+ HashMap.hash = function (keys) {
+ return keys.x1 + SPACE + keys.y1 + SPACE + keys.x2 + SPACE + keys.y2;
+ };
+
+ function Entry(keys, obj, map) {
+ this.keys = keys;
+ this.map = map;
+ this.obj = obj;
+ }
+
+ Entry.prototype = {
+ update: function (rect) {
+ //check if buckets change
+ if (HashMap.hash(HashMap.key(rect)) != HashMap.hash(this.keys)) {
+ this.map.remove(this.keys, this.obj);
+ var e = this.map.insert(this.obj);
+ this.keys = e.keys;
+ }
+ }
+ };
+
+ parent.HashMap = HashMap;
+})(Crafty);
+ /**@
+* #Crafty.map
+* @category 2D
+* Functions related with quering entities.
+* @see Crafty.HashMap
+*/
+Crafty.map = new Crafty.HashMap();
+var M = Math,
+ Mc = M.cos,
+ Ms = M.sin,
+ PI = M.PI,
+ DEG_TO_RAD = PI / 180;
+
+
+/**@
+* #2D
+* @category 2D
+* Component for any entity that has a position on the stage.
+* @trigger Move - when the entity has moved - { _x:Number, _y:Number, _w:Number, _h:Number } - Old position
+* @trigger Change - when the entity has moved - { _x:Number, _y:Number, _w:Number, _h:Number } - Old position
+* @trigger Rotate - when the entity is rotated - { cos:Number, sin:Number, deg:Number, rad:Number, o: {x:Number, y:Number}, matrix: {M11, M12, M21, M22} }
+*/
+Crafty.c("2D", {
+/**@
+ * #.x
+ * @comp 2D
+ * The `x` position on the stage. When modified, will automatically be redrawn.
+ * Is actually a getter/setter so when using this value for calculations and not modifying it,
+ * use the `._x` property.
+ * @see ._attr
+ */
+ _x: 0,
+ /**@
+ * #.y
+ * @comp 2D
+ * The `y` position on the stage. When modified, will automatically be redrawn.
+ * Is actually a getter/setter so when using this value for calculations and not modifying it,
+ * use the `._y` property.
+ * @see ._attr
+ */
+ _y: 0,
+ /**@
+ * #.w
+ * @comp 2D
+ * The width of the entity. When modified, will automatically be redrawn.
+ * Is actually a getter/setter so when using this value for calculations and not modifying it,
+ * use the `._w` property.
+ *
+ * Changing this value is not recommended as canvas has terrible resize quality and DOM will just clip the image.
+ * @see ._attr
+ */
+ _w: 0,
+ /**@
+ * #.h
+ * @comp 2D
+ * The height of the entity. When modified, will automatically be redrawn.
+ * Is actually a getter/setter so when using this value for calculations and not modifying it,
+ * use the `._h` property.
+ *
+ * Changing this value is not recommended as canvas has terrible resize quality and DOM will just clip the image.
+ * @see ._attr
+ */
+ _h: 0,
+ /**@
+ * #.z
+ * @comp 2D
+ * The `z` index on the stage. When modified, will automatically be redrawn.
+ * Is actually a getter/setter so when using this value for calculations and not modifying it,
+ * use the `._z` property.
+ *
+ * A higher `z` value will be closer to the front of the stage. A smaller `z` value will be closer to the back.
+ * A global Z index is produced based on its `z` value as well as the GID (which entity was created first).
+ * Therefore entities will naturally maintain order depending on when it was created if same z value.
+ * @see ._attr
+ */
+ _z: 0,
+ /**@
+ * #.rotation
+ * @comp 2D
+ * Set the rotation of your entity. Rotation takes degrees in a clockwise direction.
+ * It is important to note there is no limit on the rotation value. Setting a rotation
+ * mod 360 will give the same rotation without reaching huge numbers.
+ * @see ._attr
+ */
+ _rotation: 0,
+ /**@
+ * #.alpha
+ * @comp 2D
+ * Transparency of an entity. Must be a decimal value between 0.0 being fully transparent to 1.0 being fully opaque.
+ */
+ _alpha: 1.0,
+ /**@
+ * #.visible
+ * @comp 2D
+ * If the entity is visible or not. Accepts a true or false value.
+ * Can be used for optimization by setting an entities visibility to false when not needed to be drawn.
+ *
+ * The entity will still exist and can be collided with but just won't be drawn.
+ * @see Crafty.DrawManager.draw, Crafty.DrawManager.drawAll
+ */
+ _visible: true,
+
+ /**@
+ * #._globalZ
+ * @comp 2D
+ * When two entities overlap, the one with the larger `_globalZ` will be on top of the other.
+ * @see Crafty.DrawManager.draw, Crafty.DrawManager.drawAll
+ */
+ _globalZ: null,
+
+ _origin: null,
+ _mbr: null,
+ _entry: null,
+ _children: null,
+ _parent: null,
+ _changed: false,
+
+ _defineGetterSetter_setter: function() {
+ //create getters and setters using __defineSetter__ and __defineGetter__
+ this.__defineSetter__('x', function (v) { this._attr('_x', v); });
+ this.__defineSetter__('y', function (v) { this._attr('_y', v); });
+ this.__defineSetter__('w', function (v) { this._attr('_w', v); });
+ this.__defineSetter__('h', function (v) { this._attr('_h', v); });
+ this.__defineSetter__('z', function (v) { this._attr('_z', v); });
+ this.__defineSetter__('rotation', function (v) { this._attr('_rotation', v); });
+ this.__defineSetter__('alpha', function (v) { this._attr('_alpha', v); });
+ this.__defineSetter__('visible', function (v) { this._attr('_visible', v); });
+
+ this.__defineGetter__('x', function () { return this._x; });
+ this.__defineGetter__('y', function () { return this._y; });
+ this.__defineGetter__('w', function () { return this._w; });
+ this.__defineGetter__('h', function () { return this._h; });
+ this.__defineGetter__('z', function () { return this._z; });
+ this.__defineGetter__('rotation', function () { return this._rotation; });
+ this.__defineGetter__('alpha', function () { return this._alpha; });
+ this.__defineGetter__('visible', function () { return this._visible; });
+ this.__defineGetter__('parent', function () { return this._parent; });
+ this.__defineGetter__('numChildren', function () { return this._children.length; });
+ },
+
+ _defineGetterSetter_defineProperty: function() {
+ Object.defineProperty(this, 'x', {
+ set: function (v) { this._attr('_x', v); }
+ , get: function () { return this._x; }
+ , configurable: true
+ });
+
+ Object.defineProperty(this, 'y', {
+ set: function (v) { this._attr('_y', v); }
+ , get: function () { return this._y; }
+ , configurable: true
+ });
+
+ Object.defineProperty(this, 'w', {
+ set: function (v) { this._attr('_w', v); }
+ , get: function () { return this._w; }
+ , configurable: true
+ });
+
+ Object.defineProperty(this, 'h', {
+ set: function (v) { this._attr('_h', v); }
+ , get: function () { return this._h; }
+ , configurable: true
+ });
+
+ Object.defineProperty(this, 'z', {
+ set: function (v) { this._attr('_z', v); }
+ , get: function () { return this._z; }
+ , configurable: true
+ });
+
+ Object.defineProperty(this, 'rotation', {
+ set: function (v) { this._attr('_rotation', v); }
+ , get: function () { return this._rotation; }
+ , configurable: true
+ });
+
+ Object.defineProperty(this, 'alpha', {
+ set: function (v) { this._attr('_alpha', v); }
+ , get: function () { return this._alpha; }
+ , configurable: true
+ });
+
+ Object.defineProperty(this, 'visible', {
+ set: function (v) { this._attr('_visible', v); }
+ , get: function () { return this._visible; }
+ , configurable: true
+ });
+ },
+
+ _defineGetterSetter_fallback: function() {
+ //set the public properties to the current private properties
+ this.x = this._x;
+ this.y = this._y;
+ this.w = this._w;
+ this.h = this._h;
+ this.z = this._z;
+ this.rotation = this._rotation;
+ this.alpha = this._alpha;
+ this.visible = this._visible;
+
+ //on every frame check for a difference in any property
+ this.bind("EnterFrame", function () {
+ //if there are differences between the public and private properties
+ if (this.x !== this._x || this.y !== this._y ||
+ this.w !== this._w || this.h !== this._h ||
+ this.z !== this._z || this.rotation !== this._rotation ||
+ this.alpha !== this._alpha || this.visible !== this._visible) {
+
+ //save the old positions
+ var old = this.mbr() || this.pos();
+
+ //if rotation has changed, use the private rotate method
+ if (this.rotation !== this._rotation) {
+ this._rotate(this.rotation);
+ } else {
+ //update the MBR
+ var mbr = this._mbr, moved = false;
+ // If the browser doesn't have getters or setters,
+ // {x, y, w, h, z} and {_x, _y, _w, _h, _z} may be out of synce,
+ // in which case t checks if they are different on tick and executes the Change event.
+ if (mbr) { //check each value to see which has changed
+ if (this.x !== this._x) { mbr._x -= this.x - this._x; moved = true; }
+ else if (this.y !== this._y) { mbr._y -= this.y - this._y; moved = true; }
+ else if (this.w !== this._w) { mbr._w -= this.w - this._w; moved = true; }
+ else if (this.h !== this._h) { mbr._h -= this.h - this._h; moved = true; }
+ else if (this.z !== this._z) { mbr._z -= this.z - this._z; moved = true; }
+ }
+
+ //if the moved flag is true, trigger a move
+ if (moved) this.trigger("Move", old);
+ }
+
+ //set the public properties to the private properties
+ this._x = this.x;
+ this._y = this.y;
+ this._w = this.w;
+ this._h = this.h;
+ this._z = this.z;
+ this._rotation = this.rotation;
+ this._alpha = this.alpha;
+ this._visible = this.visible;
+
+ //trigger the changes
+ this.trigger("Change", old);
+ //without this entities weren't added correctly to Crafty.map.map in IE8.
+ //not entirely sure this is the best way to fix it though
+ this.trigger("Move", old);
+ }
+ });
+ },
+
+ init: function() {
+ this._globalZ = this[0];
+ this._origin = { x: 0, y: 0 };
+ this._children = [];
+
+ if(Crafty.support.setter) {
+ this._defineGetterSetter_setter();
+ } else if (Crafty.support.defineProperty) {
+ //IE9 supports Object.defineProperty
+ this._defineGetterSetter_defineProperty();
+ } else {
+ /*
+ If no setters and getters are supported (e.g. IE8) supports,
+ check on every frame for a difference between this._(x|y|w|h|z...)
+ and this.(x|y|w|h|z) and update accordingly.
+ */
+ this._defineGetterSetter_fallback();
+ }
+
+ //insert self into the HashMap
+ this._entry = Crafty.map.insert(this);
+
+ //when object changes, update HashMap
+ this.bind("Move", function (e) {
+ var area = this._mbr || this;
+ this._entry.update(area);
+ this._cascade(e);
+ });
+
+ this.bind("Rotate", function (e) {
+ var old = this._mbr || this;
+ this._entry.update(old);
+ this._cascade(e);
+ });
+
+ //when object is removed, remove from HashMap and destroy attached children
+ this.bind("Remove", function () {
+ if (this._children) {
+ for (var i = 0; i < this._children.length; i++) {
+ if (this._children[i].destroy) {
+ this._children[i].destroy();
+ }
+ }
+ this._children = [];
+ }
+
+ Crafty.map.remove(this);
+
+ this.detach();
+ });
+ },
+
+ /**
+ * Calculates the MBR when rotated with an origin point
+ */
+ _rotate: function (v) {
+ var theta = -1 * (v % 360), //angle always between 0 and 359
+ rad = theta * DEG_TO_RAD,
+ ct = Math.cos(rad), //cache the sin and cosine of theta
+ st = Math.sin(rad),
+ o = {
+ x: this._origin.x + this._x,
+ y: this._origin.y + this._y
+ };
+
+ //if the angle is 0 and is currently 0, skip
+ if (!theta) {
+ this._mbr = null;
+ if (!this._rotation % 360) return;
+ }
+
+ var x0 = o.x + (this._x - o.x) * ct + (this._y - o.y) * st,
+ y0 = o.y - (this._x - o.x) * st + (this._y - o.y) * ct,
+ x1 = o.x + (this._x + this._w - o.x) * ct + (this._y - o.y) * st,
+ y1 = o.y - (this._x + this._w - o.x) * st + (this._y - o.y) * ct,
+ x2 = o.x + (this._x + this._w - o.x) * ct + (this._y + this._h - o.y) * st,
+ y2 = o.y - (this._x + this._w - o.x) * st + (this._y + this._h - o.y) * ct,
+ x3 = o.x + (this._x - o.x) * ct + (this._y + this._h - o.y) * st,
+ y3 = o.y - (this._x - o.x) * st + (this._y + this._h - o.y) * ct,
+ minx = Math.floor(Math.min(x0, x1, x2, x3)),
+ miny = Math.floor(Math.min(y0, y1, y2, y3)),
+ maxx = Math.ceil(Math.max(x0, x1, x2, x3)),
+ maxy = Math.ceil(Math.max(y0, y1, y2, y3));
+
+ this._mbr = { _x: minx, _y: miny, _w: maxx - minx, _h: maxy - miny };
+
+ //trigger rotation event
+ var difference = this._rotation - v,
+ drad = difference * DEG_TO_RAD;
+
+ this.trigger("Rotate", {
+ cos: Math.cos(drad),
+ sin: Math.sin(drad),
+ deg: difference,
+ rad: drad,
+ o: { x: o.x, y: o.y },
+ matrix: { M11: ct, M12: st, M21: -st, M22: ct }
+ });
+ },
+
+ /**@
+ * #.area
+ * @comp 2D
+ * @sign public Number .area(void)
+ * Calculates the area of the entity
+ */
+ area: function () {
+ return this._w * this._h;
+ },
+
+ /**@
+ * #.intersect
+ * @comp 2D
+ * @sign public Boolean .intersect(Number x, Number y, Number w, Number h)
+ * @param x - X position of the rect
+ * @param y - Y position of the rect
+ * @param w - Width of the rect
+ * @param h - Height of the rect
+ * @sign public Boolean .intersect(Object rect)
+ * @param rect - An object that must have the `x, y, w, h` values as properties
+ * Determines if this entity intersects a rectangle.
+ */
+ intersect: function (x, y, w, h) {
+ var rect, obj = this._mbr || this;
+ if (typeof x === "object") {
+ rect = x;
+ } else {
+ rect = { x: x, y: y, w: w, h: h };
+ }
+
+ return obj._x < rect.x + rect.w && obj._x + obj._w > rect.x &&
+ obj._y < rect.y + rect.h && obj._h + obj._y > rect.y;
+ },
+
+ /**@
+ * #.within
+ * @comp 2D
+ * @sign public Boolean .within(Number x, Number y, Number w, Number h)
+ * @param x - X position of the rect
+ * @param y - Y position of the rect
+ * @param w - Width of the rect
+ * @param h - Height of the rect
+ * @sign public Boolean .within(Object rect)
+ * @param rect - An object that must have the `x, y, w, h` values as properties
+ * Determines if this current entity is within another rectangle.
+ */
+ within: function (x, y, w, h) {
+ var rect;
+ if (typeof x === "object") {
+ rect = x;
+ } else {
+ rect = { x: x, y: y, w: w, h: h };
+ }
+
+ return rect.x <= this.x && rect.x + rect.w >= this.x + this.w &&
+ rect.y <= this.y && rect.y + rect.h >= this.y + this.h;
+ },
+
+ /**@
+ * #.contains
+ * @comp 2D
+ * @sign public Boolean .contains(Number x, Number y, Number w, Number h)
+ * @param x - X position of the rect
+ * @param y - Y position of the rect
+ * @param w - Width of the rect
+ * @param h - Height of the rect
+ * @sign public Boolean .contains(Object rect)
+ * @param rect - An object that must have the `x, y, w, h` values as properties
+ * Determines if the rectangle is within the current entity.
+ */
+ contains: function (x, y, w, h) {
+ var rect;
+ if (typeof x === "object") {
+ rect = x;
+ } else {
+ rect = { x: x, y: y, w: w, h: h };
+ }
+
+ return rect.x >= this.x && rect.x + rect.w <= this.x + this.w &&
+ rect.y >= this.y && rect.y + rect.h <= this.y + this.h;
+ },
+
+ /**@
+ * #.pos
+ * @comp 2D
+ * @sign public Object .pos(void)
+ * Returns the x, y, w, h properties as a rect object
+ * (a rect object is just an object with the keys _x, _y, _w, _h).
+ *
+ * The keys have an underscore prefix. This is due to the x, y, w, h
+ * properties being merely setters and getters that wrap the properties with an underscore (_x, _y, _w, _h).
+ */
+ pos: function () {
+ return {
+ _x: (this._x),
+ _y: (this._y),
+ _w: (this._w),
+ _h: (this._h)
+ };
+ },
+
+ /**@
+ * #.mbr
+ * @comp 2D
+ * @sign public Object .mbr()
+ * Returns the minimum bounding rectangle. If there is no rotation
+ * on the entity it will return the rect.
+ */
+ mbr: function () {
+ if (!this._mbr) return this.pos();
+ return {
+ _x: (this._mbr._x),
+ _y: (this._mbr._y),
+ _w: (this._mbr._w),
+ _h: (this._mbr._h)
+ };
+ },
+
+ /**@
+ * #.isAt
+ * @comp 2D
+ * @sign public Boolean .isAt(Number x, Number y)
+ * @param x - X position of the point
+ * @param y - Y position of the point
+ * Determines whether a point is contained by the entity. Unlike other methods,
+ * an object can't be passed. The arguments require the x and y value
+ */
+ isAt: function (x, y) {
+ if (this.map) {
+ return this.map.containsPoint(x, y);
+ }
+ return this.x <= x && this.x + this.w >= x &&
+ this.y <= y && this.y + this.h >= y;
+ },
+
+ /**@
+ * #.move
+ * @comp 2D
+ * @sign public this .move(String dir, Number by)
+ * @param dir - Direction to move (n,s,e,w,ne,nw,se,sw)
+ * @param by - Amount to move in the specified direction
+ * Quick method to move the entity in a direction (n, s, e, w, ne, nw, se, sw) by an amount of pixels.
+ */
+ move: function (dir, by) {
+ if (dir.charAt(0) === 'n') this.y -= by;
+ if (dir.charAt(0) === 's') this.y += by;
+ if (dir === 'e' || dir.charAt(1) === 'e') this.x += by;
+ if (dir === 'w' || dir.charAt(1) === 'w') this.x -= by;
+
+ return this;
+ },
+
+ /**@
+ * #.shift
+ * @comp 2D
+ * @sign public this .shift(Number x, Number y, Number w, Number h)
+ * @param x - Amount to move X
+ * @param y - Amount to move Y
+ * @param w - Amount to widen
+ * @param h - Amount to increase height
+ * Shift or move the entity by an amount. Use negative values
+ * for an opposite direction.
+ */
+ shift: function (x, y, w, h) {
+ if (x) this.x += x;
+ if (y) this.y += y;
+ if (w) this.w += w;
+ if (h) this.h += h;
+
+ return this;
+ },
+
+ /**@
+ * #._cascade
+ * @comp 2D
+ * @sign public void ._cascade(e)
+ * @param e - Amount to move X
+ * Shift or move the entity by an amount. Use negative values
+ * for an opposite direction.
+ */
+ /**
+ * Move or rotate all the children for this entity
+ */
+ _cascade: function (e) {
+ if (!e) return; //no change in position
+ var i = 0, children = this._children, l = children.length, obj;
+ //rotation
+ if (e.cos) {
+ for (; i < l; ++i) {
+ obj = children[i];
+ if ('rotate' in obj) obj.rotate(e);
+ }
+ } else {
+ //use MBR or current
+ var rect = this._mbr || this,
+ dx = rect._x - e._x,
+ dy = rect._y - e._y,
+ dw = rect._w - e._w,
+ dh = rect._h - e._h;
+
+ for (; i < l; ++i) {
+ obj = children[i];
+ obj.shift(dx, dy, dw, dh);
+ }
+ }
+ },
+
+ /**
+ * #.attach
+ * @comp 2D
+ * @sign public this .attach(Entity obj[, .., Entity objN])
+ * @param obj - Entity(s) to attach
+ * Attaches an entities position and rotation to current entity. When the current entity moves,
+ * the attached entity will move by the same amount.
+ *
+ * As many objects as wanted can be attached and a hierarchy of objects is possible by attaching.
+ */
+ attach: function () {
+ var i = 0, arg = arguments, l = arguments.length, obj;
+ for (; i < l; ++i) {
+ obj = arg[i];
+ if (obj._parent) { obj._parent.detach(obj); }
+ obj._parent = this;
+ this._children.push(obj);
+ }
+
+ return this;
+ },
+
+ /**@
+ * #.detach
+ * @comp 2D
+ * @sign public this .detach([Entity obj])
+ * @param obj - The entity to detach. Left blank will remove all attached entities
+ * Stop an entity from following the current entity. Passing no arguments will stop
+ * every entity attached.
+ */
+ detach: function (obj) {
+ //if nothing passed, remove all attached objects
+ if (!obj) {
+ for (var i = 0; i < this._children.length; i++) {
+ this._children[i]._parent = null;
+ }
+ this._children = [];
+ return this;
+ }
+
+ //if obj passed, find the handler and unbind
+ for (var i = 0; i < this._children.length; i++) {
+ if (this._children[i] == obj) {
+ this._children.splice(i, 1);
+ }
+ }
+ obj._parent = null;
+
+ return this;
+ },
+
+ /**@
+ * #.origin
+ * @comp 2D
+ * @sign public this .origin(Number x, Number y)
+ * @param x - Pixel value of origin offset on the X axis
+ * @param y - Pixel value of origin offset on the Y axis
+ * @sign public this .origin(String offset)
+ * @param offset - Combination of center, top, bottom, middle, left and right
+ * Set the origin point of an entity for it to rotate around.
+ * @example
+ * ~~~
+ * this.origin("top left")
+ * this.origin("center")
+ * this.origin("bottom right")
+ * this.origin("middle right")
+ * ~~~
+ * @see .rotation
+ */
+ origin: function (x, y) {
+ //text based origin
+ if (typeof x === "string") {
+ if (x === "centre" || x === "center" || x.indexOf(' ') === -1) {
+ x = this._w / 2;
+ y = this._h / 2;
+ } else {
+ var cmd = x.split(' ');
+ if (cmd[0] === "top") y = 0;
+ else if (cmd[0] === "bottom") y = this._h;
+ else if (cmd[0] === "middle" || cmd[1] === "center" || cmd[1] === "centre") y = this._h / 2;
+
+ if (cmd[1] === "center" || cmd[1] === "centre" || cmd[1] === "middle") x = this._w / 2;
+ else if (cmd[1] === "left") x = 0;
+ else if (cmd[1] === "right") x = this._w;
+ }
+ }
+
+ this._origin.x = x;
+ this._origin.y = y;
+
+ return this;
+ },
+
+ flip: function (dir) {
+ dir = dir || "X";
+ this["_flip" + dir] = true;
+ this.trigger("Change");
+ },
+
+ /**
+ * Method for rotation rather than through a setter
+ */
+ rotate: function (e) {
+ //assume event data origin
+ this._origin.x = e.o.x - this._x;
+ this._origin.y = e.o.y - this._y;
+
+ //modify through the setter method
+ this._attr('_rotation', e.theta);
+ },
+
+ /**@
+ * #._attr
+ * @comp 2D
+ * Setter method for all 2D properties including
+ * x, y, w, h, alpha, rotation and visible.
+ */
+ _attr: function (name, value) {
+ //keep a reference of the old positions
+ var pos = this.pos(),
+ old = this.mbr() || pos;
+
+ //if rotation, use the rotate method
+ if (name === '_rotation') {
+ this._rotate(value);
+ this.trigger("Rotate");
+ //set the global Z and trigger reorder just incase
+ } else if (name === '_z') {
+ this._globalZ = parseInt(value + Crafty.zeroFill(this[0], 5), 10); //magic number 10e5 is the max num of entities
+ this.trigger("reorder");
+ //if the rect bounds change, update the MBR and trigger move
+ } else if (name == '_x' || name === '_y' || name === '_w' || name === '_h') {
+ var mbr = this._mbr;
+ if (mbr) {
+ mbr[name] -= this[name] - value;
+ }
+ this[name] = value;
+ this.trigger("Move", old);
+ }
+
+ //everything will assume the value
+ this[name] = value;
+
+ //trigger a change
+ this.trigger("Change", old);
+ }
+});
+
+Crafty.c("Physics", {
+ _gravity: 0.4,
+ _friction: 0.2,
+ _bounce: 0.5,
+
+ gravity: function (gravity) {
+ this._gravity = gravity;
+ }
+});
+
+/**@
+* #Gravity
+* @category 2D
+* Adds gravitational pull to the entity.
+*/
+Crafty.c("Gravity", {
+ _gravityConst: 0.2,
+ _gy: 0,
+ _falling: true,
+ _anti: null,
+
+ init: function () {
+ this.requires("2D");
+ },
+
+ /**@
+ * #.gravity
+ * @comp Gravity
+ * @sign public this .gravity([comp])
+ * @param comp - The name of a component that will stop this entity from falling
+ * Enable gravity for this entity no matter whether comp parameter is not specified,
+ * If comp parameter is specified all entities with that component will stop this entity from falling.
+ * For a player entity in a platform game this would be a component that is added to all entities
+ * that the player should be able to walk on.
+ * ~~~
+ * Crafty.e("2D, DOM, Color, Gravity")
+ * .color("red")
+ * .attr({ w: 100, h: 100 })
+ * .gravity("platform")
+ * ~~~
+ */
+ gravity: function (comp) {
+ if (comp) this._anti = comp;
+
+ this.bind("EnterFrame", this._enterframe);
+
+ return this;
+ },
+
+ /**@
+ * #.gravityConst
+ * @comp Gravity
+ * @sign public this .gravityConst(g)
+ * @param g - gravitational constant
+ * Set the gravitational constant to g. The default is .2. The greater g, the faster the object falls.
+ * ~~~
+ * Crafty.e("2D, DOM, Color, Gravity")
+ * .color("red")
+ * .attr({ w: 100, h: 100 })
+ * .gravity("platform")
+ * .gravityConst(2)
+ * ~~~
+ */
+ gravityConst: function(g) {
+ this._gravityConst=g;
+ return this;
+ },
+
+ _enterframe: function () {
+ if (this._falling) {
+ //if falling, move the players Y
+ //it used to be this._gy += this._gravityConst * 2;
+ //2 seems to be unnecessary. So 2 is removed. by pengyu
+ this._gy += this._gravityConst;
+ this.y += this._gy;
+ } else {
+ this._gy = 0; //reset change in y
+ }
+
+ var obj, hit = false, pos = this.pos(),
+ q, i = 0, l;
+
+ //Increase by 1 to make sure map.search() finds the floor
+ pos._y++;
+
+ //map.search wants _x and intersect wants x...
+ pos.x = pos._x;
+ pos.y = pos._y;
+ pos.w = pos._w;
+ pos.h = pos._h;
+
+ q = Crafty.map.search(pos);
+ l = q.length;
+
+ for (; i < l; ++i) {
+ obj = q[i];
+ //check for an intersection directly below the player
+ if (obj !== this && obj.has(this._anti) && obj.intersect(pos)) {
+ hit = obj;
+ break;
+ }
+ }
+
+ if (hit) { //stop falling if found
+ if (this._falling) this.stopFalling(hit);
+ } else {
+ this._falling = true; //keep falling otherwise
+ }
+ },
+
+ stopFalling: function (e) {
+ if (e) this.y = e._y - this._h; //move object
+
+ //this._gy = -1 * this._bounce;
+ this._falling = false;
+ if (this._up) this._up = false;
+ this.trigger("hit");
+ },
+
+ /**@
+ * #.antigravity
+ * @comp Gravity
+ * @sign public this .antigravity()
+ * Disable gravity for this component. It can be reenabled by calling .gravity()
+ */
+ antigravity: function () {
+ this.unbind("EnterFrame", this._enterframe);
+ }
+});
+
+/**@
+* #Crafty.Polygon
+* @category 2D
+* Polygon object used for hitboxes and click maps. Must pass an Array for each point as an
+* argument where index 0 is the x position and index 1 is the y position.
+*
+* For example one point of a polygon will look like this: `[0,5]` where the `x` is `0` and the `y` is `5`.
+*
+* Can pass an array of the points or simply put each point as an argument.
+*
+* When creating a polygon for an entity, each point should be offset or relative from the entities `x` and `y`
+* (don't include the absolute values as it will automatically calculate this).
+*
+*
+* @example
+* ~~~
+* new Crafty.polygon([50,0],[100,100],[0,100]);
+* new Crafty.polygon([[50,0],[100,100],[0,100]]);
+* ~~~
+*/
+Crafty.polygon = function (poly) {
+ if (arguments.length > 1) {
+ poly = Array.prototype.slice.call(arguments, 0);
+ }
+ this.points = poly;
+};
+
+Crafty.polygon.prototype = {
+/**@
+ * #.containsPoint
+ * @comp Crafty.Polygon
+ * @sign public Boolean .containsPoint(Number x, Number y)
+ * @param x - X position of the point
+ * @param y - Y position of the point
+ * Method is used to determine if a given point is contained by the polygon.
+ * @example
+ * ~~~
+ * var poly = new Crafty.polygon([50,0],[100,100],[0,100]);
+ * poly.containsPoint(50, 50); //TRUE
+ * poly.containsPoint(0, 0); //FALSE
+ * ~~~
+ */
+ containsPoint: function (x, y) {
+ var p = this.points, i, j, c = false;
+
+ for (i = 0, j = p.length - 1; i < p.length; j = i++) {
+ if (((p[i][1] > y) != (p[j][1] > y)) && (x < (p[j][0] - p[i][0]) * (y - p[i][1]) / (p[j][1] - p[i][1]) + p[i][0])) {
+ c = !c;
+ }
+ }
+
+ return c;
+ },
+
+ /**@
+ * #.shift
+ * @comp Crafty.Polygon
+ * @sign public void .shift(Number x, Number y)
+ * @param x - Amount to shift the `x` axis
+ * @param y - Amount to shift the `y` axis
+ * Shifts every single point in the polygon by the specified amount.
+ * @example
+ * ~~~
+ * var poly = new Crafty.polygon([50,0],[100,100],[0,100]);
+ * poly.shift(5,5);
+ * //[[55,5], [105,5], [5,105]];
+ * ~~~
+ */
+ shift: function (x, y) {
+ var i = 0, l = this.points.length, current;
+ for (; i < l; i++) {
+ current = this.points[i];
+ current[0] += x;
+ current[1] += y;
+ }
+ },
+
+ rotate: function (e) {
+ var i = 0, l = this.points.length,
+ current, x, y;
+
+ for (; i < l; i++) {
+ current = this.points[i];
+
+ x = e.o.x + (current[0] - e.o.x) * e.cos + (current[1] - e.o.y) * e.sin;
+ y = e.o.y - (current[0] - e.o.x) * e.sin + (current[1] - e.o.y) * e.cos;
+
+ current[0] = x;
+ current[1] = y;
+ }
+ }
+};
+
+/**@
+* #Crafty.Circle
+* @category 2D
+* Circle object used for hitboxes and click maps. Must pass a `x`, a `y` and a `radius` value.
+*
+* ~~~
+* var centerX = 5,
+* centerY = 10,
+* radius = 25;
+*
+* new Crafty.circle(centerX, centerY, radius);
+* ~~~
+*
+* When creating a circle for an entity, each point should be offset or relative from the entities `x` and `y`
+* (don't include the absolute values as it will automatically calculate this).
+*/
+Crafty.circle = function (x, y, radius) {
+ this.x = x;
+ this.y = y;
+ this.radius = radius;
+
+ // Creates an octogon that aproximate the circle for backward compatibility.
+ this.points = [];
+ var theta;
+
+ for (var i = 0; i < 8; i++) {
+ theta = i * Math.PI / 4;
+ this.points[i] = [Math.sin(theta) * radius, Math.cos(theta) * radius];
+ }
+};
+
+Crafty.circle.prototype = {
+/**@
+ * #.containsPoint
+ * @comp Crafty.Circle
+ * @sign public Boolean .containsPoint(Number x, Number y)
+ * @param x - X position of the point
+ * @param y - Y position of the point
+ * Method is used to determine if a given point is contained by the circle.
+ * @example
+ * ~~~
+ * var circle = new Crafty.circle(0, 0, 10);
+ * circle.containsPoint(0, 0); //TRUE
+ * circle.containsPoint(50, 50); //FALSE
+ * ~~~
+ */
+ containsPoint: function (x, y) {
+ var radius = this.radius,
+ sqrt = Math.sqrt,
+ deltaX = this.x - x,
+ deltaY = this.y - y;
+
+ return (deltaX * deltaX + deltaY * deltaY) < (radius * radius);
+ },
+
+ /**@
+ * #.shift
+ * @comp Crafty.Circle
+ * @sign public void .shift(Number x, Number y)
+ * @param x - Amount to shift the `x` axis
+ * @param y - Amount to shift the `y` axis
+ * Shifts the circle by the specified amount.
+ * @example
+ * ~~~
+ * var poly = new Crafty.circle(0, 0, 10);
+ * circle.shift(5,5);
+ * //{x: 5, y: 5, radius: 10};
+ * ~~~
+ */
+ shift: function (x, y) {
+ this.x += x;
+ this.y += y;
+
+ var i = 0, l = this.points.length, current;
+ for (; i < l; i++) {
+ current = this.points[i];
+ current[0] += x;
+ current[1] += y;
+ }
+ },
+
+ rotate: function () {
+ // We are a circle, we don't have to rotate :)
+ }
+};
+
+
+Crafty.matrix = function (m) {
+ this.mtx = m;
+ this.width = m[0].length;
+ this.height = m.length;
+};
+
+Crafty.matrix.prototype = {
+ x: function (other) {
+ if (this.width != other.height) {
+ return;
+ }
+
+ var result = [];
+ for (var i = 0; i < this.height; i++) {
+ result[i] = [];
+ for (var j = 0; j < other.width; j++) {
+ var sum = 0;
+ for (var k = 0; k < this.width; k++) {
+ sum += this.mtx[i][k] * other.mtx[k][j];
+ }
+ result[i][j] = sum;
+ }
+ }
+ return new Crafty.matrix(result);
+ },
+
+
+ e: function (row, col) {
+ //test if out of bounds
+ if (row < 1 || row > this.mtx.length || col < 1 || col > this.mtx[0].length) return null;
+ return this.mtx[row - 1][col - 1];
+ }
+}
+ /**@
+* #Collision
+* @category 2D
+* Component to detect collision between any two convex polygons.
+*/
+Crafty.c("Collision", {
+
+ init: function () {
+ this.requires("2D");
+ },
+
+ /**@
+ * #.collision
+ * @comp Collision
+ * @sign public this .collision([Crafty.Polygon polygon])
+ * @param polygon - Crafty.Polygon object that will act as the hit area
+ * Constructor takes a polygon to use as the hit area. If left empty,
+ * will create a rectangle polygon based on the x, y, w, h dimensions.
+ *
+ * This must be called before any .hit() or .onhit() methods.
+ *
+ * The hit area (polygon) must be a convex shape and not concave
+ * for the collision detection to work.
+ * @example
+ * ~~~
+ * Crafty.e("2D, Collision").collision(
+ * new Crafty.polygon([50,0], [100,100], [0,100])
+ * );
+ * ~~~
+ * @see Crafty.Polygon
+ */
+ collision: function (poly) {
+ var area = this._mbr || this;
+
+ //if no polygon presented, create a square
+ if (!poly) {
+ poly = new Crafty.polygon([0, 0], [area._w, 0], [area._w, area._h], [0, area._h]);
+ }
+ this.map = poly;
+ this.attach(this.map);
+ this.map.shift(area._x, area._y);
+
+ return this;
+ },
+
+ /**@
+ * #.hit
+ * @comp Collision
+ * @sign public Boolean/Array hit(String component)
+ * @param component - Check collision with entities that has this component
+ * @return `false` if no collision. If a collision is detected, returns an Array of objects that are colliding.
+ * Takes an argument for a component to test collision for. If a collision is found, an array of
+ * every object in collision along with the amount of overlap is passed.
+ *
+ * If no collision, will return false. The return collision data will be an Array of Objects with the
+ * type of collision used, the object collided and if the type used was SAT (a polygon was used as the hitbox) then an amount of overlap.
+ * ~~~
+ * [{
+ * obj: [entity],
+ * type "MBR" or "SAT",
+ * overlap: [number]
+ * }]
+ * ~~~
+ * `MBR` is your standard axis aligned rectangle intersection (`.intersect` in the 2D component).
+ * `SAT` is collision between any convex polygon.
+ * @see .onHit, 2D
+ */
+ hit: function (comp) {
+ var area = this._mbr || this,
+ results = Crafty.map.search(area, false),
+ i = 0, l = results.length,
+ dupes = {},
+ id, obj, oarea, key,
+ hasMap = ('map' in this && 'containsPoint' in this.map),
+ finalresult = [];
+
+ if (!l) {
+ return false;
+ }
+
+ for (; i < l; ++i) {
+ obj = results[i];
+ oarea = obj._mbr || obj; //use the mbr
+
+ if (!obj) continue;
+ id = obj[0];
+
+ //check if not added to hash and that actually intersects
+ if (!dupes[id] && this[0] !== id && obj.__c[comp] &&
+ oarea._x < area._x + area._w && oarea._x + oarea._w > area._x &&
+ oarea._y < area._y + area._h && oarea._h + oarea._y > area._y)
+ dupes[id] = obj;
+ }
+
+ for (key in dupes) {
+ obj = dupes[key];
+
+ if (hasMap && 'map' in obj) {
+ var SAT = this._SAT(this.map, obj.map);
+ SAT.obj = obj;
+ SAT.type = "SAT";
+ if (SAT) finalresult.push(SAT);
+ } else {
+ finalresult.push({ obj: obj, type: "MBR" });
+ }
+ }
+
+ if (!finalresult.length) {
+ return false;
+ }
+
+ return finalresult;
+ },
+
+ /**@
+ * #.onHit
+ * @comp Collision
+ * @sign public this .onHit(String component, Function hit[, Function noHit])
+ * @param component - Component to check collisions for
+ * @param hit - Callback method to execute when collided with component
+ * @param noHit - Callback method executed once as soon as collision stops
+ * Creates an enterframe event calling .hit() each time and if collision detected will invoke the callback.
+ * @see .hit
+ */
+ onHit: function (comp, callback, callbackOff) {
+ var justHit = false;
+ this.bind("EnterFrame", function () {
+ var hitdata = this.hit(comp);
+ if (hitdata) {
+ justHit = true;
+ callback.call(this, hitdata);
+ } else if (justHit) {
+ if (typeof callbackOff == 'function') {
+ callbackOff.call(this);
+ }
+ justHit = false;
+ }
+ });
+ return this;
+ },
+
+ _SAT: function (poly1, poly2) {
+ var points1 = poly1.points,
+ points2 = poly2.points,
+ i = 0, l = points1.length,
+ j, k = points2.length,
+ normal = { x: 0, y: 0 },
+ length,
+ min1, min2,
+ max1, max2,
+ interval,
+ MTV = null,
+ MTV2 = null,
+ MN = null,
+ dot,
+ nextPoint,
+ currentPoint;
+
+ //loop through the edges of Polygon 1
+ for (; i < l; i++) {
+ nextPoint = points1[(i == l - 1 ? 0 : i + 1)];
+ currentPoint = points1[i];
+
+ //generate the normal for the current edge
+ normal.x = -(nextPoint[1] - currentPoint[1]);
+ normal.y = (nextPoint[0] - currentPoint[0]);
+
+ //normalize the vector
+ length = Math.sqrt(normal.x * normal.x + normal.y * normal.y);
+ normal.x /= length;
+ normal.y /= length;
+
+ //default min max
+ min1 = min2 = -1;
+ max1 = max2 = -1;
+
+ //project all vertices from poly1 onto axis
+ for (j = 0; j < l; ++j) {
+ dot = points1[j][0] * normal.x + points1[j][1] * normal.y;
+ if (dot > max1 || max1 === -1) max1 = dot;
+ if (dot < min1 || min1 === -1) min1 = dot;
+ }
+
+ //project all vertices from poly2 onto axis
+ for (j = 0; j < k; ++j) {
+ dot = points2[j][0] * normal.x + points2[j][1] * normal.y;
+ if (dot > max2 || max2 === -1) max2 = dot;
+ if (dot < min2 || min2 === -1) min2 = dot;
+ }
+
+ //calculate the minimum translation vector should be negative
+ if (min1 < min2) {
+ interval = min2 - max1;
+
+ normal.x = -normal.x;
+ normal.y = -normal.y;
+ } else {
+ interval = min1 - max2;
+ }
+
+ //exit early if positive
+ if (interval >= 0) {
+ return false;
+ }
+
+ if (MTV === null || interval > MTV) {
+ MTV = interval;
+ MN = { x: normal.x, y: normal.y };
+ }
+ }
+
+ //loop through the edges of Polygon 2
+ for (i = 0; i < k; i++) {
+ nextPoint = points2[(i == k - 1 ? 0 : i + 1)];
+ currentPoint = points2[i];
+
+ //generate the normal for the current edge
+ normal.x = -(nextPoint[1] - currentPoint[1]);
+ normal.y = (nextPoint[0] - currentPoint[0]);
+
+ //normalize the vector
+ length = Math.sqrt(normal.x * normal.x + normal.y * normal.y);
+ normal.x /= length;
+ normal.y /= length;
+
+ //default min max
+ min1 = min2 = -1;
+ max1 = max2 = -1;
+
+ //project all vertices from poly1 onto axis
+ for (j = 0; j < l; ++j) {
+ dot = points1[j][0] * normal.x + points1[j][1] * normal.y;
+ if (dot > max1 || max1 === -1) max1 = dot;
+ if (dot < min1 || min1 === -1) min1 = dot;
+ }
+
+ //project all vertices from poly2 onto axis
+ for (j = 0; j < k; ++j) {
+ dot = points2[j][0] * normal.x + points2[j][1] * normal.y;
+ if (dot > max2 || max2 === -1) max2 = dot;
+ if (dot < min2 || min2 === -1) min2 = dot;
+ }
+
+ //calculate the minimum translation vector should be negative
+ if (min1 < min2) {
+ interval = min2 - max1;
+
+ normal.x = -normal.x;
+ normal.y = -normal.y;
+ } else {
+ interval = min1 - max2;
+
+
+ }
+
+ //exit early if positive
+ if (interval >= 0) {
+ return false;
+ }
+
+ if (MTV === null || interval > MTV) MTV = interval;
+ if (interval > MTV2 || MTV2 === null) {
+ MTV2 = interval;
+ MN = { x: normal.x, y: normal.y };
+ }
+ }
+
+ return { overlap: MTV2, normal: MN };
+ }
+});
+ /**@
+* #DOM
+* @category Graphics
+* Draws entities as DOM nodes, specifically `
`s.
+*/
+Crafty.c("DOM", {
+/**@
+ * #._element
+ * @comp DOM
+ * The DOM element used to represent the entity.
+ */
+ _element: null,
+
+ init: function () {
+ this._element = document.createElement("div");
+ Crafty.stage.inner.appendChild(this._element);
+ this._element.style.position = "absolute";
+ this._element.id = "ent" + this[0];
+
+ this.bind("Change", function () {
+ if (!this._changed) {
+ this._changed = true;
+ Crafty.DrawManager.add(this);
+ }
+ });
+
+ function updateClass() {
+ var i = 0, c = this.__c, str = "";
+ for (i in c) {
+ str += ' ' + i;
+ }
+ str = str.substr(1);
+ this._element.className = str;
+ }
+
+ this.bind("NewComponent", updateClass).bind("RemoveComponent", updateClass);
+
+ if (Crafty.support.prefix === "ms" && Crafty.support.version < 9) {
+ this._filters = {};
+
+ this.bind("Rotate", function (e) {
+ var m = e.matrix,
+ elem = this._element.style,
+ M11 = m.M11.toFixed(8),
+ M12 = m.M12.toFixed(8),
+ M21 = m.M21.toFixed(8),
+ M22 = m.M22.toFixed(8);
+
+ this._filters.rotation = "progid:DXImageTransform.Microsoft.Matrix(M11=" + M11 + ", M12=" + M12 + ", M21=" + M21 + ", M22=" + M22 + ",sizingMethod='auto expand')";
+ });
+ }
+
+ this.bind("Remove", this.undraw);
+ this.bind("RemoveComponent", function (compName) {
+ if (compName === "DOM")
+ this.undraw();
+ });
+ },
+
+ /**@
+ * #.getDomId
+ * @comp DOM
+ * @sign public this .getId()
+ * Get the Id of the DOM element used to represent the entity.
+ */
+ getDomId: function() {
+ return this._element.id;
+ },
+
+ /**@
+ * #.DOM
+ * @comp DOM
+ * @trigger Draw - when the entity is ready to be drawn to the stage - { style:String, type:"DOM", co}
+ * @sign public this .DOM(HTMLElement elem)
+ * @param elem - HTML element that will replace the dynamically created one
+ * Pass a DOM element to use rather than one created. Will set `._element` to this value. Removes the old element.
+ */
+ DOM: function (elem) {
+ if (elem && elem.nodeType) {
+ this.undraw();
+ this._element = elem;
+ this._element.style.position = 'absolute';
+ }
+ return this;
+ },
+
+ /**@
+ * #.draw
+ * @comp DOM
+ * @sign public this .draw(void)
+ * Updates the CSS properties of the node to draw on the stage.
+ */
+ draw: function () {
+ var style = this._element.style,
+ coord = this.__coord || [0, 0, 0, 0],
+ co = { x: coord[0], y: coord[1] },
+ prefix = Crafty.support.prefix,
+ trans = [];
+
+ if (!this._visible) style.visibility = "hidden";
+ else style.visibility = "visible";
+
+ //utilize CSS3 if supported
+ if (Crafty.support.css3dtransform) {
+ trans.push("translate3d(" + (~~this._x) + "px," + (~~this._y) + "px,0)");
+ } else {
+ style.left = ~~(this._x) + "px";
+ style.top = ~~(this._y) + "px";
+ }
+
+ style.width = ~~(this._w) + "px";
+ style.height = ~~(this._h) + "px";
+ style.zIndex = this._z;
+
+ style.opacity = this._alpha;
+ style[prefix + "Opacity"] = this._alpha;
+
+ //if not version 9 of IE
+ if (prefix === "ms" && Crafty.support.version < 9) {
+ //for IE version 8, use ImageTransform filter
+ if (Crafty.support.version === 8) {
+ this._filters.alpha = "progid:DXImageTransform.Microsoft.Alpha(Opacity=" + (this._alpha * 100) + ")"; // first!
+ //all other versions use filter
+ } else {
+ this._filters.alpha = "alpha(opacity=" + (this._alpha * 100) + ")";
+ }
+ }
+
+ if (this._mbr) {
+ var origin = this._origin.x + "px " + this._origin.y + "px";
+ style.transformOrigin = origin;
+ style[prefix + "TransformOrigin"] = origin;
+ if (Crafty.support.css3dtransform) trans.push("rotateZ(" + this._rotation + "deg)");
+ else trans.push("rotate(" + this._rotation + "deg)");
+ }
+
+ if (this._flipX) {
+ trans.push("scaleX(-1)");
+ if (prefix === "ms" && Crafty.support.version < 9) {
+ this._filters.flipX = "fliph";
+ }
+ }
+
+ if (this._flipY) {
+ trans.push("scaleY(-1)");
+ if (prefix === "ms" && Crafty.support.version < 9) {
+ this._filters.flipY = "flipv";
+ }
+ }
+
+ //apply the filters if IE
+ if (prefix === "ms" && Crafty.support.version < 9) {
+ this.applyFilters();
+ }
+
+ style.transform = trans.join(" ");
+ style[prefix + "Transform"] = trans.join(" ");
+
+ this.trigger("Draw", { style: style, type: "DOM", co: co });
+
+ return this;
+ },
+
+ applyFilters: function () {
+ this._element.style.filter = "";
+ var str = "";
+
+ for (var filter in this._filters) {
+ if (!this._filters.hasOwnProperty(filter)) continue;
+ str += this._filters[filter] + " ";
+ }
+
+ this._element.style.filter = str;
+ },
+
+ /**@
+ * #.undraw
+ * @comp DOM
+ * @sign public this .undraw(void)
+ * Removes the element from the stage.
+ */
+ undraw: function () {
+ if (this._element) {
+ Crafty.stage.inner.removeChild(this._element);
+ }
+ return this;
+ },
+
+ /**@
+ * #.css
+ * @comp DOM
+ * @sign public * css(String property, String value)
+ * @param property - CSS property to modify
+ * @param value - Value to give the CSS property
+ * @sign public * css(Object map)
+ * @param map - Object where the key is the CSS property and the value is CSS value
+ * Apply CSS styles to the element.
+ *
+ * Can pass an object where the key is the style property and the value is style value.
+ *
+ * For setting one style, simply pass the style as the first argument and the value as the second.
+ *
+ * The notation can be CSS or JS (e.g. `text-align` or `textAlign`).
+ *
+ * To return a value, pass the property.
+ * @example
+ * ~~~
+ * this.css({'text-align', 'center', font: 'Arial'});
+ * this.css("textAlign", "center");
+ * this.css("text-align"); //returns center
+ * ~~~
+ */
+ css: function (obj, value) {
+ var key,
+ elem = this._element,
+ val,
+ style = elem.style;
+
+ //if an object passed
+ if (typeof obj === "object") {
+ for (key in obj) {
+ if (!obj.hasOwnProperty(key)) continue;
+ val = obj[key];
+ if (typeof val === "number") val += 'px';
+
+ style[Crafty.DOM.camelize(key)] = val;
+ }
+ } else {
+ //if a value is passed, set the property
+ if (value) {
+ if (typeof value === "number") value += 'px';
+ style[Crafty.DOM.camelize(obj)] = value;
+ } else { //otherwise return the computed property
+ return Crafty.DOM.getStyle(elem, obj);
+ }
+ }
+
+ this.trigger("Change");
+
+ return this;
+ }
+});
+
+/**
+* Fix IE6 background flickering
+*/
+try {
+ document.execCommand("BackgroundImageCache", false, true);
+} catch (e) { }
+
+Crafty.extend({
+/**@
+ * #Crafty.DOM
+ * @category Graphics
+ * Collection of utilities for using the DOM.
+ */
+ DOM: {
+ /**@
+ * #Crafty.DOM.window
+ * @comp Crafty.DOM
+ * Object with `width` and `height` values representing the width
+ * and height of the `window`.
+ */
+ window: {
+ init: function () {
+ this.width = window.innerWidth || (window.document.documentElement.clientWidth || window.document.body.clientWidth);
+ this.height = window.innerHeight || (window.document.documentElement.clientHeight || window.document.body.clientHeight);
+ },
+
+ width: 0,
+ height: 0
+ },
+
+ /**@
+ * #Crafty.DOM.inner
+ * @comp Crafty.DOM
+ * @sign public Object Crafty.DOM.inner(HTMLElement obj)
+ * @param obj - HTML element to calculate the position
+ * @returns Object with `x` key being the `x` position, `y` being the `y` position
+ * Find a DOM elements position including
+ * padding and border.
+ */
+ inner: function (obj) {
+ var rect = obj.getBoundingClientRect(),
+ x = rect.left + (window.pageXOffset ? window.pageXOffset : document.body.scrollTop),
+ y = rect.top + (window.pageYOffset ? window.pageYOffset : document.body.scrollLeft),
+
+ //border left
+ borderX = parseInt(this.getStyle(obj, 'border-left-width') || 0, 10) || parseInt(this.getStyle(obj, 'borderLeftWidth') || 0, 10) || 0,
+ borderY = parseInt(this.getStyle(obj, 'border-top-width') || 0, 10) || parseInt(this.getStyle(obj, 'borderTopWidth') || 0, 10) || 0;
+
+ x += borderX;
+ y += borderY;
+
+ return { x: x, y: y };
+ },
+
+ /**@
+ * #Crafty.DOM.getStyle
+ * @comp Crafty.DOM
+ * @sign public Object Crafty.DOM.getStyle(HTMLElement obj, String property)
+ * @param obj - HTML element to find the style
+ * @param property - Style to return
+ * Determine the value of a style on an HTML element. Notation can be
+ * in either CSS or JS.
+ */
+ getStyle: function (obj, prop) {
+ var result;
+ if (obj.currentStyle)
+ result = obj.currentStyle[this.camelize(prop)];
+ else if (window.getComputedStyle)
+ result = document.defaultView.getComputedStyle(obj, null).getPropertyValue(this.csselize(prop));
+ return result;
+ },
+
+ /**
+ * Used in the Zepto framework
+ *
+ * Converts CSS notation to JS notation
+ */
+ camelize: function (str) {
+ return str.replace(/-+(.)?/g, function (match, chr){ return chr ? chr.toUpperCase() : '' });
+ },
+
+ /**
+ * Converts JS notation to CSS notation
+ */
+ csselize: function (str) {
+ return str.replace(/[A-Z]/g, function (chr){ return chr ? '-' + chr.toLowerCase() : '' });
+ },
+
+ /**@
+ * #Crafty.DOM.translate
+ * @comp Crafty.DOM
+ * @sign public Object Crafty.DOM.translate(Number x, Number y)
+ * @param x - x position to translate
+ * @param y - y position to translate
+ * @return Object with x and y as keys and translated values
+ *
+ * Method will translate x and y positions to positions on the
+ * stage. Useful for mouse events with `e.clientX` and `e.clientY`.
+ */
+ translate: function (x, y) {
+ return {
+ x: x - Crafty.stage.x + document.body.scrollLeft + document.documentElement.scrollLeft - Crafty.viewport._x,
+ y: y - Crafty.stage.y + document.body.scrollTop + document.documentElement.scrollTop - Crafty.viewport._y
+ }
+ }
+ }
+});
+ /**@
+* #Crafty.support
+* @category Misc, Core
+* Determines feature support for what Crafty can do.
+*/
+(function testSupport() {
+ var support = Crafty.support = {},
+ ua = navigator.userAgent.toLowerCase(),
+ match = /(webkit)[ \/]([\w.]+)/.exec(ua) ||
+ /(o)pera(?:.*version)?[ \/]([\w.]+)/.exec(ua) ||
+ /(ms)ie ([\w.]+)/.exec(ua) ||
+ /(moz)illa(?:.*? rv:([\w.]+))?/.exec(ua) || [],
+ mobile = /iPad|iPod|iPhone|Android|webOS/i.exec(ua);
+
+ if (mobile) Crafty.mobile = mobile[0];
+
+ /**@
+ * #Crafty.support.setter
+ * @comp Crafty.support
+ * Is `__defineSetter__` supported?
+ */
+ support.setter = ('__defineSetter__' in this && '__defineGetter__' in this);
+
+ /**@
+ * #Crafty.support.defineProperty
+ * @comp Crafty.support
+ * Is `Object.defineProperty` supported?
+ */
+ support.defineProperty = (function () {
+ if (!'defineProperty' in Object) return false;
+ try { Object.defineProperty({}, 'x', {}); }
+ catch (e) { return false };
+ return true;
+ })();
+
+ /**@
+ * #Crafty.support.audio
+ * @comp Crafty.support
+ * Is HTML5 `Audio` supported?
+ */
+ support.audio = ('Audio' in window);
+
+ /**@
+ * #Crafty.support.prefix
+ * @comp Crafty.support
+ * Returns the browser specific prefix (`Moz`, `O`, `ms`, `webkit`).
+ */
+ support.prefix = (match[1] || match[0]);
+
+ //browser specific quirks
+ if (support.prefix === "moz") support.prefix = "Moz";
+ if (support.prefix === "o") support.prefix = "O";
+
+ if (match[2]) {
+ /**@
+ * #Crafty.support.versionName
+ * @comp Crafty.support
+ * Version of the browser
+ */
+ support.versionName = match[2];
+
+ /**@
+ * #Crafty.support.version
+ * @comp Crafty.support
+ * Version number of the browser as an Integer (first number)
+ */
+ support.version = +(match[2].split("."))[0];
+ }
+
+ /**@
+ * #Crafty.support.canvas
+ * @comp Crafty.support
+ * Is the `canvas` element supported?
+ */
+ support.canvas = ('getContext' in document.createElement("canvas"));
+
+ /**@
+ * #Crafty.support.webgl
+ * @comp Crafty.support
+ * Is WebGL supported on the canvas element?
+ */
+ if (support.canvas) {
+ var gl;
+ try {
+ gl = document.createElement("canvas").getContext("experimental-webgl");
+ gl.viewportWidth = canvas.width;
+ gl.viewportHeight = canvas.height;
+ }
+ catch (e) { }
+ support.webgl = !!gl;
+ }
+ else {
+ support.webgl = false;
+ }
+
+ support.css3dtransform = (typeof document.createElement("div").style["Perspective"] !== "undefined")
+ || (typeof document.createElement("div").style[support.prefix + "Perspective"] !== "undefined");
+})();
+Crafty.extend({
+
+ zeroFill: function (number, width) {
+ width -= number.toString().length;
+ if (width > 0)
+ return new Array(width + (/\./.test(number) ? 2 : 1)).join('0') + number;
+ return number.toString();
+ },
+
+ /**@
+ * #Crafty.sprite
+ * @category Graphics
+ * @sign public this Crafty.sprite([Number tile], String url, Object map[, Number paddingX[, Number paddingY]])
+ * @param tile - Tile size of the sprite map, defaults to 1
+ * @param url - URL of the sprite image
+ * @param map - Object where the key is what becomes a new component and the value points to a position on the sprite map
+ * @param paddingX - Horizontal space inbetween tiles. Defaults to 0.
+ * @param paddingY - Vertical space inbetween tiles. Defaults to paddingX.
+ * Generates components based on positions in a sprite image to be applied to entities.
+ *
+ * Accepts a tile size, URL and map for the name of the sprite and it's position.
+ *
+ * The position must be an array containing the position of the sprite where index `0`
+ * is the `x` position, `1` is the `y` position and optionally `2` is the width and `3`
+ * is the height. If the sprite map has padding, pass the values for the `x` padding
+ * or `y` padding. If they are the same, just add one value.
+ *
+ * If the sprite image has no consistent tile size, `1` or no argument need be
+ * passed for tile size.
+ *
+ * Entities that add the generated components are also given a component called `Sprite`.
+ * @see Sprite
+ */
+ sprite: function (tile, tileh, url, map, paddingX, paddingY) {
+ var spriteName, temp, x, y, w, h, img;
+
+ //if no tile value, default to 1
+ if (typeof tile === "string") {
+ paddingY = paddingX;
+ paddingX = map;
+ map = tileh;
+ url = tile;
+ tile = 1;
+ tileh = 1;
+ }
+
+ if (typeof tileh == "string") {
+ paddingY = paddingX;
+ paddingX = map;
+ map = url;
+ url = tileh;
+ tileh = tile;
+ }
+
+ //if no paddingY, use paddingX
+ if (!paddingY && paddingX) paddingY = paddingX;
+ paddingX = parseInt(paddingX || 0, 10); //just incase
+ paddingY = parseInt(paddingY || 0, 10);
+
+ img = Crafty.assets[url];
+ if (!img) {
+ img = new Image();
+ img.src = url;
+ Crafty.assets[url] = img;
+ img.onload = function () {
+ //all components with this img are now ready
+ for (spriteName in map) {
+ Crafty(spriteName).each(function () {
+ this.ready = true;
+ this.trigger("Change");
+ });
+ }
+ };
+ }
+
+ for (spriteName in map) {
+ if (!map.hasOwnProperty(spriteName)) continue;
+
+ temp = map[spriteName];
+ x = temp[0] * (tile + paddingX);
+ y = temp[1] * (tileh + paddingY);
+ w = temp[2] * tile || tile;
+ h = temp[3] * tileh || tileh;
+
+ //generates sprite components for each tile in the map
+ Crafty.c(spriteName, {
+ ready: false,
+ __coord: [x, y, w, h],
+
+ init: function () {
+ this.requires("Sprite");
+ this.__trim = [0, 0, 0, 0];
+ this.__image = url;
+ this.__coord = [this.__coord[0], this.__coord[1], this.__coord[2], this.__coord[3]];
+ this.__tile = tile;
+ this.__tileh = tileh;
+ this.__padding = [paddingX, paddingY];
+ this.img = img;
+
+ //draw now
+ if (this.img.complete && this.img.width > 0) {
+ this.ready = true;
+ this.trigger("Change");
+ }
+
+ //set the width and height to the sprite size
+ this.w = this.__coord[2];
+ this.h = this.__coord[3];
+ }
+ });
+ }
+
+ return this;
+ },
+
+ _events: {},
+
+ /**@
+ * #Crafty.addEvent
+ * @category Events, Misc
+ * @sign public this Crafty.addEvent(Object ctx, HTMLElement obj, String event, Function callback)
+ * @param ctx - Context of the callback or the value of `this`
+ * @param obj - Element to add the DOM event to
+ * @param event - Event name to bind to
+ * @param callback - Method to execute when triggered
+ * Adds DOM level 3 events to elements. The arguments it accepts are the call
+ * context (the value of `this`), the DOM element to attach the event to,
+ * the event name (without `on` (`click` rather than `onclick`)) and
+ * finally the callback method.
+ *
+ * If no element is passed, the default element will be `window.document`.
+ *
+ * Callbacks are passed with event data.
+ * @see Crafty.removeEvent
+ */
+ addEvent: function (ctx, obj, type, callback) {
+ if (arguments.length === 3) {
+ callback = type;
+ type = obj;
+ obj = window.document;
+ }
+
+ //save anonymous function to be able to remove
+ var afn = function (e) { var e = e || window.event; callback.call(ctx, e) },
+ id = ctx[0] || "";
+
+ if (!this._events[id + obj + type + callback]) this._events[id + obj + type + callback] = afn;
+ else return;
+
+ if (obj.attachEvent) { //IE
+ obj.attachEvent('on' + type, afn);
+ } else { //Everyone else
+ obj.addEventListener(type, afn, false);
+ }
+ },
+
+ /**@
+ * #Crafty.removeEvent
+ * @category Events, Misc
+ * @sign public this Crafty.removeEvent(Object ctx, HTMLElement obj, String event, Function callback)
+ * @param ctx - Context of the callback or the value of `this`
+ * @param obj - Element the event is on
+ * @param event - Name of the event
+ * @param callback - Method executed when triggered
+ * Removes events attached by `Crafty.addEvent()`. All parameters must
+ * be the same that were used to attach the event including a reference
+ * to the callback method.
+ * @see Crafty.addEvent
+ */
+ removeEvent: function (ctx, obj, type, callback) {
+ if (arguments.length === 3) {
+ callback = type;
+ type = obj;
+ obj = window.document;
+ }
+
+ //retrieve anonymouse function
+ var id = ctx[0] || "",
+ afn = this._events[id + obj + type + callback];
+
+ if (afn) {
+ if (obj.detachEvent) {
+ obj.detachEvent('on' + type, afn);
+ } else obj.removeEventListener(type, afn, false);
+ delete this._events[id + obj + type + callback];
+ }
+ },
+
+ /**@
+ * #Crafty.background
+ * @category Graphics, Stage
+ * @sign public void Crafty.background(String value)
+ * @param color - Modify the background with a color or image
+ * This method is essentially a shortcut for adding a background
+ * style to the stage element.
+ */
+ background: function (color) {
+ Crafty.stage.elem.style.background = color;
+ },
+
+ /**@
+ * #Crafty.viewport
+ * @category Stage
+ * Viewport is essentially a 2D camera looking at the stage. Can be moved which
+ * in turn will react just like a camera moving in that direction.
+ */
+ viewport: {
+ /**@
+ * #Crafty.viewport.clampToEntities
+ * @comp Crafty.viewport
+ * Decides if the viewport functions should clamp to game entities.
+ * When set to `true` functions such as Crafty.viewport.mouselook() will not allow you to move the
+ * viewport over areas of the game that has no entities.
+ * For development it can be useful to set this to false.
+ */
+ clampToEntities: true,
+ width: 0,
+ height: 0,
+ /**@
+ * #Crafty.viewport.x
+ * @comp Crafty.viewport
+ * Will move the stage and therefore every visible entity along the `x`
+ * axis in the opposite direction.
+ *
+ * When this value is set, it will shift the entire stage. This means that entity
+ * positions are not exactly where they are on screen. To get the exact position,
+ * simply add `Crafty.viewport.x` onto the entities `x` position.
+ */
+ _x: 0,
+ /**@
+ * #Crafty.viewport.y
+ * @comp Crafty.viewport
+ * Will move the stage and therefore every visible entity along the `y`
+ * axis in the opposite direction.
+ *
+ * When this value is set, it will shift the entire stage. This means that entity
+ * positions are not exactly where they are on screen. To get the exact position,
+ * simply add `Crafty.viewport.y` onto the entities `y` position.
+ */
+ _y: 0,
+
+ /**@
+ * #Crafty.viewport.scroll
+ * @comp Crafty.viewport
+ * @sign Crafty.viewport.scroll(String axis, Number v)
+ * @param axis - 'x' or 'y'
+ * @param v - The new absolute position on the axis
+ *
+ * Will move the viewport to the position given on the specified axis
+ * @example Crafty.viewport.scroll('_x', 500);
+ * Will move the camera 500 pixels right of its initial position, in effect
+ * shifting everything in the viewport 500 pixels to the left.
+ */
+ scroll: function (axis, v) {
+ v = Math.floor(v);
+ var change = v - this[axis], //change in direction
+ context = Crafty.canvas.context,
+ style = Crafty.stage.inner.style,
+ canvas;
+
+ //update viewport and DOM scroll
+ this[axis] = v;
+ if (axis == '_x') {
+ if (context) context.translate(change, 0);
+ } else {
+ if (context) context.translate(0, change);
+ }
+ if (context) Crafty.DrawManager.drawAll();
+ style[axis == '_x' ? "left" : "top"] = v + "px";
+ },
+
+ rect: function () {
+ return { _x: -this._x, _y: -this._y, _w: this.width, _h: this.height };
+ },
+
+ /**
+ * #Crafty.viewport.pan
+ * @comp Crafty.viewport
+ * @sign public void Crafty.viewport.pan(String axis, Number v, Number time)
+ * @param String axis - 'x' or 'y'. The axis to move the camera on
+ * @param Number v - the distance to move the camera by
+ * @param Number time - The duration in frames for the entire camera movement
+ *
+ * Pans the camera a given number of pixels over a given number of frames
+ */
+ pan: (function () {
+ var tweens = {}, i, bound = false;
+
+ function enterFrame(e) {
+ var l = 0;
+ for (i in tweens) {
+ var prop = tweens[i];
+ if (prop.remTime > 0) {
+ prop.current += prop.diff;
+ prop.remTime--;
+ Crafty.viewport[i] = Math.floor(prop.current);
+ l++;
+ }
+ else {
+ delete tweens[i];
+ }
+ }
+ if (l) Crafty.viewport._clamp();
+ }
+
+ return function (axis, v, time) {
+ Crafty.viewport.follow();
+ if (axis == 'reset') {
+ for (i in tweens) {
+ tweens[i].remTime = 0;
+ }
+ return;
+ }
+ if (time == 0) time = 1;
+ tweens[axis] = {
+ diff: -v / time,
+ current: Crafty.viewport[axis],
+ remTime: time
+ };
+ if (!bound) {
+ Crafty.bind("EnterFrame", enterFrame);
+ bound = true;
+ }
+ }
+ })(),
+
+ /**
+ * #Crafty.viewport.follow
+ * @comp Crafty.viewport
+ * @sign public void Crafty.viewport.follow(Object target, Number offsetx, Number offsety)
+ * @param Object target - An entity with the 2D component
+ * @param Number offsetx - Follow target should be offsetx pixels away from center
+ * @param Number offsety - Positive puts targ to the right of center
+ *
+ * Follows a given entity with the 2D component. If following target will take a portion of
+ * the viewport out of bounds of the world, following will stop until the target moves away.
+ * @example
+ * var ent = Crafty.e('2D, DOM').attr({w: 100, h: 100:});
+ * Crafty.viewport.follow(ent, 0, 0);
+ */
+ follow: (function () {
+ var oldTarget, offx, offy;
+
+ function change() {
+ Crafty.viewport.scroll('_x', -(this.x + (this.w / 2) - (Crafty.viewport.width / 2) - offx));
+ Crafty.viewport.scroll('_y', -(this.y + (this.h / 2) - (Crafty.viewport.height / 2) - offy));
+ Crafty.viewport._clamp();
+ }
+
+ return function (target, offsetx, offsety) {
+ if (oldTarget)
+ oldTarget.unbind('Change', change);
+ if (!target || !target.has('2D'))
+ return;
+ Crafty.viewport.pan('reset');
+
+ oldTarget = target;
+ offx = (typeof offsetx != 'undefined') ? offsetx : 0;
+ offy = (typeof offsety != 'undefined') ? offsety : 0;
+
+ target.bind('Change', change);
+ change.call(target);
+ }
+ })(),
+
+ /**
+ * #Crafty.viewport.centerOn
+ * @comp Crafty.viewport
+ * @sign public void Crafty.viewport.centerOn(Object target, Number time)
+ * @param Object target - An entity with the 2D component
+ * @param Number time - The number of frames to perform the centering over
+ *
+ * Centers the viewport on the given entity
+ */
+ centerOn: function (targ, time) {
+ var x = targ.x,
+ y = targ.y,
+ mid_x = targ.w / 2,
+ mid_y = targ.h / 2,
+ cent_x = Crafty.viewport.width / 2,
+ cent_y = Crafty.viewport.height / 2,
+ new_x = x + mid_x - cent_x,
+ new_y = y + mid_y - cent_y;
+
+ Crafty.viewport.pan('reset');
+ Crafty.viewport.pan('x', new_x, time);
+ Crafty.viewport.pan('y', new_y, time);
+ },
+
+ /**
+ * #Crafty.viewport.zoom
+ * @comp Crafty.viewport
+ * @sign public void Crafty.viewport.zoom(Number amt, Number cent_x, Number cent_y, Number time)
+ * @param Number amt - amount to zoom in on the target by (eg. 2, 4, 0.5)
+ * @param Number cent_x - the center to zoom on
+ * @param Number cent_y - the center to zoom on
+ * @param Number time - the duration in frames of the entire zoom operation
+ *
+ * Zooms the camera in on a given point. amt > 1 will bring the camera closer to the subject
+ * amt < 1 will bring it farther away. amt = 0 will do nothing.
+ * Zooming is multiplicative. To reset the zoom amount, pass 0.
+ */
+ zoom: (function () {
+ var zoom = 1,
+ zoom_tick = 0,
+ dur = 0,
+ prop = Crafty.support.prefix + "Transform",
+ bound = false,
+ act = {},
+ prct = {};
+ // what's going on:
+ // 1. Get the original point as a percentage of the stage
+ // 2. Scale the stage
+ // 3. Get the new size of the stage
+ // 4. Get the absolute position of our point using previous percentage
+ // 4. Offset inner by that much
+
+ function enterFrame() {
+ if (dur > 0) {
+ var old = {
+ width: act.width * zoom,
+ height: act.height * zoom
+ };
+ zoom += zoom_tick;
+ var new_s = {
+ width: act.width * zoom,
+ height: act.height * zoom
+ },
+ diff = {
+ width: new_s.width - old.width,
+ height: new_s.height - old.height
+ };
+ Crafty.stage.inner.style[prop] = 'scale(' + zoom + ',' + zoom + ')';
+ Crafty.viewport.x -= diff.width * prct.width;
+ Crafty.viewport.y -= diff.height * prct.height;
+ dur--;
+ }
+ }
+
+ return function (amt, cent_x, cent_y, time) {
+ var bounds = Crafty.map.boundaries(),
+ final_zoom = amt ? zoom * amt : 1;
+
+ act.width = bounds.max.x - bounds.min.x;
+ act.height = bounds.max.y - bounds.min.y;
+
+ prct.width = cent_x / act.width;
+ prct.height = cent_y / act.height;
+
+ if (time == 0) time = 1;
+ zoom_tick = (final_zoom - zoom) / time;
+ dur = time;
+
+ Crafty.viewport.pan('reset');
+ if (!bound) {
+ Crafty.bind('EnterFrame', enterFrame);
+ bound = true;
+ }
+ }
+ })(),
+
+ /**
+ * #Crafty.viewport.mouselook
+ * @comp Crafty.viewport
+ * @sign public void Crafty.viewport.mouselook(Boolean active)
+ * @param Boolean active - Activate or deactivate mouselook
+ *
+ * Toggle mouselook on the current viewport.
+ * Simply call this function and the user will be able to
+ * drag the viewport around.
+ */
+ mouselook: (function () {
+ var active = false,
+ dragging = false,
+ lastMouse = {}
+ old = {};
+
+
+ return function (op, arg) {
+ if (typeof op == 'boolean') {
+ active = op;
+ if (active) {
+ Crafty.mouseObjs++;
+ }
+ else {
+ Crafty.mouseObjs = Math.max(0, Crafty.mouseObjs - 1);
+ }
+ return;
+ }
+ if (!active) return;
+ switch (op) {
+ case 'move':
+ case 'drag':
+ if (!dragging) return;
+ diff = {
+ x: arg.clientX - lastMouse.x,
+ y: arg.clientY - lastMouse.y
+ };
+
+ Crafty.viewport.x += diff.x;
+ Crafty.viewport.y += diff.y;
+ Crafty.viewport._clamp();
+ case 'start':
+ lastMouse.x = arg.clientX;
+ lastMouse.y = arg.clientY;
+ dragging = true;
+ break;
+ case 'stop':
+ dragging = false;
+ break;
+ }
+ };
+ })(),
+
+ _clamp: function () {
+ // clamps the viewport to the viewable area
+ // under no circumstances should the viewport see something outside the boundary of the 'world'
+ if (!this.clampToEntities) return;
+ var bound = Crafty.map.boundaries();
+ if (bound.max.x - bound.min.x > Crafty.viewport.width) {
+ bound.max.x -= Crafty.viewport.width;
+
+ if (Crafty.viewport.x < -bound.max.x) {
+ Crafty.viewport.x = -bound.max.x;
+ }
+ else if (Crafty.viewport.x > -bound.min.x) {
+ Crafty.viewport.x = -bound.min.x;
+ }
+ }
+ else {
+ Crafty.viewport.x = -1 * (bound.min.x + (bound.max.x - bound.min.x) / 2 - Crafty.viewport.width / 2);
+ }
+ if (bound.max.y - bound.min.y > Crafty.viewport.height) {
+ bound.max.y -= Crafty.viewport.height;
+
+ if (Crafty.viewport.y < -bound.max.y) {
+ Crafty.viewport.y = -bound.max.y;
+ }
+ else if (Crafty.viewport.y > -bound.min.y) {
+ Crafty.viewport.y = -bound.min.y;
+ }
+ }
+ else {
+ Crafty.viewport.y = -1 * (bound.min.y + (bound.max.y - bound.min.y) / 2 - Crafty.viewport.height / 2);
+ }
+ },
+
+ /**@
+ * #Crafty.viewport.init
+ * @comp Crafty.viewport
+ * @sign public void Crafty.viewport.init([Number width, Number height])
+ * @param width - Width of the viewport
+ * @param height - Height of the viewport
+ *
+ * Initialize the viewport. If the arguments 'width' or 'height' are missing, or Crafty.mobile is true, use Crafty.DOM.window.width and Crafty.DOM.window.height (full screen model).
+ * Create a div with id `cr-stage`, if there is not already an HTMLElement with id `cr-stage` (by `Crafty.viewport.init`).
+ *
+ * @see Crafty.mobile
+ * @see Crafty.DOM.window.width
+ * @see Crafty.DOM.window.height
+ * @see Crafty.stage
+ */
+ init: function (w, h) {
+ Crafty.DOM.window.init();
+
+ //fullscreen if mobile or not specified
+ this.width = (!w || Crafty.mobile) ? Crafty.DOM.window.width : w;
+ this.height = (!h || Crafty.mobile) ? Crafty.DOM.window.height : h;
+
+ //check if stage exists
+ var crstage = document.getElementById("cr-stage");
+
+ /**@
+ * #Crafty.stage
+ * @category Core
+ * The stage where all the DOM entities will be placed.
+ */
+
+ /**@
+ * #Crafty.stage.elem
+ * @comp Crafty.stage
+ * The `#cr-stage` div element.
+ */
+
+ /**@
+ * #Crafty.stage.inner
+ * @comp Crafty.stage
+ * `Crafty.stage.inner` is a div inside the `#cr-stage` div that holds all DOM entities.
+ * If you use canvas, a `canvas` element is created at the same level in the dom
+ * as the the `Crafty.stage.inner` div. So the hierarchy in the DOM is
+ *
+ * `Crafty.stage.elem`
+ *
+ *
+ * - `Crafty.stage.inner` (a div HTMLElement)
+ *
+ * - `Crafty.canvas._canvas` (a canvas HTMLElement)
+ */
+
+ //create stage div to contain everything
+ Crafty.stage = {
+ x: 0,
+ y: 0,
+ fullscreen: false,
+ elem: (crstage ? crstage : document.createElement("div")),
+ inner: document.createElement("div")
+ };
+
+ //fullscreen, stop scrollbars
+ if ((!w && !h) || Crafty.mobile) {
+ document.body.style.overflow = "hidden";
+ Crafty.stage.fullscreen = true;
+ }
+
+ Crafty.addEvent(this, window, "resize", function () {
+ Crafty.DOM.window.init();
+ var w = Crafty.DOM.window.width,
+ h = Crafty.DOM.window.height,
+ offset;
+
+
+ if (Crafty.stage.fullscreen) {
+ this.width = w;
+ this.height = h;
+ Crafty.stage.elem.style.width = w + "px";
+ Crafty.stage.elem.style.height = h + "px";
+
+ if (Crafty.canvas._canvas) {
+ Crafty.canvas._canvas.width = w;
+ Crafty.canvas._canvas.height = h;
+ Crafty.DrawManager.drawAll();
+ }
+ }
+
+ offset = Crafty.DOM.inner(Crafty.stage.elem);
+ Crafty.stage.x = offset.x;
+ Crafty.stage.y = offset.y;
+ });
+
+ Crafty.addEvent(this, window, "blur", function () {
+ if (Crafty.settings.get("autoPause")) {
+ Crafty.pause();
+ }
+ });
+ Crafty.addEvent(this, window, "focus", function () {
+ if (Crafty._paused && Crafty.settings.get("autoPause")) {
+ Crafty.pause();
+ }
+ });
+
+ //make the stage unselectable
+ Crafty.settings.register("stageSelectable", function (v) {
+ Crafty.stage.elem.onselectstart = v ? function () { return true; } : function () { return false; };
+ });
+ Crafty.settings.modify("stageSelectable", false);
+
+ //make the stage have no context menu
+ Crafty.settings.register("stageContextMenu", function (v) {
+ Crafty.stage.elem.oncontextmenu = v ? function () { return true; } : function () { return false; };
+ });
+ Crafty.settings.modify("stageContextMenu", false);
+
+ Crafty.settings.register("autoPause", function (){ });
+ Crafty.settings.modify("autoPause", false);
+
+ //add to the body and give it an ID if not exists
+ if (!crstage) {
+ document.body.appendChild(Crafty.stage.elem);
+ Crafty.stage.elem.id = "cr-stage";
+ }
+
+ var elem = Crafty.stage.elem.style,
+ offset;
+
+ Crafty.stage.elem.appendChild(Crafty.stage.inner);
+ Crafty.stage.inner.style.position = "absolute";
+ Crafty.stage.inner.style.zIndex = "1";
+
+ //css style
+ elem.width = this.width + "px";
+ elem.height = this.height + "px";
+ elem.overflow = "hidden";
+
+ if (Crafty.mobile) {
+ elem.position = "absolute";
+ elem.left = "0px";
+ elem.top = "0px";
+
+ var meta = document.createElement("meta"),
+ head = document.getElementsByTagName("HEAD")[0];
+
+ //stop mobile zooming and scrolling
+ meta.setAttribute("name", "viewport");
+ meta.setAttribute("content", "width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no");
+ head.appendChild(meta);
+
+ //hide the address bar
+ meta = document.createElement("meta");
+ meta.setAttribute("name", "apple-mobile-web-app-capable");
+ meta.setAttribute("content", "yes");
+ head.appendChild(meta);
+ setTimeout(function () { window.scrollTo(0, 1); }, 0);
+
+ Crafty.addEvent(this, window, "touchmove", function (e) {
+ e.preventDefault();
+ });
+
+ Crafty.stage.x = 0;
+ Crafty.stage.y = 0;
+
+ } else {
+ elem.position = "relative";
+ //find out the offset position of the stage
+ offset = Crafty.DOM.inner(Crafty.stage.elem);
+ Crafty.stage.x = offset.x;
+ Crafty.stage.y = offset.y;
+ }
+
+ if (Crafty.support.setter) {
+ //define getters and setters to scroll the viewport
+ this.__defineSetter__('x', function (v) { this.scroll('_x', v); });
+ this.__defineSetter__('y', function (v) { this.scroll('_y', v); });
+ this.__defineGetter__('x', function () { return this._x; });
+ this.__defineGetter__('y', function () { return this._y; });
+ //IE9
+ } else if (Crafty.support.defineProperty) {
+ Object.defineProperty(this, 'x', { set: function (v) { this.scroll('_x', v); }, get: function () { return this._x; } });
+ Object.defineProperty(this, 'y', { set: function (v) { this.scroll('_y', v); }, get: function () { return this._y; } });
+ } else {
+ //create empty entity waiting for enterframe
+ this.x = this._x;
+ this.y = this._y;
+ Crafty.e("viewport");
+ }
+ }
+ },
+
+ /**@
+ * #Crafty.keys
+ * @category Input
+ * Object of key names and the corresponding key code.
+ * ~~~
+ * BACKSPACE: 8,
+ * TAB: 9,
+ * ENTER: 13,
+ * PAUSE: 19,
+ * CAPS: 20,
+ * ESC: 27,
+ * SPACE: 32,
+ * PAGE_UP: 33,
+ * PAGE_DOWN: 34,
+ * END: 35,
+ * HOME: 36,
+ * LEFT_ARROW: 37,
+ * UP_ARROW: 38,
+ * RIGHT_ARROW: 39,
+ * DOWN_ARROW: 40,
+ * INSERT: 45,
+ * DELETE: 46,
+ * 0: 48,
+ * 1: 49,
+ * 2: 50,
+ * 3: 51,
+ * 4: 52,
+ * 5: 53,
+ * 6: 54,
+ * 7: 55,
+ * 8: 56,
+ * 9: 57,
+ * A: 65,
+ * B: 66,
+ * C: 67,
+ * D: 68,
+ * E: 69,
+ * F: 70,
+ * G: 71,
+ * H: 72,
+ * I: 73,
+ * J: 74,
+ * K: 75,
+ * L: 76,
+ * M: 77,
+ * N: 78,
+ * O: 79,
+ * P: 80,
+ * Q: 81,
+ * R: 82,
+ * S: 83,
+ * T: 84,
+ * U: 85,
+ * V: 86,
+ * W: 87,
+ * X: 88,
+ * Y: 89,
+ * Z: 90,
+ * NUMPAD_0: 96,
+ * NUMPAD_1: 97,
+ * NUMPAD_2: 98,
+ * NUMPAD_3: 99,
+ * NUMPAD_4: 100,
+ * NUMPAD_5: 101,
+ * NUMPAD_6: 102,
+ * NUMPAD_7: 103,
+ * NUMPAD_8: 104,
+ * NUMPAD_9: 105,
+ * MULTIPLY: 106,
+ * ADD: 107,
+ * SUBSTRACT: 109,
+ * DECIMAL: 110,
+ * DIVIDE: 111,
+ * F1: 112,
+ * F2: 113,
+ * F3: 114,
+ * F4: 115,
+ * F5: 116,
+ * F6: 117,
+ * F7: 118,
+ * F8: 119,
+ * F9: 120,
+ * F10: 121,
+ * F11: 122,
+ * F12: 123,
+ * SHIFT: 16,
+ * CTRL: 17,
+ * ALT: 18,
+ * PLUS: 187,
+ * COMMA: 188,
+ * MINUS: 189,
+ * PERIOD: 190
+ * ~~~
+ */
+ keys: {
+ 'BACKSPACE': 8,
+ 'TAB': 9,
+ 'ENTER': 13,
+ 'PAUSE': 19,
+ 'CAPS': 20,
+ 'ESC': 27,
+ 'SPACE': 32,
+ 'PAGE_UP': 33,
+ 'PAGE_DOWN': 34,
+ 'END': 35,
+ 'HOME': 36,
+ 'LEFT_ARROW': 37,
+ 'UP_ARROW': 38,
+ 'RIGHT_ARROW': 39,
+ 'DOWN_ARROW': 40,
+ 'INSERT': 45,
+ 'DELETE': 46,
+ '0': 48,
+ '1': 49,
+ '2': 50,
+ '3': 51,
+ '4': 52,
+ '5': 53,
+ '6': 54,
+ '7': 55,
+ '8': 56,
+ '9': 57,
+ 'A': 65,
+ 'B': 66,
+ 'C': 67,
+ 'D': 68,
+ 'E': 69,
+ 'F': 70,
+ 'G': 71,
+ 'H': 72,
+ 'I': 73,
+ 'J': 74,
+ 'K': 75,
+ 'L': 76,
+ 'M': 77,
+ 'N': 78,
+ 'O': 79,
+ 'P': 80,
+ 'Q': 81,
+ 'R': 82,
+ 'S': 83,
+ 'T': 84,
+ 'U': 85,
+ 'V': 86,
+ 'W': 87,
+ 'X': 88,
+ 'Y': 89,
+ 'Z': 90,
+ 'NUMPAD_0': 96,
+ 'NUMPAD_1': 97,
+ 'NUMPAD_2': 98,
+ 'NUMPAD_3': 99,
+ 'NUMPAD_4': 100,
+ 'NUMPAD_5': 101,
+ 'NUMPAD_6': 102,
+ 'NUMPAD_7': 103,
+ 'NUMPAD_8': 104,
+ 'NUMPAD_9': 105,
+ 'MULTIPLY': 106,
+ 'ADD': 107,
+ 'SUBSTRACT': 109,
+ 'DECIMAL': 110,
+ 'DIVIDE': 111,
+ 'F1': 112,
+ 'F2': 113,
+ 'F3': 114,
+ 'F4': 115,
+ 'F5': 116,
+ 'F6': 117,
+ 'F7': 118,
+ 'F8': 119,
+ 'F9': 120,
+ 'F10': 121,
+ 'F11': 122,
+ 'F12': 123,
+ 'SHIFT': 16,
+ 'CTRL': 17,
+ 'ALT': 18,
+ 'PLUS': 187,
+ 'COMMA': 188,
+ 'MINUS': 189,
+ 'PERIOD': 190
+ },
+
+ /**@
+ * #Crafty.mouseButtons
+ * @category Input
+ * Object of mouseButton names and the corresponding button ID.
+ * In all mouseEvents we add the e.mouseButton property with a value normalized to match e.button of modern webkit
+ * ~~~
+ * LEFT: 0,
+ * MIDDLE: 1,
+ * RIGHT: 2
+ * ~~~
+ */
+ mouseButtons: {
+ LEFT: 0,
+ MIDDLE: 1,
+ RIGHT: 2
+ }
+});
+
+
+
+/**
+* Entity fixes the lack of setter support
+*/
+Crafty.c("viewport", {
+ init: function () {
+ this.bind("EnterFrame", function () {
+ if (Crafty.viewport._x !== Crafty.viewport.x) {
+ Crafty.viewport.scroll('_x', Crafty.viewport.x);
+ }
+
+ if (Crafty.viewport._y !== Crafty.viewport.y) {
+ Crafty.viewport.scroll('_y', Crafty.viewport.y);
+ }
+ });
+ }
+});
+ /**@
+* #Canvas
+* @category Graphics
+* Draws itself onto a canvas. Crafty.canvas.init() will be automatically called it is not called already (hence the canvas element dosen't exist).
+* @trigger Draw - when the entity is ready to be drawn to the stage - {type: "canvas", pos, co, ctx}
+* @trigger NoCanvas - if the browser does not support canvas
+*/
+Crafty.c("Canvas", {
+
+ init: function () {
+ if (!Crafty.canvas.context) {
+ Crafty.canvas.init();
+ }
+
+ //increment the amount of canvas objs
+ Crafty.DrawManager.total2D++;
+
+ this.bind("Change", function (e) {
+ //if within screen, add to list
+ if (this._changed === false) {
+ this._changed = Crafty.DrawManager.add(e || this, this);
+ } else {
+ if (e) this._changed = Crafty.DrawManager.add(e, this);
+ }
+ });
+
+ this.bind("Remove", function () {
+ Crafty.DrawManager.total2D--;
+ Crafty.DrawManager.add(this, this);
+ });
+ },
+
+ /**@
+ * #.draw
+ * @comp Canvas
+ * @sign public this .draw([[Context ctx, ]Number x, Number y, Number w, Number h])
+ * @param ctx - Canvas 2D context if drawing on another canvas is required
+ * @param x - X offset for drawing a segment
+ * @param y - Y offset for drawing a segment
+ * @param w - Width of the segement to draw
+ * @param h - Height of the segment to draw
+ * Method to draw the entity on the canvas element. Can pass rect values for redrawing a segment of the entity.
+ */
+ draw: function (ctx, x, y, w, h) {
+ if (!this.ready) return;
+ if (arguments.length === 4) {
+ h = w;
+ w = y;
+ y = x;
+ x = ctx;
+ ctx = Crafty.canvas.context;
+ }
+
+ var pos = { //inlined pos() function, for speed
+ _x: (this._x + (x || 0)),
+ _y: (this._y + (y || 0)),
+ _w: (w || this._w),
+ _h: (h || this._h)
+ },
+ context = ctx || Crafty.canvas.context,
+ coord = this.__coord || [0, 0, 0, 0],
+ co = {
+ x: coord[0] + (x || 0),
+ y: coord[1] + (y || 0),
+ w: w || coord[2],
+ h: h || coord[3]
+ };
+
+ if (this._mbr) {
+ context.save();
+
+ context.translate(this._origin.x + this._x, this._origin.y + this._y);
+ pos._x = -this._origin.x;
+ pos._y = -this._origin.y;
+
+ context.rotate((this._rotation % 360) * (Math.PI / 180));
+ }
+
+ if(this._flipX || this._flipY) {
+ context.save();
+ context.scale((this._flipX ? -1 : 1), (this._flipY ? -1 : 1));
+ if(this._flipX) {
+ pos._x = -(pos._x + pos._w)
+ }
+ if(this._flipY) {
+ pos._y = -(pos._y + pos._h)
+ }
+ }
+
+ //draw with alpha
+ if (this._alpha < 1.0) {
+ var globalpha = context.globalAlpha;
+ context.globalAlpha = this._alpha;
+ }
+
+ this.trigger("Draw", { type: "canvas", pos: pos, co: co, ctx: context });
+
+ if (this._mbr || (this._flipX || this._flipY)) {
+ context.restore();
+ }
+ if (globalpha) {
+ context.globalAlpha = globalpha;
+ }
+ return this;
+ }
+});
+
+/**@
+* #Crafty.canvas
+* @category Graphics
+* Collection of methods to draw on canvas.
+*/
+Crafty.extend({
+ canvas: {
+ /**@
+ * #Crafty.canvas.context
+ * @comp Crafty.canvas
+ * This will return the 2D context of the main canvas element.
+ * The value returned from `Crafty.canvas._canvas.getContext('2d')`.
+ */
+ context: null,
+ /**@
+ * #Crafty.canvas._canvas
+ * @comp Crafty.canvas
+ * Main Canvas element
+ */
+
+ /**@
+ * #Crafty.canvas.init
+ * @comp Crafty.canvas
+ * @sign public void Crafty.canvas.init(void)
+ * @trigger NoCanvas - triggered if `Crafty.support.canvas` is false
+ * Creates a `canvas` element inside `Crafty.stage.elem`. Must be called
+ * before any entities with the Canvas component can be drawn.
+ *
+ * This method will automatically be called if no `Crafty.canvas.context` is
+ * found.
+ */
+ init: function () {
+ //check if canvas is supported
+ if (!Crafty.support.canvas) {
+ Crafty.trigger("NoCanvas");
+ Crafty.stop();
+ return;
+ }
+
+ //create 3 empty canvas elements
+ var c;
+ c = document.createElement("canvas");
+ c.width = Crafty.viewport.width;
+ c.height = Crafty.viewport.height;
+ c.style.position = 'absolute';
+ c.style.left = "0px";
+ c.style.top = "0px";
+
+ Crafty.stage.elem.appendChild(c);
+ Crafty.canvas.context = c.getContext('2d');
+ Crafty.canvas._canvas = c;
+ }
+ }
+});
+ Crafty.extend({
+ over: null, //object mouseover, waiting for out
+ mouseObjs: 0,
+ mousePos: {},
+ lastEvent: null,
+ keydown: {},
+
+ /**@
+ * #Crafty.keydown
+ * @category Input
+ * Remembering what keys (referred by Unicode) are down.
+ * ~~~
+ * Crafty.c("Keyboard", {
+ * isDown: function (key) {
+ * if (typeof key === "string") {
+ * key = Crafty.keys[key];
+ * }
+ * return !!Crafty.keydown[key];
+ * }
+ * });
+ * ~~~
+ * @see Keyboard
+ * @see Crafty.keys
+ */
+
+ mouseDispatch: function (e) {
+ if (!Crafty.mouseObjs) return;
+ Crafty.lastEvent = e;
+
+ var maxz = -1,
+ closest,
+ q,
+ i = 0, l,
+ pos = Crafty.DOM.translate(e.clientX, e.clientY),
+ x, y,
+ dupes = {},
+ tar = e.target ? e.target : e.srcElement,
+ type = e.type;
+
+ if (type === "touchstart") type = "mousedown";
+ else if (type === "touchmove") type = "mousemove";
+ else if (type === "touchend") type = "mouseup";
+
+ //Normalize button according to http://unixpapa.com/js/mouse.html
+ if (e.which == null) {
+ e.mouseButton = (e.button < 2) ? Crafty.mouseButtons.LEFT : ((e.button == 4) ? Crafty.mouseButtons.MIDDLE : Crafty.mouseButtons.RIGHT);
+ } else {
+ e.mouseButton = (e.which < 2) ? Crafty.mouseButtons.LEFT : ((e.which == 2) ? Crafty.mouseButtons.MIDDLE : Crafty.mouseButtons.RIGHT);
+ }
+
+ e.realX = x = Crafty.mousePos.x = pos.x;
+ e.realY = y = Crafty.mousePos.y = pos.y;
+
+ //if it's a DOM element with Mouse component we are done
+ if (tar.nodeName != "CANVAS") {
+ while (typeof (tar.id) != 'string' && tar.id.indexOf('ent') == -1) {
+ tar = tar.parentNode;
+ }
+ ent = Crafty(parseInt(tar.id.replace('ent', '')))
+ if (ent.has('Mouse') && ent.isAt(x, y))
+ closest = ent;
+ }
+ //else we search for an entity with Mouse component
+ if (!closest) {
+ q = Crafty.map.search({ _x: x, _y: y, _w: 1, _h: 1 }, false);
+
+ for (l = q.length; i < l; ++i) {
+ if (!q[i].__c.Mouse || !q[i]._visible) continue;
+
+ var current = q[i],
+ flag = false;
+
+ //weed out duplicates
+ if (dupes[current[0]]) continue;
+ else dupes[current[0]] = true;
+
+ if (current.map) {
+ if (current.map.containsPoint(x, y)) {
+ flag = true;
+ }
+ } else if (current.isAt(x, y)) flag = true;
+
+ if (flag && (current._z >= maxz || maxz === -1)) {
+ //if the Z is the same, select the closest GUID
+ if (current._z === maxz && current[0] < closest[0]) {
+ continue;
+ }
+ maxz = current._z;
+ closest = current;
+ }
+ }
+ }
+
+ //found closest object to mouse
+ if (closest) {
+ //click must mousedown and out on tile
+ if (type === "mousedown") {
+ closest.trigger("MouseDown", e);
+ } else if (type === "mouseup") {
+ closest.trigger("MouseUp", e);
+ } else if (type == "dblclick") {
+ closest.trigger("DoubleClick", e);
+ } else if (type == "click") {
+ closest.trigger("Click", e);
+ }else if (type === "mousemove") {
+ closest.trigger("MouseMove", e);
+ if (this.over !== closest) { //if new mousemove, it is over
+ if (this.over) {
+ this.over.trigger("MouseOut", e); //if over wasn't null, send mouseout
+ this.over = null;
+ }
+ this.over = closest;
+ closest.trigger("MouseOver", e);
+ }
+ } else closest.trigger(type, e); //trigger whatever it is
+ } else {
+ if (type === "mousemove" && this.over) {
+ this.over.trigger("MouseOut", e);
+ this.over = null;
+ }
+ if (type === "mousedown") {
+ Crafty.viewport.mouselook('start', e);
+ }
+ else if (type === "mousemove") {
+ Crafty.viewport.mouselook('drag', e);
+ }
+ else if (type == "mouseup") {
+ Crafty.viewport.mouselook('stop');
+ }
+ }
+
+ if (type === "mousemove") {
+ this.lastEvent = e;
+ }
+
+ },
+
+
+ /**@
+ * #KeyboardEvent
+ * @category Input
+ * Keyboard Event triggerd by Crafty Core
+ * @trigger KeyDown - is triggered for each entity when the DOM 'keydown' event is triggered.
+ * @trigger KeyUp - is triggered for each entity when the DOM 'keyup' event is triggered.
+ * @example
+ * ~~~
+ * Crafty.e("2D, DOM, Color")
+ * .attr({x: 100, y: 100, w: 50, h: 50})
+ * .color("red")
+ * .bind('KeyDown', function(e) {
+ * if(e.key == Crafty.keys['LEFT_ARROW']) {
+ * this.x=this.x-1;
+ * } else if (e.key == Crafty.keys['RIGHT_ARROW']) {
+ * this.x=this.x+1;
+ * } else if (e.key == Crafty.keys['UP_ARROW']) {
+ * this.y=this.y-1;
+ * } else if (e.key == Crafty.keys['DOWN_ARROW']) {
+ * this.y=this.y+1;
+ * }
+ * });
+ * ~~~
+ * @see Crafty.keys
+ */
+
+ /**@
+ * #Crafty.eventObject
+ * @category Input
+ * Event Object used in Crafty for cross browser compatiblity
+ */
+
+ /**@
+ * #.key
+ * @comp Crafty.eventObject
+ * Unicode of the key pressed
+ */
+ keyboardDispatch: function (e) {
+ e.key = e.keyCode || e.which;
+ if (e.type === "keydown") {
+ if (Crafty.keydown[e.key] !== true) {
+ Crafty.keydown[e.key] = true;
+ Crafty.trigger("KeyDown", e);
+ }
+ } else if (e.type === "keyup") {
+ delete Crafty.keydown[e.key];
+ Crafty.trigger("KeyUp", e);
+ }
+
+ //prevent searchable keys
+ /*
+ if((e.metaKey || e.altKey || e.ctrlKey) && !(e.key == 8 || e.key >= 112 && e.key <= 135)) {
+ console.log(e);
+ if(e.preventDefault) e.preventDefault();
+ else e.returnValue = false;
+ return false;
+ }*/
+ }
+});
+
+//initialize the input events onload
+Crafty.bind("Load", function () {
+ Crafty.addEvent(this, "keydown", Crafty.keyboardDispatch);
+ Crafty.addEvent(this, "keyup", Crafty.keyboardDispatch);
+
+ Crafty.addEvent(this, Crafty.stage.elem, "mousedown", Crafty.mouseDispatch);
+ Crafty.addEvent(this, Crafty.stage.elem, "mouseup", Crafty.mouseDispatch);
+ Crafty.addEvent(this, Crafty.stage.elem, "mousemove", Crafty.mouseDispatch);
+ Crafty.addEvent(this, Crafty.stage.elem, "click", Crafty.mouseDispatch);
+ Crafty.addEvent(this, Crafty.stage.elem, "dblclick", Crafty.mouseDispatch);
+
+ Crafty.addEvent(this, Crafty.stage.elem, "touchstart", Crafty.mouseDispatch);
+ Crafty.addEvent(this, Crafty.stage.elem, "touchmove", Crafty.mouseDispatch);
+ Crafty.addEvent(this, Crafty.stage.elem, "touchend", Crafty.mouseDispatch);
+});
+
+/**@
+* #Mouse
+* @category Input
+* Provides the entity with mouse related events
+* @trigger MouseOver - when the mouse enters the entity - MouseEvent
+* @trigger MouseOut - when the mouse leaves the entity - MouseEvent
+* @trigger MouseDown - when the mouse button is pressed on the entity - MouseEvent
+* @trigger MouseUp - when the mouse button is released on the entity - MouseEvent
+* @trigger Click - when the user clicks the entity. [See documentation](http://www.quirksmode.org/dom/events/click.html) - MouseEvent
+* @trigger DoubleClick - when the user double clicks the entity - MouseEvent
+* @trigger MouseMove - when the mouse is over the entity and moves - MouseEvent
+* Crafty adds the mouseButton property to MouseEvents that match one of
+*
+* - Crafty.mouseButtons.LEFT
+* - Crafty.mouseButtons.RIGHT
+* - Crafty.mouseButtons.MIDDLE
+* @example
+* ~~~
+* myEntity.bind('Click', function() {
+* console.log("Clicked!!");
+* })
+*
+* myEntity.bind('Click', function(e) {
+* if( e.mouseButton == Crafty.mouseButtons.RIGHT )
+* console.log("Clicked right button");
+* })
+* ~~~
+*/
+Crafty.c("Mouse", {
+ init: function () {
+ Crafty.mouseObjs++;
+ this.bind("Remove", function () {
+ Crafty.mouseObjs--;
+ });
+ },
+
+ /**@
+ * #.areaMap
+ * @comp Mouse
+ * @sign public this .areaMap(Crafty.Polygon polygon)
+ * @param polygon - Instance of Crafty.Polygon used to check if the mouse coordinates are inside this region
+ * @sign public this .areaMap(Array point1, .., Array pointN)
+ * @param point# - Array with an `x` and `y` position to generate a polygon
+ * Assign a polygon to the entity so that mouse events will only be triggered if
+ * the coordinates are inside the given polygon.
+ * ~~~
+ * Crafty.e("2D, DOM, Color, Mouse")
+ * .color("red")
+ * .attr({ w: 100, h: 100 })
+ * .bind('MouseOver', function() {console.log("over")})
+ * .areaMap([0,0], [50,0], [50,50], [0,50])
+ * ~~~
+ * @see Crafty.Polygon
+ */
+ areaMap: function (poly) {
+ //create polygon
+ if (arguments.length > 1) {
+ //convert args to array to create polygon
+ var args = Array.prototype.slice.call(arguments, 0);
+ poly = new Crafty.polygon(args);
+ }
+
+ poly.shift(this._x, this._y);
+ this.map = poly;
+
+ this.attach(this.map);
+ return this;
+ }
+});
+
+/**@
+* #Draggable
+* @category Input
+* Enable drag and drop of the entity.
+* @trigger Dragging - is triggered each frame the entity is being dragged - MouseEvent
+* @trigger StartDrag - is triggered when dragging begins - MouseEvent
+* @trigger StopDrag - is triggered when dragging ends - MouseEvent
+*/
+Crafty.c("Draggable", {
+ _origMouseDOMPos: null,
+ _oldX: null,
+ _oldY: null,
+ _dragging: false,
+ _dir:null,
+
+ _ondrag: null,
+ _ondown: null,
+ _onup: null,
+
+ //Note: the code is note tested with zoom, etc., that may distort the direction between the viewport and the coordinate on the canvas.
+ init: function () {
+ this.requires("Mouse");
+ this._ondrag = function (e) {
+ var pos = Crafty.DOM.translate(e.clientX, e.clientY);
+ if(this._dir) {
+ var len = (pos.x - this._origMouseDOMPos.x) * this._dir.x
+ + (pos.y - this._origMouseDOMPos.y) * this._dir.y;
+ this.x = this._oldX + len * this._dir.x;
+ this.y = this._oldY + len * this._dir.y;
+ } else {
+ this.x = this._oldX + (pos.x - this._origMouseDOMPos.x);
+ this.y = this._oldY + (pos.y - this._origMouseDOMPos.y);
+ }
+
+ this.trigger("Dragging", e);
+ };
+
+ this._ondown = function (e) {
+ if (e.mouseButton !== Crafty.mouseButtons.LEFT) return;
+
+ //start drag
+ this._origMouseDOMPos = Crafty.DOM.translate(e.clientX, e.clientY);
+ this._oldX = this._x;
+ this._oldY = this._y;
+ this._dragging = true;
+
+ Crafty.addEvent(this, Crafty.stage.elem, "mousemove", this._ondrag);
+ Crafty.addEvent(this, Crafty.stage.elem, "mouseup", this._onup);
+ this.trigger("StartDrag", e);
+ };
+
+ this._onup = function upper(e) {
+ Crafty.removeEvent(this, Crafty.stage.elem, "mousemove", this._ondrag);
+ Crafty.removeEvent(this, Crafty.stage.elem, "mouseup", this._onup);
+ this._dragging = false;
+ this.trigger("StopDrag", e);
+ };
+
+ this.enableDrag();
+ },
+
+ /**@
+ * #.dragDirection
+ * @comp Draggable
+ * @sign public this .dragDirection()
+ * Remove any previously specifed direction.
+ *
+ * @sign public this .dragDirection(vector)
+ * @param vector - Of the form of {x: valx, y: valy}, the vector (valx, valy) denotes the move direction.
+ * @sign public this .dragDirection(degree)
+ * @param degree - A number, the degree (clockwise) of the move direction with respect to the x axis.
+ * Specify the dragging direction.
+ * @example
+ * ~~~
+ * this.dragDirection()
+ * this.dragDirection({x:1, y:0}) //Horizonatal
+ * this.dragDirection({x:0, y:1}) //Vertical
+ * // Note: because of the orientation of x and y axis,
+ * // this is 45 degree clockwise with respect to the x axis.
+ * this.dragDirection({x:1, y:1}) //45 degree.
+ * this.dragDirection(60) //60 degree.
+ * ~~~
+ */
+ dragDirection: function(dir) {
+ if (typeof dir === 'undefined') {
+ this._dir=null;
+ } else if (("" + parseInt(dir)) == dir) { //dir is a number
+ this._dir={
+ x: Math.cos(dir/180*Math.PI)
+ , y: Math.sin(dir/180*Math.PI)
+ };
+ }
+ else {
+ var r=Math.sqrt(dir.x * dir.x + dir.y * dir.y)
+ this._dir={
+ x: dir.x/r
+ , y: dir.y/r
+ };
+ }
+ },
+
+ /**@
+ * #.stopDrag
+ * @comp Draggable
+ * @sign public this .stopDrag(void)
+ * Stop the entity from dragging. Essentially reproducing the drop.
+ * @trigger StopDrag - Called right after the mouse listeners are removed
+ * @see .startDrag
+ */
+ stopDrag: function () {
+ Crafty.removeEvent(this, Crafty.stage.elem, "mousemove", this._ondrag);
+ Crafty.removeEvent(this, Crafty.stage.elem, "mouseup", this._onup);
+
+ this._dragging = false;
+ this.trigger("StopDrag");
+ return this;
+ },
+
+ /**@
+ * #.startDrag
+ * @comp Draggable
+ * @sign public this .startDrag(void)
+ * Make the entity follow the mouse positions.
+ * @see .stopDrag
+ */
+ startDrag: function () {
+ if (!this._dragging) {
+ this._dragging = true;
+ Crafty.addEvent(this, Crafty.stage.elem, "mousemove", this._ondrag);
+ }
+ return this;
+ },
+
+ /**@
+ * #.enableDrag
+ * @comp Draggable
+ * @sign public this .enableDrag(void)
+ * Rebind the mouse events. Use if `.disableDrag` has been called.
+ * @see .disableDrag
+ */
+ enableDrag: function () {
+ this.bind("MouseDown", this._ondown);
+
+ Crafty.addEvent(this, Crafty.stage.elem, "mouseup", this._onup);
+ return this;
+ },
+
+ /**@
+ * #.disableDrag
+ * @comp Draggable
+ * @sign public this .disableDrag(void)
+ * Stops entity from being draggable. Reenable with `.enableDrag()`.
+ * @see .enableDrag
+ */
+ disableDrag: function () {
+ this.unbind("MouseDown", this._ondown);
+ this.stopDrag();
+ return this;
+ }
+});
+
+/**@
+* #Keyboard
+* @category Input
+* Give entities keyboard events (`keydown` and `keyup`).
+*/
+Crafty.c("Keyboard", {
+/**@
+ * #.isDown
+ * @comp Keyboard
+ * @sign public Boolean isDown(String keyName)
+ * @param keyName - Name of the key to check. See `Crafty.keys`.
+ * @sign public Boolean isDown(Number keyCode)
+ * @param keyCode - Key code in `Crafty.keys`.
+ * Determine if a certain key is currently down.
+ * ~~~
+ * entity.requires('KeyBoard').bind('KeyDown', function () { if (this.isDown('SPACE')) jump(); });
+ * ~~~
+ * @see Crafty.keys
+ */
+ isDown: function (key) {
+ if (typeof key === "string") {
+ key = Crafty.keys[key];
+ }
+ return !!Crafty.keydown[key];
+ }
+});
+
+/**@
+* #Multiway
+* @category Input
+* Used to bind keys to directions and have the entity move acordingly
+* @trigger NewDirection - triggered when direction changes - { x:Number, y:Number } - New direction
+* @trigger Moved - triggered on movement on either x or y axis. If the entity has moved on both axes for diagonal movement the event is triggered twice - { x:Number, y:Number } - Old position
+*/
+Crafty.c("Multiway", {
+ _speed: 3,
+
+ _keydown: function (e) {
+ if (this._keys[e.key]) {
+ this._movement.x = Math.round((this._movement.x + this._keys[e.key].x) * 1000) / 1000;
+ this._movement.y = Math.round((this._movement.y + this._keys[e.key].y) * 1000) / 1000;
+ this.trigger('NewDirection', this._movement);
+ }
+ },
+
+ _keyup: function (e) {
+ if (this._keys[e.key]) {
+ this._movement.x = Math.round((this._movement.x - this._keys[e.key].x) * 1000) / 1000;
+ this._movement.y = Math.round((this._movement.y - this._keys[e.key].y) * 1000) / 1000;
+ this.trigger('NewDirection', this._movement);
+ }
+ },
+
+ _enterframe: function () {
+ if (this.disableControls) return;
+
+ if (this._movement.x !== 0) {
+ this.x += this._movement.x;
+ this.trigger('Moved', { x: this.x - this._movement.x, y: this.y });
+ }
+ if (this._movement.y !== 0) {
+ this.y += this._movement.y;
+ this.trigger('Moved', { x: this.x, y: this.y - this._movement.y });
+ }
+ },
+
+ init: function () {
+ this._keyDirection = {};
+ this._keys = {};
+ this._movement = { x: 0, y: 0 };
+ this._speed = { x: 3, y: 3 };
+ },
+
+ /**@
+ * #.multiway
+ * @comp Multiway
+ * @sign public this .multiway([Number speed,] Object keyBindings )
+ * @param speed - Amount of pixels to move the entity whilst a key is down
+ * @param keyBindings - What keys should make the entity go in which direction. Direction is specified in degrees
+ * Constructor to initialize the speed and keyBindings. Component will listen to key events and move the entity appropriately.
+ *
+ * When direction changes a NewDirection event is triggered with an object detailing the new direction: {x: x_movement, y: y_movement}
+ * When entity has moved on either x- or y-axis a Moved event is triggered with an object specifying the old position {x: old_x, y: old_y}
+ * @example
+ * ~~~
+ * this.multiway(3, {UP_ARROW: -90, DOWN_ARROW: 90, RIGHT_ARROW: 0, LEFT_ARROW: 180});
+ * this.multiway({x:3,y:1.5}, {UP_ARROW: -90, DOWN_ARROW: 90, RIGHT_ARROW: 0, LEFT_ARROW: 180});
+ * this.multiway({W: -90, S: 90, D: 0, A: 180});
+ * ~~~
+ */
+ multiway: function (speed, keys) {
+ if (keys) {
+ if (speed.x && speed.y) {
+ this._speed.x = speed.x;
+ this._speed.y = speed.y;
+ } else {
+ this._speed.x = speed;
+ this._speed.y = speed;
+ }
+ } else {
+ keys = speed;
+ }
+
+ this._keyDirection = keys;
+ this.speed(this._speed);
+
+ this.enableControl();
+
+ //Apply movement if key is down when created
+ for (var k in keys) {
+ if (Crafty.keydown[Crafty.keys[k]]) {
+ this.trigger("KeyDown", { key: Crafty.keys[k] });
+ }
+ }
+
+ return this;
+ },
+
+ /**@
+ * #.enableControl
+ * @comp Multiway
+ * @sign public this .enableControl()
+ * Enable the component to listen to key events.
+ *
+ * @example
+ * ~~~
+ * this.enableControl();
+ * ~~~
+ */
+ enableControl: function() {
+ this.bind("KeyDown", this._keydown)
+ .bind("KeyUp", this._keyup)
+ .bind("EnterFrame", this._enterframe);
+ return this;
+ },
+
+ /**@
+ * #.disableControl
+ * @comp Multiway
+ * @sign public this .disableControl()
+ * Disable the component to listen to key events.
+ *
+ * @example
+ * ~~~
+ * this.disableControl();
+ * ~~~
+ */
+
+ disableControl: function() {
+ this.unbind("KeyDown", this._keydown)
+ .unbind("KeyUp", this._keyup)
+ .unbind("EnterFrame", this._enterframe);
+ return this;
+ },
+
+ speed: function (speed) {
+ for (var k in this._keyDirection) {
+ var keyCode = Crafty.keys[k] || k;
+ this._keys[keyCode] = {
+ x: Math.round(Math.cos(this._keyDirection[k] * (Math.PI / 180)) * 1000 * speed.x) / 1000,
+ y: Math.round(Math.sin(this._keyDirection[k] * (Math.PI / 180)) * 1000 * speed.y) / 1000
+ };
+ }
+ return this;
+ }
+});
+
+/**@
+* #Fourway
+* @category Input
+* Move an entity in four directions by using the
+* arrow keys or `W`, `A`, `S`, `D`.
+*/
+Crafty.c("Fourway", {
+
+ init: function () {
+ this.requires("Multiway");
+ },
+
+ /**@
+ * #.fourway
+ * @comp Fourway
+ * @sign public this .fourway(Number speed)
+ * @param speed - Amount of pixels to move the entity whilst a key is down
+ * Constructor to initialize the speed. Component will listen for key events and move the entity appropriately.
+ * This includes `Up Arrow`, `Right Arrow`, `Down Arrow`, `Left Arrow` as well as `W`, `A`, `S`, `D`.
+ *
+ * When direction changes a NewDirection event is triggered with an object detailing the new direction: {x: x_movement, y: y_movement}
+ * When entity has moved on either x- or y-axis a Moved event is triggered with an object specifying the old position {x: old_x, y: old_y}
+ *
+ * The key presses will move the entity in that direction by the speed passed in the argument.
+ * @see Multiway
+ */
+ fourway: function (speed) {
+ this.multiway(speed, {
+ UP_ARROW: -90,
+ DOWN_ARROW: 90,
+ RIGHT_ARROW: 0,
+ LEFT_ARROW: 180,
+ W: -90,
+ S: 90,
+ D: 0,
+ A: 180
+ });
+
+ return this;
+ }
+});
+
+/**@
+* #Twoway
+* @category Input
+* Move an entity left or right using the arrow keys or `D` and `A` and jump using up arrow or `W`.
+*
+* When direction changes a NewDirection event is triggered with an object detailing the new direction: {x: x_movement, y: y_movement}. This is consistent with Fourway and Multiway components.
+* When entity has moved on x-axis a Moved event is triggered with an object specifying the old position {x: old_x, y: old_y}
+*
+*/
+Crafty.c("Twoway", {
+ _speed: 3,
+ _up: false,
+
+ init: function () {
+ this.requires("Fourway, Keyboard");
+ },
+
+ /**@
+ * #.twoway
+ * @comp Twoway
+ * @sign public this .twoway(Number speed[, Number jumpSpeed])
+ * @param speed - Amount of pixels to move left or right
+ * @param jumpSpeed - How high the entity should jump
+ * Constructor to initialize the speed and power of jump. Component will
+ * listen for key events and move the entity appropriately. This includes
+ * `Up Arrow`, `Right Arrow`, `Left Arrow` as well as W, A, D. Used with the
+ * `gravity` component to simulate jumping.
+ *
+ * The key presses will move the entity in that direction by the speed passed in
+ * the argument. Pressing the `Up Arrow` or `W` will cause the entiy to jump.
+ * @see Gravity, Fourway
+ */
+ twoway: function (speed, jump) {
+
+ this.multiway(speed, {
+ RIGHT_ARROW: 0,
+ LEFT_ARROW: 180,
+ D: 0,
+ A: 180
+ });
+
+ if (speed) this._speed = speed;
+ jump = jump || this._speed * 2;
+
+ this.bind("EnterFrame", function () {
+ if (this.disableControls) return;
+ if (this._up) {
+ this.y -= jump;
+ this._falling = true;
+ }
+ }).bind("KeyDown", function () {
+ if (this.isDown("UP_ARROW") || this.isDown("W")) this._up = true;
+ });
+
+ return this;
+ }
+});
+ /**@
+* #SpriteAnimation
+* @category Animation
+* Used to animate sprites by changing the sprites in the sprite map.
+* @trigger AnimationEnd - When the animation finishes - { reel }
+* @trigger Change - On each frame
+*/
+Crafty.c("SpriteAnimation", {
+ /**@
+ * #._reels
+ * @comp SpriteAnimation
+ *
+ * A map consists of arrays that contains the coordinates of each frame within the sprite, e.g.,
+ * `{"walk_left":[[96,48],[112,48],[128,48]]}`
+ */
+ _reels: null,
+ _frame: null,
+ /**@
+ * #._currentReelId
+ * @comp SpriteAnimation
+ *
+ * The current playing reel (one element of `this._reels`). It is `null` if no reel is playing.
+ */
+ _currentReelId: null,
+
+ init: function () {
+ this._reels = {};
+ },
+
+ /**@
+ * #.animate
+ * @comp SpriteAnimation
+ * @sign public this .animate(String reelId, Number fromX, Number y, Number toX)
+ * @param reelId - ID of the animation reel being created
+ * @param fromX - Starting `x` position (in the unit of sprite horizontal size) on the sprite map
+ * @param y - `y` position on the sprite map (in the unit of sprite vertical size). Remains constant through the animation.
+ * @param toX - End `x` position on the sprite map (in the unit of sprite horizontal size)
+ * @sign public this .animate(String reelId, Array frames)
+ * @param reelId - ID of the animation reel being created
+ * @param frames - Array of arrays containing the `x` and `y` values: [[x1,y1],[x2,y2],...]
+ * @sign public this .animate(String reelId, Number duration[, Number repeatCount])
+ * @param reelId - ID of the animation reel to play
+ * @param duration - Play the animation within a duration (in frames)
+ * @param repeatCount - number of times to repeat the animation. Use -1 for infinitely
+ *
+ * Method to setup animation reels or play pre-made reels. Animation works by changing the sprites over
+ * a duration. Only works for sprites built with the Crafty.sprite methods. See the Tween component for animation of 2D properties.
+ *
+ * To setup an animation reel, pass the name of the reel (used to identify the reel and play it later), and either an
+ * array of absolute sprite positions or the start x on the sprite map, the y on the sprite map and then the end x on the sprite map.
+ *
+ * To play a reel, pass the name of the reel and the duration it should play for (in frames). If you need
+ * to repeat the animation, simply pass in the amount of times the animation should repeat. To repeat
+ * forever, pass in `-1`.
+ *
+ * @example
+ * ~~~
+ * Crafty.sprite(16, "images/sprite.png", {
+ * PlayerSprite: [0,0]
+ * });
+ *
+ * Crafty.e("2D, DOM, SpriteAnimation, PlayerSprite")
+ * .animate('PlayerRunning', 0, 0, 3) //setup animation
+ * .animate('PlayerRunning', 15, -1) // start animation
+ *
+ * Crafty.e("2D, DOM, SpriteAnimation, PlayerSprite")
+ * .animate('PlayerRunning', 0, 3, 0) //setup animation
+ * .animate('PlayerRunning', 15, -1) // start animation
+ * ~~~
+ *
+ * @see crafty.sprite
+ */
+ animate: function (reelId, fromx, y, tox) {
+ var reel, i, tile, tileh, duration, pos;
+
+ //play a reel
+ //.animate('PlayerRunning', 15, -1) // start animation
+ if (arguments.length < 4 && typeof fromx === "number") {
+ duration = fromx;
+
+ //make sure not currently animating
+ this._currentReelId = reelId;
+
+ currentReel = this._reels[reelId];
+
+ this._frame = {
+ currentReel: currentReel,
+ numberOfFramesBetweenSlides: Math.ceil(duration / currentReel.length),
+ currentSlideNumber: 0,
+ frameNumberBetweenSlides: 0,
+ repeat: 0
+ };
+ if (arguments.length === 3 && typeof y === "number") {
+ //User provided repetition count
+ if (y === -1) this._frame.repeatInfinitly = true;
+ else this._frame.repeat = y;
+ }
+
+ pos = this._frame.currentReel[0];
+ this.__coord[0] = pos[0];
+ this.__coord[1] = pos[1];
+
+ this.bind("EnterFrame", this.updateSprite);
+ return this;
+ }
+ // .animate('PlayerRunning', 0, 0, 3) //setup animation
+ if (typeof fromx === "number") {
+ // Defind in Sprite component.
+ tile = this.__tile + parseInt(this.__padding[0] || 0, 10);
+ tileh = this.__tileh + parseInt(this.__padding[1] || 0, 10);
+
+ reel = [];
+ i = fromx;
+ if (tox > fromx) {
+ for (; i <= tox; i++) {
+ reel.push([i * tile, y * tileh]);
+ }
+ } else {
+ for (; i >= tox; i--) {
+ reel.push([i * tile, y * tileh]);
+ }
+ }
+
+ this._reels[reelId] = reel;
+ } else if (typeof fromx === "object") {
+ // @sign public this .animate(reelId, [[x1,y1],[x2,y2],...])
+ i = 0;
+ reel = [];
+ tox = fromx.length - 1;
+ tile = this.__tile + parseInt(this.__padding[0] || 0, 10);
+ tileh = this.__tileh + parseInt(this.__padding[1] || 0, 10);
+
+ for (; i <= tox; i++) {
+ pos = fromx[i];
+ reel.push([pos[0] * tile, pos[1] * tileh]);
+ }
+
+ this._reels[reelId] = reel;
+ }
+
+ return this;
+ },
+
+ /**@
+ * #.updateSprite
+ * @comp SpriteAnimation
+ * @sign private void .updateSprite()
+ *
+ * This is called at every `EnterFrame` event when `.animate()` enables animation. It update the SpriteAnimation component when the slide in the sprite should be updated.
+ *
+ * @example
+ * ~~~
+ * this.bind("EnterFrame", this.updateSprite);
+ * ~~~
+ *
+ * @see crafty.sprite
+ */
+ updateSprite: function () {
+ var data = this._frame;
+
+ if (this._frame.frameNumberBetweenSlides++ === data.numberOfFramesBetweenSlides) {
+ var pos = data.currentReel[data.currentSlideNumber++];
+
+ this.__coord[0] = pos[0];
+ this.__coord[1] = pos[1];
+ this._frame.frameNumberBetweenSlides = 0;
+ }
+
+
+ if (data.currentSlideNumber === data.currentReel.length) {
+ data.currentSlideNumber = 0;
+ if (this._frame.repeatInfinitly === true || this._frame.repeat > 0) {
+ if (this._frame.repeat) this._frame.repeat--;
+ this._frame.frameNumberBetweenSlides = 0;
+ this._frame.currentSlideNumber = 0;
+ } else {
+ this.trigger("AnimationEnd", { reel: data.currentReel });
+ this.stop();
+ return;
+ }
+ }
+
+ this.trigger("Change");
+ },
+
+ /**@
+ * #.stop
+ * @comp SpriteAnimation
+ * @sign public this .stop(void)
+ * Stop any animation currently playing.
+ */
+ stop: function () {
+ this.unbind("EnterFrame", this.updateSprite);
+ this.unbind("AnimationEnd");
+ this._currentReelId = null;
+ this._frame = null;
+
+ return this;
+ },
+
+ /**@
+ * #.reset
+ * @comp SpriteAnimation
+ * @sign public this .reset(void)
+ * Method will reset the entities sprite to its original.
+ */
+ reset: function () {
+ if (!this._frame) return this;
+
+ var co = this._frame.currentReel[0];
+ this.__coord[0] = co[0];
+ this.__coord[1] = co[1];
+ this.stop();
+
+ return this;
+ },
+
+ /**@
+ * #.isPlaying
+ * @comp SpriteAnimation
+ * @sign public Boolean .isPlaying([String reelId])
+ * @param reelId - Determine if the animation reel with this reelId is playing
+ * Determines if an animation is currently playing. If a reel is passed, it will determine
+ * if the passed reel is playing.
+ * ~~~
+ * myEntity.isPlaying() //is any animation playing
+ * myEntity.isPlaying('PlayerRunning') //is the PlayerRunning animation playing
+ * ~~~
+ */
+ isPlaying: function (reelId) {
+ if (!reelId) return !!this._interval;
+ return this._currentReelId === reelId;
+ }
+});
+
+/**@
+* #Tween
+* @category Animation
+* Component to animate the change in 2D properties over time.
+* @trigger TweenEnd - when a tween finishes - String - property
+*/
+Crafty.c("Tween", {
+ _step: null,
+ _numProps: 0,
+
+ /**@
+ * #.tween
+ * @comp Tween
+ * @sign public this .tween(Object properties, Number duration)
+ * @param properties - Object of 2D properties and what they should animate to
+ * @param duration - Duration to animate the properties over (in frames)
+ * This method will animate a 2D entities properties over the specified duration.
+ * These include `x`, `y`, `w`, `h`, `alpha` and `rotation`.
+ *
+ * The object passed should have the properties as keys and the value should be the resulting
+ * values of the properties.
+ * @example
+ * Move an object to 100,100 and fade out in 200 frames.
+ * ~~~
+ * Crafty.e("2D, Tween")
+ * .attr({alpha: 1.0, x: 0, y: 0})
+ * .tween({alpha: 0.0, x: 100, y: 100}, 200)
+ * ~~~
+ */
+ tween: function (props, duration) {
+ this.each(function () {
+ if (this._step == null) {
+ this._step = {};
+ this.bind('EnterFrame', tweenEnterFrame);
+ this.bind('RemoveComponent', function (c) {
+ if (c == 'Tween') {
+ this.unbind('EnterFrame', tweenEnterFrame);
+ }
+ });
+ }
+
+ for (var prop in props) {
+ this._step[prop] = { prop: props[prop], val: (props[prop] - this[prop]) / duration, rem: duration };
+ this._numProps++;
+ }
+ });
+ return this;
+ }
+});
+
+function tweenEnterFrame(e) {
+ if (this._numProps <= 0) return;
+
+ var prop, k;
+ for (k in this._step) {
+ prop = this._step[k];
+ this[k] += prop.val;
+ if (--prop.rem == 0) {
+ // decimal numbers rounding fix
+ this[k] = prop.prop;
+ this.trigger("TweenEnd", k);
+ // make sure the duration wasn't changed in TweenEnd
+ if (this._step[k].rem <= 0) {
+ delete this._step[k];
+ }
+ this._numProps--;
+ }
+ }
+
+ if (this.has('Mouse')) {
+ var over = Crafty.over,
+ mouse = Crafty.mousePos;
+ if (over && over[0] == this[0] && !this.isAt(mouse.x, mouse.y)) {
+ this.trigger('MouseOut', Crafty.lastEvent);
+ Crafty.over = null;
+ }
+ else if ((!over || over[0] != this[0]) && this.isAt(mouse.x, mouse.y)) {
+ Crafty.over = this;
+ this.trigger('MouseOver', Crafty.lastEvent);
+ }
+ }
+}
+
+ /**@
+* #Sprite
+* @category Graphics
+* @trigger Change - when the sprites change
+* Component for using tiles in a sprite map.
+*/
+Crafty.c("Sprite", {
+ __image: '',
+ /*
+ * #.__tile
+ * @comp Sprite
+ *
+ * Horizontal sprite tile size.
+ */
+ __tile: 0,
+ /*
+ * #.__tileh
+ * @comp Sprite
+ *
+ * Vertical sprite tile size.
+ */
+ __tileh: 0,
+ __padding: null,
+ __trim: null,
+ img: null,
+ //ready is changed to true in Crafty.sprite
+ ready: false,
+
+ init: function () {
+ this.__trim = [0, 0, 0, 0];
+
+ var draw = function (e) {
+ var co = e.co,
+ pos = e.pos,
+ context = e.ctx;
+
+ if (e.type === "canvas") {
+ //draw the image on the canvas element
+ context.drawImage(this.img, //image element
+ co.x, //x position on sprite
+ co.y, //y position on sprite
+ co.w, //width on sprite
+ co.h, //height on sprite
+ pos._x, //x position on canvas
+ pos._y, //y position on canvas
+ pos._w, //width on canvas
+ pos._h //height on canvas
+ );
+ } else if (e.type === "DOM") {
+ this._element.style.background = "url('" + this.__image + "') no-repeat -" + co.x + "px -" + co.y + "px";
+ }
+ };
+
+ this.bind("Draw", draw).bind("RemoveComponent", function (id) {
+ if (id === "Sprite") this.unbind("Draw", draw);
+ });
+ },
+
+ /**@
+ * #.sprite
+ * @comp Sprite
+ * @sign public this .sprite(Number x, Number y, Number w, Number h)
+ * @param x - X cell position
+ * @param y - Y cell position
+ * @param w - Width in cells
+ * @param h - Height in cells
+ * Uses a new location on the sprite map as its sprite.
+ *
+ * Values should be in tiles or cells (not pixels).
+ *
+ * @example
+ * ~~~
+ * Crafty.e("2D, DOM, Sprite")
+ * .sprite(0, 0, 2, 2);
+ * ~~~
+ */
+
+ /**@
+ * #.__coord
+ * @comp Sprite
+ *
+ * The coordinate of the slide within the sprite in the format of [x, y, w, h].
+ */
+ sprite: function (x, y, w, h) {
+ this.__coord = [x * this.__tile + this.__padding[0] + this.__trim[0],
+ y * this.__tileh + this.__padding[1] + this.__trim[1],
+ this.__trim[2] || w * this.__tile || this.__tile,
+ this.__trim[3] || h * this.__tileh || this.__tileh];
+
+ this.trigger("Change");
+ return this;
+ },
+
+ /**@
+ * #.crop
+ * @comp Sprite
+ * @sign public this .crop(Number x, Number y, Number w, Number h)
+ * @param x - Offset x position
+ * @param y - Offset y position
+ * @param w - New width
+ * @param h - New height
+ * If the entity needs to be smaller than the tile size, use this method to crop it.
+ *
+ * The values should be in pixels rather than tiles.
+ *
+ * @example
+ * ~~~
+ * Crafty.e("2D, DOM, Sprite")
+ * .crop(40, 40, 22, 23);
+ * ~~~
+ */
+ crop: function (x, y, w, h) {
+ var old = this._mbr || this.pos();
+ this.__trim = [];
+ this.__trim[0] = x;
+ this.__trim[1] = y;
+ this.__trim[2] = w;
+ this.__trim[3] = h;
+
+ this.__coord[0] += x;
+ this.__coord[1] += y;
+ this.__coord[2] = w;
+ this.__coord[3] = h;
+ this._w = w;
+ this._h = h;
+
+ this.trigger("Change", old);
+ return this;
+ }
+});
+ /**@
+* #Color
+* @category Graphics
+* Draw a solid color for the entity
+*/
+Crafty.c("Color", {
+ _color: "",
+ ready: true,
+
+ init: function () {
+ this.bind("Draw", function (e) {
+ if (e.type === "DOM") {
+ e.style.background = this._color;
+ e.style.lineHeight = 0;
+ } else if (e.type === "canvas") {
+ if (this._color) e.ctx.fillStyle = this._color;
+ e.ctx.fillRect(e.pos._x, e.pos._y, e.pos._w, e.pos._h);
+ }
+ });
+ },
+
+ /**@
+ * #.color
+ * @comp Color
+ * @trigger Change - when the color changes
+ * @sign public this .color(String color)
+ * @sign public String .color()
+ * @param color - Color of the rectangle
+ * Will create a rectangle of solid color for the entity, or return the color if no argument is given.
+ *
+ * The argument must be a color readable depending on which browser you
+ * choose to support. IE 8 and below doesn't support the rgb() syntax.
+ * @example
+ * ~~~
+ * Crafty.e("2D, DOM, Color")
+ * .color("#969696");
+ * ~~~
+ */
+ color: function (color) {
+ if (!color) return this._color;
+ this._color = color;
+ this.trigger("Change");
+ return this;
+ }
+});
+
+/**@
+* #Tint
+* @category Graphics
+* Similar to Color by adding an overlay of semi-transparent color.
+*
+* *Note: Currently only works for Canvas*
+*/
+Crafty.c("Tint", {
+ _color: null,
+ _strength: 1.0,
+
+ init: function () {
+ var draw = function d(e) {
+ var context = e.ctx || Crafty.canvas.context;
+
+ context.fillStyle = this._color || "rgb(0,0,0)";
+ context.fillRect(e.pos._x, e.pos._y, e.pos._w, e.pos._h);
+ };
+
+ this.bind("Draw", draw).bind("RemoveComponent", function (id) {
+ if (id === "Tint") this.unbind("Draw", draw);
+ });
+ },
+
+ /**@
+ * #.tint
+ * @comp Tint
+ * @trigger Change - when the tint is applied
+ * @sign public this .tint(String color, Number strength)
+ * @param color - The color in hexidecimal
+ * @param strength - Level of opacity
+ * Modify the color and level opacity to give a tint on the entity.
+ * @example
+ * ~~~
+ * Crafty.e("2D, Canvas, Tint")
+ * .tint("#969696", 0.3);
+ * ~~~
+ */
+ tint: function (color, strength) {
+ this._strength = strength;
+ this._color = Crafty.toRGB(color, this._strength);
+
+ this.trigger("Change");
+ return this;
+ }
+});
+
+/**@
+* #Image
+* @category Graphics
+* Draw an image with or without repeating (tiling).
+*/
+Crafty.c("Image", {
+ _repeat: "repeat",
+ ready: false,
+
+ init: function () {
+ var draw = function (e) {
+ if (e.type === "canvas") {
+ //skip if no image
+ if (!this.ready || !this._pattern) return;
+
+ var context = e.ctx;
+
+ context.fillStyle = this._pattern;
+
+ //context.save();
+ //context.translate(e.pos._x, e.pos._y);
+ context.fillRect(this._x, this._y, this._w, this._h);
+ //context.restore();
+ } else if (e.type === "DOM") {
+ if (this.__image)
+ e.style.background = "url(" + this.__image + ") " + this._repeat;
+ }
+ };
+
+ this.bind("Draw", draw).bind("RemoveComponent", function (id) {
+ if (id === "Image") this.unbind("Draw", draw);
+ });
+ },
+
+ /**@
+ * #.image
+ * @comp Image
+ * @trigger Change - when the image is loaded
+ * @sign public this .image(String url[, String repeat])
+ * @param url - URL of the image
+ * @param repeat - If the image should be repeated to fill the entity.
+ * Draw specified image. Repeat follows CSS syntax (`"no-repeat", "repeat", "repeat-x", "repeat-y"`);
+ *
+ * *Note: Default repeat is `no-repeat` which is different to standard DOM (which is `repeat`)*
+ *
+ * If the width and height are `0` and repeat is set to `no-repeat` the width and
+ * height will automatically assume that of the image. This is an
+ * easy way to create an image without needing sprites.
+ * @example
+ * Will default to no-repeat. Entity width and height will be set to the images width and height
+ * ~~~
+ * var ent = Crafty.e("2D, DOM, Image").image("myimage.png");
+ * ~~~
+ * Create a repeating background.
+ * ~~~
+ * var bg = Crafty.e("2D, DOM, Image")
+ * .attr({w: Crafty.viewport.width, h: Crafty.viewport.height})
+ * .image("bg.png", "repeat");
+ * ~~~
+ * @see Crafty.sprite
+ */
+ image: function (url, repeat) {
+ this.__image = url;
+ this._repeat = repeat || "no-repeat";
+
+
+ this.img = Crafty.assets[url];
+ if (!this.img) {
+ this.img = new Image();
+ Crafty.assets[url] = this.img;
+ this.img.src = url;
+ var self = this;
+
+ this.img.onload = function () {
+ if (self.has("Canvas")) self._pattern = Crafty.canvas.context.createPattern(self.img, self._repeat);
+ self.ready = true;
+
+ if (self._repeat === "no-repeat") {
+ self.w = self.img.width;
+ self.h = self.img.height;
+ }
+
+ self.trigger("Change");
+ };
+
+ return this;
+ } else {
+ this.ready = true;
+ if (this.has("Canvas")) this._pattern = Crafty.canvas.context.createPattern(this.img, this._repeat);
+ if (this._repeat === "no-repeat") {
+ this.w = this.img.width;
+ this.h = this.img.height;
+ }
+ }
+
+
+ this.trigger("Change");
+
+ return this;
+ }
+});
+
+Crafty.extend({
+ _scenes: [],
+ _current: null,
+
+ /**@
+ * #Crafty.scene
+ * @category Scenes, Stage
+ * @trigger SceneChange - when a scene is played - { oldScene:String, newScene:String }
+ * @sign public void Crafty.scene(String sceneName, Function init[, Function uninit])
+ * @param sceneName - Name of the scene to add
+ * @param init - Function to execute when scene is played
+ * @param uninit - Function to execute before next scene is played, after entities with `2D` are destroyed
+ * @sign public void Crafty.scene(String sceneName)
+ * @param sceneName - Name of scene to play
+ * Method to create scenes on the stage. Pass an ID and function to register a scene.
+ *
+ * To play a scene, just pass the ID. When a scene is played, all
+ * entities with the `2D` component on the stage are destroyed.
+ *
+ * If you want some entities to persist over scenes (as in not be destroyed)
+ * simply add the component `Persist`.
+ *
+ * @example
+ * ~~~
+ * Crafty.scene("loading", function() {});
+ *
+ * Crafty.scene("loading", function() {}, function() {});
+ *
+ * Crafty.scene("loading");
+ * ~~~
+ */
+ scene: function (name, intro, outro) {
+ //play scene
+ if (arguments.length === 1) {
+ Crafty("2D").each(function () {
+ if (!this.has("Persist")) this.destroy();
+ });
+ // uninitialize previous scene
+ if (this._current !== null && 'uninitialize' in this._scenes[this._current]) {
+ this._scenes[this._current].uninitialize.call(this);
+ }
+ // initialize next scene
+ this._scenes[name].initialize.call(this);
+ var oldScene = this._current;
+ this._current = name;
+ Crafty.trigger("SceneChange", { oldScene: oldScene, newScene: name });
+ return;
+ }
+ //add scene
+ this._scenes[name] = {}
+ this._scenes[name].initialize = intro
+ if (typeof outro !== 'undefined') {
+ this._scenes[name].uninitialize = outro;
+ }
+ return;
+ },
+
+ /**@
+ * #Crafty.toRGB
+ * @category Graphics
+ * @sign public String Crafty.scene(String hex[, Number alpha])
+ * @param hex - a 6 character hex number string representing RGB color
+ * @param alpha - The alpha value.
+ * Get a rgb string or rgba string (if `alpha` presents).
+ * @example
+ * ~~~
+ * Crafty.toRGB("ffffff"); // rgb(255,255,255)
+ * Crafty.toRGB("#ffffff"); // rgb(255,255,255)
+ * Crafty.toRGB("ffffff", .5); // rgba(255,255,255,0.5)
+ * ~~~
+ * @see Text.textColor
+ */
+ toRGB: function (hex, alpha) {
+ var hex = (hex.charAt(0) === '#') ? hex.substr(1) : hex,
+ c = [], result;
+
+ c[0] = parseInt(hex.substr(0, 2), 16);
+ c[1] = parseInt(hex.substr(2, 2), 16);
+ c[2] = parseInt(hex.substr(4, 2), 16);
+
+ result = alpha === undefined ? 'rgb(' + c.join(',') + ')' : 'rgba(' + c.join(',') + ',' + alpha + ')';
+
+ return result;
+ }
+});
+
+/**@
+* #Crafty.DrawManager
+* @category Graphics
+* @sign Crafty.DrawManager
+* An internal object manage objects to be drawn and implement
+* the best method of drawing in both DOM and canvas
+*/
+Crafty.DrawManager = (function () {
+ /** array of dirty rects on screen */
+ var dirty_rects = [],
+ /** array of DOMs needed updating */
+ dom = [];
+
+ return {
+ /**@
+ * #Crafty.DrawManager.total2D
+ * @comp Crafty.DrawManager
+ * Total number of the entities that have the `2D` component.
+ */
+ total2D: Crafty("2D").length,
+
+ /**@
+ * #Crafty.DrawManager.onScreen
+ * @comp Crafty.DrawManager
+ * @sign public Crafty.DrawManager.onScreen(Object rect)
+ * @param rect - A rectangle with field {_x: x_val, _y: y_val, _w: w_val, _h: h_val}
+ * Test if a rectangle is completely in viewport
+ */
+ onScreen: function (rect) {
+ return Crafty.viewport._x + rect._x + rect._w > 0 && Crafty.viewport._y + rect._y + rect._h > 0 &&
+ Crafty.viewport._x + rect._x < Crafty.viewport.width && Crafty.viewport._y + rect._y < Crafty.viewport.height;
+ },
+
+ /**@
+ * #Crafty.DrawManager.merge
+ * @comp Crafty.DrawManager
+ * @sign public Object Crafty.DrawManager.merge(Object set)
+ * @param set - an array of rectangular regions
+ * Merged into non overlapping rectangular region
+ * Its an optimization for the redraw regions.
+ */
+ merge: function (set) {
+ do {
+ var newset = [], didMerge = false, i = 0,
+ l = set.length, current, next, merger;
+
+ while (i < l) {
+ current = set[i];
+ next = set[i + 1];
+
+ if (i < l - 1 && current._x < next._x + next._w && current._x + current._w > next._x &&
+ current._y < next._y + next._h && current._h + current._y > next._y) {
+
+ merger = {
+ _x: ~~Math.min(current._x, next._x),
+ _y: ~~Math.min(current._y, next._y),
+ _w: Math.max(current._x, next._x) + Math.max(current._w, next._w),
+ _h: Math.max(current._y, next._y) + Math.max(current._h, next._h)
+ };
+ merger._w = merger._w - merger._x;
+ merger._h = merger._h - merger._y;
+ merger._w = (merger._w == ~~merger._w) ? merger._w : merger._w + 1 | 0;
+ merger._h = (merger._h == ~~merger._h) ? merger._h : merger._h + 1 | 0;
+
+ newset.push(merger);
+
+ i++;
+ didMerge = true;
+ } else newset.push(current);
+ i++;
+ }
+
+ set = newset.length ? Crafty.clone(newset) : set;
+
+ if (didMerge) i = 0;
+ } while (didMerge);
+
+ return set;
+ },
+
+ /**@
+ * #Crafty.DrawManager.add
+ * @comp Crafty.DrawManager
+ * @sign public Crafty.DrawManager.add(old, current)
+ * @param old - Undocumented
+ * @param current - Undocumented
+ * Calculate the bounding rect of dirty data and add to the register of dirty rectangles
+ */
+ add: function add(old, current) {
+ if (!current) {
+ dom.push(old);
+ return;
+ }
+
+ var rect,
+ before = old._mbr || old,
+ after = current._mbr || current;
+
+ if (old === current) {
+ rect = old.mbr() || old.pos();
+ } else {
+ rect = {
+ _x: ~~Math.min(before._x, after._x),
+ _y: ~~Math.min(before._y, after._y),
+ _w: Math.max(before._w, after._w) + Math.max(before._x, after._x),
+ _h: Math.max(before._h, after._h) + Math.max(before._y, after._y)
+ };
+
+ rect._w = (rect._w - rect._x);
+ rect._h = (rect._h - rect._y);
+ }
+
+ if (rect._w === 0 || rect._h === 0 || !this.onScreen(rect)) {
+ return false;
+ }
+
+ //floor/ceil
+ rect._x = ~~rect._x;
+ rect._y = ~~rect._y;
+ rect._w = (rect._w === ~~rect._w) ? rect._w : rect._w + 1 | 0;
+ rect._h = (rect._h === ~~rect._h) ? rect._h : rect._h + 1 | 0;
+
+ //add to dirty_rects, check for merging
+ dirty_rects.push(rect);
+
+ //if it got merged
+ return true;
+ },
+
+ /**@
+ * #Crafty.DrawManager.debug
+ * @comp Crafty.DrawManager
+ * @sign public Crafty.DrawManager.debug()
+ * Undocumented
+ */
+ debug: function () {
+ console.log(dirty_rects, dom);
+ },
+
+ /**@
+ * #Crafty.DrawManager.draw
+ * @comp Crafty.DrawManager
+ * @sign public Crafty.DrawManager.draw([Object rect])
+ * @param rect - a rectangular region {_x: x_val, _y: y_val, _w: w_val, _h: h_val}
+ * - If rect is omitted, redraw within the viewport
+ * - If rect is provided, redraw within the rect
+ */
+ drawAll: function (rect) {
+ var rect = rect || Crafty.viewport.rect(),
+ q = Crafty.map.search(rect),
+ i = 0,
+ l = q.length,
+ ctx = Crafty.canvas.context,
+ current;
+
+ ctx.clearRect(rect._x, rect._y, rect._w, rect._h);
+
+ //sort the objects by the global Z
+ q.sort(function (a, b) { return a._globalZ - b._globalZ; });
+ for (; i < l; i++) {
+ current = q[i];
+ if (current._visible && current.__c.Canvas) {
+ current.draw();
+ current._changed = false;
+ }
+ }
+ },
+
+ /**@
+ * #Crafty.DrawManager.boundingRect
+ * @comp Crafty.DrawManager
+ * @sign public Crafty.DrawManager.boundingRect(set)
+ * @param set - Undocumented
+ * - Calculate the common bounding rect of multiple canvas entities.
+ * - Returns coords
+ */
+ boundingRect: function (set) {
+ if (!set || !set.length) return;
+ var newset = [], i = 1,
+ l = set.length, current, master = set[0], tmp;
+ master = [master._x, master._y, master._x + master._w, master._y + master._h];
+ while (i < l) {
+ current = set[i];
+ tmp = [current._x, current._y, current._x + current._w, current._y + current._h];
+ if (tmp[0] < master[0]) master[0] = tmp[0];
+ if (tmp[1] < master[1]) master[1] = tmp[1];
+ if (tmp[2] > master[2]) master[2] = tmp[2];
+ if (tmp[3] > master[3]) master[3] = tmp[3];
+ i++;
+ }
+ tmp = master;
+ master = { _x: tmp[0], _y: tmp[1], _w: tmp[2] - tmp[0], _h: tmp[3] - tmp[1] };
+
+ return master;
+ },
+
+ /**@
+ * #Crafty.DrawManager.draw
+ * @comp Crafty.DrawManager
+ * @sign public Crafty.DrawManager.draw()
+ * - If the number of rects is over 60% of the total number of objects
+ * do the naive method redrawing `Crafty.DrawManager.drawAll`
+ * - Otherwise, clear the dirty regions, and redraw entities overlapping the dirty regions.
+ * @see Canvas.draw, DOM.draw
+ */
+ draw: function draw() {
+ //if nothing in dirty_rects, stop
+ if (!dirty_rects.length && !dom.length) return;
+
+ var i = 0, l = dirty_rects.length, k = dom.length, rect, q,
+ j, len, dupes, obj, ent, objs = [], ctx = Crafty.canvas.context;
+
+ //loop over all DOM elements needing updating
+ for (; i < k; ++i) {
+ dom[i].draw()._changed = false;
+ }
+ //reset DOM array
+ dom.length = 0;
+ //again, stop if nothing in dirty_rects
+ if (!l) { return; }
+
+ //if the amount of rects is over 60% of the total objects
+ //do the naive method redrawing
+ if (l / this.total2D > 0.6) {
+ this.drawAll();
+ dirty_rects.length = 0;
+ return;
+ }
+
+ dirty_rects = this.merge(dirty_rects);
+ for (i = 0; i < l; ++i) { //loop over every dirty rect
+ rect = dirty_rects[i];
+ if (!rect) continue;
+ q = Crafty.map.search(rect, false); //search for ents under dirty rect
+
+ dupes = {};
+
+ //loop over found objects removing dupes and adding to obj array
+ for (j = 0, len = q.length; j < len; ++j) {
+ obj = q[j];
+
+ if (dupes[obj[0]] || !obj._visible || !obj.__c.Canvas)
+ continue;
+ dupes[obj[0]] = true;
+
+ objs.push({ obj: obj, rect: rect });
+ }
+
+ //clear the rect from the main canvas
+ ctx.clearRect(rect._x, rect._y, rect._w, rect._h);
+
+ }
+
+ //sort the objects by the global Z
+ objs.sort(function (a, b) { return a.obj._globalZ - b.obj._globalZ; });
+ if (!objs.length){ return; }
+
+ //loop over the objects
+ for (i = 0, l = objs.length; i < l; ++i) {
+ obj = objs[i];
+ rect = obj.rect;
+ ent = obj.obj;
+
+ var area = ent._mbr || ent,
+ x = (rect._x - area._x <= 0) ? 0 : ~~(rect._x - area._x),
+ y = (rect._y - area._y < 0) ? 0 : ~~(rect._y - area._y),
+ w = ~~Math.min(area._w - x, rect._w - (area._x - rect._x), rect._w, area._w),
+ h = ~~Math.min(area._h - y, rect._h - (area._y - rect._y), rect._h, area._h);
+
+ //no point drawing with no width or height
+ if (h === 0 || w === 0) continue;
+
+ ctx.save();
+ ctx.beginPath();
+ ctx.moveTo(rect._x, rect._y);
+ ctx.lineTo(rect._x + rect._w, rect._y);
+ ctx.lineTo(rect._x + rect._w, rect._h + rect._y);
+ ctx.lineTo(rect._x, rect._h + rect._y);
+ ctx.lineTo(rect._x, rect._y);
+
+ ctx.clip();
+
+ ent.draw();
+ ctx.closePath();
+ ctx.restore();
+
+ //allow entity to re-dirty_rects
+ ent._changed = false;
+ }
+
+ //empty dirty_rects
+ dirty_rects.length = 0;
+ //all merged IDs are now invalid
+ merged = {};
+ }
+ };
+})();
+ Crafty.extend({
+ /**@
+* #Crafty.isometric
+* @category 2D
+* Place entities in a 45deg isometric fashion.
+*/
+ isometric: {
+ _tile: {
+ width: 0,
+ height: 0
+ },
+ _elements:{},
+ _pos: {
+ x:0,
+ y:0
+ },
+ _z: 0,
+ /**@
+* #Crafty.isometric.size
+* @comp Crafty.isometric
+* @sign public this Crafty.isometric.size(Number tileSize)
+* @param tileSize - The size of the tiles to place.
+* Method used to initialize the size of the isometric placement.
+* Recommended to use a size alues in the power of `2` (128, 64 or 32).
+* This makes it easy to calculate positions and implement zooming.
+* ~~~
+* var iso = Crafty.isometric.size(128);
+* ~~~
+* @see Crafty.isometric.place
+*/
+ size: function (width, height) {
+ this._tile.width = width;
+ this._tile.height = height > 0 ? height : width/2; //Setup width/2 if height doesnt set
+ return this;
+ },
+ /**@
+ * #Crafty.isometric.place
+ * @comp Crafty.isometric
+ * @sign public this Crafty.isometric.size(Number x, Number y, Number z, Entity tile)
+ * @param x - The `x` position to place the tile
+ * @param y - The `y` position to place the tile
+ * @param z - The `z` position or height to place the tile
+ * @param tile - The entity that should be position in the isometric fashion
+ * Use this method to place an entity in an isometric grid.
+ * ~~~
+ * var iso = Crafty.isometric.size(128);
+ * isos.place(2, 1, 0, Crafty.e('2D, DOM, Color').color('red').attr({w:128, h:128}));
+ * ~~~
+ * @see Crafty.isometric.size
+ */
+ place: function (x, y, z, obj) {
+ var pos = this.pos2px(x,y);
+ pos.top -= z * (this._tile.width / 2);
+ obj.attr({
+ x: pos.left + Crafty.viewport._x,
+ y: pos.top + Crafty.viewport._y
+ }).z += z;
+ return this;
+ },
+ /**@
+ * #Crafty.isometric.pos2px
+ * @comp Crafty.isometric
+ * @sign public this Crafty.isometric.pos2px(Number x,Number y)
+ * @param x
+ * @param y
+ * @return Object {left Number,top Number}
+ * This method calculate the X and Y Coordiantes to Pixel Positions
+ * ~~~
+ * var iso = Crafty.isometric.size(128,96);
+ * var position = iso.pos2px(100,100);
+ * console.log(position); //Object { left=12800, top=4800}
+ * ~~~
+ */
+ pos2px:function(x,y){
+ return {
+ left:x * this._tile.width + (y & 1) * (this._tile.width / 2),
+ top:y * this._tile.height / 2
+ }
+ },
+ /**@
+ * #Crafty.isometric.px2pos
+ * @comp Crafty.isometric
+ * @sign public this Crafty.isometric.px2pos(Number left,Number top)
+ * @param top
+ * @param left
+ * @return Object {x Number,y Number}
+ * This method calculate pixel top,left positions to x,y coordiantes
+ * ~~~
+ * var iso = Crafty.isometric.size(128,96);
+ * var px = iso.pos2px(12800,4800);
+ * console.log(px); //Object { x=-100, y=-100}
+ * ~~~
+ */
+ px2pos:function(left,top){
+ return {
+ x:Math.ceil(-left / this._tile.width - (top & 1)*0.5),
+ y:-top / this._tile.height * 2
+ };
+ },
+ /**@
+ * #Crafty.isometric.centerAt
+ * @comp Crafty.isometric
+ * @sign public this Crafty.isometric.centerAt(Number x,Number y)
+ * @param top
+ * @param left
+ * This method center the Viewport at x/y location or gives the current centerpoint of the viewport
+ * ~~~
+ * var iso = Crafty.isometric.size(128,96).centerAt(10,10); //Viewport is now moved
+ * //After moving the viewport by another event you can get the new center point
+ * console.log(iso.centerAt());
+ * ~~~
+ */
+ centerAt:function(x,y){
+ if(typeof x == "number" && typeof y == "number"){
+ var center = this.pos2px(x,y);
+ Crafty.viewport._x = -center.left+Crafty.viewport.width/2-this._tile.width/2;
+ Crafty.viewport._y = -center.top+Crafty.viewport.height/2-this._tile.height/2;
+ return this;
+ }else{
+ return {
+ top:-Crafty.viewport._y+Crafty.viewport.height/2-this._tile.height/2,
+ left:-Crafty.viewport._x+Crafty.viewport.width/2-this._tile.width/2
+ }
+ }
+ },
+ /**@
+ * #Crafty.isometric.area
+ * @comp Crafty.isometric
+ * @sign public this Crafty.isometric.area()
+ * @return Object {x:{start Number,end Number},y:{start Number,end Number}}
+ * This method get the Area surounding by the centerpoint depends on viewport height and width
+ * ~~~
+ * var iso = Crafty.isometric.size(128,96).centerAt(10,10); //Viewport is now moved
+ * var area = iso.area(); //get the area
+ *for(var y = area.y.start;y <= area.y.end;y++){
+ *for(var x = area.x.start ;x <= area.x.end;x++){
+ * iso.place(x,y,0,Crafty.e("2D,DOM,gras")); //Display tiles in the Screen
+ * }
+ *}
+ * ~~~
+ */
+ area:function(){
+ //Get the center Point in the viewport
+ var center = this.centerAt();
+ var start = this.px2pos(-center.left+Crafty.viewport.width/2,-center.top+Crafty.viewport.height/2);
+ var end = this.px2pos(-center.left-Crafty.viewport.width/2,-center.top-Crafty.viewport.height/2);
+ return {
+ x:{
+ start : start.x,
+ end : end.x
+ },
+ y:{
+ start : start.y,
+ end : end.y
+ }
+ };
+ }
+ }
+}); /**@
+* #Particles
+* @category Graphics
+* Based on Parcycle by Mr. Speaker, licensed under the MIT, Ported by Leo Koppelkamm
+* **This is canvas only & won't do anything if the browser doesn't support it!**
+* To see how this works take a look in https://github.com/louisstow/Crafty/blob/master/src/particles.js
+*/
+Crafty.c("particles", {
+ init: function () {
+ //We need to clone it
+ this._Particles = Crafty.clone(this._Particles);
+ },
+ particles: function (options) {
+
+ if (!Crafty.support.canvas || Crafty.deactivateParticles) return this;
+
+ //If we drew on the main canvas, we'd have to redraw
+ //potentially huge sections of the screen every frame
+ //So we create a separate canvas, where we only have to redraw
+ //the changed particles.
+ var c, ctx, relativeX, relativeY, bounding;
+
+ c = document.createElement("canvas");
+ c.width = Crafty.viewport.width;
+ c.height = Crafty.viewport.height;
+ c.style.position = 'absolute';
+
+ Crafty.stage.elem.appendChild(c);
+
+ ctx = c.getContext('2d');
+
+ this._Particles.init(options);
+
+ relativeX = this.x + Crafty.viewport.x;
+ relativeY = this.y + Crafty.viewport.y;
+ this._Particles.position = this._Particles.vectorHelpers.create(relativeX, relativeY);
+
+ var oldViewport = { x: Crafty.viewport.x, y: Crafty.viewport.y };
+
+ this.bind('EnterFrame', function () {
+ relativeX = this.x + Crafty.viewport.x;
+ relativeY = this.y + Crafty.viewport.y;
+ this._Particles.viewportDelta = { x: Crafty.viewport.x - oldViewport.x, y: Crafty.viewport.y - oldViewport.y };
+
+ oldViewport = { x: Crafty.viewport.x, y: Crafty.viewport.y };
+
+ this._Particles.position = this._Particles.vectorHelpers.create(relativeX, relativeY);
+
+ //Selective clearing
+ if (typeof Crafty.DrawManager.boundingRect == 'function') {
+ bounding = Crafty.DrawManager.boundingRect(this._Particles.register);
+ if (bounding) ctx.clearRect(bounding._x, bounding._y, bounding._w, bounding._h);
+ } else {
+ ctx.clearRect(0, 0, Crafty.viewport.width, Crafty.viewport.height);
+ }
+
+ //This updates all particle colors & positions
+ this._Particles.update();
+
+ //This renders the updated particles
+ this._Particles.render(ctx);
+ });
+ return this;
+ },
+ _Particles: {
+ presets: {
+ maxParticles: 150,
+ size: 18,
+ sizeRandom: 4,
+ speed: 1,
+ speedRandom: 1.2,
+ // Lifespan in frames
+ lifeSpan: 29,
+ lifeSpanRandom: 7,
+ // Angle is calculated clockwise: 12pm is 0deg, 3pm is 90deg etc.
+ angle: 65,
+ angleRandom: 34,
+ startColour: [255, 131, 0, 1],
+ startColourRandom: [48, 50, 45, 0],
+ endColour: [245, 35, 0, 0],
+ endColourRandom: [60, 60, 60, 0],
+ // Only applies when fastMode is off, specifies how sharp the gradients are drawn
+ sharpness: 20,
+ sharpnessRandom: 10,
+ // Random spread from origin
+ spread: 10,
+ // How many frames should this last
+ duration: -1,
+ // Will draw squares instead of circle gradients
+ fastMode: false,
+ gravity: { x: 0, y: 0.1 },
+ // sensible values are 0-3
+ jitter: 0,
+
+ //Don't modify the following
+ particles: [],
+ active: true,
+ particleCount: 0,
+ elapsedFrames: 0,
+ emissionRate: 0,
+ emitCounter: 0,
+ particleIndex: 0
+ },
+
+
+ init: function (options) {
+ this.position = this.vectorHelpers.create(0, 0);
+ if (typeof options == 'undefined') var options = {};
+
+ //Create current config by mergin given options and presets.
+ for (key in this.presets) {
+ if (typeof options[key] != 'undefined') this[key] = options[key];
+ else this[key] = this.presets[key];
+ }
+
+ this.emissionRate = this.maxParticles / this.lifeSpan;
+ this.positionRandom = this.vectorHelpers.create(this.spread, this.spread);
+ },
+
+ addParticle: function () {
+ if (this.particleCount == this.maxParticles) {
+ return false;
+ }
+
+ // Take the next particle out of the particle pool we have created and initialize it
+ var particle = new this.particle(this.vectorHelpers);
+ this.initParticle(particle);
+ this.particles[this.particleCount] = particle;
+ // Increment the particle count
+ this.particleCount++;
+
+ return true;
+ },
+ RANDM1TO1: function () {
+ return Math.random() * 2 - 1;
+ },
+ initParticle: function (particle) {
+ particle.position.x = this.position.x + this.positionRandom.x * this.RANDM1TO1();
+ particle.position.y = this.position.y + this.positionRandom.y * this.RANDM1TO1();
+
+ var newAngle = (this.angle + this.angleRandom * this.RANDM1TO1()) * (Math.PI / 180); // convert to radians
+ var vector = this.vectorHelpers.create(Math.sin(newAngle), -Math.cos(newAngle)); // Could move to lookup for speed
+ var vectorSpeed = this.speed + this.speedRandom * this.RANDM1TO1();
+ particle.direction = this.vectorHelpers.multiply(vector, vectorSpeed);
+
+ particle.size = this.size + this.sizeRandom * this.RANDM1TO1();
+ particle.size = particle.size < 0 ? 0 : ~~particle.size;
+ particle.timeToLive = this.lifeSpan + this.lifeSpanRandom * this.RANDM1TO1();
+
+ particle.sharpness = this.sharpness + this.sharpnessRandom * this.RANDM1TO1();
+ particle.sharpness = particle.sharpness > 100 ? 100 : particle.sharpness < 0 ? 0 : particle.sharpness;
+ // internal circle gradient size - affects the sharpness of the radial gradient
+ particle.sizeSmall = ~~((particle.size / 200) * particle.sharpness); //(size/2/100)
+ var start = [
+ this.startColour[0] + this.startColourRandom[0] * this.RANDM1TO1(),
+ this.startColour[1] + this.startColourRandom[1] * this.RANDM1TO1(),
+ this.startColour[2] + this.startColourRandom[2] * this.RANDM1TO1(),
+ this.startColour[3] + this.startColourRandom[3] * this.RANDM1TO1()
+ ];
+
+ var end = [
+ this.endColour[0] + this.endColourRandom[0] * this.RANDM1TO1(),
+ this.endColour[1] + this.endColourRandom[1] * this.RANDM1TO1(),
+ this.endColour[2] + this.endColourRandom[2] * this.RANDM1TO1(),
+ this.endColour[3] + this.endColourRandom[3] * this.RANDM1TO1()
+ ];
+
+ particle.colour = start;
+ particle.deltaColour[0] = (end[0] - start[0]) / particle.timeToLive;
+ particle.deltaColour[1] = (end[1] - start[1]) / particle.timeToLive;
+ particle.deltaColour[2] = (end[2] - start[2]) / particle.timeToLive;
+ particle.deltaColour[3] = (end[3] - start[3]) / particle.timeToLive;
+ },
+ update: function () {
+ if (this.active && this.emissionRate > 0) {
+ var rate = 1 / this.emissionRate;
+ this.emitCounter++;
+ while (this.particleCount < this.maxParticles && this.emitCounter > rate) {
+ this.addParticle();
+ this.emitCounter -= rate;
+ }
+ this.elapsedFrames++;
+ if (this.duration != -1 && this.duration < this.elapsedFrames) {
+ this.stop();
+ }
+ }
+
+ this.particleIndex = 0;
+ this.register = [];
+ var draw;
+ while (this.particleIndex < this.particleCount) {
+
+ var currentParticle = this.particles[this.particleIndex];
+
+ // If the current particle is alive then update it
+ if (currentParticle.timeToLive > 0) {
+
+ // Calculate the new direction based on gravity
+ currentParticle.direction = this.vectorHelpers.add(currentParticle.direction, this.gravity);
+ currentParticle.position = this.vectorHelpers.add(currentParticle.position, currentParticle.direction);
+ currentParticle.position = this.vectorHelpers.add(currentParticle.position, this.viewportDelta);
+ if (this.jitter) {
+ currentParticle.position.x += this.jitter * this.RANDM1TO1();
+ currentParticle.position.y += this.jitter * this.RANDM1TO1();
+ }
+ currentParticle.timeToLive--;
+
+ // Update colours
+ var r = currentParticle.colour[0] += currentParticle.deltaColour[0];
+ var g = currentParticle.colour[1] += currentParticle.deltaColour[1];
+ var b = currentParticle.colour[2] += currentParticle.deltaColour[2];
+ var a = currentParticle.colour[3] += currentParticle.deltaColour[3];
+
+ // Calculate the rgba string to draw.
+ draw = [];
+ draw.push("rgba(" + (r > 255 ? 255 : r < 0 ? 0 : ~~r));
+ draw.push(g > 255 ? 255 : g < 0 ? 0 : ~~g);
+ draw.push(b > 255 ? 255 : b < 0 ? 0 : ~~b);
+ draw.push((a > 1 ? 1 : a < 0 ? 0 : a.toFixed(2)) + ")");
+ currentParticle.drawColour = draw.join(",");
+
+ if (!this.fastMode) {
+ draw[3] = "0)";
+ currentParticle.drawColourEnd = draw.join(",");
+ }
+
+ this.particleIndex++;
+ } else {
+ // Replace particle with the last active
+ if (this.particleIndex != this.particleCount - 1) {
+ this.particles[this.particleIndex] = this.particles[this.particleCount - 1];
+ }
+ this.particleCount--;
+ }
+ var rect = {};
+ rect._x = ~~currentParticle.position.x;
+ rect._y = ~~currentParticle.position.y;
+ rect._w = currentParticle.size;
+ rect._h = currentParticle.size;
+
+ this.register.push(rect);
+ }
+ },
+
+ stop: function () {
+ this.active = false;
+ this.elapsedFrames = 0;
+ this.emitCounter = 0;
+ },
+
+ render: function (context) {
+
+ for (var i = 0, j = this.particleCount; i < j; i++) {
+ var particle = this.particles[i];
+ var size = particle.size;
+ var halfSize = size >> 1;
+
+ if (particle.position.x + size < 0
+ || particle.position.y + size < 0
+ || particle.position.x - size > Crafty.viewport.width
+ || particle.position.y - size > Crafty.viewport.height) {
+ //Particle is outside
+ continue;
+ }
+ var x = ~~particle.position.x;
+ var y = ~~particle.position.y;
+
+ if (this.fastMode) {
+ context.fillStyle = particle.drawColour;
+ } else {
+ var radgrad = context.createRadialGradient(x + halfSize, y + halfSize, particle.sizeSmall, x + halfSize, y + halfSize, halfSize);
+ radgrad.addColorStop(0, particle.drawColour);
+ //0.9 to avoid visible boxing
+ radgrad.addColorStop(0.9, particle.drawColourEnd);
+ context.fillStyle = radgrad;
+ }
+ context.fillRect(x, y, size, size);
+ }
+ },
+ particle: function (vectorHelpers) {
+ this.position = vectorHelpers.create(0, 0);
+ this.direction = vectorHelpers.create(0, 0);
+ this.size = 0;
+ this.sizeSmall = 0;
+ this.timeToLive = 0;
+ this.colour = [];
+ this.drawColour = "";
+ this.deltaColour = [];
+ this.sharpness = 0;
+ },
+ vectorHelpers: {
+ create: function (x, y) {
+ return {
+ "x": x,
+ "y": y
+ };
+ },
+ multiply: function (vector, scaleFactor) {
+ vector.x *= scaleFactor;
+ vector.y *= scaleFactor;
+ return vector;
+ },
+ add: function (vector1, vector2) {
+ vector1.x += vector2.x;
+ vector1.y += vector2.y;
+ return vector1;
+ }
+ }
+ }
+}); Crafty.extend({
+/**@
+ * #Crafty.audio
+ * @category Audio
+ * Add sound files and play them. Chooses best format for browser support.
+ * Due to the nature of HTML5 audio, three types of audio files will be
+ * required for cross-browser capabilities. These formats are MP3, Ogg and WAV.
+ * When sound was not muted on before pause, sound will be unmuted after unpause.
+ * When sound is muted Crafty.pause() does not have any effect on sound.
+ */
+ audio: {
+ _elems: {},
+ _muted: false,
+
+ /**@
+ * #Crafty.audio.MAX_CHANNELS
+ * @comp Crafty.audio
+ * Amount of Audio objects for a sound so overlapping of the
+ * same sound can occur. More channels means more of the same sound
+ * playing at the same time.
+ */
+ MAX_CHANNELS: 5,
+
+ type: {
+ 'mp3': 'audio/mpeg;',
+ 'ogg': 'audio/ogg; codecs="vorbis"',
+ 'wav': 'audio/wav; codecs="1"',
+ 'mp4': 'audio/mp4; codecs="mp4a.40.2"'
+ },
+
+ /**@
+ * #Crafty.audio.add
+ * @comp Crafty.audio
+ * @sign public this Crafty.audio.add(String id, String url)
+ * @param id - A string to reffer to sounds
+ * @param url - A string pointing to the sound file
+ * @sign public this Crafty.audio.add(String id, Array urls)
+ * @param urls - Array of urls pointing to different format of the same sound, selecting the first that is playable
+ * @sign public this Crafty.audio.add(Object map)
+ * @param map - key-value pairs where the key is the `id` and the value is either a `url` or `urls`
+ *
+ * Loads a sound to be played. Due to the nature of HTML5 audio,
+ * three types of audio files will be required for cross-browser capabilities.
+ * These formats are MP3, Ogg and WAV.
+ *
+ * Passing an array of URLs will determine which format the browser can play and select it over any other.
+ *
+ * Accepts an object where the key is the audio name and
+ * either a URL or an Array of URLs (to determine which type to use).
+ *
+ * The ID you use will be how you refer to that sound when using `Crafty.audio.play`.
+ *
+ * @example
+ * ~~~
+ * //adding audio from an object
+ * Crafty.audio.add({
+ * shoot: ["sounds/shoot.wav",
+ * "sounds/shoot.mp3",
+ * "sounds/shoot.ogg"],
+ *
+ * coin: "sounds/coin.mp3"
+ * });
+ *
+ * //adding a single sound
+ * Crafty.audio.add("walk", [
+ * "sounds/walk.mp3",
+ * "sounds/walk.ogg",
+ * "sounds/walk.wav"
+ * ]);
+ *
+ * //only one format
+ * Crafty.audio.add("jump", "sounds/jump.mp3");
+ * ~~~
+ */
+ add: function (id, url) {
+ if (!Crafty.support.audio) return this;
+
+ var elem,
+ key,
+ audio = new Audio(),
+ canplay,
+ i = 0,
+ sounds = [];
+
+ //if an object is passed
+ if (arguments.length === 1 && typeof id === "object") {
+ for (key in id) {
+ if (!id.hasOwnProperty(key)) continue;
+
+ //if array passed, add fallback sources
+ if (typeof id[key] !== "string") {
+ var sources = id[key], i = 0, l = sources.length,
+ source;
+
+ for (; i < l; ++i) {
+ source = sources[i];
+ //get the file extension
+ ext = source.substr(source.lastIndexOf('.') + 1).toLowerCase();
+ canplay = audio.canPlayType(this.type[ext]);
+
+ //if browser can play this type, use it
+ if (canplay !== "" && canplay !== "no") {
+ url = source;
+ break;
+ }
+ }
+ } else {
+ url = id[key];
+ }
+
+ for (; i < this.MAX_CHANNELS; i++) {
+ audio = new Audio(url);
+ audio.preload = "auto";
+ audio.load();
+ sounds.push(audio);
+ }
+ this._elems[key] = sounds;
+ if (!Crafty.assets[url]) Crafty.assets[url] = this._elems[key][0];
+ }
+
+ return this;
+ }
+ //standard method
+ if (typeof url !== "string") {
+ var i = 0, l = url.length,
+ source;
+
+ for (; i < l; ++i) {
+ source = url[i];
+ //get the file extension
+ ext = source.substr(source.lastIndexOf('.') + 1);
+ canplay = audio.canPlayType(this.type[ext]);
+
+ //if browser can play this type, use it
+ if (canplay !== "" && canplay !== "no") {
+ url = source;
+ break;
+ }
+ }
+ }
+
+ //create a new Audio object and add it to assets
+ for (; i < this.MAX_CHANNELS; i++) {
+ audio = new Audio(url);
+ audio.preload = "auto";
+ audio.load();
+ sounds.push(audio);
+ }
+ this._elems[id] = sounds;
+ if (!Crafty.assets[url]) Crafty.assets[url] = this._elems[id][0];
+
+ return this;
+ },
+ /**@
+ * #Crafty.audio.play
+ * @comp Crafty.audio
+ * @sign public this Crafty.audio.play(String id)
+ * @sign public this Crafty.audio.play(String id, Number repeatCount)
+ * @param id - A string to reffer to sounds
+ * @param repeatCount - Repeat count for the file, where -1 stands for repeat forever.
+ *
+ * Will play a sound previously added by using the ID that was used in `Crafty.audio.add`.
+ * Has a default maximum of 5 channels so that the same sound can play simultaneously unless all of the channels are playing.
+
+ * *Note that the implementation of HTML5 Audio is buggy at best.*
+ *
+ * @example
+ * ~~~
+ * Crafty.audio.play("walk");
+ *
+ * //play and repeat forever
+ * Crafty.audio.play("backgroundMusic", -1);
+ * ~~~
+ */
+ play: function (id, repeat) {
+ if (!Crafty.support.audio) return;
+
+ var sounds = this._elems[id],
+ sound,
+ i = 0, l = sounds.length;
+
+ for (; i < l; i++) {
+ sound = sounds[i];
+ //go through the channels and play a sound that is stopped
+ if (sound.ended || !sound.currentTime) {
+ sound.play();
+ break;
+ } else if (i === l - 1) { //if all sounds playing, try stop the last one
+ sound.currentTime = 0;
+ sound.play();
+ }
+ }
+ if (typeof repeat == "number") {
+ var j = 0;
+ //i is still set to the sound we played
+ sounds[i].addEventListener('ended', function () {
+ if (repeat == -1 || j <= repeat) {
+ this.currentTime = 0;
+ this.play();
+ j++;
+ }
+ }, false);
+ }
+ return this;
+ },
+
+ /**@
+ * #Crafty.audio.settings
+ * @comp Crafty.audio
+ * @sign public this Crafty.audio.settings(String id, Object settings)
+ * @param id - The audio instance added by `Crafty.audio.add`
+ * @param settings - An object where the key is the setting and the value is what to modify the setting with
+ * Used to modify settings of the HTML5 `Audio` object. For a list of all the settings available,
+ * see the [Mozilla Documentation](https://developer.mozilla.org/en/XPCOM_Interface_Reference/nsIDOMHTMLMediaElement).
+ */
+ settings: function (id, settings) {
+ //apply to all
+ if (!settings) {
+ for (var key in this._elems) {
+ this.settings(key, id);
+ }
+ return this;
+ }
+
+ var sounds = this._elems[id],
+ sound,
+ setting,
+ i = 0, l = sounds.length;
+
+ for (var setting in settings) {
+ for (; i < l; i++) {
+ sound = sounds[i];
+ sound[setting] = settings[setting];
+ }
+ }
+
+ return this;
+ },
+
+ /**@
+ * #Crafty.audio.mute
+ * @sign public this Crafty.audio.mute([Boolean mute])
+ * Mute or unmute every Audio instance that is playing. Toggles between
+ * pausing or playing depending on the state.
+ * @example
+ * ~~~
+ * //toggle mute and unmute depending on current state
+ * Crafty.audio.mute();
+ *
+ * //mute or unmute no matter what the current state is
+ * Crafty.audio.mute(true);
+ * Crafty.audio.mute(false);
+ * ~~~
+ */
+ mute: function (mute) {
+ var sounds, sound, i, l, elem;
+
+ if (arguments.length == 1 && typeof(mute) == "boolean")
+ this._muted = mute;
+ else
+ this._muted = !this._muted;
+
+ //loop over every sound
+ for (sounds in this._elems) {
+ elem = this._elems[sounds];
+
+ //loop over every channel for a sound
+ for (i = 0, l = elem.length; i < l; ++i) {
+ sound = elem[i];
+
+ //if playing, stop
+ if (!sound.ended && sound.currentTime) {
+ if (this._muted)
+ sound.pause();
+ else
+ sound.play();
+ }
+ }
+ }
+ return this;
+ }
+ }
+});
+
+//When there is sound stop sound on Pause Event.
+//When there was sound on Pause, enable sound on Unpause Event.
+(function() {
+ var prev_mute_state;
+ Crafty.bind("Pause", function () {
+ prev_mute_state=Crafty.audio._muted;
+ Crafty.audio.mute(true);
+ });
+ Crafty.bind("Unpause", function () {
+ if(!prev_mute_state) {
+ Crafty.audio.mute(false);
+ }
+ });
+})();
+ /**@
+* #HTML
+* @category Graphics
+* Component allow for insertion of arbitrary HTML into an entity
+*/
+Crafty.c("HTML", {
+ inner: '',
+
+ init: function () {
+ this.requires('2D, DOM');
+ },
+
+ /**@
+ * #.replace
+ * @comp HTML
+ * @sign public this .replace(String html)
+ * @param html - arbitrary html
+ * This method will replace the content of this entity with the supplied html
+ *
+ * @example
+ * Create a link
+ * ~~~
+ * Crafty.e("HTML")
+ * .attr({x:20, y:20, w:100, h:100})
+ * .replace("Crafty.js");
+ * ~~~
+ */
+ replace: function (new_html) {
+ this.inner = new_html;
+ this._element.innerHTML = new_html;
+ return this;
+ },
+
+ /**@
+ * #.append
+ * @comp HTML
+ * @sign public this .append(String html)
+ * @param html - arbitrary html
+ * This method will add the supplied html in the end of the entity
+ *
+ * @example
+ * Create a link
+ * ~~~
+ * Crafty.e("HTML")
+ * .attr({x:20, y:20, w:100, h:100})
+ * .append("Crafty.js");
+ * ~~~
+ */
+ append: function (new_html) {
+ this.inner += new_html;
+ this._element.innerHTML += new_html;
+ return this;
+ },
+
+ /**@
+ * #.prepend
+ * @comp HTML
+ * @sign public this .prepend(String html)
+ * @param html - arbitrary html
+ * This method will add the supplied html in the beginning of the entity
+ *
+ * @example
+ * Create a link
+ * ~~~
+ * Crafty.e("HTML")
+ * .attr({x:20, y:20, w:100, h:100})
+ * .prepend("Crafty.js");
+ * ~~~
+ */
+ prepend: function (new_html) {
+ this.inner = new_html + this.inner;
+ this._element.innerHTML = new_html + this.inner;
+ return this;
+ }
+}); /**@
+ * #Storage
+ * @category Utilities
+ * Utility to allow data to be saved to a permanent storage solution: IndexedDB, WebSql, localstorage or cookies
+ */
+/**@
+ * #.open
+ * @comp Storage
+ * @sign .open(String gameName)
+ * @param gameName - a machine readable string to uniquely identify your game
+ * Opens a connection to the database. If the best they have is localstorage or lower, it does nothing
+ *
+ * @example
+ * Open a database
+ * ~~~
+ * Crafty.storage.open('MyGame');
+ * ~~~
+ */
+
+/**@
+ * #.save
+ * @comp Storage
+ * @sign .save(String key, String type, Mixed data)
+ * @param key - A unique key for identifying this piece of data
+ * @param type - 'save' or 'cache'
+ * @param data - Some kind of data.
+ * Saves a piece of data to the database. Can be anything, although entities are preferred.
+ * For all storage methods but IndexedDB, the data will be serialized as a string
+ * During serialization, an entity's SaveData event will be triggered.
+ * Components should implement a SaveData handler and attach the necessary information to the passed object
+ *
+ * @example
+ * Saves an entity to the database
+ * ~~~
+ * var ent = Crafty.e("2D, DOM")
+ * .attr({x: 20, y: 20, w: 100, h:100});
+ * Crafty.storage.open('MyGame');
+ * Crafty.storage.save('MyEntity', 'save', ent);
+ * ~~~
+ */
+
+/**@
+ * #.load
+ * @comp Storage
+ * @sign .load(String key, String type)
+ * @param key - A unique key to search for
+ * @param type - 'save' or 'cache'
+ * @param callback - Do things with the data you get back
+ * Loads a piece of data from the database.
+ * Entities will be reconstructed from the serialized string
+
+ * @example
+ * Loads an entity from the database
+ * ~~~
+ * Crafty.storage.open('MyGame');
+ * Crafty.storage.load('MyEntity', 'save', function (data) { // do things });
+ * ~~~
+ */
+
+/**@
+ * #.getAllKeys
+ * @comp Storage
+ * @sign .getAllKeys(String type)
+ * @param type - 'save' or 'cache'
+ * Gets all the keys for a given type
+
+ * @example
+ * Gets all the save games saved
+ * ~~~
+ * Crafty.storage.open('MyGame');
+ * var saves = Crafty.storage.getAllKeys('save');
+ * ~~~
+ */
+
+/**@
+ * #.external
+ * @comp Storage
+ * @sign .external(String url)
+ * @param url - URL to an external to save games too
+ * Enables and sets the url for saving games to an external server
+
+ * @example
+ * Save an entity to an external server
+ * ~~~
+ * Crafty.storage.external('http://somewhere.com/server.php');
+ * Crafty.storage.open('MyGame');
+ * var ent = Crafty.e('2D, DOM')
+ * .attr({x: 20, y: 20, w: 100, h:100});
+ * Crafty.storage.save('save01', 'save', ent);
+ * ~~~
+ */
+
+/**@
+ * #SaveData event
+ * @comp Storage
+ * @param data - An object containing all of the data to be serialized
+ * @param prepare - The function to prepare an entity for serialization
+ * Any data a component wants to save when it's serialized should be added to this object.
+ * Straight attribute should be set in data.attr.
+ * Anything that requires a special handler should be set in a unique property.
+ *
+ * @example
+ * Saves the innerHTML of an entity
+ * ~~~
+ * Crafty.e("2D DOM").bind("SaveData", function (data, prepare) {
+ * data.attr.x = this.x;
+ * data.attr.y = this.y;
+ * data.dom = this.element.innerHTML;
+ * });
+ * ~~~
+ */
+
+/**@
+ * #LoadData event
+ * @param data - An object containing all the data that been saved
+ * @param process - The function to turn a string into an entity
+ * Handlers for processing any data that needs more than straight assignment
+ *
+ * Note that data stord in the .attr object is automatically added to the entity.
+ * It does not need to be handled here
+ *
+ * @example
+ * ~~~
+ * Sets the innerHTML from a saved entity
+ * Crafty.e("2D DOM").bind("LoadData", function (data, process) {
+ * this.element.innerHTML = data.dom;
+ * });
+ * ~~~
+ */
+
+Crafty.storage = (function () {
+ var db = null, url, gameName, timestamps = {};
+
+ /*
+ * Processes a retrieved object.
+ * Creates an entity if it is one
+ */
+ function process(obj) {
+ if (obj.c) {
+ var d = Crafty.e(obj.c)
+ .attr(obj.attr)
+ .trigger('LoadData', obj, process);
+ return d;
+ }
+ else if (typeof obj == 'object') {
+ for (var prop in obj) {
+ obj[prop] = process(obj[prop]);
+ }
+ }
+ return obj;
+ }
+
+ function unserialize(str) {
+ if (typeof str != 'string') return null;
+ var data = (JSON ? JSON.parse(str) : eval('(' + str + ')'));
+ return process(data);
+ }
+
+ /* recursive function
+ * searches for entities in an object and processes them for serialization
+ */
+ function prep(obj) {
+ if (obj.__c) {
+ // object is entity
+ var data = { c: [], attr: {} };
+ obj.trigger("SaveData", data, prep);
+ for (var i in obj.__c) {
+ data.c.push(i);
+ }
+ data.c = data.c.join(', ');
+ obj = data;
+ }
+ else if (typeof obj == 'object') {
+ // recurse and look for entities
+ for (var prop in obj) {
+ obj[prop] = prep(obj[prop]);
+ }
+ }
+ return obj;
+ }
+
+ function serialize(e) {
+ if (JSON) {
+ var data = prep(e);
+ return JSON.stringify(data);
+ }
+ else {
+ alert("Crafty does not support saving on your browser. Please upgrade to a newer browser.");
+ return false;
+ }
+ }
+
+ // for saving a game to a central server
+ function external(setUrl) {
+ url = setUrl;
+ }
+
+ function openExternal() {
+ if (1 && typeof url == "undefined") return;
+ // get the timestamps for external saves and compare them to local
+ // if the external is newer, load it
+
+ var xml = new XMLHttpRequest();
+ xhr.open("POST", url);
+ xhr.onreadystatechange = function (evt) {
+ if (xhr.readyState == 4) {
+ if (xhr.status == 200) {
+ var data = eval("(" + xhr.responseText + ")");
+ for (var i in data) {
+ if (Crafty.storage.check(data[i].key, data[i].timestamp)) {
+ loadExternal(data[i].key);
+ }
+ }
+ }
+ }
+ }
+ xhr.send("mode=timestamps&game=" + gameName);
+ }
+
+ function saveExternal(key, data, ts) {
+ if (1 && typeof url == "undefined") return;
+ var xhr = new XMLHttpRequest();
+ xhr.open("POST", url);
+ xhr.send("mode=save&key=" + key + "&data=" + encodeURIComponent(data) + "&ts=" + ts + "&game=" + gameName);
+ }
+
+ function loadExternal(key) {
+ if (1 && typeof url == "undefined") return;
+ var xhr = new XMLHttpRequest();
+ xhr.open("POST", url);
+ xhr.onreadystatechange = function (evt) {
+ if (xhr.readyState == 4) {
+ if (xhr.status == 200) {
+ var data = eval("(" + xhr.responseText + ")");
+ Crafty.storage.save(key, 'save', data);
+ }
+ }
+ }
+ xhr.send("mode=load&key=" + key + "&game=" + gameName);
+ }
+
+ /**
+ * get timestamp
+ */
+ function ts() {
+ var d = new Date();
+ return d.getTime();
+ }
+
+ // everyone names their object different. Fix that nonsense.
+ if (typeof indexedDB != 'object') {
+ if (typeof mozIndexedDB == 'object') {
+ window.indexedDB = mozIndexedDB;
+ }
+ if (typeof webkitIndexedDB == 'object') {
+ window.indexedDB = webkitIndexedDB;
+ window.IDBTransaction = webkitIDBTransaction;
+ }
+ }
+
+ if (typeof indexedDB == 'object') {
+
+ return {
+ open: function (gameName_n) {
+ gameName = gameName_n;
+ var stores = [];
+
+ if (arguments.length == 1) {
+ stores.push('save');
+ stores.push('cache');
+ }
+ else {
+ stores = arguments;
+ stores.shift();
+ stores.push('save');
+ stores.push('cache');
+ }
+ if (db == null) {
+ var request = indexedDB.open(gameName, "Database for " + gameName);
+ request.onsuccess = function (e) {
+ db = e.target.result;
+ createStores();
+ getTimestamps();
+ openExternal();
+ };
+ }
+ else {
+ createStores();
+ getTimestamps();
+ openExternal();
+ }
+
+ // get all the timestamps for existing keys
+ function getTimestamps() {
+ try {
+ var trans = db.transaction(['save'], IDBTransaction.READ),
+ store = trans.objectStore('save'),
+ request = store.getAll();
+ request.onsuccess = function (e) {
+ var i = 0, a = event.target.result, l = a.length;
+ for (; i < l; i++) {
+ timestamps[a[i].key] = a[i].timestamp;
+ }
+ };
+ }
+ catch (e) {
+ }
+ }
+
+ function createStores() {
+ var request = db.setVersion("1.0");
+ request.onsuccess = function (e) {
+ for (var i = 0; i < stores.length; i++) {
+ var st = stores[i];
+ if (db.objectStoreNames.contains(st)) continue;
+ db.createObjectStore(st, { keyPath: "key" });
+ }
+ };
+ }
+ },
+
+ save: function (key, type, data) {
+ if (db == null) {
+ setTimeout(function () { Crafty.storage.save(key, type, data); }, 1);
+ return;
+ }
+
+ var str = serialize(data), t = ts();
+ if (type == 'save') saveExternal(key, str, t);
+ try {
+ var trans = db.transaction([type], IDBTransaction.READ_WRITE),
+ store = trans.objectStore(type),
+ request = store.put({
+ "data": str,
+ "timestamp": t,
+ "key": key
+ });
+ }
+ catch (e) {
+ console.error(e);
+ }
+ },
+
+ load: function (key, type, callback) {
+ if (db == null) {
+ setTimeout(function () { Crafty.storage.load(key, type, callback); }, 1);
+ return;
+ }
+ try {
+ var trans = db.transaction([type], IDBTransaction.READ),
+ store = trans.objectStore(type),
+ request = store.get(key);
+ request.onsuccess = function (e) {
+ callback(unserialize(e.target.result.data));
+ };
+ }
+ catch (e) {
+ console.error(e);
+ }
+ },
+
+ getAllKeys: function (type, callback) {
+ if (db == null) {
+ setTimeout(function () { Crafty.storage.getAllkeys(type, callback); }, 1);
+ }
+ try {
+ var trans = db.transaction([type], IDBTransaction.READ),
+ store = trans.objectStore(type),
+ request = store.getCursor(),
+ res = [];
+ request.onsuccess = function (e) {
+ var cursor = e.target.result;
+ if (cursor) {
+ res.push(cursor.key);
+ // 'continue' is a reserved word, so .continue() causes IE8 to completely bark with "SCRIPT1010: Expected identifier".
+ cursor['continue']();
+ }
+ else {
+ callback(res);
+ }
+ };
+ }
+ catch (e) {
+ console.error(e);
+ }
+ },
+
+ check: function (key, timestamp) {
+ return (timestamps[key] > timestamp);
+ },
+
+ external: external
+ };
+ }
+ else if (typeof openDatabase == 'function') {
+ return {
+ open: function (gameName_n) {
+ gameName = gameName_n;
+ if (arguments.length == 1) {
+ db = {
+ save: openDatabase(gameName_n + '_save', '1.0', 'Saves games for ' + gameName_n, 5 * 1024 * 1024),
+ cache: openDatabase(gameName_n + '_cache', '1.0', 'Cache for ' + gameName_n, 5 * 1024 * 1024)
+ }
+ }
+ else {
+ // allows for any other types that can be thought of
+ var args = arguments, i = 0;
+ args.shift();
+ for (; i < args.length; i++) {
+ if (typeof db[args[i]] == 'undefined')
+ db[args[i]] = openDatabase(gameName + '_' + args[i], '1.0', type, 5 * 1024 * 1024);
+ }
+ }
+
+ db['save'].transaction(function (tx) {
+ tx.executeSql('SELECT key, timestamp FROM data', [], function (tx, res) {
+ var i = 0, a = res.rows, l = a.length;
+ for (; i < l; i++) {
+ timestamps[a.item(i).key] = a.item(i).timestamp;
+ }
+ });
+ });
+ },
+
+ save: function (key, type, data) {
+ if (typeof db[type] == 'undefined' && gameName != '') {
+ this.open(gameName, type);
+ }
+
+ var str = serialize(data), t = ts();
+ if (type == 'save') saveExternal(key, str, t);
+ db[type].transaction(function (tx) {
+ tx.executeSql('CREATE TABLE IF NOT EXISTS data (key unique, text, timestamp)');
+ tx.executeSql('SELECT * FROM data WHERE key = ?', [key], function (tx, results) {
+ if (results.rows.length) {
+ tx.executeSql('UPDATE data SET text = ?, timestamp = ? WHERE key = ?', [str, t, key]);
+ }
+ else {
+ tx.executeSql('INSERT INTO data VALUES (?, ?, ?)', [key, str, t]);
+ }
+ });
+ });
+ },
+
+ load: function (key, type, callback) {
+ if (db[type] == null) {
+ setTimeout(function () { Crafty.storage.load(key, type, callback); }, 1);
+ return;
+ }
+ db[type].transaction(function (tx) {
+ tx.executeSql('SELECT text FROM data WHERE key = ?', [key], function (tx, results) {
+ if (results.rows.length) {
+ res = unserialize(results.rows.item(0).text);
+ callback(res);
+ }
+ });
+ });
+ },
+
+ getAllKeys: function (type, callback) {
+ if (db[type] == null) {
+ setTimeout(function () { Crafty.storage.getAllKeys(type, callback); }, 1);
+ return;
+ }
+ db[type].transaction(function (tx) {
+ tx.executeSql('SELECT key FROM data', [], function (tx, results) {
+ callback(results.rows);
+ });
+ });
+ },
+
+ check: function (key, timestamp) {
+ return (timestamps[key] > timestamp);
+ },
+
+ external: external
+ };
+ }
+ else if (typeof window.localStorage == 'object') {
+ return {
+ open: function (gameName_n) {
+ gameName = gameName_n;
+ },
+
+ save: function (key, type, data) {
+ var k = gameName + '.' + type + '.' + key,
+ str = serialize(data),
+ t = ts();
+ if (type == 'save') saveExternal(key, str, t);
+ window.localStorage[k] = str;
+ if (type == 'save')
+ window.localStorage[k + '.ts'] = t;
+ },
+
+ load: function (key, type, callback) {
+ var k = gameName + '.' + type + '.' + key,
+ str = window.localStorage[k];
+
+ callback(unserialize(str));
+ },
+
+ getAllKeys: function (type, callback) {
+ var res = {}, output = [], header = gameName + '.' + type;
+ for (var i in window.localStorage) {
+ if (i.indexOf(header) != -1) {
+ var key = i.replace(header, '').replace('.ts', '');
+ res[key] = true;
+ }
+ }
+ for (i in res) {
+ output.push(i);
+ }
+ callback(output);
+ },
+
+ check: function (key, timestamp) {
+ var ts = window.localStorage[gameName + '.save.' + key + '.ts'];
+
+ return (parseInt(timestamp) > parseInt(ts));
+ },
+
+ external: external
+ };
+ }
+ else {
+ // default fallback to cookies
+ return {
+ open: function (gameName_n) {
+ gameName = gameName_n;
+ },
+
+ save: function (key, type, data) {
+ // cookies are very limited in space. we can only keep saves there
+ if (type != 'save') return;
+ var str = serialize(data), t = ts();
+ if (type == 'save') saveExternal(key, str, t);
+ document.cookie = gameName + '_' + key + '=' + str + '; ' + gameName + '_' + key + '_ts=' + t + '; expires=Thur, 31 Dec 2099 23:59:59 UTC; path=/';
+ },
+
+ load: function (key, type, callback) {
+ if (type != 'save') return;
+ var reg = new RegExp(gameName + '_' + key + '=[^;]*'),
+ result = reg.exec(document.cookie),
+ data = unserialize(result[0].replace(gameName + '_' + key + '=', ''));
+
+ callback(data);
+ },
+
+ getAllKeys: function (type, callback) {
+ if (type != 'save') return;
+ var reg = new RegExp(gameName + '_[^_=]', 'g'),
+ matches = reg.exec(document.cookie),
+ i = 0, l = matches.length, res = {}, output = [];
+ for (; i < l; i++) {
+ var key = matches[i].replace(gameName + '_', '');
+ res[key] = true;
+ }
+ for (i in res) {
+ output.push(i);
+ }
+ callback(output);
+ },
+
+ check: function (key, timestamp) {
+ var header = gameName + '_' + key + '_ts',
+ reg = new RegExp(header + '=[^;]'),
+ result = reg.exec(document.cookie),
+ ts = result[0].replace(header + '=', '');
+
+ return (parseInt(timestamp) > parseInt(ts));
+ },
+
+ external: external
+ };
+ }
+ /* template
+ return {
+ open: function (gameName) {
+ },
+ save: function (key, type, data) {
+ },
+ load: function (key, type, callback) {
+ },
+ }*/
+})(); /**@
+* #Text
+* @category Graphics
+* @trigger Change - when the text is changed
+* @requires Canvas or DOM
+* Component to draw text inside the body of an entity.
+*/
+Crafty.c("Text", {
+ _text: "",
+ _textFont: {
+ "type": "",
+ "weight": "",
+ "size": "",
+ "family": ""
+ },
+ ready: true,
+
+ init: function () {
+ this.requires("2D");
+
+ this.bind("Draw", function (e) {
+ var font = this._textFont["type"] + ' ' + this._textFont["weight"] + ' ' +
+ this._textFont["size"] + ' ' + this._textFont["family"];
+
+ if (e.type === "DOM") {
+ var el = this._element,
+ style = el.style;
+
+ style.color = this._textColor;
+ style.font = font;
+ el.innerHTML = this._text;
+ } else if (e.type === "canvas") {
+ var context = e.ctx,
+ metrics = null;
+
+ context.save();
+
+ context.fillStyle = this._textColor || "rgb(0,0,0)";
+ context.font = font;
+
+ context.translate(this.x, this.y + this.h);
+ context.fillText(this._text, 0, 0);
+
+ metrics = context.measureText(this._text);
+ this._w = metrics.width;
+
+ context.restore();
+ }
+ });
+ },
+
+ /**@
+ * #.text
+ * @comp Text
+ * @sign public this .text(String text)
+ * @sign public this .text(Function textgenerator)
+ * @param text - String of text that will be inserted into the DOM or Canvas element.
+ * This method will update the text inside the entity.
+ * If you use DOM, to modify the font, use the `.css` method inherited from the DOM component.
+ *
+ * If you need to reference attributes on the entity itself you can pass a function instead of a string.
+ * @example
+ * ~~~
+ * Crafty.e("2D, DOM, Text").attr({ x: 100, y: 100 }).text("Look at me!!");
+ *
+ * Crafty.e("2D, DOM, Text").attr({ x: 100, y: 100 })
+ * .text(function () { return "My position is " + this._x });
+ *
+ * Crafty.e("2D, Canvas, Text").attr({ x: 100, y: 100 }).text("Look at me!!");
+ *
+ * Crafty.e("2D, Canvas, Text").attr({ x: 100, y: 100 })
+ * .text(function () { return "My position is " + this._x });
+ * ~~~
+ */
+ text: function (text) {
+ if (!text) return this._text;
+ if (typeof(text) == "function")
+ this._text = text.call(this);
+ else
+ this._text = text;
+ this.trigger("Change");
+ return this;
+ },
+
+ /**@
+ * #.textColor
+ * @comp Text
+ * @sign public this .textColor(String color, Number strength)
+ * @param color - The color in hexidecimal
+ * @param strength - Level of opacity
+ *
+ * Modify the text color and level of opacity.
+ * @example
+ * ~~~
+ * Crafty.e("2D, DOM, Text").attr({ x: 100, y: 100 }).text("Look at me!!")
+ * .textColor('#FF0000');
+ *
+ * Crafty.e("2D, Canvas, Text").attr({ x: 100, y: 100 }).text('Look at me!!')
+ * .textColor('#FF0000', 0.6);
+ * ~~~
+ * @see Crafty.toRGB
+ */
+ textColor: function (color, strength) {
+ this._strength = strength;
+ this._textColor = Crafty.toRGB(color, this._strength);
+ this.trigger("Change");
+ return this;
+ },
+
+ /**@
+ * #.textFont
+ * @comp Text
+ * @sign public this .textFont(String key, * value)
+ * @param key - Property of the entity to modify
+ * @param value - Value to set the property to
+ *
+ * @sign public this .textFont(Object map)
+ * @param map - Object where the key is the property to modify and the value as the property value
+ * @triggers Change
+ *
+ * Use this method to set font property of the text entity.
+ * @example
+ * ~~~
+ * Crafty.e("2D, DOM, Text").textFont({ type: 'italic', family: 'Arial' });
+ * Crafty.e("2D, Canvas, Text").textFont({ size: '20px', weight: 'bold' });
+ *
+ * Crafty.e("2D, Canvas, Text").textFont("type", "italic");
+ * Crafty.e("2D, Canvas, Text").textFont("type"); // italic
+ * ~~~
+ */
+ textFont: function (key, value) {
+ if (arguments.length === 1) {
+ //if just the key, return the value
+ if (typeof key === "string") {
+ return this._textFont[key];
+ }
+
+ if (typeof key === "object") {
+ for (propertyKey in key) {
+ this._textFont[propertyKey] = key[propertyKey];
+ }
+ }
+ } else {
+ this._textFont[key] = value;
+ }
+
+ this.trigger("Change");
+ return this;
+ }
+});
+ Crafty.extend({
+/**@
+ * #Crafty.assets
+ * @category Assets
+ * An object containing every asset used in the current Crafty game.
+ * The key is the URL and the value is the `Audio` or `Image` object.
+ *
+ * If loading an asset, check that it is in this object first to avoid loading twice.
+ * @example
+ * ~~~
+ * var isLoaded = !!Crafty.assets["images/sprite.png"];
+ * ~~~
+ * @see Crafty.loader
+ */
+ assets: {},
+
+ /**@
+ * #Crafty.loader
+ * @category Assets
+ * @sign public void Crafty.load(Array assets, Function onLoad[, Function onProgress, Function onError])
+ * @param assets - Array of assets to load (accepts sounds and images)
+ * @param onLoad - Callback when the assets are loaded
+ * @param onProgress - Callback when an asset is loaded. Contains information about assets loaded
+ * @param onError - Callback when an asset fails to load
+ * Preloader for all assets. Takes an array of URLs and
+ * adds them to the `Crafty.assets` object.
+ *
+ * Files with suffixes `jpg`, `jpeg`, `gif` and `png` (case insensitive) will be loaded.
+ *
+ * If `Crafty.support.audio` is `true`, files with the following suffixes `mp3`, `wav`, `ogg` and `mp4` (case insensitive) can be loaded.
+ *
+ * The `onProgress` function will be passed on object with information about
+ * the progress including how many assets loaded, total of all the assets to
+ * load and a percentage of the progress.
+ *
+ *
+ * { loaded: j, total: total, percent: (j / total * 100) })
+ *
+ * `onError` will be passed with the asset that couldn't load.
+ *
+ * When `onError` is not provided, the onLoad is loaded even some assests are not successfully loaded. Otherwise, onLoad will be called no matter whether there are errors or not.
+ * @example
+ * ~~~
+ * Crafty.load(["images/sprite.png", "sounds/jump.mp3"],
+ * function() {
+ * //when loaded
+ * Crafty.scene("main"); //go to main scene
+ * },
+ *
+ * function(e) {
+ * //progress
+ * },
+ *
+ * function(e) {
+ * //uh oh, error loading
+ * }
+ * );
+ * ~~~
+ * @see Crafty.assets
+ */
+ load: function (data, oncomplete, onprogress, onerror) {
+ var i, l = data.length, current, obj, total = l, j = 0, ext;
+ for (i = 0; i < l; ++i) {
+ current = data[i];
+ ext = current.substr(current.lastIndexOf('.') + 1).toLowerCase();
+
+ if (Crafty.support.audio && (ext === "mp3" || ext === "wav" || ext === "ogg" || ext === "mp4")) {
+ obj = new Audio(current);
+ //Chrome doesn't trigger onload on audio, see http://code.google.com/p/chromium/issues/detail?id=77794
+ if (navigator.userAgent.indexOf('Chrome') != -1) j++;
+ } else if (ext === "jpg" || ext === "jpeg" || ext === "gif" || ext === "png") {
+ obj = new Image();
+ obj.src = current;
+ } else {
+ total--;
+ continue; //skip if not applicable
+ }
+
+ //add to global asset collection
+ this.assets[current] = obj;
+
+ obj.onload = function () {
+ ++j;
+
+ //if progress callback, give information of assets loaded, total and percent
+ if (onprogress) {
+ onprogress.call(this, { loaded: j, total: total, percent: (j / total * 100) });
+ }
+ if (j === total) {
+ if (oncomplete) oncomplete();
+ }
+ };
+
+ //if there is an error, pass it in the callback (this will be the object that didn't load)
+ obj.onerror = function () {
+ if (onerror) {
+ onerror.call(this, { loaded: j, total: total, percent: (j / total * 100) });
+ } else {
+ j++;
+ if (j === total) {
+ if (oncomplete) oncomplete();
+ }
+ }
+ };
+ }
+ },
+ /**@
+ * #Crafty.modules
+ * @category Assets
+ * @sign public void Crafty.modules([String repoLocation,] Object moduleMap[, Function onLoad])
+ * @param modules - Map of name:version pairs for modules to load
+ * @param onLoad - Callback when the modules are loaded
+ * Browse the selection of modules on crafty repositories.
+ * Downloads and executes the javascript in the specified modules.
+ * If no repository is specified it defaults to http://cdn.craftycomponents.com
+ *
+ * Available repositories:
+ *
+ * - http://cdn.crafty-modules.com
+ * - http://cdn.craftycomponents.com
+ *
+ *
+ * @example
+ * ~~~
+ * // Loading from default repository
+ * Crafty.modules({ moveto: 'DEV' }, function () {
+ * //module is ready
+ * Crafty.e("MoveTo, 2D, DOM");
+ * });
+ *
+ * // Loading from your own server
+ * Crafty.modules({ 'http://mydomain.com/js/mystuff.js': 'DEV' }, function () {
+ * //module is ready
+ * Crafty.e("MoveTo, 2D, DOM");
+ * });
+ *
+ * // Loading from alternative repository
+ * Crafty.modules('http://cdn.crafty-modules.com', { moveto: 'DEV' }, function () {
+ * //module is ready
+ * Crafty.e("MoveTo, 2D, DOM");
+ * });
+ *
+ * // Loading from the latest component website
+ * Crafty.modules(
+ * 'http://cdn.craftycomponents.com'
+ * , { MoveTo: 'release' }
+ * , function () {
+ * Crafty.e("2D, DOM, Color, MoveTo")
+ * .attr({x: 0, y: 0, w: 50, h: 50})
+ * .color("green");
+ * });
+ * });
+ * ~~~
+ *
+ */
+ modules: function (modulesRepository, moduleMap, oncomplete) {
+
+ if (arguments.length === 2 && typeof modulesRepository === "object") {
+ oncomplete = moduleMap;
+ moduleMap = modulesRepository;
+ modulesRepository = 'http://cdn.craftycomponents.com';
+ }
+
+ /*!
+ * $script.js Async loader & dependency manager
+ * https://github.com/ded/script.js
+ * (c) Dustin Diaz, Jacob Thornton 2011
+ * License: MIT
+ */
+ var $script = (function () {
+ var win = this, doc = document
+ , head = doc.getElementsByTagName('head')[0]
+ , validBase = /^https?:\/\//
+ , old = win.$script, list = {}, ids = {}, delay = {}, scriptpath
+ , scripts = {}, s = 'string', f = false
+ , push = 'push', domContentLoaded = 'DOMContentLoaded', readyState = 'readyState'
+ , addEventListener = 'addEventListener', onreadystatechange = 'onreadystatechange'
+
+ function every(ar, fn, i) {
+ for (i = 0, j = ar.length; i < j; ++i) if (!fn(ar[i])) return f
+ return 1
+ }
+ function each(ar, fn) {
+ every(ar, function (el) {
+ return !fn(el)
+ })
+ }
+
+ if (!doc[readyState] && doc[addEventListener]) {
+ doc[addEventListener](domContentLoaded, function fn() {
+ doc.removeEventListener(domContentLoaded, fn, f)
+ doc[readyState] = 'complete'
+ }, f)
+ doc[readyState] = 'loading'
+ }
+
+ function $script(paths, idOrDone, optDone) {
+ paths = paths[push] ? paths : [paths]
+ var idOrDoneIsDone = idOrDone && idOrDone.call
+ , done = idOrDoneIsDone ? idOrDone : optDone
+ , id = idOrDoneIsDone ? paths.join('') : idOrDone
+ , queue = paths.length
+ function loopFn(item) {
+ return item.call ? item() : list[item]
+ }
+ function callback() {
+ if (!--queue) {
+ list[id] = 1
+ done && done()
+ for (var dset in delay) {
+ every(dset.split('|'), loopFn) && !each(delay[dset], loopFn) && (delay[dset] = [])
+ }
+ }
+ }
+ setTimeout(function () {
+ each(paths, function (path) {
+ if (scripts[path]) {
+ id && (ids[id] = 1)
+ return scripts[path] == 2 && callback()
+ }
+ scripts[path] = 1
+ id && (ids[id] = 1)
+ create(!validBase.test(path) && scriptpath ? scriptpath + path + '.js' : path, callback)
+ })
+ }, 0)
+ return $script
+ }
+
+ function create(path, fn) {
+ var el = doc.createElement('script')
+ , loaded = f
+ el.onload = el.onerror = el[onreadystatechange] = function () {
+ if ((el[readyState] && !(/^c|loade/.test(el[readyState]))) || loaded) return;
+ el.onload = el[onreadystatechange] = null
+ loaded = 1
+ scripts[path] = 2
+ fn()
+ }
+ el.async = 1
+ el.src = path
+ head.insertBefore(el, head.firstChild)
+ }
+
+ $script.get = create
+
+ $script.order = function (scripts, id, done) {
+ (function callback(s) {
+ s = scripts.shift()
+ if (!scripts.length) $script(s, id, done)
+ else $script(s, callback)
+ }())
+ }
+
+ $script.path = function (p) {
+ scriptpath = p
+ }
+ $script.ready = function (deps, ready, req) {
+ deps = deps[push] ? deps : [deps]
+ var missing = [];
+ !each(deps, function (dep) {
+ list[dep] || missing[push](dep);
+ }) && every(deps, function (dep) { return list[dep] }) ?
+ ready() : !function (key) {
+ delay[key] = delay[key] || []
+ delay[key][push](ready)
+ req && req(missing)
+ }(deps.join('|'))
+ return $script
+ }
+
+ $script.noConflict = function () {
+ win.$script = old;
+ return this
+ }
+
+ return $script
+ })();
+
+ var modules = [];
+ for (var i in moduleMap) {
+ if (i.indexOf("http://") != -1)
+ modules.push(i)
+ else
+ modules.push(modulesRepository + '/' + i.toLowerCase() + '-' + moduleMap[i].toLowerCase() + '.js');
+ }
+
+ $script(modules, function () {
+ if (oncomplete) oncomplete();
+ });
+ }
+});
+ /**@
+* #Crafty.math
+* @category 2D
+* Static functions.
+*/
+Crafty.math = {
+/**@
+ * #Crafty.math.abs
+ * @comp Crafty.math
+ * @sign public this Crafty.math.abs(Number n)
+ * @param n - Some value.
+ * @return Absolute value.
+ * Returns the absolute value.
+ */
+ abs: function (x) {
+ return x < 0 ? -x : x;
+ },
+
+ /**@
+ * #Crafty.math.amountOf
+ * @comp Crafty.math
+ * @sign public Number Crafty.math.amountOf(Number checkValue, Number minValue, Number maxValue)
+ * @param checkValue - Value that should checked with minimum and maximum.
+ * @param minValue - Minimum value to check.
+ * @param maxValue - Maximum value to check.
+ * @return Amount of checkValue compared to minValue and maxValue.
+ * Returns the amount of how much a checkValue is more like minValue (=0)
+ * or more like maxValue (=1)
+ */
+ amountOf: function (checkValue, minValue, maxValue) {
+ if (minValue < maxValue)
+ return (checkValue - minValue) / (maxValue - minValue);
+ else
+ return (checkValue - maxValue) / (minValue - maxValue);
+ },
+
+
+ /**@
+ * #Crafty.math.clamp
+ * @comp Crafty.math
+ * @sign public Number Crafty.math.clamp(Number value, Number min, Number max)
+ * @param value - A value.
+ * @param max - Maximum that value can be.
+ * @param min - Minimum that value can be.
+ * @return The value between minimum and maximum.
+ * Restricts a value to be within a specified range.
+ */
+ clamp: function (value, min, max) {
+ if (value > max)
+ return max;
+ else if (value < min)
+ return min;
+ else
+ return value;
+ },
+
+ /**@
+ * Converts angle from degree to radian.
+ * @comp Crafty.math
+ * @param angleInDeg - The angle in degree.
+ * @return The angle in radian.
+ */
+ degToRad: function (angleInDeg) {
+ return angleInDeg * Math.PI / 180;
+ },
+
+ /**@
+ * #Crafty.math.distance
+ * @comp Crafty.math
+ * @sign public Number Crafty.math.distance(Number x1, Number y1, Number x2, Number y2)
+ * @param x1 - First x coordinate.
+ * @param y1 - First y coordinate.
+ * @param x2 - Second x coordinate.
+ * @param y2 - Second y coordinate.
+ * @return The distance between the two points.
+ * Distance between two points.
+ */
+ distance: function (x1, y1, x2, y2) {
+ var squaredDistance = Crafty.math.squaredDistance(x1, y1, x2, y2);
+ return Math.sqrt(parseFloat(squaredDistance));
+ },
+
+ /**@
+ * #Crafty.math.lerp
+ * @comp Crafty.math
+ * @sign public Number Crafty.math.lerp(Number value1, Number value2, Number amount)
+ * @param value1 - One value.
+ * @param value2 - Another value.
+ * @param amount - Amount of value2 to value1.
+ * @return Linear interpolated value.
+ * Linear interpolation. Passing amount with a value of 0 will cause value1 to be returned,
+ * a value of 1 will cause value2 to be returned.
+ */
+ lerp: function (value1, value2, amount) {
+ return value1 + (value2 - value1) * amount;
+ },
+
+ /**@
+ * #Crafty.math.negate
+ * @comp Crafty.math
+ * @sign public Number Crafty.math.negate(Number percent)
+ * @param percent - If you pass 1 a -1 will be returned. If you pass 0 a 1 will be returned.
+ * @return 1 or -1.
+ * Returnes "randomly" -1.
+ */
+ negate: function (percent) {
+ if (Math.random() < percent)
+ return -1;
+ else
+ return 1;
+ },
+
+ /**@
+ * #Crafty.math.radToDeg
+ * @comp Crafty.math
+ * @sign public Number Crafty.math.radToDeg(Number angle)
+ * @param angleInRad - The angle in radian.
+ * @return The angle in degree.
+ * Converts angle from radian to degree.
+ */
+ radToDeg: function (angleInRad) {
+ return angleInRad * 180 / Math.PI;
+ },
+
+ /**@
+ * #Crafty.math.randomElementOfArray
+ * @comp Crafty.math
+ * @sign public Object Crafty.math.randomElementOfArray(Array array)
+ * @param array - A specific array.
+ * @return A random element of a specific array.
+ * Returns a random element of a specific array.
+ */
+ randomElementOfArray: function (array) {
+ return array[Math.floor(array.length * Math.random())];
+ },
+
+ /**@
+ * #Crafty.math.randomInt
+ * @comp Crafty.math
+ * @sign public Number Crafty.math.randomInt(Number start, Number end)
+ * @param start - Smallest int value that can be returned.
+ * @param end - Biggest int value that can be returned.
+ * @return A random int.
+ * Returns a random int in within a specific range.
+ */
+ randomInt: function (start, end) {
+ return start + Math.floor((1 + end - start) * Math.random());
+ },
+
+ /**@
+ * #Crafty.math.randomNumber
+ * @comp Crafty.math
+ * @sign public Number Crafty.math.randomInt(Number start, Number end)
+ * @param start - Smallest number value that can be returned.
+ * @param end - Biggest number value that can be returned.
+ * @return A random number.
+ * Returns a random number in within a specific range.
+ */
+ randomNumber: function (start, end) {
+ return start + (end - start) * Math.random();
+ },
+
+ /**@
+ * #Crafty.math.squaredDistance
+ * @comp Crafty.math
+ * @sign public Number Crafty.math.squaredDistance(Number x1, Number y1, Number x2, Number y2)
+ * @param x1 - First x coordinate.
+ * @param y1 - First y coordinate.
+ * @param x2 - Second x coordinate.
+ * @param y2 - Second y coordinate.
+ * @return The squared distance between the two points.
+ * Squared distance between two points.
+ */
+ squaredDistance: function (x1, y1, x2, y2) {
+ return (x1 - x2) * (x1 - x2) + (y1 - y2) * (y1 - y2);
+ },
+
+ /**@
+ * #Crafty.math.squaredDistance
+ * @comp Crafty.math
+ * @sign public Boolean Crafty.math.withinRange(Number value, Number min, Number max)
+ * @param value - The specific value.
+ * @param min - Minimum value.
+ * @param max - Maximum value.
+ * @return Returns true if value is within a specific range.
+ * Check if a value is within a specific range.
+ */
+ withinRange: function (value, min, max) {
+ return (value >= min && value <= max);
+ }
+};
+
+Crafty.math.Vector2D = (function () {
+ /**@
+ * #Crafty.math.Vector2D
+ *
+ * @class This is a general purpose 2D vector class
+ *
+ * Vector2D uses the following form:
+ *
+ *
+ * @public
+ * @sign public {Vector2D} Vector2D();
+ * @sign public {Vector2D} Vector2D(Vector2D);
+ * @sign public {Vector2D} Vector2D(Number, Number);
+ * @param {Vector2D|Number=0} x
+ * @param {Number=0} y
+ */
+ function Vector2D(x, y) {
+ if (x instanceof Vector2D) {
+ this.x = x.x;
+ this.y = x.y;
+ } else if (arguments.length === 2) {
+ this.x = x;
+ this.y = y;
+ } else if (arguments.length > 0)
+ throw "Unexpected number of arguments for Vector2D()";
+ } // class Vector2D
+
+ Vector2D.prototype.x = 0;
+ Vector2D.prototype.y = 0;
+
+ /**@
+ * #.add( )
+ *
+ * Adds the passed vector to this vector
+ *
+ * @public
+ * @sign public {Vector2D} add(Vector2D);
+ * @param {vector2D} vecRH
+ * @returns {Vector2D} this after adding
+ */
+ Vector2D.prototype.add = function (vecRH) {
+ this.x += vecRH.x;
+ this.y += vecRH.y;
+ return this;
+ } // add( )
+
+ /**@
+ * #.angleBetween( )
+ *
+ * Calculates the angle between the passed vector and this vector, using <0,0> as the point of reference.
+ * Angles returned have the range (−π, π].
+ *
+ * @public
+ * @sign public {Number} angleBetween(Vector2D);
+ * @param {Vector2D} vecRH
+ * @returns {Number} the angle between the two vectors in radians
+ */
+ Vector2D.prototype.angleBetween = function (vecRH) {
+ return Math.atan2(this.x * vecRH.y - this.y * vecRH.x, this.x * vecRH.x + this.y * vecRH.y);
+ } // angleBetween( )
+
+ /**@
+ * #.angleTo( )
+ *
+ * Calculates the angle to the passed vector from this vector, using this vector as the point of reference.
+ *
+ * @public
+ * @sign public {Number} angleTo(Vector2D);
+ * @param {Vector2D} vecRH
+ * @returns {Number} the angle to the passed vector in radians
+ */
+ Vector2D.prototype.angleTo = function (vecRH) {
+ return Math.atan2(vecRH.y - this.y, vecRH.x - this.x);
+ };
+
+ /**@
+ * #.clone( )
+ *
+ * Creates and exact, numeric copy of this vector
+ *
+ * @public
+ * @sign public {Vector2D} clone();
+ * @returns {Vector2D} the new vector
+ */
+ Vector2D.prototype.clone = function () {
+ return new Vector2D(this);
+ } // clone( )
+
+ /**@
+ * #.distance( )
+ *
+ * Calculates the distance from this vector to the passed vector.
+ *
+ * @public
+ * @sign public {Number} distance(Vector2D);
+ * @param {Vector2D} vecRH
+ * @returns {Number} the distance between the two vectors
+ */
+ Vector2D.prototype.distance = function (vecRH) {
+ return Math.sqrt((vecRH.x - this.x) * (vecRH.x - this.x) + (vecRH.y - this.y) * (vecRH.y - this.y));
+ } // distance( )
+
+ /**@
+ * #.distanceSq( )
+ *
+ * Calculates the squared distance from this vector to the passed vector.
+ * This function avoids calculating the square root, thus being slightly faster than .distance( ).
+ *
+ * @public
+ * @sign public {Number} distanceSq(Vector2D);
+ * @param {Vector2D} vecRH
+ * @returns {Number} the squared distance between the two vectors
+ * @see Vector2D.distance( )
+ */
+ Vector2D.prototype.distanceSq = function (vecRH) {
+ return (vecRH.x - this.x) * (vecRH.x - this.x) + (vecRH.y - this.y) * (vecRH.y - this.y);
+ } // distanceSq( )
+
+ /**@
+ * #.divide( )
+ *
+ * Divides this vector by the passed vector.
+ *
+ * @public
+ * @sign public {Vector2D} divide(Vector2D);
+ * @param {Vector2D} vecRH
+ * @returns {Vector2D} this vector after dividing
+ */
+ Vector2D.prototype.divide = function (vecRH) {
+ this.x /= vecRH.x;
+ this.y /= vecRH.y;
+ return this;
+ } // divide( )
+
+ /**@
+ * #.dotProduct( )
+ *
+ * Calculates the dot product of this and the passed vectors
+ *
+ * @public
+ * @sign public {Number} dotProduct(Vector2D);
+ * @param {Vector2D} vecRH
+ * @returns {Number} the resultant dot product
+ */
+ Vector2D.prototype.dotProduct = function (vecRH) {
+ return this.x * vecRH.x + this.y * vecRH.y;
+ } // dotProduct( )
+
+ /**@
+ * #.equals( )
+ *
+ * Determines if this vector is numerically equivalent to the passed vector.
+ *
+ * @public
+ * @sign public {Boolean} equals(Vector2D);
+ * @param {Vector2D} vecRH
+ * @returns {Boolean} true if the vectors are equivalent
+ */
+ Vector2D.prototype.equals = function (vecRH) {
+ return vecRH instanceof Vector2D &&
+ this.x == vecRH.x && this.y == vecRH.y;
+ } // equals( )
+
+ /**@
+ * #.getNormal( )
+ *
+ * Calculates a new right-handed normal vector for the line created by this and the passed vectors.
+ *
+ * @public
+ * @sign public {Vector2D} getNormal([Vector2D]);
+ * @param {Vector2D=<0,0>} [vecRH]
+ * @returns {Vector2D} the new normal vector
+ */
+ Vector2D.prototype.getNormal = function (vecRH) {
+ if (vecRH === undefined)
+ return new Vector2D(-this.y, this.x); // assume vecRH is <0, 0>
+ return new Vector2D(vecRH.y - this.y, this.x - vecRH.x).normalize();
+ } // getNormal( )
+
+ /**@
+ * #.isZero( )
+ *
+ * Determines if this vector is equal to <0,0>
+ *
+ * @public
+ * @sign public {Boolean} isZero();
+ * @returns {Boolean} true if this vector is equal to <0,0>
+ */
+ Vector2D.prototype.isZero = function () {
+ return this.x === 0 && this.y === 0;
+ } // isZero( )
+
+ /**@
+ * #.magnitude( )
+ *
+ * Calculates the magnitude of this vector.
+ * Note: Function objects in JavaScript already have a 'length' member, hence the use of magnitude instead.
+ *
+ * @public
+ * @sign public {Number} magnitude();
+ * @returns {Number} the magnitude of this vector
+ */
+ Vector2D.prototype.magnitude = function () {
+ return Math.sqrt(this.x * this.x + this.y * this.y);
+ } // magnitude( )
+
+ /**@
+ * #.magnitudeSq( )
+ *
+ * Calculates the square of the magnitude of this vector.
+ * This function avoids calculating the square root, thus being slightly faster than .magnitude( ).
+ *
+ * @public
+ * @sign public {Number} magnitudeSq();
+ * @returns {Number} the square of the magnitude of this vector
+ * @see Vector2D.magnitude( )
+ */
+ Vector2D.prototype.magnitudeSq = function () {
+ return this.x * this.x + this.y * this.y;
+ } // magnitudeSq( )
+
+ /**@
+ * #.multiply( )
+ *
+ * Multiplies this vector by the passed vector
+ *
+ * @public
+ * @sign public {Vector2D} multiply(Vector2D);
+ * @param {Vector2D} vecRH
+ * @returns {Vector2D} this vector after multiplying
+ */
+ Vector2D.prototype.multiply = function (vecRH) {
+ this.x *= vecRH.x;
+ this.y *= vecRH.y;
+ return this;
+ } // multiply( )
+
+ /**@
+ * #.negate( )
+ *
+ * Negates this vector (ie. <-x,-y>)
+ *
+ * @public
+ * @sign public {Vector2D} negate();
+ * @returns {Vector2D} this vector after negation
+ */
+ Vector2D.prototype.negate = function () {
+ this.x = -this.x;
+ this.y = -this.y;
+ return this;
+ } // negate( )
+
+ /**@
+ * #.normalize( )
+ *
+ * Normalizes this vector (scales the vector so that its new magnitude is 1)
+ * For vectors where magnitude is 0, <1,0> is returned.
+ *
+ * @public
+ * @sign public {Vector2D} normalize();
+ * @returns {Vector2D} this vector after normalization
+ */
+ Vector2D.prototype.normalize = function () {
+ var lng = Math.sqrt(this.x * this.x + this.y * this.y);
+
+ if (lng === 0) {
+ // default due East
+ this.x = 1;
+ this.y = 0;
+ } else {
+ this.x /= lng;
+ this.y /= lng;
+ } // else
+
+ return this;
+ } // normalize( )
+
+ /**@
+ * #.scale( )
+ *
+ * Scales this vector by the passed amount(s)
+ * If scalarY is omitted, scalarX is used for both axes
+ *
+ * @public
+ * @sign public {Vector2D} scale(Number[, Number]);
+ * @param {Number} scalarX
+ * @param {Number} [scalarY]
+ * @returns {Vector2D} this after scaling
+ */
+ Vector2D.prototype.scale = function (scalarX, scalarY) {
+ if (scalarY === undefined)
+ scalarY = scalarX;
+
+ this.x *= scalarX;
+ this.y *= scalarY;
+
+ return this;
+ } // scale( )
+
+ /**@
+ * #.scaleToMagnitude( )
+ *
+ * Scales this vector such that its new magnitude is equal to the passed value.
+ *
+ * @public
+ * @sign public {Vector2D} scaleToMagnitude(Number);
+ * @param {Number} mag
+ * @returns {Vector2D} this vector after scaling
+ */
+ Vector2D.prototype.scaleToMagnitude = function (mag) {
+ var k = mag / this.magnitude();
+ this.x *= k;
+ this.y *= k;
+ return this;
+ } // scaleToMagnitude( )
+
+ /**@
+ * #.setValues( )
+ *
+ * Sets the values of this vector using a passed vector or pair of numbers.
+ *
+ * @public
+ * @sign public {Vector2D} setValues(Vector2D);
+ * @sign public {Vector2D} setValues(Number, Number);
+ * @param {Number|Vector2D} x
+ * @param {Number} y
+ * @returns {Vector2D} this vector after setting of values
+ */
+ Vector2D.prototype.setValues = function (x, y) {
+ if (x instanceof Vector2D) {
+ this.x = x.x;
+ this.y = x.y;
+ } else {
+ this.x = x;
+ this.y = y;
+ } // else
+
+ return this;
+ } // setValues( )
+
+ /**@
+ * #.subtract( )
+ *
+ * Subtracts the passed vector from this vector.
+ *
+ * @public
+ * @sign public {Vector2D} subtract(Vector2D);
+ * @param {Vector2D} vecRH
+ * @returns {vector2D} this vector after subtracting
+ */
+ Vector2D.prototype.subtract = function (vecRH) {
+ this.x -= vecRH.x;
+ this.y -= vecRH.y;
+ return this;
+ } // subtract( )
+
+ /**@
+ * #.toString( )
+ *
+ * Returns a string representation of this vector.
+ *
+ * @public
+ * @sign public {String} toString();
+ * @returns {String}
+ */
+ Vector2D.prototype.toString = function () {
+ return "Vector2D(" + this.x + ", " + this.y + ")";
+ } // toString( )
+
+ /**@
+ * #.translate( )
+ *
+ * Translates (moves) this vector by the passed amounts.
+ * If dy is omitted, dx is used for both axes.
+ *
+ * @public
+ * @sign public {Vector2D} translate(Number[, Number]);
+ * @param {Number} dx
+ * @param {Number} [dy]
+ * @returns {Vector2D} this vector after translating
+ */
+ Vector2D.prototype.translate = function (dx, dy) {
+ if (dy === undefined)
+ dy = dx;
+
+ this.x += dx;
+ this.y += dy;
+
+ return this;
+ } // translate( )
+
+ /**@
+ * #.tripleProduct( )
+ *
+ * Calculates the triple product of three vectors.
+ * triple vector product = b(a•c) - a(b•c)
+ *
+ * @public
+ * @static
+ * @sign public {Vector2D} tripleProduct(Vector2D, Vector2D, Vector2D);
+ * @param {Vector2D} a
+ * @param {Vector2D} b
+ * @param {Vector2D} c
+ * @return {Vector2D} the triple product as a new vector
+ */
+ Vector2D.tripleProduct = function (a, b, c) {
+ var ac = a.dotProduct(c);
+ var bc = b.dotProduct(c);
+ return new Crafty.math.Vector2D(b.x * ac - a.x * bc, b.y * ac - a.y * bc);
+ };
+
+ return Vector2D;
+})();
+
+Crafty.math.Matrix2D = (function () {
+ /**@
+ * #Crafty.math.Matrix2D
+ *
+ * @class This is a 2D Matrix2D class. It is 3x3 to allow for affine transformations in 2D space.
+ * The third row is always assumed to be [0, 0, 1].
+ *
+ * Matrix2D uses the following form, as per the whatwg.org specifications for canvas.transform():
+ * [a, c, e]
+ * [b, d, f]
+ * [0, 0, 1]
+ *
+ * @public
+ * @sign public {Matrix2D} new Matrix2D();
+ * @sign public {Matrix2D} new Matrix2D(Matrix2D);
+ * @sign public {Matrix2D} new Matrix2D(Number, Number, Number, Number, Number, Number);
+ * @param {Matrix2D|Number=1} a
+ * @param {Number=0} b
+ * @param {Number=0} c
+ * @param {Number=1} d
+ * @param {Number=0} e
+ * @param {Number=0} f
+ */
+ Matrix2D = function (a, b, c, d, e, f) {
+ if (a instanceof Matrix2D) {
+ this.a = a.a;
+ this.b = a.b;
+ this.c = a.c;
+ this.d = a.d;
+ this.e = a.e;
+ this.f = a.f;
+ } else if (arguments.length === 6) {
+ this.a = a;
+ this.b = b;
+ this.c = c;
+ this.d = d;
+ this.e = e;
+ this.f = f;
+ } else if (arguments.length > 0)
+ throw "Unexpected number of arguments for Matrix2D()";
+ } // class Matrix2D
+
+ Matrix2D.prototype.a = 1;
+ Matrix2D.prototype.b = 0;
+ Matrix2D.prototype.c = 0;
+ Matrix2D.prototype.d = 1;
+ Matrix2D.prototype.e = 0;
+ Matrix2D.prototype.f = 0;
+
+ /**@
+ * #.apply( )
+ *
+ * Applies the matrix transformations to the passed object
+ *
+ * @public
+ * @sign public {Vector2D} apply(Vector2D);
+ * @param {Vector2D} vecRH - vector to be transformed
+ * @returns {Vector2D} the passed vector object after transforming
+ */
+ Matrix2D.prototype.apply = function (vecRH) {
+ // I'm not sure of the best way for this function to be implemented. Ideally
+ // support for other objects (rectangles, polygons, etc) should be easily
+ // addable in the future. Maybe a function (apply) is not the best way to do
+ // this...?
+
+ var tmpX = vecRH.x;
+ vecRH.x = tmpX * this.a + vecRH.y * this.c + this.e;
+ vecRH.y = tmpX * this.b + vecRH.y * this.d + this.f;
+ // no need to homogenize since the third row is always [0, 0, 1]
+
+ return vecRH;
+ } // apply( )
+
+ /**@
+ * #.clone( )
+ *
+ * Creates an exact, numeric copy of the current matrix
+ *
+ * @public
+ * @sign public {Matrix2D} clone();
+ * @returns {Matrix2D}
+ */
+ Matrix2D.prototype.clone = function () {
+ return new Matrix2D(this);
+ } // clone( )
+
+ /**@
+ * #.combine( )
+ *
+ * Multiplies this matrix with another, overriding the values of this matrix.
+ * The passed matrix is assumed to be on the right-hand side.
+ *
+ * @public
+ * @sign public {Matrix2D} combine(Matrix2D);
+ * @param {Matrix2D} mtrxRH
+ * @returns {Matrix2D} this matrix after combination
+ */
+ Matrix2D.prototype.combine = function (mtrxRH) {
+ var tmp = this.a;
+ this.a = tmp * mtrxRH.a + this.b * mtrxRH.c;
+ this.b = tmp * mtrxRH.b + this.b * mtrxRH.d;
+ tmp = this.c;
+ this.c = tmp * mtrxRH.a + this.d * mtrxRH.c;
+ this.d = tmp * mtrxRH.b + this.d * mtrxRH.d;
+ tmp = this.e;
+ this.e = tmp * mtrxRH.a + this.f * mtrxRH.c + mtrxRH.e;
+ this.f = tmp * mtrxRH.b + this.f * mtrxRH.d + mtrxRH.f;
+ return this;
+ } // combine( )
+
+ /**@
+ * #.equals( )
+ *
+ * Checks for the numeric equality of this matrix versus another.
+ *
+ * @public
+ * @sign public {Boolean} equals(Matrix2D);
+ * @param {Matrix2D} mtrxRH
+ * @returns {Boolean} true if the two matrices are numerically equal
+ */
+ Matrix2D.prototype.equals = function (mtrxRH) {
+ return mtrxRH instanceof Matrix2D &&
+ this.a == mtrxRH.a && this.b == mtrxRH.b && this.c == mtrxRH.c &&
+ this.d == mtrxRH.d && this.e == mtrxRH.e && this.f == mtrxRH.f;
+ } // equals( )
+
+ /**@
+ * #.determinant( )
+ *
+ * Calculates the determinant of this matrix
+ *
+ * @public
+ * @sign public {Number} determinant();
+ * @returns {Number} det(this matrix)
+ */
+ Matrix2D.prototype.determinant = function () {
+ return this.a * this.d - this.b * this.c;
+ } // determinant( )
+
+ /**@
+ * #.invert( )
+ *
+ * Inverts this matrix if possible
+ *
+ * @public
+ * @sign public {Matrix2D} invert();
+ * @returns {Matrix2D} this inverted matrix or the original matrix on failure
+ * @see Matrix2D.isInvertible( )
+ */
+ Matrix2D.prototype.invert = function () {
+ var det = this.determinant();
+
+ // matrix is invertible if its determinant is non-zero
+ if (det !== 0) {
+ var old = {
+ a: this.a,
+ b: this.b,
+ c: this.c,
+ d: this.d,
+ e: this.e,
+ f: this.f
+ };
+ this.a = old.d / det;
+ this.b = -old.b / det;
+ this.c = -old.c / det;
+ this.d = old.a / det;
+ this.e = (old.c * old.f - old.e * old.d) / det;
+ this.f = (old.e * old.b - old.a * old.f) / det;
+ } // if
+
+ return this;
+ } // invert( )
+
+ /**@
+ * #.isIdentity( )
+ *
+ * Returns true if this matrix is the identity matrix
+ *
+ * @public
+ * @sign public {Boolean} isIdentity();
+ * @returns {Boolean}
+ */
+ Matrix2D.prototype.isIdentity = function () {
+ return this.a === 1 && this.b === 0 && this.c === 0 && this.d === 1 && this.e === 0 && this.f === 0;
+ } // isIdentity( )
+
+ /**@
+ * #.isInvertible( )
+ *
+ * Determines is this matrix is invertible.
+ *
+ * @public
+ * @sign public {Boolean} isInvertible();
+ * @returns {Boolean} true if this matrix is invertible
+ * @see Matrix2D.invert( )
+ */
+ Matrix2D.prototype.isInvertible = function () {
+ return this.determinant() !== 0;
+ } // isInvertible( )
+
+ /**@
+ * #.preRotate( )
+ *
+ * Applies a counter-clockwise pre-rotation to this matrix
+ *
+ * @public
+ * @sign public {Matrix2D} preRotate(Number);
+ * @param {number} rads - angle to rotate in radians
+ * @returns {Matrix2D} this matrix after pre-rotation
+ */
+ Matrix2D.prototype.preRotate = function (rads) {
+ var nCos = Math.cos(rads);
+ var nSin = Math.sin(rads);
+
+ var tmp = this.a;
+ this.a = nCos * tmp - nSin * this.b;
+ this.b = nSin * tmp + nCos * this.b;
+ tmp = this.c;
+ this.c = nCos * tmp - nSin * this.d;
+ this.d = nSin * tmp + nCos * this.d;
+
+ return this;
+ } // preRotate( )
+
+ /**@
+ * #.preScale( )
+ *
+ * Applies a pre-scaling to this matrix
+ *
+ * @public
+ * @sign public {Matrix2D} preScale(Number[, Number]);
+ * @param {Number} scalarX
+ * @param {Number} [scalarY] scalarX is used if scalarY is undefined
+ * @returns {Matrix2D} this after pre-scaling
+ */
+ Matrix2D.prototype.preScale = function (scalarX, scalarY) {
+ if (scalarY === undefined)
+ scalarY = scalarX;
+
+ this.a *= scalarX;
+ this.b *= scalarY;
+ this.c *= scalarX;
+ this.d *= scalarY;
+
+ return this;
+ } // preScale( )
+
+ /**@
+ * #.preTranslate( )
+ *
+ * Applies a pre-translation to this matrix
+ *
+ * @public
+ * @sign public {Matrix2D} preTranslate(Vector2D);
+ * @sign public {Matrix2D} preTranslate(Number, Number);
+ * @param {Number|Vector2D} dx
+ * @param {Number} dy
+ * @returns {Matrix2D} this matrix after pre-translation
+ */
+ Matrix2D.prototype.preTranslate = function (dx, dy) {
+ if (typeof dx === "number") {
+ this.e += dx;
+ this.f += dy;
+ } else {
+ this.e += dx.x;
+ this.f += dx.y;
+ } // else
+
+ return this;
+ } // preTranslate( )
+
+ /**@
+ * #.rotate( )
+ *
+ * Applies a counter-clockwise post-rotation to this matrix
+ *
+ * @public
+ * @sign public {Matrix2D} rotate(Number);
+ * @param {Number} rads - angle to rotate in radians
+ * @returns {Matrix2D} this matrix after rotation
+ */
+ Matrix2D.prototype.rotate = function (rads) {
+ var nCos = Math.cos(rads);
+ var nSin = Math.sin(rads);
+
+ var tmp = this.a;
+ this.a = nCos * tmp - nSin * this.b;
+ this.b = nSin * tmp + nCos * this.b;
+ tmp = this.c;
+ this.c = nCos * tmp - nSin * this.d;
+ this.d = nSin * tmp + nCos * this.d;
+ tmp = this.e;
+ this.e = nCos * tmp - nSin * this.f;
+ this.f = nSin * tmp + nCos * this.f;
+
+ return this;
+ } // rotate( )
+
+ /**@
+ * #.scale( )
+ *
+ * Applies a post-scaling to this matrix
+ *
+ * @public
+ * @sign public {Matrix2D} scale(Number[, Number]);
+ * @param {Number} scalarX
+ * @param {Number} [scalarY] scalarX is used if scalarY is undefined
+ * @returns {Matrix2D} this after post-scaling
+ */
+ Matrix2D.prototype.scale = function (scalarX, scalarY) {
+ if (scalarY === undefined)
+ scalarY = scalarX;
+
+ this.a *= scalarX;
+ this.b *= scalarY;
+ this.c *= scalarX;
+ this.d *= scalarY;
+ this.e *= scalarX;
+ this.f *= scalarY;
+
+ return this;
+ } // scale( )
+
+ /**@
+ * #.setValues( )
+ *
+ * Sets the values of this matrix
+ *
+ * @public
+ * @sign public {Matrix2D} setValues(Matrix2D);
+ * @sign public {Matrix2D} setValues(Number, Number, Number, Number, Number, Number);
+ * @param {Matrix2D|Number} a
+ * @param {Number} b
+ * @param {Number} c
+ * @param {Number} d
+ * @param {Number} e
+ * @param {Number} f
+ * @returns {Matrix2D} this matrix containing the new values
+ */
+ Matrix2D.prototype.setValues = function (a, b, c, d, e, f) {
+ if (a instanceof Matrix2D) {
+ this.a = a.a;
+ this.b = a.b;
+ this.c = a.c;
+ this.d = a.d;
+ this.e = a.e;
+ this.f = a.f;
+ } else {
+ this.a = a;
+ this.b = b;
+ this.c = c;
+ this.d = d;
+ this.e = e;
+ this.f = f;
+ } // else
+
+ return this;
+ } // setValues( )
+
+ /**@
+ * #.toString( )
+ *
+ * Returns the string representation of this matrix.
+ *
+ * @public
+ * @sign public {String} toString();
+ * @returns {String}
+ */
+ Matrix2D.prototype.toString = function () {
+ return "Matrix2D([" + this.a + ", " + this.c + ", " + this.e +
+ "] [" + this.b + ", " + this.d + ", " + this.f + "] [0, 0, 1])";
+ } // toString( )
+
+ /**@
+ * #.translate( )
+ *
+ * Applies a post-translation to this matrix
+ *
+ * @public
+ * @sign public {Matrix2D} translate(Vector2D);
+ * @sign public {Matrix2D} translate(Number, Number);
+ * @param {Number|Vector2D} dx
+ * @param {Number} dy
+ * @returns {Matrix2D} this matrix after post-translation
+ */
+ Matrix2D.prototype.translate = function (dx, dy) {
+ if (typeof dx === "number") {
+ this.e += this.a * dx + this.c * dy;
+ this.f += this.b * dx + this.d * dy;
+ } else {
+ this.e += this.a * dx.x + this.c * dx.y;
+ this.f += this.b * dx.x + this.d * dx.y;
+ } // else
+
+ return this;
+ } // translate( )
+
+ return Matrix2D;
+})();
+ /**@
+* #.delay
+* @comp Crafty Time
+* @sign public this.delay(Function callback, Number delay)
+* @param callback - Method to execute after given amount of milliseconds
+* @param delay - Amount of milliseconds to execute the method
+* The delay method will execute a function after a given amount of time in milliseconds.
+*
+* It is not a wrapper for `setTimeout`.
+*
+* If Crafty is paused, the delay is interrupted with the pause and then resume when unpaused
+*
+* If the entity is destroyed, the delay is also destroyed and will not have effect.
+*
+* @example
+* ~~~
+* console.log("start");
+* this.delay(function() {
+ console.log("100ms later");
+* }, 100);
+* ~~~
+*/
+Crafty.c("Delay", {
+ init : function() {
+ this._delays = [];
+ this.bind("EnterFrame", function() {
+ var now = new Date().getTime();
+ for(var index in this._delays) {
+ var item = this._delays[index];
+ if(!item.triggered && item.start + item.delay + item.pause < now) {
+ item.triggered=true;
+ item.func.call(this);
+ }
+ }
+ });
+ this.bind("Pause", function() {
+ var now = new Date().getTime();
+ for(var index in this._delays) {
+ this._delays[index].pauseBuffer = now;
+ }
+ });
+ this.bind("Unpause", function() {
+ var now = new Date().getTime();
+ for(var index in this._delays) {
+ var item = this._delays[index];
+ item.pause += now-item.pauseBuffer;
+ }
+ });
+ },
+ /**@
+ * Set a new delay.
+ * @param func the callback function
+ * @param delay the delay in ms
+ */
+ delay : function(func, delay) {
+ return this._delays.push({
+ start : new Date().getTime(),
+ func : func,
+ delay : delay,
+ triggered : false,
+ pauseBuffer: 0,
+ pause: 0
+ });
+ }
+});
+ })(Crafty,window,window.document);
\ No newline at end of file
diff --git a/assets/js/libs/jquery.js b/assets/js/libs/jquery.js
new file mode 100644
index 0000000..8ccd0ea
--- /dev/null
+++ b/assets/js/libs/jquery.js
@@ -0,0 +1,9266 @@
+/*!
+ * jQuery JavaScript Library v1.7.1
+ * http://jquery.com/
+ *
+ * Copyright 2011, John Resig
+ * Dual licensed under the MIT or GPL Version 2 licenses.
+ * http://jquery.org/license
+ *
+ * Includes Sizzle.js
+ * http://sizzlejs.com/
+ * Copyright 2011, The Dojo Foundation
+ * Released under the MIT, BSD, and GPL Licenses.
+ *
+ * Date: Mon Nov 21 21:11:03 2011 -0500
+ */
+(function( window, undefined ) {
+
+// Use the correct document accordingly with window argument (sandbox)
+var document = window.document,
+ navigator = window.navigator,
+ location = window.location;
+var jQuery = (function() {
+
+// Define a local copy of jQuery
+var jQuery = function( selector, context ) {
+ // The jQuery object is actually just the init constructor 'enhanced'
+ return new jQuery.fn.init( selector, context, rootjQuery );
+ },
+
+ // Map over jQuery in case of overwrite
+ _jQuery = window.jQuery,
+
+ // Map over the $ in case of overwrite
+ _$ = window.$,
+
+ // A central reference to the root jQuery(document)
+ rootjQuery,
+
+ // A simple way to check for HTML strings or ID strings
+ // Prioritize #id over to avoid XSS via location.hash (#9521)
+ quickExpr = /^(?:[^#<]*(<[\w\W]+>)[^>]*$|#([\w\-]*)$)/,
+
+ // Check if a string has a non-whitespace character in it
+ rnotwhite = /\S/,
+
+ // Used for trimming whitespace
+ trimLeft = /^\s+/,
+ trimRight = /\s+$/,
+
+ // Match a standalone tag
+ rsingleTag = /^<(\w+)\s*\/?>(?:<\/\1>)?$/,
+
+ // JSON RegExp
+ rvalidchars = /^[\],:{}\s]*$/,
+ rvalidescape = /\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g,
+ rvalidtokens = /"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g,
+ rvalidbraces = /(?:^|:|,)(?:\s*\[)+/g,
+
+ // Useragent RegExp
+ rwebkit = /(webkit)[ \/]([\w.]+)/,
+ ropera = /(opera)(?:.*version)?[ \/]([\w.]+)/,
+ rmsie = /(msie) ([\w.]+)/,
+ rmozilla = /(mozilla)(?:.*? rv:([\w.]+))?/,
+
+ // Matches dashed string for camelizing
+ rdashAlpha = /-([a-z]|[0-9])/ig,
+ rmsPrefix = /^-ms-/,
+
+ // Used by jQuery.camelCase as callback to replace()
+ fcamelCase = function( all, letter ) {
+ return ( letter + "" ).toUpperCase();
+ },
+
+ // Keep a UserAgent string for use with jQuery.browser
+ userAgent = navigator.userAgent,
+
+ // For matching the engine and version of the browser
+ browserMatch,
+
+ // The deferred used on DOM ready
+ readyList,
+
+ // The ready event handler
+ DOMContentLoaded,
+
+ // Save a reference to some core methods
+ toString = Object.prototype.toString,
+ hasOwn = Object.prototype.hasOwnProperty,
+ push = Array.prototype.push,
+ slice = Array.prototype.slice,
+ trim = String.prototype.trim,
+ indexOf = Array.prototype.indexOf,
+
+ // [[Class]] -> type pairs
+ class2type = {};
+
+jQuery.fn = jQuery.prototype = {
+ constructor: jQuery,
+ init: function( selector, context, rootjQuery ) {
+ var match, elem, ret, doc;
+
+ // Handle $(""), $(null), or $(undefined)
+ if ( !selector ) {
+ return this;
+ }
+
+ // Handle $(DOMElement)
+ if ( selector.nodeType ) {
+ this.context = this[0] = selector;
+ this.length = 1;
+ return this;
+ }
+
+ // The body element only exists once, optimize finding it
+ if ( selector === "body" && !context && document.body ) {
+ this.context = document;
+ this[0] = document.body;
+ this.selector = selector;
+ this.length = 1;
+ return this;
+ }
+
+ // Handle HTML strings
+ if ( typeof selector === "string" ) {
+ // Are we dealing with HTML string or an ID?
+ if ( selector.charAt(0) === "<" && selector.charAt( selector.length - 1 ) === ">" && selector.length >= 3 ) {
+ // Assume that strings that start and end with <> are HTML and skip the regex check
+ match = [ null, selector, null ];
+
+ } else {
+ match = quickExpr.exec( selector );
+ }
+
+ // Verify a match, and that no context was specified for #id
+ if ( match && (match[1] || !context) ) {
+
+ // HANDLE: $(html) -> $(array)
+ if ( match[1] ) {
+ context = context instanceof jQuery ? context[0] : context;
+ doc = ( context ? context.ownerDocument || context : document );
+
+ // If a single string is passed in and it's a single tag
+ // just do a createElement and skip the rest
+ ret = rsingleTag.exec( selector );
+
+ if ( ret ) {
+ if ( jQuery.isPlainObject( context ) ) {
+ selector = [ document.createElement( ret[1] ) ];
+ jQuery.fn.attr.call( selector, context, true );
+
+ } else {
+ selector = [ doc.createElement( ret[1] ) ];
+ }
+
+ } else {
+ ret = jQuery.buildFragment( [ match[1] ], [ doc ] );
+ selector = ( ret.cacheable ? jQuery.clone(ret.fragment) : ret.fragment ).childNodes;
+ }
+
+ return jQuery.merge( this, selector );
+
+ // HANDLE: $("#id")
+ } else {
+ elem = document.getElementById( match[2] );
+
+ // Check parentNode to catch when Blackberry 4.6 returns
+ // nodes that are no longer in the document #6963
+ if ( elem && elem.parentNode ) {
+ // Handle the case where IE and Opera return items
+ // by name instead of ID
+ if ( elem.id !== match[2] ) {
+ return rootjQuery.find( selector );
+ }
+
+ // Otherwise, we inject the element directly into the jQuery object
+ this.length = 1;
+ this[0] = elem;
+ }
+
+ this.context = document;
+ this.selector = selector;
+ return this;
+ }
+
+ // HANDLE: $(expr, $(...))
+ } else if ( !context || context.jquery ) {
+ return ( context || rootjQuery ).find( selector );
+
+ // HANDLE: $(expr, context)
+ // (which is just equivalent to: $(context).find(expr)
+ } else {
+ return this.constructor( context ).find( selector );
+ }
+
+ // HANDLE: $(function)
+ // Shortcut for document ready
+ } else if ( jQuery.isFunction( selector ) ) {
+ return rootjQuery.ready( selector );
+ }
+
+ if ( selector.selector !== undefined ) {
+ this.selector = selector.selector;
+ this.context = selector.context;
+ }
+
+ return jQuery.makeArray( selector, this );
+ },
+
+ // Start with an empty selector
+ selector: "",
+
+ // The current version of jQuery being used
+ jquery: "1.7.1",
+
+ // The default length of a jQuery object is 0
+ length: 0,
+
+ // The number of elements contained in the matched element set
+ size: function() {
+ return this.length;
+ },
+
+ toArray: function() {
+ return slice.call( this, 0 );
+ },
+
+ // Get the Nth element in the matched element set OR
+ // Get the whole matched element set as a clean array
+ get: function( num ) {
+ return num == null ?
+
+ // Return a 'clean' array
+ this.toArray() :
+
+ // Return just the object
+ ( num < 0 ? this[ this.length + num ] : this[ num ] );
+ },
+
+ // Take an array of elements and push it onto the stack
+ // (returning the new matched element set)
+ pushStack: function( elems, name, selector ) {
+ // Build a new jQuery matched element set
+ var ret = this.constructor();
+
+ if ( jQuery.isArray( elems ) ) {
+ push.apply( ret, elems );
+
+ } else {
+ jQuery.merge( ret, elems );
+ }
+
+ // Add the old object onto the stack (as a reference)
+ ret.prevObject = this;
+
+ ret.context = this.context;
+
+ if ( name === "find" ) {
+ ret.selector = this.selector + ( this.selector ? " " : "" ) + selector;
+ } else if ( name ) {
+ ret.selector = this.selector + "." + name + "(" + selector + ")";
+ }
+
+ // Return the newly-formed element set
+ return ret;
+ },
+
+ // Execute a callback for every element in the matched set.
+ // (You can seed the arguments with an array of args, but this is
+ // only used internally.)
+ each: function( callback, args ) {
+ return jQuery.each( this, callback, args );
+ },
+
+ ready: function( fn ) {
+ // Attach the listeners
+ jQuery.bindReady();
+
+ // Add the callback
+ readyList.add( fn );
+
+ return this;
+ },
+
+ eq: function( i ) {
+ i = +i;
+ return i === -1 ?
+ this.slice( i ) :
+ this.slice( i, i + 1 );
+ },
+
+ first: function() {
+ return this.eq( 0 );
+ },
+
+ last: function() {
+ return this.eq( -1 );
+ },
+
+ slice: function() {
+ return this.pushStack( slice.apply( this, arguments ),
+ "slice", slice.call(arguments).join(",") );
+ },
+
+ map: function( callback ) {
+ return this.pushStack( jQuery.map(this, function( elem, i ) {
+ return callback.call( elem, i, elem );
+ }));
+ },
+
+ end: function() {
+ return this.prevObject || this.constructor(null);
+ },
+
+ // For internal use only.
+ // Behaves like an Array's method, not like a jQuery method.
+ push: push,
+ sort: [].sort,
+ splice: [].splice
+};
+
+// Give the init function the jQuery prototype for later instantiation
+jQuery.fn.init.prototype = jQuery.fn;
+
+jQuery.extend = jQuery.fn.extend = function() {
+ var options, name, src, copy, copyIsArray, clone,
+ target = arguments[0] || {},
+ i = 1,
+ length = arguments.length,
+ deep = false;
+
+ // Handle a deep copy situation
+ if ( typeof target === "boolean" ) {
+ deep = target;
+ target = arguments[1] || {};
+ // skip the boolean and the target
+ i = 2;
+ }
+
+ // Handle case when target is a string or something (possible in deep copy)
+ if ( typeof target !== "object" && !jQuery.isFunction(target) ) {
+ target = {};
+ }
+
+ // extend jQuery itself if only one argument is passed
+ if ( length === i ) {
+ target = this;
+ --i;
+ }
+
+ for ( ; i < length; i++ ) {
+ // Only deal with non-null/undefined values
+ if ( (options = arguments[ i ]) != null ) {
+ // Extend the base object
+ for ( name in options ) {
+ src = target[ name ];
+ copy = options[ name ];
+
+ // Prevent never-ending loop
+ if ( target === copy ) {
+ continue;
+ }
+
+ // Recurse if we're merging plain objects or arrays
+ if ( deep && copy && ( jQuery.isPlainObject(copy) || (copyIsArray = jQuery.isArray(copy)) ) ) {
+ if ( copyIsArray ) {
+ copyIsArray = false;
+ clone = src && jQuery.isArray(src) ? src : [];
+
+ } else {
+ clone = src && jQuery.isPlainObject(src) ? src : {};
+ }
+
+ // Never move original objects, clone them
+ target[ name ] = jQuery.extend( deep, clone, copy );
+
+ // Don't bring in undefined values
+ } else if ( copy !== undefined ) {
+ target[ name ] = copy;
+ }
+ }
+ }
+ }
+
+ // Return the modified object
+ return target;
+};
+
+jQuery.extend({
+ noConflict: function( deep ) {
+ if ( window.$ === jQuery ) {
+ window.$ = _$;
+ }
+
+ if ( deep && window.jQuery === jQuery ) {
+ window.jQuery = _jQuery;
+ }
+
+ return jQuery;
+ },
+
+ // Is the DOM ready to be used? Set to true once it occurs.
+ isReady: false,
+
+ // A counter to track how many items to wait for before
+ // the ready event fires. See #6781
+ readyWait: 1,
+
+ // Hold (or release) the ready event
+ holdReady: function( hold ) {
+ if ( hold ) {
+ jQuery.readyWait++;
+ } else {
+ jQuery.ready( true );
+ }
+ },
+
+ // Handle when the DOM is ready
+ ready: function( wait ) {
+ // Either a released hold or an DOMready/load event and not yet ready
+ if ( (wait === true && !--jQuery.readyWait) || (wait !== true && !jQuery.isReady) ) {
+ // Make sure body exists, at least, in case IE gets a little overzealous (ticket #5443).
+ if ( !document.body ) {
+ return setTimeout( jQuery.ready, 1 );
+ }
+
+ // Remember that the DOM is ready
+ jQuery.isReady = true;
+
+ // If a normal DOM Ready event fired, decrement, and wait if need be
+ if ( wait !== true && --jQuery.readyWait > 0 ) {
+ return;
+ }
+
+ // If there are functions bound, to execute
+ readyList.fireWith( document, [ jQuery ] );
+
+ // Trigger any bound ready events
+ if ( jQuery.fn.trigger ) {
+ jQuery( document ).trigger( "ready" ).off( "ready" );
+ }
+ }
+ },
+
+ bindReady: function() {
+ if ( readyList ) {
+ return;
+ }
+
+ readyList = jQuery.Callbacks( "once memory" );
+
+ // Catch cases where $(document).ready() is called after the
+ // browser event has already occurred.
+ if ( document.readyState === "complete" ) {
+ // Handle it asynchronously to allow scripts the opportunity to delay ready
+ return setTimeout( jQuery.ready, 1 );
+ }
+
+ // Mozilla, Opera and webkit nightlies currently support this event
+ if ( document.addEventListener ) {
+ // Use the handy event callback
+ document.addEventListener( "DOMContentLoaded", DOMContentLoaded, false );
+
+ // A fallback to window.onload, that will always work
+ window.addEventListener( "load", jQuery.ready, false );
+
+ // If IE event model is used
+ } else if ( document.attachEvent ) {
+ // ensure firing before onload,
+ // maybe late but safe also for iframes
+ document.attachEvent( "onreadystatechange", DOMContentLoaded );
+
+ // A fallback to window.onload, that will always work
+ window.attachEvent( "onload", jQuery.ready );
+
+ // If IE and not a frame
+ // continually check to see if the document is ready
+ var toplevel = false;
+
+ try {
+ toplevel = window.frameElement == null;
+ } catch(e) {}
+
+ if ( document.documentElement.doScroll && toplevel ) {
+ doScrollCheck();
+ }
+ }
+ },
+
+ // See test/unit/core.js for details concerning isFunction.
+ // Since version 1.3, DOM methods and functions like alert
+ // aren't supported. They return false on IE (#2968).
+ isFunction: function( obj ) {
+ return jQuery.type(obj) === "function";
+ },
+
+ isArray: Array.isArray || function( obj ) {
+ return jQuery.type(obj) === "array";
+ },
+
+ // A crude way of determining if an object is a window
+ isWindow: function( obj ) {
+ return obj && typeof obj === "object" && "setInterval" in obj;
+ },
+
+ isNumeric: function( obj ) {
+ return !isNaN( parseFloat(obj) ) && isFinite( obj );
+ },
+
+ type: function( obj ) {
+ return obj == null ?
+ String( obj ) :
+ class2type[ toString.call(obj) ] || "object";
+ },
+
+ isPlainObject: function( obj ) {
+ // Must be an Object.
+ // Because of IE, we also have to check the presence of the constructor property.
+ // Make sure that DOM nodes and window objects don't pass through, as well
+ if ( !obj || jQuery.type(obj) !== "object" || obj.nodeType || jQuery.isWindow( obj ) ) {
+ return false;
+ }
+
+ try {
+ // Not own constructor property must be Object
+ if ( obj.constructor &&
+ !hasOwn.call(obj, "constructor") &&
+ !hasOwn.call(obj.constructor.prototype, "isPrototypeOf") ) {
+ return false;
+ }
+ } catch ( e ) {
+ // IE8,9 Will throw exceptions on certain host objects #9897
+ return false;
+ }
+
+ // Own properties are enumerated firstly, so to speed up,
+ // if last one is own, then all properties are own.
+
+ var key;
+ for ( key in obj ) {}
+
+ return key === undefined || hasOwn.call( obj, key );
+ },
+
+ isEmptyObject: function( obj ) {
+ for ( var name in obj ) {
+ return false;
+ }
+ return true;
+ },
+
+ error: function( msg ) {
+ throw new Error( msg );
+ },
+
+ parseJSON: function( data ) {
+ if ( typeof data !== "string" || !data ) {
+ return null;
+ }
+
+ // Make sure leading/trailing whitespace is removed (IE can't handle it)
+ data = jQuery.trim( data );
+
+ // Attempt to parse using the native JSON parser first
+ if ( window.JSON && window.JSON.parse ) {
+ return window.JSON.parse( data );
+ }
+
+ // Make sure the incoming data is actual JSON
+ // Logic borrowed from http://json.org/json2.js
+ if ( rvalidchars.test( data.replace( rvalidescape, "@" )
+ .replace( rvalidtokens, "]" )
+ .replace( rvalidbraces, "")) ) {
+
+ return ( new Function( "return " + data ) )();
+
+ }
+ jQuery.error( "Invalid JSON: " + data );
+ },
+
+ // Cross-browser xml parsing
+ parseXML: function( data ) {
+ var xml, tmp;
+ try {
+ if ( window.DOMParser ) { // Standard
+ tmp = new DOMParser();
+ xml = tmp.parseFromString( data , "text/xml" );
+ } else { // IE
+ xml = new ActiveXObject( "Microsoft.XMLDOM" );
+ xml.async = "false";
+ xml.loadXML( data );
+ }
+ } catch( e ) {
+ xml = undefined;
+ }
+ if ( !xml || !xml.documentElement || xml.getElementsByTagName( "parsererror" ).length ) {
+ jQuery.error( "Invalid XML: " + data );
+ }
+ return xml;
+ },
+
+ noop: function() {},
+
+ // Evaluates a script in a global context
+ // Workarounds based on findings by Jim Driscoll
+ // http://weblogs.java.net/blog/driscoll/archive/2009/09/08/eval-javascript-global-context
+ globalEval: function( data ) {
+ if ( data && rnotwhite.test( data ) ) {
+ // We use execScript on Internet Explorer
+ // We use an anonymous function so that context is window
+ // rather than jQuery in Firefox
+ ( window.execScript || function( data ) {
+ window[ "eval" ].call( window, data );
+ } )( data );
+ }
+ },
+
+ // Convert dashed to camelCase; used by the css and data modules
+ // Microsoft forgot to hump their vendor prefix (#9572)
+ camelCase: function( string ) {
+ return string.replace( rmsPrefix, "ms-" ).replace( rdashAlpha, fcamelCase );
+ },
+
+ nodeName: function( elem, name ) {
+ return elem.nodeName && elem.nodeName.toUpperCase() === name.toUpperCase();
+ },
+
+ // args is for internal usage only
+ each: function( object, callback, args ) {
+ var name, i = 0,
+ length = object.length,
+ isObj = length === undefined || jQuery.isFunction( object );
+
+ if ( args ) {
+ if ( isObj ) {
+ for ( name in object ) {
+ if ( callback.apply( object[ name ], args ) === false ) {
+ break;
+ }
+ }
+ } else {
+ for ( ; i < length; ) {
+ if ( callback.apply( object[ i++ ], args ) === false ) {
+ break;
+ }
+ }
+ }
+
+ // A special, fast, case for the most common use of each
+ } else {
+ if ( isObj ) {
+ for ( name in object ) {
+ if ( callback.call( object[ name ], name, object[ name ] ) === false ) {
+ break;
+ }
+ }
+ } else {
+ for ( ; i < length; ) {
+ if ( callback.call( object[ i ], i, object[ i++ ] ) === false ) {
+ break;
+ }
+ }
+ }
+ }
+
+ return object;
+ },
+
+ // Use native String.trim function wherever possible
+ trim: trim ?
+ function( text ) {
+ return text == null ?
+ "" :
+ trim.call( text );
+ } :
+
+ // Otherwise use our own trimming functionality
+ function( text ) {
+ return text == null ?
+ "" :
+ text.toString().replace( trimLeft, "" ).replace( trimRight, "" );
+ },
+
+ // results is for internal usage only
+ makeArray: function( array, results ) {
+ var ret = results || [];
+
+ if ( array != null ) {
+ // The window, strings (and functions) also have 'length'
+ // Tweaked logic slightly to handle Blackberry 4.7 RegExp issues #6930
+ var type = jQuery.type( array );
+
+ if ( array.length == null || type === "string" || type === "function" || type === "regexp" || jQuery.isWindow( array ) ) {
+ push.call( ret, array );
+ } else {
+ jQuery.merge( ret, array );
+ }
+ }
+
+ return ret;
+ },
+
+ inArray: function( elem, array, i ) {
+ var len;
+
+ if ( array ) {
+ if ( indexOf ) {
+ return indexOf.call( array, elem, i );
+ }
+
+ len = array.length;
+ i = i ? i < 0 ? Math.max( 0, len + i ) : i : 0;
+
+ for ( ; i < len; i++ ) {
+ // Skip accessing in sparse arrays
+ if ( i in array && array[ i ] === elem ) {
+ return i;
+ }
+ }
+ }
+
+ return -1;
+ },
+
+ merge: function( first, second ) {
+ var i = first.length,
+ j = 0;
+
+ if ( typeof second.length === "number" ) {
+ for ( var l = second.length; j < l; j++ ) {
+ first[ i++ ] = second[ j ];
+ }
+
+ } else {
+ while ( second[j] !== undefined ) {
+ first[ i++ ] = second[ j++ ];
+ }
+ }
+
+ first.length = i;
+
+ return first;
+ },
+
+ grep: function( elems, callback, inv ) {
+ var ret = [], retVal;
+ inv = !!inv;
+
+ // Go through the array, only saving the items
+ // that pass the validator function
+ for ( var i = 0, length = elems.length; i < length; i++ ) {
+ retVal = !!callback( elems[ i ], i );
+ if ( inv !== retVal ) {
+ ret.push( elems[ i ] );
+ }
+ }
+
+ return ret;
+ },
+
+ // arg is for internal usage only
+ map: function( elems, callback, arg ) {
+ var value, key, ret = [],
+ i = 0,
+ length = elems.length,
+ // jquery objects are treated as arrays
+ isArray = elems instanceof jQuery || length !== undefined && typeof length === "number" && ( ( length > 0 && elems[ 0 ] && elems[ length -1 ] ) || length === 0 || jQuery.isArray( elems ) ) ;
+
+ // Go through the array, translating each of the items to their
+ if ( isArray ) {
+ for ( ; i < length; i++ ) {
+ value = callback( elems[ i ], i, arg );
+
+ if ( value != null ) {
+ ret[ ret.length ] = value;
+ }
+ }
+
+ // Go through every key on the object,
+ } else {
+ for ( key in elems ) {
+ value = callback( elems[ key ], key, arg );
+
+ if ( value != null ) {
+ ret[ ret.length ] = value;
+ }
+ }
+ }
+
+ // Flatten any nested arrays
+ return ret.concat.apply( [], ret );
+ },
+
+ // A global GUID counter for objects
+ guid: 1,
+
+ // Bind a function to a context, optionally partially applying any
+ // arguments.
+ proxy: function( fn, context ) {
+ if ( typeof context === "string" ) {
+ var tmp = fn[ context ];
+ context = fn;
+ fn = tmp;
+ }
+
+ // Quick check to determine if target is callable, in the spec
+ // this throws a TypeError, but we will just return undefined.
+ if ( !jQuery.isFunction( fn ) ) {
+ return undefined;
+ }
+
+ // Simulated bind
+ var args = slice.call( arguments, 2 ),
+ proxy = function() {
+ return fn.apply( context, args.concat( slice.call( arguments ) ) );
+ };
+
+ // Set the guid of unique handler to the same of original handler, so it can be removed
+ proxy.guid = fn.guid = fn.guid || proxy.guid || jQuery.guid++;
+
+ return proxy;
+ },
+
+ // Mutifunctional method to get and set values to a collection
+ // The value/s can optionally be executed if it's a function
+ access: function( elems, key, value, exec, fn, pass ) {
+ var length = elems.length;
+
+ // Setting many attributes
+ if ( typeof key === "object" ) {
+ for ( var k in key ) {
+ jQuery.access( elems, k, key[k], exec, fn, value );
+ }
+ return elems;
+ }
+
+ // Setting one attribute
+ if ( value !== undefined ) {
+ // Optionally, function values get executed if exec is true
+ exec = !pass && exec && jQuery.isFunction(value);
+
+ for ( var i = 0; i < length; i++ ) {
+ fn( elems[i], key, exec ? value.call( elems[i], i, fn( elems[i], key ) ) : value, pass );
+ }
+
+ return elems;
+ }
+
+ // Getting an attribute
+ return length ? fn( elems[0], key ) : undefined;
+ },
+
+ now: function() {
+ return ( new Date() ).getTime();
+ },
+
+ // Use of jQuery.browser is frowned upon.
+ // More details: http://docs.jquery.com/Utilities/jQuery.browser
+ uaMatch: function( ua ) {
+ ua = ua.toLowerCase();
+
+ var match = rwebkit.exec( ua ) ||
+ ropera.exec( ua ) ||
+ rmsie.exec( ua ) ||
+ ua.indexOf("compatible") < 0 && rmozilla.exec( ua ) ||
+ [];
+
+ return { browser: match[1] || "", version: match[2] || "0" };
+ },
+
+ sub: function() {
+ function jQuerySub( selector, context ) {
+ return new jQuerySub.fn.init( selector, context );
+ }
+ jQuery.extend( true, jQuerySub, this );
+ jQuerySub.superclass = this;
+ jQuerySub.fn = jQuerySub.prototype = this();
+ jQuerySub.fn.constructor = jQuerySub;
+ jQuerySub.sub = this.sub;
+ jQuerySub.fn.init = function init( selector, context ) {
+ if ( context && context instanceof jQuery && !(context instanceof jQuerySub) ) {
+ context = jQuerySub( context );
+ }
+
+ return jQuery.fn.init.call( this, selector, context, rootjQuerySub );
+ };
+ jQuerySub.fn.init.prototype = jQuerySub.fn;
+ var rootjQuerySub = jQuerySub(document);
+ return jQuerySub;
+ },
+
+ browser: {}
+});
+
+// Populate the class2type map
+jQuery.each("Boolean Number String Function Array Date RegExp Object".split(" "), function(i, name) {
+ class2type[ "[object " + name + "]" ] = name.toLowerCase();
+});
+
+browserMatch = jQuery.uaMatch( userAgent );
+if ( browserMatch.browser ) {
+ jQuery.browser[ browserMatch.browser ] = true;
+ jQuery.browser.version = browserMatch.version;
+}
+
+// Deprecated, use jQuery.browser.webkit instead
+if ( jQuery.browser.webkit ) {
+ jQuery.browser.safari = true;
+}
+
+// IE doesn't match non-breaking spaces with \s
+if ( rnotwhite.test( "\xA0" ) ) {
+ trimLeft = /^[\s\xA0]+/;
+ trimRight = /[\s\xA0]+$/;
+}
+
+// All jQuery objects should point back to these
+rootjQuery = jQuery(document);
+
+// Cleanup functions for the document ready method
+if ( document.addEventListener ) {
+ DOMContentLoaded = function() {
+ document.removeEventListener( "DOMContentLoaded", DOMContentLoaded, false );
+ jQuery.ready();
+ };
+
+} else if ( document.attachEvent ) {
+ DOMContentLoaded = function() {
+ // Make sure body exists, at least, in case IE gets a little overzealous (ticket #5443).
+ if ( document.readyState === "complete" ) {
+ document.detachEvent( "onreadystatechange", DOMContentLoaded );
+ jQuery.ready();
+ }
+ };
+}
+
+// The DOM ready check for Internet Explorer
+function doScrollCheck() {
+ if ( jQuery.isReady ) {
+ return;
+ }
+
+ try {
+ // If IE is used, use the trick by Diego Perini
+ // http://javascript.nwbox.com/IEContentLoaded/
+ document.documentElement.doScroll("left");
+ } catch(e) {
+ setTimeout( doScrollCheck, 1 );
+ return;
+ }
+
+ // and execute any waiting functions
+ jQuery.ready();
+}
+
+return jQuery;
+
+})();
+
+
+// String to Object flags format cache
+var flagsCache = {};
+
+// Convert String-formatted flags into Object-formatted ones and store in cache
+function createFlags( flags ) {
+ var object = flagsCache[ flags ] = {},
+ i, length;
+ flags = flags.split( /\s+/ );
+ for ( i = 0, length = flags.length; i < length; i++ ) {
+ object[ flags[i] ] = true;
+ }
+ return object;
+}
+
+/*
+ * Create a callback list using the following parameters:
+ *
+ * flags: an optional list of space-separated flags that will change how
+ * the callback list behaves
+ *
+ * By default a callback list will act like an event callback list and can be
+ * "fired" multiple times.
+ *
+ * Possible flags:
+ *
+ * once: will ensure the callback list can only be fired once (like a Deferred)
+ *
+ * memory: will keep track of previous values and will call any callback added
+ * after the list has been fired right away with the latest "memorized"
+ * values (like a Deferred)
+ *
+ * unique: will ensure a callback can only be added once (no duplicate in the list)
+ *
+ * stopOnFalse: interrupt callings when a callback returns false
+ *
+ */
+jQuery.Callbacks = function( flags ) {
+
+ // Convert flags from String-formatted to Object-formatted
+ // (we check in cache first)
+ flags = flags ? ( flagsCache[ flags ] || createFlags( flags ) ) : {};
+
+ var // Actual callback list
+ list = [],
+ // Stack of fire calls for repeatable lists
+ stack = [],
+ // Last fire value (for non-forgettable lists)
+ memory,
+ // Flag to know if list is currently firing
+ firing,
+ // First callback to fire (used internally by add and fireWith)
+ firingStart,
+ // End of the loop when firing
+ firingLength,
+ // Index of currently firing callback (modified by remove if needed)
+ firingIndex,
+ // Add one or several callbacks to the list
+ add = function( args ) {
+ var i,
+ length,
+ elem,
+ type,
+ actual;
+ for ( i = 0, length = args.length; i < length; i++ ) {
+ elem = args[ i ];
+ type = jQuery.type( elem );
+ if ( type === "array" ) {
+ // Inspect recursively
+ add( elem );
+ } else if ( type === "function" ) {
+ // Add if not in unique mode and callback is not in
+ if ( !flags.unique || !self.has( elem ) ) {
+ list.push( elem );
+ }
+ }
+ }
+ },
+ // Fire callbacks
+ fire = function( context, args ) {
+ args = args || [];
+ memory = !flags.memory || [ context, args ];
+ firing = true;
+ firingIndex = firingStart || 0;
+ firingStart = 0;
+ firingLength = list.length;
+ for ( ; list && firingIndex < firingLength; firingIndex++ ) {
+ if ( list[ firingIndex ].apply( context, args ) === false && flags.stopOnFalse ) {
+ memory = true; // Mark as halted
+ break;
+ }
+ }
+ firing = false;
+ if ( list ) {
+ if ( !flags.once ) {
+ if ( stack && stack.length ) {
+ memory = stack.shift();
+ self.fireWith( memory[ 0 ], memory[ 1 ] );
+ }
+ } else if ( memory === true ) {
+ self.disable();
+ } else {
+ list = [];
+ }
+ }
+ },
+ // Actual Callbacks object
+ self = {
+ // Add a callback or a collection of callbacks to the list
+ add: function() {
+ if ( list ) {
+ var length = list.length;
+ add( arguments );
+ // Do we need to add the callbacks to the
+ // current firing batch?
+ if ( firing ) {
+ firingLength = list.length;
+ // With memory, if we're not firing then
+ // we should call right away, unless previous
+ // firing was halted (stopOnFalse)
+ } else if ( memory && memory !== true ) {
+ firingStart = length;
+ fire( memory[ 0 ], memory[ 1 ] );
+ }
+ }
+ return this;
+ },
+ // Remove a callback from the list
+ remove: function() {
+ if ( list ) {
+ var args = arguments,
+ argIndex = 0,
+ argLength = args.length;
+ for ( ; argIndex < argLength ; argIndex++ ) {
+ for ( var i = 0; i < list.length; i++ ) {
+ if ( args[ argIndex ] === list[ i ] ) {
+ // Handle firingIndex and firingLength
+ if ( firing ) {
+ if ( i <= firingLength ) {
+ firingLength--;
+ if ( i <= firingIndex ) {
+ firingIndex--;
+ }
+ }
+ }
+ // Remove the element
+ list.splice( i--, 1 );
+ // If we have some unicity property then
+ // we only need to do this once
+ if ( flags.unique ) {
+ break;
+ }
+ }
+ }
+ }
+ }
+ return this;
+ },
+ // Control if a given callback is in the list
+ has: function( fn ) {
+ if ( list ) {
+ var i = 0,
+ length = list.length;
+ for ( ; i < length; i++ ) {
+ if ( fn === list[ i ] ) {
+ return true;
+ }
+ }
+ }
+ return false;
+ },
+ // Remove all callbacks from the list
+ empty: function() {
+ list = [];
+ return this;
+ },
+ // Have the list do nothing anymore
+ disable: function() {
+ list = stack = memory = undefined;
+ return this;
+ },
+ // Is it disabled?
+ disabled: function() {
+ return !list;
+ },
+ // Lock the list in its current state
+ lock: function() {
+ stack = undefined;
+ if ( !memory || memory === true ) {
+ self.disable();
+ }
+ return this;
+ },
+ // Is it locked?
+ locked: function() {
+ return !stack;
+ },
+ // Call all callbacks with the given context and arguments
+ fireWith: function( context, args ) {
+ if ( stack ) {
+ if ( firing ) {
+ if ( !flags.once ) {
+ stack.push( [ context, args ] );
+ }
+ } else if ( !( flags.once && memory ) ) {
+ fire( context, args );
+ }
+ }
+ return this;
+ },
+ // Call all the callbacks with the given arguments
+ fire: function() {
+ self.fireWith( this, arguments );
+ return this;
+ },
+ // To know if the callbacks have already been called at least once
+ fired: function() {
+ return !!memory;
+ }
+ };
+
+ return self;
+};
+
+
+
+
+var // Static reference to slice
+ sliceDeferred = [].slice;
+
+jQuery.extend({
+
+ Deferred: function( func ) {
+ var doneList = jQuery.Callbacks( "once memory" ),
+ failList = jQuery.Callbacks( "once memory" ),
+ progressList = jQuery.Callbacks( "memory" ),
+ state = "pending",
+ lists = {
+ resolve: doneList,
+ reject: failList,
+ notify: progressList
+ },
+ promise = {
+ done: doneList.add,
+ fail: failList.add,
+ progress: progressList.add,
+
+ state: function() {
+ return state;
+ },
+
+ // Deprecated
+ isResolved: doneList.fired,
+ isRejected: failList.fired,
+
+ then: function( doneCallbacks, failCallbacks, progressCallbacks ) {
+ deferred.done( doneCallbacks ).fail( failCallbacks ).progress( progressCallbacks );
+ return this;
+ },
+ always: function() {
+ deferred.done.apply( deferred, arguments ).fail.apply( deferred, arguments );
+ return this;
+ },
+ pipe: function( fnDone, fnFail, fnProgress ) {
+ return jQuery.Deferred(function( newDefer ) {
+ jQuery.each( {
+ done: [ fnDone, "resolve" ],
+ fail: [ fnFail, "reject" ],
+ progress: [ fnProgress, "notify" ]
+ }, function( handler, data ) {
+ var fn = data[ 0 ],
+ action = data[ 1 ],
+ returned;
+ if ( jQuery.isFunction( fn ) ) {
+ deferred[ handler ](function() {
+ returned = fn.apply( this, arguments );
+ if ( returned && jQuery.isFunction( returned.promise ) ) {
+ returned.promise().then( newDefer.resolve, newDefer.reject, newDefer.notify );
+ } else {
+ newDefer[ action + "With" ]( this === deferred ? newDefer : this, [ returned ] );
+ }
+ });
+ } else {
+ deferred[ handler ]( newDefer[ action ] );
+ }
+ });
+ }).promise();
+ },
+ // Get a promise for this deferred
+ // If obj is provided, the promise aspect is added to the object
+ promise: function( obj ) {
+ if ( obj == null ) {
+ obj = promise;
+ } else {
+ for ( var key in promise ) {
+ obj[ key ] = promise[ key ];
+ }
+ }
+ return obj;
+ }
+ },
+ deferred = promise.promise({}),
+ key;
+
+ for ( key in lists ) {
+ deferred[ key ] = lists[ key ].fire;
+ deferred[ key + "With" ] = lists[ key ].fireWith;
+ }
+
+ // Handle state
+ deferred.done( function() {
+ state = "resolved";
+ }, failList.disable, progressList.lock ).fail( function() {
+ state = "rejected";
+ }, doneList.disable, progressList.lock );
+
+ // Call given func if any
+ if ( func ) {
+ func.call( deferred, deferred );
+ }
+
+ // All done!
+ return deferred;
+ },
+
+ // Deferred helper
+ when: function( firstParam ) {
+ var args = sliceDeferred.call( arguments, 0 ),
+ i = 0,
+ length = args.length,
+ pValues = new Array( length ),
+ count = length,
+ pCount = length,
+ deferred = length <= 1 && firstParam && jQuery.isFunction( firstParam.promise ) ?
+ firstParam :
+ jQuery.Deferred(),
+ promise = deferred.promise();
+ function resolveFunc( i ) {
+ return function( value ) {
+ args[ i ] = arguments.length > 1 ? sliceDeferred.call( arguments, 0 ) : value;
+ if ( !( --count ) ) {
+ deferred.resolveWith( deferred, args );
+ }
+ };
+ }
+ function progressFunc( i ) {
+ return function( value ) {
+ pValues[ i ] = arguments.length > 1 ? sliceDeferred.call( arguments, 0 ) : value;
+ deferred.notifyWith( promise, pValues );
+ };
+ }
+ if ( length > 1 ) {
+ for ( ; i < length; i++ ) {
+ if ( args[ i ] && args[ i ].promise && jQuery.isFunction( args[ i ].promise ) ) {
+ args[ i ].promise().then( resolveFunc(i), deferred.reject, progressFunc(i) );
+ } else {
+ --count;
+ }
+ }
+ if ( !count ) {
+ deferred.resolveWith( deferred, args );
+ }
+ } else if ( deferred !== firstParam ) {
+ deferred.resolveWith( deferred, length ? [ firstParam ] : [] );
+ }
+ return promise;
+ }
+});
+
+
+
+
+jQuery.support = (function() {
+
+ var support,
+ all,
+ a,
+ select,
+ opt,
+ input,
+ marginDiv,
+ fragment,
+ tds,
+ events,
+ eventName,
+ i,
+ isSupported,
+ div = document.createElement( "div" ),
+ documentElement = document.documentElement;
+
+ // Preliminary tests
+ div.setAttribute("className", "t");
+ div.innerHTML = "
a";
+
+ all = div.getElementsByTagName( "*" );
+ a = div.getElementsByTagName( "a" )[ 0 ];
+
+ // Can't get basic test support
+ if ( !all || !all.length || !a ) {
+ return {};
+ }
+
+ // First batch of supports tests
+ select = document.createElement( "select" );
+ opt = select.appendChild( document.createElement("option") );
+ input = div.getElementsByTagName( "input" )[ 0 ];
+
+ support = {
+ // IE strips leading whitespace when .innerHTML is used
+ leadingWhitespace: ( div.firstChild.nodeType === 3 ),
+
+ // Make sure that tbody elements aren't automatically inserted
+ // IE will insert them into empty tables
+ tbody: !div.getElementsByTagName("tbody").length,
+
+ // Make sure that link elements get serialized correctly by innerHTML
+ // This requires a wrapper element in IE
+ htmlSerialize: !!div.getElementsByTagName("link").length,
+
+ // Get the style information from getAttribute
+ // (IE uses .cssText instead)
+ style: /top/.test( a.getAttribute("style") ),
+
+ // Make sure that URLs aren't manipulated
+ // (IE normalizes it by default)
+ hrefNormalized: ( a.getAttribute("href") === "/a" ),
+
+ // Make sure that element opacity exists
+ // (IE uses filter instead)
+ // Use a regex to work around a WebKit issue. See #5145
+ opacity: /^0.55/.test( a.style.opacity ),
+
+ // Verify style float existence
+ // (IE uses styleFloat instead of cssFloat)
+ cssFloat: !!a.style.cssFloat,
+
+ // Make sure that if no value is specified for a checkbox
+ // that it defaults to "on".
+ // (WebKit defaults to "" instead)
+ checkOn: ( input.value === "on" ),
+
+ // Make sure that a selected-by-default option has a working selected property.
+ // (WebKit defaults to false instead of true, IE too, if it's in an optgroup)
+ optSelected: opt.selected,
+
+ // Test setAttribute on camelCase class. If it works, we need attrFixes when doing get/setAttribute (ie6/7)
+ getSetAttribute: div.className !== "t",
+
+ // Tests for enctype support on a form(#6743)
+ enctype: !!document.createElement("form").enctype,
+
+ // Makes sure cloning an html5 element does not cause problems
+ // Where outerHTML is undefined, this still works
+ html5Clone: document.createElement("nav").cloneNode( true ).outerHTML !== "<:nav>",
+
+ // Will be defined later
+ submitBubbles: true,
+ changeBubbles: true,
+ focusinBubbles: false,
+ deleteExpando: true,
+ noCloneEvent: true,
+ inlineBlockNeedsLayout: false,
+ shrinkWrapBlocks: false,
+ reliableMarginRight: true
+ };
+
+ // Make sure checked status is properly cloned
+ input.checked = true;
+ support.noCloneChecked = input.cloneNode( true ).checked;
+
+ // Make sure that the options inside disabled selects aren't marked as disabled
+ // (WebKit marks them as disabled)
+ select.disabled = true;
+ support.optDisabled = !opt.disabled;
+
+ // Test to see if it's possible to delete an expando from an element
+ // Fails in Internet Explorer
+ try {
+ delete div.test;
+ } catch( e ) {
+ support.deleteExpando = false;
+ }
+
+ if ( !div.addEventListener && div.attachEvent && div.fireEvent ) {
+ div.attachEvent( "onclick", function() {
+ // Cloning a node shouldn't copy over any
+ // bound event handlers (IE does this)
+ support.noCloneEvent = false;
+ });
+ div.cloneNode( true ).fireEvent( "onclick" );
+ }
+
+ // Check if a radio maintains its value
+ // after being appended to the DOM
+ input = document.createElement("input");
+ input.value = "t";
+ input.setAttribute("type", "radio");
+ support.radioValue = input.value === "t";
+
+ input.setAttribute("checked", "checked");
+ div.appendChild( input );
+ fragment = document.createDocumentFragment();
+ fragment.appendChild( div.lastChild );
+
+ // WebKit doesn't clone checked state correctly in fragments
+ support.checkClone = fragment.cloneNode( true ).cloneNode( true ).lastChild.checked;
+
+ // Check if a disconnected checkbox will retain its checked
+ // value of true after appended to the DOM (IE6/7)
+ support.appendChecked = input.checked;
+
+ fragment.removeChild( input );
+ fragment.appendChild( div );
+
+ div.innerHTML = "";
+
+ // Check if div with explicit width and no margin-right incorrectly
+ // gets computed margin-right based on width of container. For more
+ // info see bug #3333
+ // Fails in WebKit before Feb 2011 nightlies
+ // WebKit Bug 13343 - getComputedStyle returns wrong value for margin-right
+ if ( window.getComputedStyle ) {
+ marginDiv = document.createElement( "div" );
+ marginDiv.style.width = "0";
+ marginDiv.style.marginRight = "0";
+ div.style.width = "2px";
+ div.appendChild( marginDiv );
+ support.reliableMarginRight =
+ ( parseInt( ( window.getComputedStyle( marginDiv, null ) || { marginRight: 0 } ).marginRight, 10 ) || 0 ) === 0;
+ }
+
+ // Technique from Juriy Zaytsev
+ // http://perfectionkills.com/detecting-event-support-without-browser-sniffing/
+ // We only care about the case where non-standard event systems
+ // are used, namely in IE. Short-circuiting here helps us to
+ // avoid an eval call (in setAttribute) which can cause CSP
+ // to go haywire. See: https://developer.mozilla.org/en/Security/CSP
+ if ( div.attachEvent ) {
+ for( i in {
+ submit: 1,
+ change: 1,
+ focusin: 1
+ }) {
+ eventName = "on" + i;
+ isSupported = ( eventName in div );
+ if ( !isSupported ) {
+ div.setAttribute( eventName, "return;" );
+ isSupported = ( typeof div[ eventName ] === "function" );
+ }
+ support[ i + "Bubbles" ] = isSupported;
+ }
+ }
+
+ fragment.removeChild( div );
+
+ // Null elements to avoid leaks in IE
+ fragment = select = opt = marginDiv = div = input = null;
+
+ // Run tests that need a body at doc ready
+ jQuery(function() {
+ var container, outer, inner, table, td, offsetSupport,
+ conMarginTop, ptlm, vb, style, html,
+ body = document.getElementsByTagName("body")[0];
+
+ if ( !body ) {
+ // Return for frameset docs that don't have a body
+ return;
+ }
+
+ conMarginTop = 1;
+ ptlm = "position:absolute;top:0;left:0;width:1px;height:1px;margin:0;";
+ vb = "visibility:hidden;border:0;";
+ style = "style='" + ptlm + "border:5px solid #000;padding:0;'";
+ html = "
" +
+ "
" +
+ "
";
+
+ container = document.createElement("div");
+ container.style.cssText = vb + "width:0;height:0;position:static;top:0;margin-top:" + conMarginTop + "px";
+ body.insertBefore( container, body.firstChild );
+
+ // Construct the test element
+ div = document.createElement("div");
+ container.appendChild( div );
+
+ // Check if table cells still have offsetWidth/Height when they are set
+ // to display:none and there are still other visible table cells in a
+ // table row; if so, offsetWidth/Height are not reliable for use when
+ // determining if an element has been hidden directly using
+ // display:none (it is still safe to use offsets if a parent element is
+ // hidden; don safety goggles and see bug #4512 for more information).
+ // (only IE 8 fails this test)
+ div.innerHTML = "
t
";
+ tds = div.getElementsByTagName( "td" );
+ isSupported = ( tds[ 0 ].offsetHeight === 0 );
+
+ tds[ 0 ].style.display = "";
+ tds[ 1 ].style.display = "none";
+
+ // Check if empty table cells still have offsetWidth/Height
+ // (IE <= 8 fail this test)
+ support.reliableHiddenOffsets = isSupported && ( tds[ 0 ].offsetHeight === 0 );
+
+ // Figure out if the W3C box model works as expected
+ div.innerHTML = "";
+ div.style.width = div.style.paddingLeft = "1px";
+ jQuery.boxModel = support.boxModel = div.offsetWidth === 2;
+
+ if ( typeof div.style.zoom !== "undefined" ) {
+ // Check if natively block-level elements act like inline-block
+ // elements when setting their display to 'inline' and giving
+ // them layout
+ // (IE < 8 does this)
+ div.style.display = "inline";
+ div.style.zoom = 1;
+ support.inlineBlockNeedsLayout = ( div.offsetWidth === 2 );
+
+ // Check if elements with layout shrink-wrap their children
+ // (IE 6 does this)
+ div.style.display = "";
+ div.innerHTML = "";
+ support.shrinkWrapBlocks = ( div.offsetWidth !== 2 );
+ }
+
+ div.style.cssText = ptlm + vb;
+ div.innerHTML = html;
+
+ outer = div.firstChild;
+ inner = outer.firstChild;
+ td = outer.nextSibling.firstChild.firstChild;
+
+ offsetSupport = {
+ doesNotAddBorder: ( inner.offsetTop !== 5 ),
+ doesAddBorderForTableAndCells: ( td.offsetTop === 5 )
+ };
+
+ inner.style.position = "fixed";
+ inner.style.top = "20px";
+
+ // safari subtracts parent border width here which is 5px
+ offsetSupport.fixedPosition = ( inner.offsetTop === 20 || inner.offsetTop === 15 );
+ inner.style.position = inner.style.top = "";
+
+ outer.style.overflow = "hidden";
+ outer.style.position = "relative";
+
+ offsetSupport.subtractsBorderForOverflowNotVisible = ( inner.offsetTop === -5 );
+ offsetSupport.doesNotIncludeMarginInBodyOffset = ( body.offsetTop !== conMarginTop );
+
+ body.removeChild( container );
+ div = container = null;
+
+ jQuery.extend( support, offsetSupport );
+ });
+
+ return support;
+})();
+
+
+
+
+var rbrace = /^(?:\{.*\}|\[.*\])$/,
+ rmultiDash = /([A-Z])/g;
+
+jQuery.extend({
+ cache: {},
+
+ // Please use with caution
+ uuid: 0,
+
+ // Unique for each copy of jQuery on the page
+ // Non-digits removed to match rinlinejQuery
+ expando: "jQuery" + ( jQuery.fn.jquery + Math.random() ).replace( /\D/g, "" ),
+
+ // The following elements throw uncatchable exceptions if you
+ // attempt to add expando properties to them.
+ noData: {
+ "embed": true,
+ // Ban all objects except for Flash (which handle expandos)
+ "object": "clsid:D27CDB6E-AE6D-11cf-96B8-444553540000",
+ "applet": true
+ },
+
+ hasData: function( elem ) {
+ elem = elem.nodeType ? jQuery.cache[ elem[jQuery.expando] ] : elem[ jQuery.expando ];
+ return !!elem && !isEmptyDataObject( elem );
+ },
+
+ data: function( elem, name, data, pvt /* Internal Use Only */ ) {
+ if ( !jQuery.acceptData( elem ) ) {
+ return;
+ }
+
+ var privateCache, thisCache, ret,
+ internalKey = jQuery.expando,
+ getByName = typeof name === "string",
+
+ // We have to handle DOM nodes and JS objects differently because IE6-7
+ // can't GC object references properly across the DOM-JS boundary
+ isNode = elem.nodeType,
+
+ // Only DOM nodes need the global jQuery cache; JS object data is
+ // attached directly to the object so GC can occur automatically
+ cache = isNode ? jQuery.cache : elem,
+
+ // Only defining an ID for JS objects if its cache already exists allows
+ // the code to shortcut on the same path as a DOM node with no cache
+ id = isNode ? elem[ internalKey ] : elem[ internalKey ] && internalKey,
+ isEvents = name === "events";
+
+ // Avoid doing any more work than we need to when trying to get data on an
+ // object that has no data at all
+ if ( (!id || !cache[id] || (!isEvents && !pvt && !cache[id].data)) && getByName && data === undefined ) {
+ return;
+ }
+
+ if ( !id ) {
+ // Only DOM nodes need a new unique ID for each element since their data
+ // ends up in the global cache
+ if ( isNode ) {
+ elem[ internalKey ] = id = ++jQuery.uuid;
+ } else {
+ id = internalKey;
+ }
+ }
+
+ if ( !cache[ id ] ) {
+ cache[ id ] = {};
+
+ // Avoids exposing jQuery metadata on plain JS objects when the object
+ // is serialized using JSON.stringify
+ if ( !isNode ) {
+ cache[ id ].toJSON = jQuery.noop;
+ }
+ }
+
+ // An object can be passed to jQuery.data instead of a key/value pair; this gets
+ // shallow copied over onto the existing cache
+ if ( typeof name === "object" || typeof name === "function" ) {
+ if ( pvt ) {
+ cache[ id ] = jQuery.extend( cache[ id ], name );
+ } else {
+ cache[ id ].data = jQuery.extend( cache[ id ].data, name );
+ }
+ }
+
+ privateCache = thisCache = cache[ id ];
+
+ // jQuery data() is stored in a separate object inside the object's internal data
+ // cache in order to avoid key collisions between internal data and user-defined
+ // data.
+ if ( !pvt ) {
+ if ( !thisCache.data ) {
+ thisCache.data = {};
+ }
+
+ thisCache = thisCache.data;
+ }
+
+ if ( data !== undefined ) {
+ thisCache[ jQuery.camelCase( name ) ] = data;
+ }
+
+ // Users should not attempt to inspect the internal events object using jQuery.data,
+ // it is undocumented and subject to change. But does anyone listen? No.
+ if ( isEvents && !thisCache[ name ] ) {
+ return privateCache.events;
+ }
+
+ // Check for both converted-to-camel and non-converted data property names
+ // If a data property was specified
+ if ( getByName ) {
+
+ // First Try to find as-is property data
+ ret = thisCache[ name ];
+
+ // Test for null|undefined property data
+ if ( ret == null ) {
+
+ // Try to find the camelCased property
+ ret = thisCache[ jQuery.camelCase( name ) ];
+ }
+ } else {
+ ret = thisCache;
+ }
+
+ return ret;
+ },
+
+ removeData: function( elem, name, pvt /* Internal Use Only */ ) {
+ if ( !jQuery.acceptData( elem ) ) {
+ return;
+ }
+
+ var thisCache, i, l,
+
+ // Reference to internal data cache key
+ internalKey = jQuery.expando,
+
+ isNode = elem.nodeType,
+
+ // See jQuery.data for more information
+ cache = isNode ? jQuery.cache : elem,
+
+ // See jQuery.data for more information
+ id = isNode ? elem[ internalKey ] : internalKey;
+
+ // If there is already no cache entry for this object, there is no
+ // purpose in continuing
+ if ( !cache[ id ] ) {
+ return;
+ }
+
+ if ( name ) {
+
+ thisCache = pvt ? cache[ id ] : cache[ id ].data;
+
+ if ( thisCache ) {
+
+ // Support array or space separated string names for data keys
+ if ( !jQuery.isArray( name ) ) {
+
+ // try the string as a key before any manipulation
+ if ( name in thisCache ) {
+ name = [ name ];
+ } else {
+
+ // split the camel cased version by spaces unless a key with the spaces exists
+ name = jQuery.camelCase( name );
+ if ( name in thisCache ) {
+ name = [ name ];
+ } else {
+ name = name.split( " " );
+ }
+ }
+ }
+
+ for ( i = 0, l = name.length; i < l; i++ ) {
+ delete thisCache[ name[i] ];
+ }
+
+ // If there is no data left in the cache, we want to continue
+ // and let the cache object itself get destroyed
+ if ( !( pvt ? isEmptyDataObject : jQuery.isEmptyObject )( thisCache ) ) {
+ return;
+ }
+ }
+ }
+
+ // See jQuery.data for more information
+ if ( !pvt ) {
+ delete cache[ id ].data;
+
+ // Don't destroy the parent cache unless the internal data object
+ // had been the only thing left in it
+ if ( !isEmptyDataObject(cache[ id ]) ) {
+ return;
+ }
+ }
+
+ // Browsers that fail expando deletion also refuse to delete expandos on
+ // the window, but it will allow it on all other JS objects; other browsers
+ // don't care
+ // Ensure that `cache` is not a window object #10080
+ if ( jQuery.support.deleteExpando || !cache.setInterval ) {
+ delete cache[ id ];
+ } else {
+ cache[ id ] = null;
+ }
+
+ // We destroyed the cache and need to eliminate the expando on the node to avoid
+ // false lookups in the cache for entries that no longer exist
+ if ( isNode ) {
+ // IE does not allow us to delete expando properties from nodes,
+ // nor does it have a removeAttribute function on Document nodes;
+ // we must handle all of these cases
+ if ( jQuery.support.deleteExpando ) {
+ delete elem[ internalKey ];
+ } else if ( elem.removeAttribute ) {
+ elem.removeAttribute( internalKey );
+ } else {
+ elem[ internalKey ] = null;
+ }
+ }
+ },
+
+ // For internal use only.
+ _data: function( elem, name, data ) {
+ return jQuery.data( elem, name, data, true );
+ },
+
+ // A method for determining if a DOM node can handle the data expando
+ acceptData: function( elem ) {
+ if ( elem.nodeName ) {
+ var match = jQuery.noData[ elem.nodeName.toLowerCase() ];
+
+ if ( match ) {
+ return !(match === true || elem.getAttribute("classid") !== match);
+ }
+ }
+
+ return true;
+ }
+});
+
+jQuery.fn.extend({
+ data: function( key, value ) {
+ var parts, attr, name,
+ data = null;
+
+ if ( typeof key === "undefined" ) {
+ if ( this.length ) {
+ data = jQuery.data( this[0] );
+
+ if ( this[0].nodeType === 1 && !jQuery._data( this[0], "parsedAttrs" ) ) {
+ attr = this[0].attributes;
+ for ( var i = 0, l = attr.length; i < l; i++ ) {
+ name = attr[i].name;
+
+ if ( name.indexOf( "data-" ) === 0 ) {
+ name = jQuery.camelCase( name.substring(5) );
+
+ dataAttr( this[0], name, data[ name ] );
+ }
+ }
+ jQuery._data( this[0], "parsedAttrs", true );
+ }
+ }
+
+ return data;
+
+ } else if ( typeof key === "object" ) {
+ return this.each(function() {
+ jQuery.data( this, key );
+ });
+ }
+
+ parts = key.split(".");
+ parts[1] = parts[1] ? "." + parts[1] : "";
+
+ if ( value === undefined ) {
+ data = this.triggerHandler("getData" + parts[1] + "!", [parts[0]]);
+
+ // Try to fetch any internally stored data first
+ if ( data === undefined && this.length ) {
+ data = jQuery.data( this[0], key );
+ data = dataAttr( this[0], key, data );
+ }
+
+ return data === undefined && parts[1] ?
+ this.data( parts[0] ) :
+ data;
+
+ } else {
+ return this.each(function() {
+ var self = jQuery( this ),
+ args = [ parts[0], value ];
+
+ self.triggerHandler( "setData" + parts[1] + "!", args );
+ jQuery.data( this, key, value );
+ self.triggerHandler( "changeData" + parts[1] + "!", args );
+ });
+ }
+ },
+
+ removeData: function( key ) {
+ return this.each(function() {
+ jQuery.removeData( this, key );
+ });
+ }
+});
+
+function dataAttr( elem, key, data ) {
+ // If nothing was found internally, try to fetch any
+ // data from the HTML5 data-* attribute
+ if ( data === undefined && elem.nodeType === 1 ) {
+
+ var name = "data-" + key.replace( rmultiDash, "-$1" ).toLowerCase();
+
+ data = elem.getAttribute( name );
+
+ if ( typeof data === "string" ) {
+ try {
+ data = data === "true" ? true :
+ data === "false" ? false :
+ data === "null" ? null :
+ jQuery.isNumeric( data ) ? parseFloat( data ) :
+ rbrace.test( data ) ? jQuery.parseJSON( data ) :
+ data;
+ } catch( e ) {}
+
+ // Make sure we set the data so it isn't changed later
+ jQuery.data( elem, key, data );
+
+ } else {
+ data = undefined;
+ }
+ }
+
+ return data;
+}
+
+// checks a cache object for emptiness
+function isEmptyDataObject( obj ) {
+ for ( var name in obj ) {
+
+ // if the public data object is empty, the private is still empty
+ if ( name === "data" && jQuery.isEmptyObject( obj[name] ) ) {
+ continue;
+ }
+ if ( name !== "toJSON" ) {
+ return false;
+ }
+ }
+
+ return true;
+}
+
+
+
+
+function handleQueueMarkDefer( elem, type, src ) {
+ var deferDataKey = type + "defer",
+ queueDataKey = type + "queue",
+ markDataKey = type + "mark",
+ defer = jQuery._data( elem, deferDataKey );
+ if ( defer &&
+ ( src === "queue" || !jQuery._data(elem, queueDataKey) ) &&
+ ( src === "mark" || !jQuery._data(elem, markDataKey) ) ) {
+ // Give room for hard-coded callbacks to fire first
+ // and eventually mark/queue something else on the element
+ setTimeout( function() {
+ if ( !jQuery._data( elem, queueDataKey ) &&
+ !jQuery._data( elem, markDataKey ) ) {
+ jQuery.removeData( elem, deferDataKey, true );
+ defer.fire();
+ }
+ }, 0 );
+ }
+}
+
+jQuery.extend({
+
+ _mark: function( elem, type ) {
+ if ( elem ) {
+ type = ( type || "fx" ) + "mark";
+ jQuery._data( elem, type, (jQuery._data( elem, type ) || 0) + 1 );
+ }
+ },
+
+ _unmark: function( force, elem, type ) {
+ if ( force !== true ) {
+ type = elem;
+ elem = force;
+ force = false;
+ }
+ if ( elem ) {
+ type = type || "fx";
+ var key = type + "mark",
+ count = force ? 0 : ( (jQuery._data( elem, key ) || 1) - 1 );
+ if ( count ) {
+ jQuery._data( elem, key, count );
+ } else {
+ jQuery.removeData( elem, key, true );
+ handleQueueMarkDefer( elem, type, "mark" );
+ }
+ }
+ },
+
+ queue: function( elem, type, data ) {
+ var q;
+ if ( elem ) {
+ type = ( type || "fx" ) + "queue";
+ q = jQuery._data( elem, type );
+
+ // Speed up dequeue by getting out quickly if this is just a lookup
+ if ( data ) {
+ if ( !q || jQuery.isArray(data) ) {
+ q = jQuery._data( elem, type, jQuery.makeArray(data) );
+ } else {
+ q.push( data );
+ }
+ }
+ return q || [];
+ }
+ },
+
+ dequeue: function( elem, type ) {
+ type = type || "fx";
+
+ var queue = jQuery.queue( elem, type ),
+ fn = queue.shift(),
+ hooks = {};
+
+ // If the fx queue is dequeued, always remove the progress sentinel
+ if ( fn === "inprogress" ) {
+ fn = queue.shift();
+ }
+
+ if ( fn ) {
+ // Add a progress sentinel to prevent the fx queue from being
+ // automatically dequeued
+ if ( type === "fx" ) {
+ queue.unshift( "inprogress" );
+ }
+
+ jQuery._data( elem, type + ".run", hooks );
+ fn.call( elem, function() {
+ jQuery.dequeue( elem, type );
+ }, hooks );
+ }
+
+ if ( !queue.length ) {
+ jQuery.removeData( elem, type + "queue " + type + ".run", true );
+ handleQueueMarkDefer( elem, type, "queue" );
+ }
+ }
+});
+
+jQuery.fn.extend({
+ queue: function( type, data ) {
+ if ( typeof type !== "string" ) {
+ data = type;
+ type = "fx";
+ }
+
+ if ( data === undefined ) {
+ return jQuery.queue( this[0], type );
+ }
+ return this.each(function() {
+ var queue = jQuery.queue( this, type, data );
+
+ if ( type === "fx" && queue[0] !== "inprogress" ) {
+ jQuery.dequeue( this, type );
+ }
+ });
+ },
+ dequeue: function( type ) {
+ return this.each(function() {
+ jQuery.dequeue( this, type );
+ });
+ },
+ // Based off of the plugin by Clint Helfers, with permission.
+ // http://blindsignals.com/index.php/2009/07/jquery-delay/
+ delay: function( time, type ) {
+ time = jQuery.fx ? jQuery.fx.speeds[ time ] || time : time;
+ type = type || "fx";
+
+ return this.queue( type, function( next, hooks ) {
+ var timeout = setTimeout( next, time );
+ hooks.stop = function() {
+ clearTimeout( timeout );
+ };
+ });
+ },
+ clearQueue: function( type ) {
+ return this.queue( type || "fx", [] );
+ },
+ // Get a promise resolved when queues of a certain type
+ // are emptied (fx is the type by default)
+ promise: function( type, object ) {
+ if ( typeof type !== "string" ) {
+ object = type;
+ type = undefined;
+ }
+ type = type || "fx";
+ var defer = jQuery.Deferred(),
+ elements = this,
+ i = elements.length,
+ count = 1,
+ deferDataKey = type + "defer",
+ queueDataKey = type + "queue",
+ markDataKey = type + "mark",
+ tmp;
+ function resolve() {
+ if ( !( --count ) ) {
+ defer.resolveWith( elements, [ elements ] );
+ }
+ }
+ while( i-- ) {
+ if (( tmp = jQuery.data( elements[ i ], deferDataKey, undefined, true ) ||
+ ( jQuery.data( elements[ i ], queueDataKey, undefined, true ) ||
+ jQuery.data( elements[ i ], markDataKey, undefined, true ) ) &&
+ jQuery.data( elements[ i ], deferDataKey, jQuery.Callbacks( "once memory" ), true ) )) {
+ count++;
+ tmp.add( resolve );
+ }
+ }
+ resolve();
+ return defer.promise();
+ }
+});
+
+
+
+
+var rclass = /[\n\t\r]/g,
+ rspace = /\s+/,
+ rreturn = /\r/g,
+ rtype = /^(?:button|input)$/i,
+ rfocusable = /^(?:button|input|object|select|textarea)$/i,
+ rclickable = /^a(?:rea)?$/i,
+ rboolean = /^(?:autofocus|autoplay|async|checked|controls|defer|disabled|hidden|loop|multiple|open|readonly|required|scoped|selected)$/i,
+ getSetAttribute = jQuery.support.getSetAttribute,
+ nodeHook, boolHook, fixSpecified;
+
+jQuery.fn.extend({
+ attr: function( name, value ) {
+ return jQuery.access( this, name, value, true, jQuery.attr );
+ },
+
+ removeAttr: function( name ) {
+ return this.each(function() {
+ jQuery.removeAttr( this, name );
+ });
+ },
+
+ prop: function( name, value ) {
+ return jQuery.access( this, name, value, true, jQuery.prop );
+ },
+
+ removeProp: function( name ) {
+ name = jQuery.propFix[ name ] || name;
+ return this.each(function() {
+ // try/catch handles cases where IE balks (such as removing a property on window)
+ try {
+ this[ name ] = undefined;
+ delete this[ name ];
+ } catch( e ) {}
+ });
+ },
+
+ addClass: function( value ) {
+ var classNames, i, l, elem,
+ setClass, c, cl;
+
+ if ( jQuery.isFunction( value ) ) {
+ return this.each(function( j ) {
+ jQuery( this ).addClass( value.call(this, j, this.className) );
+ });
+ }
+
+ if ( value && typeof value === "string" ) {
+ classNames = value.split( rspace );
+
+ for ( i = 0, l = this.length; i < l; i++ ) {
+ elem = this[ i ];
+
+ if ( elem.nodeType === 1 ) {
+ if ( !elem.className && classNames.length === 1 ) {
+ elem.className = value;
+
+ } else {
+ setClass = " " + elem.className + " ";
+
+ for ( c = 0, cl = classNames.length; c < cl; c++ ) {
+ if ( !~setClass.indexOf( " " + classNames[ c ] + " " ) ) {
+ setClass += classNames[ c ] + " ";
+ }
+ }
+ elem.className = jQuery.trim( setClass );
+ }
+ }
+ }
+ }
+
+ return this;
+ },
+
+ removeClass: function( value ) {
+ var classNames, i, l, elem, className, c, cl;
+
+ if ( jQuery.isFunction( value ) ) {
+ return this.each(function( j ) {
+ jQuery( this ).removeClass( value.call(this, j, this.className) );
+ });
+ }
+
+ if ( (value && typeof value === "string") || value === undefined ) {
+ classNames = ( value || "" ).split( rspace );
+
+ for ( i = 0, l = this.length; i < l; i++ ) {
+ elem = this[ i ];
+
+ if ( elem.nodeType === 1 && elem.className ) {
+ if ( value ) {
+ className = (" " + elem.className + " ").replace( rclass, " " );
+ for ( c = 0, cl = classNames.length; c < cl; c++ ) {
+ className = className.replace(" " + classNames[ c ] + " ", " ");
+ }
+ elem.className = jQuery.trim( className );
+
+ } else {
+ elem.className = "";
+ }
+ }
+ }
+ }
+
+ return this;
+ },
+
+ toggleClass: function( value, stateVal ) {
+ var type = typeof value,
+ isBool = typeof stateVal === "boolean";
+
+ if ( jQuery.isFunction( value ) ) {
+ return this.each(function( i ) {
+ jQuery( this ).toggleClass( value.call(this, i, this.className, stateVal), stateVal );
+ });
+ }
+
+ return this.each(function() {
+ if ( type === "string" ) {
+ // toggle individual class names
+ var className,
+ i = 0,
+ self = jQuery( this ),
+ state = stateVal,
+ classNames = value.split( rspace );
+
+ while ( (className = classNames[ i++ ]) ) {
+ // check each className given, space seperated list
+ state = isBool ? state : !self.hasClass( className );
+ self[ state ? "addClass" : "removeClass" ]( className );
+ }
+
+ } else if ( type === "undefined" || type === "boolean" ) {
+ if ( this.className ) {
+ // store className if set
+ jQuery._data( this, "__className__", this.className );
+ }
+
+ // toggle whole className
+ this.className = this.className || value === false ? "" : jQuery._data( this, "__className__" ) || "";
+ }
+ });
+ },
+
+ hasClass: function( selector ) {
+ var className = " " + selector + " ",
+ i = 0,
+ l = this.length;
+ for ( ; i < l; i++ ) {
+ if ( this[i].nodeType === 1 && (" " + this[i].className + " ").replace(rclass, " ").indexOf( className ) > -1 ) {
+ return true;
+ }
+ }
+
+ return false;
+ },
+
+ val: function( value ) {
+ var hooks, ret, isFunction,
+ elem = this[0];
+
+ if ( !arguments.length ) {
+ if ( elem ) {
+ hooks = jQuery.valHooks[ elem.nodeName.toLowerCase() ] || jQuery.valHooks[ elem.type ];
+
+ if ( hooks && "get" in hooks && (ret = hooks.get( elem, "value" )) !== undefined ) {
+ return ret;
+ }
+
+ ret = elem.value;
+
+ return typeof ret === "string" ?
+ // handle most common string cases
+ ret.replace(rreturn, "") :
+ // handle cases where value is null/undef or number
+ ret == null ? "" : ret;
+ }
+
+ return;
+ }
+
+ isFunction = jQuery.isFunction( value );
+
+ return this.each(function( i ) {
+ var self = jQuery(this), val;
+
+ if ( this.nodeType !== 1 ) {
+ return;
+ }
+
+ if ( isFunction ) {
+ val = value.call( this, i, self.val() );
+ } else {
+ val = value;
+ }
+
+ // Treat null/undefined as ""; convert numbers to string
+ if ( val == null ) {
+ val = "";
+ } else if ( typeof val === "number" ) {
+ val += "";
+ } else if ( jQuery.isArray( val ) ) {
+ val = jQuery.map(val, function ( value ) {
+ return value == null ? "" : value + "";
+ });
+ }
+
+ hooks = jQuery.valHooks[ this.nodeName.toLowerCase() ] || jQuery.valHooks[ this.type ];
+
+ // If set returns undefined, fall back to normal setting
+ if ( !hooks || !("set" in hooks) || hooks.set( this, val, "value" ) === undefined ) {
+ this.value = val;
+ }
+ });
+ }
+});
+
+jQuery.extend({
+ valHooks: {
+ option: {
+ get: function( elem ) {
+ // attributes.value is undefined in Blackberry 4.7 but
+ // uses .value. See #6932
+ var val = elem.attributes.value;
+ return !val || val.specified ? elem.value : elem.text;
+ }
+ },
+ select: {
+ get: function( elem ) {
+ var value, i, max, option,
+ index = elem.selectedIndex,
+ values = [],
+ options = elem.options,
+ one = elem.type === "select-one";
+
+ // Nothing was selected
+ if ( index < 0 ) {
+ return null;
+ }
+
+ // Loop through all the selected options
+ i = one ? index : 0;
+ max = one ? index + 1 : options.length;
+ for ( ; i < max; i++ ) {
+ option = options[ i ];
+
+ // Don't return options that are disabled or in a disabled optgroup
+ if ( option.selected && (jQuery.support.optDisabled ? !option.disabled : option.getAttribute("disabled") === null) &&
+ (!option.parentNode.disabled || !jQuery.nodeName( option.parentNode, "optgroup" )) ) {
+
+ // Get the specific value for the option
+ value = jQuery( option ).val();
+
+ // We don't need an array for one selects
+ if ( one ) {
+ return value;
+ }
+
+ // Multi-Selects return an array
+ values.push( value );
+ }
+ }
+
+ // Fixes Bug #2551 -- select.val() broken in IE after form.reset()
+ if ( one && !values.length && options.length ) {
+ return jQuery( options[ index ] ).val();
+ }
+
+ return values;
+ },
+
+ set: function( elem, value ) {
+ var values = jQuery.makeArray( value );
+
+ jQuery(elem).find("option").each(function() {
+ this.selected = jQuery.inArray( jQuery(this).val(), values ) >= 0;
+ });
+
+ if ( !values.length ) {
+ elem.selectedIndex = -1;
+ }
+ return values;
+ }
+ }
+ },
+
+ attrFn: {
+ val: true,
+ css: true,
+ html: true,
+ text: true,
+ data: true,
+ width: true,
+ height: true,
+ offset: true
+ },
+
+ attr: function( elem, name, value, pass ) {
+ var ret, hooks, notxml,
+ nType = elem.nodeType;
+
+ // don't get/set attributes on text, comment and attribute nodes
+ if ( !elem || nType === 3 || nType === 8 || nType === 2 ) {
+ return;
+ }
+
+ if ( pass && name in jQuery.attrFn ) {
+ return jQuery( elem )[ name ]( value );
+ }
+
+ // Fallback to prop when attributes are not supported
+ if ( typeof elem.getAttribute === "undefined" ) {
+ return jQuery.prop( elem, name, value );
+ }
+
+ notxml = nType !== 1 || !jQuery.isXMLDoc( elem );
+
+ // All attributes are lowercase
+ // Grab necessary hook if one is defined
+ if ( notxml ) {
+ name = name.toLowerCase();
+ hooks = jQuery.attrHooks[ name ] || ( rboolean.test( name ) ? boolHook : nodeHook );
+ }
+
+ if ( value !== undefined ) {
+
+ if ( value === null ) {
+ jQuery.removeAttr( elem, name );
+ return;
+
+ } else if ( hooks && "set" in hooks && notxml && (ret = hooks.set( elem, value, name )) !== undefined ) {
+ return ret;
+
+ } else {
+ elem.setAttribute( name, "" + value );
+ return value;
+ }
+
+ } else if ( hooks && "get" in hooks && notxml && (ret = hooks.get( elem, name )) !== null ) {
+ return ret;
+
+ } else {
+
+ ret = elem.getAttribute( name );
+
+ // Non-existent attributes return null, we normalize to undefined
+ return ret === null ?
+ undefined :
+ ret;
+ }
+ },
+
+ removeAttr: function( elem, value ) {
+ var propName, attrNames, name, l,
+ i = 0;
+
+ if ( value && elem.nodeType === 1 ) {
+ attrNames = value.toLowerCase().split( rspace );
+ l = attrNames.length;
+
+ for ( ; i < l; i++ ) {
+ name = attrNames[ i ];
+
+ if ( name ) {
+ propName = jQuery.propFix[ name ] || name;
+
+ // See #9699 for explanation of this approach (setting first, then removal)
+ jQuery.attr( elem, name, "" );
+ elem.removeAttribute( getSetAttribute ? name : propName );
+
+ // Set corresponding property to false for boolean attributes
+ if ( rboolean.test( name ) && propName in elem ) {
+ elem[ propName ] = false;
+ }
+ }
+ }
+ }
+ },
+
+ attrHooks: {
+ type: {
+ set: function( elem, value ) {
+ // We can't allow the type property to be changed (since it causes problems in IE)
+ if ( rtype.test( elem.nodeName ) && elem.parentNode ) {
+ jQuery.error( "type property can't be changed" );
+ } else if ( !jQuery.support.radioValue && value === "radio" && jQuery.nodeName(elem, "input") ) {
+ // Setting the type on a radio button after the value resets the value in IE6-9
+ // Reset value to it's default in case type is set after value
+ // This is for element creation
+ var val = elem.value;
+ elem.setAttribute( "type", value );
+ if ( val ) {
+ elem.value = val;
+ }
+ return value;
+ }
+ }
+ },
+ // Use the value property for back compat
+ // Use the nodeHook for button elements in IE6/7 (#1954)
+ value: {
+ get: function( elem, name ) {
+ if ( nodeHook && jQuery.nodeName( elem, "button" ) ) {
+ return nodeHook.get( elem, name );
+ }
+ return name in elem ?
+ elem.value :
+ null;
+ },
+ set: function( elem, value, name ) {
+ if ( nodeHook && jQuery.nodeName( elem, "button" ) ) {
+ return nodeHook.set( elem, value, name );
+ }
+ // Does not return so that setAttribute is also used
+ elem.value = value;
+ }
+ }
+ },
+
+ propFix: {
+ tabindex: "tabIndex",
+ readonly: "readOnly",
+ "for": "htmlFor",
+ "class": "className",
+ maxlength: "maxLength",
+ cellspacing: "cellSpacing",
+ cellpadding: "cellPadding",
+ rowspan: "rowSpan",
+ colspan: "colSpan",
+ usemap: "useMap",
+ frameborder: "frameBorder",
+ contenteditable: "contentEditable"
+ },
+
+ prop: function( elem, name, value ) {
+ var ret, hooks, notxml,
+ nType = elem.nodeType;
+
+ // don't get/set properties on text, comment and attribute nodes
+ if ( !elem || nType === 3 || nType === 8 || nType === 2 ) {
+ return;
+ }
+
+ notxml = nType !== 1 || !jQuery.isXMLDoc( elem );
+
+ if ( notxml ) {
+ // Fix name and attach hooks
+ name = jQuery.propFix[ name ] || name;
+ hooks = jQuery.propHooks[ name ];
+ }
+
+ if ( value !== undefined ) {
+ if ( hooks && "set" in hooks && (ret = hooks.set( elem, value, name )) !== undefined ) {
+ return ret;
+
+ } else {
+ return ( elem[ name ] = value );
+ }
+
+ } else {
+ if ( hooks && "get" in hooks && (ret = hooks.get( elem, name )) !== null ) {
+ return ret;
+
+ } else {
+ return elem[ name ];
+ }
+ }
+ },
+
+ propHooks: {
+ tabIndex: {
+ get: function( elem ) {
+ // elem.tabIndex doesn't always return the correct value when it hasn't been explicitly set
+ // http://fluidproject.org/blog/2008/01/09/getting-setting-and-removing-tabindex-values-with-javascript/
+ var attributeNode = elem.getAttributeNode("tabindex");
+
+ return attributeNode && attributeNode.specified ?
+ parseInt( attributeNode.value, 10 ) :
+ rfocusable.test( elem.nodeName ) || rclickable.test( elem.nodeName ) && elem.href ?
+ 0 :
+ undefined;
+ }
+ }
+ }
+});
+
+// Add the tabIndex propHook to attrHooks for back-compat (different case is intentional)
+jQuery.attrHooks.tabindex = jQuery.propHooks.tabIndex;
+
+// Hook for boolean attributes
+boolHook = {
+ get: function( elem, name ) {
+ // Align boolean attributes with corresponding properties
+ // Fall back to attribute presence where some booleans are not supported
+ var attrNode,
+ property = jQuery.prop( elem, name );
+ return property === true || typeof property !== "boolean" && ( attrNode = elem.getAttributeNode(name) ) && attrNode.nodeValue !== false ?
+ name.toLowerCase() :
+ undefined;
+ },
+ set: function( elem, value, name ) {
+ var propName;
+ if ( value === false ) {
+ // Remove boolean attributes when set to false
+ jQuery.removeAttr( elem, name );
+ } else {
+ // value is true since we know at this point it's type boolean and not false
+ // Set boolean attributes to the same name and set the DOM property
+ propName = jQuery.propFix[ name ] || name;
+ if ( propName in elem ) {
+ // Only set the IDL specifically if it already exists on the element
+ elem[ propName ] = true;
+ }
+
+ elem.setAttribute( name, name.toLowerCase() );
+ }
+ return name;
+ }
+};
+
+// IE6/7 do not support getting/setting some attributes with get/setAttribute
+if ( !getSetAttribute ) {
+
+ fixSpecified = {
+ name: true,
+ id: true
+ };
+
+ // Use this for any attribute in IE6/7
+ // This fixes almost every IE6/7 issue
+ nodeHook = jQuery.valHooks.button = {
+ get: function( elem, name ) {
+ var ret;
+ ret = elem.getAttributeNode( name );
+ return ret && ( fixSpecified[ name ] ? ret.nodeValue !== "" : ret.specified ) ?
+ ret.nodeValue :
+ undefined;
+ },
+ set: function( elem, value, name ) {
+ // Set the existing or create a new attribute node
+ var ret = elem.getAttributeNode( name );
+ if ( !ret ) {
+ ret = document.createAttribute( name );
+ elem.setAttributeNode( ret );
+ }
+ return ( ret.nodeValue = value + "" );
+ }
+ };
+
+ // Apply the nodeHook to tabindex
+ jQuery.attrHooks.tabindex.set = nodeHook.set;
+
+ // Set width and height to auto instead of 0 on empty string( Bug #8150 )
+ // This is for removals
+ jQuery.each([ "width", "height" ], function( i, name ) {
+ jQuery.attrHooks[ name ] = jQuery.extend( jQuery.attrHooks[ name ], {
+ set: function( elem, value ) {
+ if ( value === "" ) {
+ elem.setAttribute( name, "auto" );
+ return value;
+ }
+ }
+ });
+ });
+
+ // Set contenteditable to false on removals(#10429)
+ // Setting to empty string throws an error as an invalid value
+ jQuery.attrHooks.contenteditable = {
+ get: nodeHook.get,
+ set: function( elem, value, name ) {
+ if ( value === "" ) {
+ value = "false";
+ }
+ nodeHook.set( elem, value, name );
+ }
+ };
+}
+
+
+// Some attributes require a special call on IE
+if ( !jQuery.support.hrefNormalized ) {
+ jQuery.each([ "href", "src", "width", "height" ], function( i, name ) {
+ jQuery.attrHooks[ name ] = jQuery.extend( jQuery.attrHooks[ name ], {
+ get: function( elem ) {
+ var ret = elem.getAttribute( name, 2 );
+ return ret === null ? undefined : ret;
+ }
+ });
+ });
+}
+
+if ( !jQuery.support.style ) {
+ jQuery.attrHooks.style = {
+ get: function( elem ) {
+ // Return undefined in the case of empty string
+ // Normalize to lowercase since IE uppercases css property names
+ return elem.style.cssText.toLowerCase() || undefined;
+ },
+ set: function( elem, value ) {
+ return ( elem.style.cssText = "" + value );
+ }
+ };
+}
+
+// Safari mis-reports the default selected property of an option
+// Accessing the parent's selectedIndex property fixes it
+if ( !jQuery.support.optSelected ) {
+ jQuery.propHooks.selected = jQuery.extend( jQuery.propHooks.selected, {
+ get: function( elem ) {
+ var parent = elem.parentNode;
+
+ if ( parent ) {
+ parent.selectedIndex;
+
+ // Make sure that it also works with optgroups, see #5701
+ if ( parent.parentNode ) {
+ parent.parentNode.selectedIndex;
+ }
+ }
+ return null;
+ }
+ });
+}
+
+// IE6/7 call enctype encoding
+if ( !jQuery.support.enctype ) {
+ jQuery.propFix.enctype = "encoding";
+}
+
+// Radios and checkboxes getter/setter
+if ( !jQuery.support.checkOn ) {
+ jQuery.each([ "radio", "checkbox" ], function() {
+ jQuery.valHooks[ this ] = {
+ get: function( elem ) {
+ // Handle the case where in Webkit "" is returned instead of "on" if a value isn't specified
+ return elem.getAttribute("value") === null ? "on" : elem.value;
+ }
+ };
+ });
+}
+jQuery.each([ "radio", "checkbox" ], function() {
+ jQuery.valHooks[ this ] = jQuery.extend( jQuery.valHooks[ this ], {
+ set: function( elem, value ) {
+ if ( jQuery.isArray( value ) ) {
+ return ( elem.checked = jQuery.inArray( jQuery(elem).val(), value ) >= 0 );
+ }
+ }
+ });
+});
+
+
+
+
+var rformElems = /^(?:textarea|input|select)$/i,
+ rtypenamespace = /^([^\.]*)?(?:\.(.+))?$/,
+ rhoverHack = /\bhover(\.\S+)?\b/,
+ rkeyEvent = /^key/,
+ rmouseEvent = /^(?:mouse|contextmenu)|click/,
+ rfocusMorph = /^(?:focusinfocus|focusoutblur)$/,
+ rquickIs = /^(\w*)(?:#([\w\-]+))?(?:\.([\w\-]+))?$/,
+ quickParse = function( selector ) {
+ var quick = rquickIs.exec( selector );
+ if ( quick ) {
+ // 0 1 2 3
+ // [ _, tag, id, class ]
+ quick[1] = ( quick[1] || "" ).toLowerCase();
+ quick[3] = quick[3] && new RegExp( "(?:^|\\s)" + quick[3] + "(?:\\s|$)" );
+ }
+ return quick;
+ },
+ quickIs = function( elem, m ) {
+ var attrs = elem.attributes || {};
+ return (
+ (!m[1] || elem.nodeName.toLowerCase() === m[1]) &&
+ (!m[2] || (attrs.id || {}).value === m[2]) &&
+ (!m[3] || m[3].test( (attrs[ "class" ] || {}).value ))
+ );
+ },
+ hoverHack = function( events ) {
+ return jQuery.event.special.hover ? events : events.replace( rhoverHack, "mouseenter$1 mouseleave$1" );
+ };
+
+/*
+ * Helper functions for managing events -- not part of the public interface.
+ * Props to Dean Edwards' addEvent library for many of the ideas.
+ */
+jQuery.event = {
+
+ add: function( elem, types, handler, data, selector ) {
+
+ var elemData, eventHandle, events,
+ t, tns, type, namespaces, handleObj,
+ handleObjIn, quick, handlers, special;
+
+ // Don't attach events to noData or text/comment nodes (allow plain objects tho)
+ if ( elem.nodeType === 3 || elem.nodeType === 8 || !types || !handler || !(elemData = jQuery._data( elem )) ) {
+ return;
+ }
+
+ // Caller can pass in an object of custom data in lieu of the handler
+ if ( handler.handler ) {
+ handleObjIn = handler;
+ handler = handleObjIn.handler;
+ }
+
+ // Make sure that the handler has a unique ID, used to find/remove it later
+ if ( !handler.guid ) {
+ handler.guid = jQuery.guid++;
+ }
+
+ // Init the element's event structure and main handler, if this is the first
+ events = elemData.events;
+ if ( !events ) {
+ elemData.events = events = {};
+ }
+ eventHandle = elemData.handle;
+ if ( !eventHandle ) {
+ elemData.handle = eventHandle = function( e ) {
+ // Discard the second event of a jQuery.event.trigger() and
+ // when an event is called after a page has unloaded
+ return typeof jQuery !== "undefined" && (!e || jQuery.event.triggered !== e.type) ?
+ jQuery.event.dispatch.apply( eventHandle.elem, arguments ) :
+ undefined;
+ };
+ // Add elem as a property of the handle fn to prevent a memory leak with IE non-native events
+ eventHandle.elem = elem;
+ }
+
+ // Handle multiple events separated by a space
+ // jQuery(...).bind("mouseover mouseout", fn);
+ types = jQuery.trim( hoverHack(types) ).split( " " );
+ for ( t = 0; t < types.length; t++ ) {
+
+ tns = rtypenamespace.exec( types[t] ) || [];
+ type = tns[1];
+ namespaces = ( tns[2] || "" ).split( "." ).sort();
+
+ // If event changes its type, use the special event handlers for the changed type
+ special = jQuery.event.special[ type ] || {};
+
+ // If selector defined, determine special event api type, otherwise given type
+ type = ( selector ? special.delegateType : special.bindType ) || type;
+
+ // Update special based on newly reset type
+ special = jQuery.event.special[ type ] || {};
+
+ // handleObj is passed to all event handlers
+ handleObj = jQuery.extend({
+ type: type,
+ origType: tns[1],
+ data: data,
+ handler: handler,
+ guid: handler.guid,
+ selector: selector,
+ quick: quickParse( selector ),
+ namespace: namespaces.join(".")
+ }, handleObjIn );
+
+ // Init the event handler queue if we're the first
+ handlers = events[ type ];
+ if ( !handlers ) {
+ handlers = events[ type ] = [];
+ handlers.delegateCount = 0;
+
+ // Only use addEventListener/attachEvent if the special events handler returns false
+ if ( !special.setup || special.setup.call( elem, data, namespaces, eventHandle ) === false ) {
+ // Bind the global event handler to the element
+ if ( elem.addEventListener ) {
+ elem.addEventListener( type, eventHandle, false );
+
+ } else if ( elem.attachEvent ) {
+ elem.attachEvent( "on" + type, eventHandle );
+ }
+ }
+ }
+
+ if ( special.add ) {
+ special.add.call( elem, handleObj );
+
+ if ( !handleObj.handler.guid ) {
+ handleObj.handler.guid = handler.guid;
+ }
+ }
+
+ // Add to the element's handler list, delegates in front
+ if ( selector ) {
+ handlers.splice( handlers.delegateCount++, 0, handleObj );
+ } else {
+ handlers.push( handleObj );
+ }
+
+ // Keep track of which events have ever been used, for event optimization
+ jQuery.event.global[ type ] = true;
+ }
+
+ // Nullify elem to prevent memory leaks in IE
+ elem = null;
+ },
+
+ global: {},
+
+ // Detach an event or set of events from an element
+ remove: function( elem, types, handler, selector, mappedTypes ) {
+
+ var elemData = jQuery.hasData( elem ) && jQuery._data( elem ),
+ t, tns, type, origType, namespaces, origCount,
+ j, events, special, handle, eventType, handleObj;
+
+ if ( !elemData || !(events = elemData.events) ) {
+ return;
+ }
+
+ // Once for each type.namespace in types; type may be omitted
+ types = jQuery.trim( hoverHack( types || "" ) ).split(" ");
+ for ( t = 0; t < types.length; t++ ) {
+ tns = rtypenamespace.exec( types[t] ) || [];
+ type = origType = tns[1];
+ namespaces = tns[2];
+
+ // Unbind all events (on this namespace, if provided) for the element
+ if ( !type ) {
+ for ( type in events ) {
+ jQuery.event.remove( elem, type + types[ t ], handler, selector, true );
+ }
+ continue;
+ }
+
+ special = jQuery.event.special[ type ] || {};
+ type = ( selector? special.delegateType : special.bindType ) || type;
+ eventType = events[ type ] || [];
+ origCount = eventType.length;
+ namespaces = namespaces ? new RegExp("(^|\\.)" + namespaces.split(".").sort().join("\\.(?:.*\\.)?") + "(\\.|$)") : null;
+
+ // Remove matching events
+ for ( j = 0; j < eventType.length; j++ ) {
+ handleObj = eventType[ j ];
+
+ if ( ( mappedTypes || origType === handleObj.origType ) &&
+ ( !handler || handler.guid === handleObj.guid ) &&
+ ( !namespaces || namespaces.test( handleObj.namespace ) ) &&
+ ( !selector || selector === handleObj.selector || selector === "**" && handleObj.selector ) ) {
+ eventType.splice( j--, 1 );
+
+ if ( handleObj.selector ) {
+ eventType.delegateCount--;
+ }
+ if ( special.remove ) {
+ special.remove.call( elem, handleObj );
+ }
+ }
+ }
+
+ // Remove generic event handler if we removed something and no more handlers exist
+ // (avoids potential for endless recursion during removal of special event handlers)
+ if ( eventType.length === 0 && origCount !== eventType.length ) {
+ if ( !special.teardown || special.teardown.call( elem, namespaces ) === false ) {
+ jQuery.removeEvent( elem, type, elemData.handle );
+ }
+
+ delete events[ type ];
+ }
+ }
+
+ // Remove the expando if it's no longer used
+ if ( jQuery.isEmptyObject( events ) ) {
+ handle = elemData.handle;
+ if ( handle ) {
+ handle.elem = null;
+ }
+
+ // removeData also checks for emptiness and clears the expando if empty
+ // so use it instead of delete
+ jQuery.removeData( elem, [ "events", "handle" ], true );
+ }
+ },
+
+ // Events that are safe to short-circuit if no handlers are attached.
+ // Native DOM events should not be added, they may have inline handlers.
+ customEvent: {
+ "getData": true,
+ "setData": true,
+ "changeData": true
+ },
+
+ trigger: function( event, data, elem, onlyHandlers ) {
+ // Don't do events on text and comment nodes
+ if ( elem && (elem.nodeType === 3 || elem.nodeType === 8) ) {
+ return;
+ }
+
+ // Event object or event type
+ var type = event.type || event,
+ namespaces = [],
+ cache, exclusive, i, cur, old, ontype, special, handle, eventPath, bubbleType;
+
+ // focus/blur morphs to focusin/out; ensure we're not firing them right now
+ if ( rfocusMorph.test( type + jQuery.event.triggered ) ) {
+ return;
+ }
+
+ if ( type.indexOf( "!" ) >= 0 ) {
+ // Exclusive events trigger only for the exact event (no namespaces)
+ type = type.slice(0, -1);
+ exclusive = true;
+ }
+
+ if ( type.indexOf( "." ) >= 0 ) {
+ // Namespaced trigger; create a regexp to match event type in handle()
+ namespaces = type.split(".");
+ type = namespaces.shift();
+ namespaces.sort();
+ }
+
+ if ( (!elem || jQuery.event.customEvent[ type ]) && !jQuery.event.global[ type ] ) {
+ // No jQuery handlers for this event type, and it can't have inline handlers
+ return;
+ }
+
+ // Caller can pass in an Event, Object, or just an event type string
+ event = typeof event === "object" ?
+ // jQuery.Event object
+ event[ jQuery.expando ] ? event :
+ // Object literal
+ new jQuery.Event( type, event ) :
+ // Just the event type (string)
+ new jQuery.Event( type );
+
+ event.type = type;
+ event.isTrigger = true;
+ event.exclusive = exclusive;
+ event.namespace = namespaces.join( "." );
+ event.namespace_re = event.namespace? new RegExp("(^|\\.)" + namespaces.join("\\.(?:.*\\.)?") + "(\\.|$)") : null;
+ ontype = type.indexOf( ":" ) < 0 ? "on" + type : "";
+
+ // Handle a global trigger
+ if ( !elem ) {
+
+ // TODO: Stop taunting the data cache; remove global events and always attach to document
+ cache = jQuery.cache;
+ for ( i in cache ) {
+ if ( cache[ i ].events && cache[ i ].events[ type ] ) {
+ jQuery.event.trigger( event, data, cache[ i ].handle.elem, true );
+ }
+ }
+ return;
+ }
+
+ // Clean up the event in case it is being reused
+ event.result = undefined;
+ if ( !event.target ) {
+ event.target = elem;
+ }
+
+ // Clone any incoming data and prepend the event, creating the handler arg list
+ data = data != null ? jQuery.makeArray( data ) : [];
+ data.unshift( event );
+
+ // Allow special events to draw outside the lines
+ special = jQuery.event.special[ type ] || {};
+ if ( special.trigger && special.trigger.apply( elem, data ) === false ) {
+ return;
+ }
+
+ // Determine event propagation path in advance, per W3C events spec (#9951)
+ // Bubble up to document, then to window; watch for a global ownerDocument var (#9724)
+ eventPath = [[ elem, special.bindType || type ]];
+ if ( !onlyHandlers && !special.noBubble && !jQuery.isWindow( elem ) ) {
+
+ bubbleType = special.delegateType || type;
+ cur = rfocusMorph.test( bubbleType + type ) ? elem : elem.parentNode;
+ old = null;
+ for ( ; cur; cur = cur.parentNode ) {
+ eventPath.push([ cur, bubbleType ]);
+ old = cur;
+ }
+
+ // Only add window if we got to document (e.g., not plain obj or detached DOM)
+ if ( old && old === elem.ownerDocument ) {
+ eventPath.push([ old.defaultView || old.parentWindow || window, bubbleType ]);
+ }
+ }
+
+ // Fire handlers on the event path
+ for ( i = 0; i < eventPath.length && !event.isPropagationStopped(); i++ ) {
+
+ cur = eventPath[i][0];
+ event.type = eventPath[i][1];
+
+ handle = ( jQuery._data( cur, "events" ) || {} )[ event.type ] && jQuery._data( cur, "handle" );
+ if ( handle ) {
+ handle.apply( cur, data );
+ }
+ // Note that this is a bare JS function and not a jQuery handler
+ handle = ontype && cur[ ontype ];
+ if ( handle && jQuery.acceptData( cur ) && handle.apply( cur, data ) === false ) {
+ event.preventDefault();
+ }
+ }
+ event.type = type;
+
+ // If nobody prevented the default action, do it now
+ if ( !onlyHandlers && !event.isDefaultPrevented() ) {
+
+ if ( (!special._default || special._default.apply( elem.ownerDocument, data ) === false) &&
+ !(type === "click" && jQuery.nodeName( elem, "a" )) && jQuery.acceptData( elem ) ) {
+
+ // Call a native DOM method on the target with the same name name as the event.
+ // Can't use an .isFunction() check here because IE6/7 fails that test.
+ // Don't do default actions on window, that's where global variables be (#6170)
+ // IE<9 dies on focus/blur to hidden element (#1486)
+ if ( ontype && elem[ type ] && ((type !== "focus" && type !== "blur") || event.target.offsetWidth !== 0) && !jQuery.isWindow( elem ) ) {
+
+ // Don't re-trigger an onFOO event when we call its FOO() method
+ old = elem[ ontype ];
+
+ if ( old ) {
+ elem[ ontype ] = null;
+ }
+
+ // Prevent re-triggering of the same event, since we already bubbled it above
+ jQuery.event.triggered = type;
+ elem[ type ]();
+ jQuery.event.triggered = undefined;
+
+ if ( old ) {
+ elem[ ontype ] = old;
+ }
+ }
+ }
+ }
+
+ return event.result;
+ },
+
+ dispatch: function( event ) {
+
+ // Make a writable jQuery.Event from the native event object
+ event = jQuery.event.fix( event || window.event );
+
+ var handlers = ( (jQuery._data( this, "events" ) || {} )[ event.type ] || []),
+ delegateCount = handlers.delegateCount,
+ args = [].slice.call( arguments, 0 ),
+ run_all = !event.exclusive && !event.namespace,
+ handlerQueue = [],
+ i, j, cur, jqcur, ret, selMatch, matched, matches, handleObj, sel, related;
+
+ // Use the fix-ed jQuery.Event rather than the (read-only) native event
+ args[0] = event;
+ event.delegateTarget = this;
+
+ // Determine handlers that should run if there are delegated events
+ // Avoid disabled elements in IE (#6911) and non-left-click bubbling in Firefox (#3861)
+ if ( delegateCount && !event.target.disabled && !(event.button && event.type === "click") ) {
+
+ // Pregenerate a single jQuery object for reuse with .is()
+ jqcur = jQuery(this);
+ jqcur.context = this.ownerDocument || this;
+
+ for ( cur = event.target; cur != this; cur = cur.parentNode || this ) {
+ selMatch = {};
+ matches = [];
+ jqcur[0] = cur;
+ for ( i = 0; i < delegateCount; i++ ) {
+ handleObj = handlers[ i ];
+ sel = handleObj.selector;
+
+ if ( selMatch[ sel ] === undefined ) {
+ selMatch[ sel ] = (
+ handleObj.quick ? quickIs( cur, handleObj.quick ) : jqcur.is( sel )
+ );
+ }
+ if ( selMatch[ sel ] ) {
+ matches.push( handleObj );
+ }
+ }
+ if ( matches.length ) {
+ handlerQueue.push({ elem: cur, matches: matches });
+ }
+ }
+ }
+
+ // Add the remaining (directly-bound) handlers
+ if ( handlers.length > delegateCount ) {
+ handlerQueue.push({ elem: this, matches: handlers.slice( delegateCount ) });
+ }
+
+ // Run delegates first; they may want to stop propagation beneath us
+ for ( i = 0; i < handlerQueue.length && !event.isPropagationStopped(); i++ ) {
+ matched = handlerQueue[ i ];
+ event.currentTarget = matched.elem;
+
+ for ( j = 0; j < matched.matches.length && !event.isImmediatePropagationStopped(); j++ ) {
+ handleObj = matched.matches[ j ];
+
+ // Triggered event must either 1) be non-exclusive and have no namespace, or
+ // 2) have namespace(s) a subset or equal to those in the bound event (both can have no namespace).
+ if ( run_all || (!event.namespace && !handleObj.namespace) || event.namespace_re && event.namespace_re.test( handleObj.namespace ) ) {
+
+ event.data = handleObj.data;
+ event.handleObj = handleObj;
+
+ ret = ( (jQuery.event.special[ handleObj.origType ] || {}).handle || handleObj.handler )
+ .apply( matched.elem, args );
+
+ if ( ret !== undefined ) {
+ event.result = ret;
+ if ( ret === false ) {
+ event.preventDefault();
+ event.stopPropagation();
+ }
+ }
+ }
+ }
+ }
+
+ return event.result;
+ },
+
+ // Includes some event props shared by KeyEvent and MouseEvent
+ // *** attrChange attrName relatedNode srcElement are not normalized, non-W3C, deprecated, will be removed in 1.8 ***
+ props: "attrChange attrName relatedNode srcElement altKey bubbles cancelable ctrlKey currentTarget eventPhase metaKey relatedTarget shiftKey target timeStamp view which".split(" "),
+
+ fixHooks: {},
+
+ keyHooks: {
+ props: "char charCode key keyCode".split(" "),
+ filter: function( event, original ) {
+
+ // Add which for key events
+ if ( event.which == null ) {
+ event.which = original.charCode != null ? original.charCode : original.keyCode;
+ }
+
+ return event;
+ }
+ },
+
+ mouseHooks: {
+ props: "button buttons clientX clientY fromElement offsetX offsetY pageX pageY screenX screenY toElement".split(" "),
+ filter: function( event, original ) {
+ var eventDoc, doc, body,
+ button = original.button,
+ fromElement = original.fromElement;
+
+ // Calculate pageX/Y if missing and clientX/Y available
+ if ( event.pageX == null && original.clientX != null ) {
+ eventDoc = event.target.ownerDocument || document;
+ doc = eventDoc.documentElement;
+ body = eventDoc.body;
+
+ event.pageX = original.clientX + ( doc && doc.scrollLeft || body && body.scrollLeft || 0 ) - ( doc && doc.clientLeft || body && body.clientLeft || 0 );
+ event.pageY = original.clientY + ( doc && doc.scrollTop || body && body.scrollTop || 0 ) - ( doc && doc.clientTop || body && body.clientTop || 0 );
+ }
+
+ // Add relatedTarget, if necessary
+ if ( !event.relatedTarget && fromElement ) {
+ event.relatedTarget = fromElement === event.target ? original.toElement : fromElement;
+ }
+
+ // Add which for click: 1 === left; 2 === middle; 3 === right
+ // Note: button is not normalized, so don't use it
+ if ( !event.which && button !== undefined ) {
+ event.which = ( button & 1 ? 1 : ( button & 2 ? 3 : ( button & 4 ? 2 : 0 ) ) );
+ }
+
+ return event;
+ }
+ },
+
+ fix: function( event ) {
+ if ( event[ jQuery.expando ] ) {
+ return event;
+ }
+
+ // Create a writable copy of the event object and normalize some properties
+ var i, prop,
+ originalEvent = event,
+ fixHook = jQuery.event.fixHooks[ event.type ] || {},
+ copy = fixHook.props ? this.props.concat( fixHook.props ) : this.props;
+
+ event = jQuery.Event( originalEvent );
+
+ for ( i = copy.length; i; ) {
+ prop = copy[ --i ];
+ event[ prop ] = originalEvent[ prop ];
+ }
+
+ // Fix target property, if necessary (#1925, IE 6/7/8 & Safari2)
+ if ( !event.target ) {
+ event.target = originalEvent.srcElement || document;
+ }
+
+ // Target should not be a text node (#504, Safari)
+ if ( event.target.nodeType === 3 ) {
+ event.target = event.target.parentNode;
+ }
+
+ // For mouse/key events; add metaKey if it's not there (#3368, IE6/7/8)
+ if ( event.metaKey === undefined ) {
+ event.metaKey = event.ctrlKey;
+ }
+
+ return fixHook.filter? fixHook.filter( event, originalEvent ) : event;
+ },
+
+ special: {
+ ready: {
+ // Make sure the ready event is setup
+ setup: jQuery.bindReady
+ },
+
+ load: {
+ // Prevent triggered image.load events from bubbling to window.load
+ noBubble: true
+ },
+
+ focus: {
+ delegateType: "focusin"
+ },
+ blur: {
+ delegateType: "focusout"
+ },
+
+ beforeunload: {
+ setup: function( data, namespaces, eventHandle ) {
+ // We only want to do this special case on windows
+ if ( jQuery.isWindow( this ) ) {
+ this.onbeforeunload = eventHandle;
+ }
+ },
+
+ teardown: function( namespaces, eventHandle ) {
+ if ( this.onbeforeunload === eventHandle ) {
+ this.onbeforeunload = null;
+ }
+ }
+ }
+ },
+
+ simulate: function( type, elem, event, bubble ) {
+ // Piggyback on a donor event to simulate a different one.
+ // Fake originalEvent to avoid donor's stopPropagation, but if the
+ // simulated event prevents default then we do the same on the donor.
+ var e = jQuery.extend(
+ new jQuery.Event(),
+ event,
+ { type: type,
+ isSimulated: true,
+ originalEvent: {}
+ }
+ );
+ if ( bubble ) {
+ jQuery.event.trigger( e, null, elem );
+ } else {
+ jQuery.event.dispatch.call( elem, e );
+ }
+ if ( e.isDefaultPrevented() ) {
+ event.preventDefault();
+ }
+ }
+};
+
+// Some plugins are using, but it's undocumented/deprecated and will be removed.
+// The 1.7 special event interface should provide all the hooks needed now.
+jQuery.event.handle = jQuery.event.dispatch;
+
+jQuery.removeEvent = document.removeEventListener ?
+ function( elem, type, handle ) {
+ if ( elem.removeEventListener ) {
+ elem.removeEventListener( type, handle, false );
+ }
+ } :
+ function( elem, type, handle ) {
+ if ( elem.detachEvent ) {
+ elem.detachEvent( "on" + type, handle );
+ }
+ };
+
+jQuery.Event = function( src, props ) {
+ // Allow instantiation without the 'new' keyword
+ if ( !(this instanceof jQuery.Event) ) {
+ return new jQuery.Event( src, props );
+ }
+
+ // Event object
+ if ( src && src.type ) {
+ this.originalEvent = src;
+ this.type = src.type;
+
+ // Events bubbling up the document may have been marked as prevented
+ // by a handler lower down the tree; reflect the correct value.
+ this.isDefaultPrevented = ( src.defaultPrevented || src.returnValue === false ||
+ src.getPreventDefault && src.getPreventDefault() ) ? returnTrue : returnFalse;
+
+ // Event type
+ } else {
+ this.type = src;
+ }
+
+ // Put explicitly provided properties onto the event object
+ if ( props ) {
+ jQuery.extend( this, props );
+ }
+
+ // Create a timestamp if incoming event doesn't have one
+ this.timeStamp = src && src.timeStamp || jQuery.now();
+
+ // Mark it as fixed
+ this[ jQuery.expando ] = true;
+};
+
+function returnFalse() {
+ return false;
+}
+function returnTrue() {
+ return true;
+}
+
+// jQuery.Event is based on DOM3 Events as specified by the ECMAScript Language Binding
+// http://www.w3.org/TR/2003/WD-DOM-Level-3-Events-20030331/ecma-script-binding.html
+jQuery.Event.prototype = {
+ preventDefault: function() {
+ this.isDefaultPrevented = returnTrue;
+
+ var e = this.originalEvent;
+ if ( !e ) {
+ return;
+ }
+
+ // if preventDefault exists run it on the original event
+ if ( e.preventDefault ) {
+ e.preventDefault();
+
+ // otherwise set the returnValue property of the original event to false (IE)
+ } else {
+ e.returnValue = false;
+ }
+ },
+ stopPropagation: function() {
+ this.isPropagationStopped = returnTrue;
+
+ var e = this.originalEvent;
+ if ( !e ) {
+ return;
+ }
+ // if stopPropagation exists run it on the original event
+ if ( e.stopPropagation ) {
+ e.stopPropagation();
+ }
+ // otherwise set the cancelBubble property of the original event to true (IE)
+ e.cancelBubble = true;
+ },
+ stopImmediatePropagation: function() {
+ this.isImmediatePropagationStopped = returnTrue;
+ this.stopPropagation();
+ },
+ isDefaultPrevented: returnFalse,
+ isPropagationStopped: returnFalse,
+ isImmediatePropagationStopped: returnFalse
+};
+
+// Create mouseenter/leave events using mouseover/out and event-time checks
+jQuery.each({
+ mouseenter: "mouseover",
+ mouseleave: "mouseout"
+}, function( orig, fix ) {
+ jQuery.event.special[ orig ] = {
+ delegateType: fix,
+ bindType: fix,
+
+ handle: function( event ) {
+ var target = this,
+ related = event.relatedTarget,
+ handleObj = event.handleObj,
+ selector = handleObj.selector,
+ ret;
+
+ // For mousenter/leave call the handler if related is outside the target.
+ // NB: No relatedTarget if the mouse left/entered the browser window
+ if ( !related || (related !== target && !jQuery.contains( target, related )) ) {
+ event.type = handleObj.origType;
+ ret = handleObj.handler.apply( this, arguments );
+ event.type = fix;
+ }
+ return ret;
+ }
+ };
+});
+
+// IE submit delegation
+if ( !jQuery.support.submitBubbles ) {
+
+ jQuery.event.special.submit = {
+ setup: function() {
+ // Only need this for delegated form submit events
+ if ( jQuery.nodeName( this, "form" ) ) {
+ return false;
+ }
+
+ // Lazy-add a submit handler when a descendant form may potentially be submitted
+ jQuery.event.add( this, "click._submit keypress._submit", function( e ) {
+ // Node name check avoids a VML-related crash in IE (#9807)
+ var elem = e.target,
+ form = jQuery.nodeName( elem, "input" ) || jQuery.nodeName( elem, "button" ) ? elem.form : undefined;
+ if ( form && !form._submit_attached ) {
+ jQuery.event.add( form, "submit._submit", function( event ) {
+ // If form was submitted by the user, bubble the event up the tree
+ if ( this.parentNode && !event.isTrigger ) {
+ jQuery.event.simulate( "submit", this.parentNode, event, true );
+ }
+ });
+ form._submit_attached = true;
+ }
+ });
+ // return undefined since we don't need an event listener
+ },
+
+ teardown: function() {
+ // Only need this for delegated form submit events
+ if ( jQuery.nodeName( this, "form" ) ) {
+ return false;
+ }
+
+ // Remove delegated handlers; cleanData eventually reaps submit handlers attached above
+ jQuery.event.remove( this, "._submit" );
+ }
+ };
+}
+
+// IE change delegation and checkbox/radio fix
+if ( !jQuery.support.changeBubbles ) {
+
+ jQuery.event.special.change = {
+
+ setup: function() {
+
+ if ( rformElems.test( this.nodeName ) ) {
+ // IE doesn't fire change on a check/radio until blur; trigger it on click
+ // after a propertychange. Eat the blur-change in special.change.handle.
+ // This still fires onchange a second time for check/radio after blur.
+ if ( this.type === "checkbox" || this.type === "radio" ) {
+ jQuery.event.add( this, "propertychange._change", function( event ) {
+ if ( event.originalEvent.propertyName === "checked" ) {
+ this._just_changed = true;
+ }
+ });
+ jQuery.event.add( this, "click._change", function( event ) {
+ if ( this._just_changed && !event.isTrigger ) {
+ this._just_changed = false;
+ jQuery.event.simulate( "change", this, event, true );
+ }
+ });
+ }
+ return false;
+ }
+ // Delegated event; lazy-add a change handler on descendant inputs
+ jQuery.event.add( this, "beforeactivate._change", function( e ) {
+ var elem = e.target;
+
+ if ( rformElems.test( elem.nodeName ) && !elem._change_attached ) {
+ jQuery.event.add( elem, "change._change", function( event ) {
+ if ( this.parentNode && !event.isSimulated && !event.isTrigger ) {
+ jQuery.event.simulate( "change", this.parentNode, event, true );
+ }
+ });
+ elem._change_attached = true;
+ }
+ });
+ },
+
+ handle: function( event ) {
+ var elem = event.target;
+
+ // Swallow native change events from checkbox/radio, we already triggered them above
+ if ( this !== elem || event.isSimulated || event.isTrigger || (elem.type !== "radio" && elem.type !== "checkbox") ) {
+ return event.handleObj.handler.apply( this, arguments );
+ }
+ },
+
+ teardown: function() {
+ jQuery.event.remove( this, "._change" );
+
+ return rformElems.test( this.nodeName );
+ }
+ };
+}
+
+// Create "bubbling" focus and blur events
+if ( !jQuery.support.focusinBubbles ) {
+ jQuery.each({ focus: "focusin", blur: "focusout" }, function( orig, fix ) {
+
+ // Attach a single capturing handler while someone wants focusin/focusout
+ var attaches = 0,
+ handler = function( event ) {
+ jQuery.event.simulate( fix, event.target, jQuery.event.fix( event ), true );
+ };
+
+ jQuery.event.special[ fix ] = {
+ setup: function() {
+ if ( attaches++ === 0 ) {
+ document.addEventListener( orig, handler, true );
+ }
+ },
+ teardown: function() {
+ if ( --attaches === 0 ) {
+ document.removeEventListener( orig, handler, true );
+ }
+ }
+ };
+ });
+}
+
+jQuery.fn.extend({
+
+ on: function( types, selector, data, fn, /*INTERNAL*/ one ) {
+ var origFn, type;
+
+ // Types can be a map of types/handlers
+ if ( typeof types === "object" ) {
+ // ( types-Object, selector, data )
+ if ( typeof selector !== "string" ) {
+ // ( types-Object, data )
+ data = selector;
+ selector = undefined;
+ }
+ for ( type in types ) {
+ this.on( type, selector, data, types[ type ], one );
+ }
+ return this;
+ }
+
+ if ( data == null && fn == null ) {
+ // ( types, fn )
+ fn = selector;
+ data = selector = undefined;
+ } else if ( fn == null ) {
+ if ( typeof selector === "string" ) {
+ // ( types, selector, fn )
+ fn = data;
+ data = undefined;
+ } else {
+ // ( types, data, fn )
+ fn = data;
+ data = selector;
+ selector = undefined;
+ }
+ }
+ if ( fn === false ) {
+ fn = returnFalse;
+ } else if ( !fn ) {
+ return this;
+ }
+
+ if ( one === 1 ) {
+ origFn = fn;
+ fn = function( event ) {
+ // Can use an empty set, since event contains the info
+ jQuery().off( event );
+ return origFn.apply( this, arguments );
+ };
+ // Use same guid so caller can remove using origFn
+ fn.guid = origFn.guid || ( origFn.guid = jQuery.guid++ );
+ }
+ return this.each( function() {
+ jQuery.event.add( this, types, fn, data, selector );
+ });
+ },
+ one: function( types, selector, data, fn ) {
+ return this.on.call( this, types, selector, data, fn, 1 );
+ },
+ off: function( types, selector, fn ) {
+ if ( types && types.preventDefault && types.handleObj ) {
+ // ( event ) dispatched jQuery.Event
+ var handleObj = types.handleObj;
+ jQuery( types.delegateTarget ).off(
+ handleObj.namespace? handleObj.type + "." + handleObj.namespace : handleObj.type,
+ handleObj.selector,
+ handleObj.handler
+ );
+ return this;
+ }
+ if ( typeof types === "object" ) {
+ // ( types-object [, selector] )
+ for ( var type in types ) {
+ this.off( type, selector, types[ type ] );
+ }
+ return this;
+ }
+ if ( selector === false || typeof selector === "function" ) {
+ // ( types [, fn] )
+ fn = selector;
+ selector = undefined;
+ }
+ if ( fn === false ) {
+ fn = returnFalse;
+ }
+ return this.each(function() {
+ jQuery.event.remove( this, types, fn, selector );
+ });
+ },
+
+ bind: function( types, data, fn ) {
+ return this.on( types, null, data, fn );
+ },
+ unbind: function( types, fn ) {
+ return this.off( types, null, fn );
+ },
+
+ live: function( types, data, fn ) {
+ jQuery( this.context ).on( types, this.selector, data, fn );
+ return this;
+ },
+ die: function( types, fn ) {
+ jQuery( this.context ).off( types, this.selector || "**", fn );
+ return this;
+ },
+
+ delegate: function( selector, types, data, fn ) {
+ return this.on( types, selector, data, fn );
+ },
+ undelegate: function( selector, types, fn ) {
+ // ( namespace ) or ( selector, types [, fn] )
+ return arguments.length == 1? this.off( selector, "**" ) : this.off( types, selector, fn );
+ },
+
+ trigger: function( type, data ) {
+ return this.each(function() {
+ jQuery.event.trigger( type, data, this );
+ });
+ },
+ triggerHandler: function( type, data ) {
+ if ( this[0] ) {
+ return jQuery.event.trigger( type, data, this[0], true );
+ }
+ },
+
+ toggle: function( fn ) {
+ // Save reference to arguments for access in closure
+ var args = arguments,
+ guid = fn.guid || jQuery.guid++,
+ i = 0,
+ toggler = function( event ) {
+ // Figure out which function to execute
+ var lastToggle = ( jQuery._data( this, "lastToggle" + fn.guid ) || 0 ) % i;
+ jQuery._data( this, "lastToggle" + fn.guid, lastToggle + 1 );
+
+ // Make sure that clicks stop
+ event.preventDefault();
+
+ // and execute the function
+ return args[ lastToggle ].apply( this, arguments ) || false;
+ };
+
+ // link all the functions, so any of them can unbind this click handler
+ toggler.guid = guid;
+ while ( i < args.length ) {
+ args[ i++ ].guid = guid;
+ }
+
+ return this.click( toggler );
+ },
+
+ hover: function( fnOver, fnOut ) {
+ return this.mouseenter( fnOver ).mouseleave( fnOut || fnOver );
+ }
+});
+
+jQuery.each( ("blur focus focusin focusout load resize scroll unload click dblclick " +
+ "mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave " +
+ "change select submit keydown keypress keyup error contextmenu").split(" "), function( i, name ) {
+
+ // Handle event binding
+ jQuery.fn[ name ] = function( data, fn ) {
+ if ( fn == null ) {
+ fn = data;
+ data = null;
+ }
+
+ return arguments.length > 0 ?
+ this.on( name, null, data, fn ) :
+ this.trigger( name );
+ };
+
+ if ( jQuery.attrFn ) {
+ jQuery.attrFn[ name ] = true;
+ }
+
+ if ( rkeyEvent.test( name ) ) {
+ jQuery.event.fixHooks[ name ] = jQuery.event.keyHooks;
+ }
+
+ if ( rmouseEvent.test( name ) ) {
+ jQuery.event.fixHooks[ name ] = jQuery.event.mouseHooks;
+ }
+});
+
+
+
+/*!
+ * Sizzle CSS Selector Engine
+ * Copyright 2011, The Dojo Foundation
+ * Released under the MIT, BSD, and GPL Licenses.
+ * More information: http://sizzlejs.com/
+ */
+(function(){
+
+var chunker = /((?:\((?:\([^()]+\)|[^()]+)+\)|\[(?:\[[^\[\]]*\]|['"][^'"]*['"]|[^\[\]'"]+)+\]|\\.|[^ >+~,(\[\\]+)+|[>+~])(\s*,\s*)?((?:.|\r|\n)*)/g,
+ expando = "sizcache" + (Math.random() + '').replace('.', ''),
+ done = 0,
+ toString = Object.prototype.toString,
+ hasDuplicate = false,
+ baseHasDuplicate = true,
+ rBackslash = /\\/g,
+ rReturn = /\r\n/g,
+ rNonWord = /\W/;
+
+// Here we check if the JavaScript engine is using some sort of
+// optimization where it does not always call our comparision
+// function. If that is the case, discard the hasDuplicate value.
+// Thus far that includes Google Chrome.
+[0, 0].sort(function() {
+ baseHasDuplicate = false;
+ return 0;
+});
+
+var Sizzle = function( selector, context, results, seed ) {
+ results = results || [];
+ context = context || document;
+
+ var origContext = context;
+
+ if ( context.nodeType !== 1 && context.nodeType !== 9 ) {
+ return [];
+ }
+
+ if ( !selector || typeof selector !== "string" ) {
+ return results;
+ }
+
+ var m, set, checkSet, extra, ret, cur, pop, i,
+ prune = true,
+ contextXML = Sizzle.isXML( context ),
+ parts = [],
+ soFar = selector;
+
+ // Reset the position of the chunker regexp (start from head)
+ do {
+ chunker.exec( "" );
+ m = chunker.exec( soFar );
+
+ if ( m ) {
+ soFar = m[3];
+
+ parts.push( m[1] );
+
+ if ( m[2] ) {
+ extra = m[3];
+ break;
+ }
+ }
+ } while ( m );
+
+ if ( parts.length > 1 && origPOS.exec( selector ) ) {
+
+ if ( parts.length === 2 && Expr.relative[ parts[0] ] ) {
+ set = posProcess( parts[0] + parts[1], context, seed );
+
+ } else {
+ set = Expr.relative[ parts[0] ] ?
+ [ context ] :
+ Sizzle( parts.shift(), context );
+
+ while ( parts.length ) {
+ selector = parts.shift();
+
+ if ( Expr.relative[ selector ] ) {
+ selector += parts.shift();
+ }
+
+ set = posProcess( selector, set, seed );
+ }
+ }
+
+ } else {
+ // Take a shortcut and set the context if the root selector is an ID
+ // (but not if it'll be faster if the inner selector is an ID)
+ if ( !seed && parts.length > 1 && context.nodeType === 9 && !contextXML &&
+ Expr.match.ID.test(parts[0]) && !Expr.match.ID.test(parts[parts.length - 1]) ) {
+
+ ret = Sizzle.find( parts.shift(), context, contextXML );
+ context = ret.expr ?
+ Sizzle.filter( ret.expr, ret.set )[0] :
+ ret.set[0];
+ }
+
+ if ( context ) {
+ ret = seed ?
+ { expr: parts.pop(), set: makeArray(seed) } :
+ Sizzle.find( parts.pop(), parts.length === 1 && (parts[0] === "~" || parts[0] === "+") && context.parentNode ? context.parentNode : context, contextXML );
+
+ set = ret.expr ?
+ Sizzle.filter( ret.expr, ret.set ) :
+ ret.set;
+
+ if ( parts.length > 0 ) {
+ checkSet = makeArray( set );
+
+ } else {
+ prune = false;
+ }
+
+ while ( parts.length ) {
+ cur = parts.pop();
+ pop = cur;
+
+ if ( !Expr.relative[ cur ] ) {
+ cur = "";
+ } else {
+ pop = parts.pop();
+ }
+
+ if ( pop == null ) {
+ pop = context;
+ }
+
+ Expr.relative[ cur ]( checkSet, pop, contextXML );
+ }
+
+ } else {
+ checkSet = parts = [];
+ }
+ }
+
+ if ( !checkSet ) {
+ checkSet = set;
+ }
+
+ if ( !checkSet ) {
+ Sizzle.error( cur || selector );
+ }
+
+ if ( toString.call(checkSet) === "[object Array]" ) {
+ if ( !prune ) {
+ results.push.apply( results, checkSet );
+
+ } else if ( context && context.nodeType === 1 ) {
+ for ( i = 0; checkSet[i] != null; i++ ) {
+ if ( checkSet[i] && (checkSet[i] === true || checkSet[i].nodeType === 1 && Sizzle.contains(context, checkSet[i])) ) {
+ results.push( set[i] );
+ }
+ }
+
+ } else {
+ for ( i = 0; checkSet[i] != null; i++ ) {
+ if ( checkSet[i] && checkSet[i].nodeType === 1 ) {
+ results.push( set[i] );
+ }
+ }
+ }
+
+ } else {
+ makeArray( checkSet, results );
+ }
+
+ if ( extra ) {
+ Sizzle( extra, origContext, results, seed );
+ Sizzle.uniqueSort( results );
+ }
+
+ return results;
+};
+
+Sizzle.uniqueSort = function( results ) {
+ if ( sortOrder ) {
+ hasDuplicate = baseHasDuplicate;
+ results.sort( sortOrder );
+
+ if ( hasDuplicate ) {
+ for ( var i = 1; i < results.length; i++ ) {
+ if ( results[i] === results[ i - 1 ] ) {
+ results.splice( i--, 1 );
+ }
+ }
+ }
+ }
+
+ return results;
+};
+
+Sizzle.matches = function( expr, set ) {
+ return Sizzle( expr, null, null, set );
+};
+
+Sizzle.matchesSelector = function( node, expr ) {
+ return Sizzle( expr, null, null, [node] ).length > 0;
+};
+
+Sizzle.find = function( expr, context, isXML ) {
+ var set, i, len, match, type, left;
+
+ if ( !expr ) {
+ return [];
+ }
+
+ for ( i = 0, len = Expr.order.length; i < len; i++ ) {
+ type = Expr.order[i];
+
+ if ( (match = Expr.leftMatch[ type ].exec( expr )) ) {
+ left = match[1];
+ match.splice( 1, 1 );
+
+ if ( left.substr( left.length - 1 ) !== "\\" ) {
+ match[1] = (match[1] || "").replace( rBackslash, "" );
+ set = Expr.find[ type ]( match, context, isXML );
+
+ if ( set != null ) {
+ expr = expr.replace( Expr.match[ type ], "" );
+ break;
+ }
+ }
+ }
+ }
+
+ if ( !set ) {
+ set = typeof context.getElementsByTagName !== "undefined" ?
+ context.getElementsByTagName( "*" ) :
+ [];
+ }
+
+ return { set: set, expr: expr };
+};
+
+Sizzle.filter = function( expr, set, inplace, not ) {
+ var match, anyFound,
+ type, found, item, filter, left,
+ i, pass,
+ old = expr,
+ result = [],
+ curLoop = set,
+ isXMLFilter = set && set[0] && Sizzle.isXML( set[0] );
+
+ while ( expr && set.length ) {
+ for ( type in Expr.filter ) {
+ if ( (match = Expr.leftMatch[ type ].exec( expr )) != null && match[2] ) {
+ filter = Expr.filter[ type ];
+ left = match[1];
+
+ anyFound = false;
+
+ match.splice(1,1);
+
+ if ( left.substr( left.length - 1 ) === "\\" ) {
+ continue;
+ }
+
+ if ( curLoop === result ) {
+ result = [];
+ }
+
+ if ( Expr.preFilter[ type ] ) {
+ match = Expr.preFilter[ type ]( match, curLoop, inplace, result, not, isXMLFilter );
+
+ if ( !match ) {
+ anyFound = found = true;
+
+ } else if ( match === true ) {
+ continue;
+ }
+ }
+
+ if ( match ) {
+ for ( i = 0; (item = curLoop[i]) != null; i++ ) {
+ if ( item ) {
+ found = filter( item, match, i, curLoop );
+ pass = not ^ found;
+
+ if ( inplace && found != null ) {
+ if ( pass ) {
+ anyFound = true;
+
+ } else {
+ curLoop[i] = false;
+ }
+
+ } else if ( pass ) {
+ result.push( item );
+ anyFound = true;
+ }
+ }
+ }
+ }
+
+ if ( found !== undefined ) {
+ if ( !inplace ) {
+ curLoop = result;
+ }
+
+ expr = expr.replace( Expr.match[ type ], "" );
+
+ if ( !anyFound ) {
+ return [];
+ }
+
+ break;
+ }
+ }
+ }
+
+ // Improper expression
+ if ( expr === old ) {
+ if ( anyFound == null ) {
+ Sizzle.error( expr );
+
+ } else {
+ break;
+ }
+ }
+
+ old = expr;
+ }
+
+ return curLoop;
+};
+
+Sizzle.error = function( msg ) {
+ throw new Error( "Syntax error, unrecognized expression: " + msg );
+};
+
+/**
+ * Utility function for retreiving the text value of an array of DOM nodes
+ * @param {Array|Element} elem
+ */
+var getText = Sizzle.getText = function( elem ) {
+ var i, node,
+ nodeType = elem.nodeType,
+ ret = "";
+
+ if ( nodeType ) {
+ if ( nodeType === 1 || nodeType === 9 ) {
+ // Use textContent || innerText for elements
+ if ( typeof elem.textContent === 'string' ) {
+ return elem.textContent;
+ } else if ( typeof elem.innerText === 'string' ) {
+ // Replace IE's carriage returns
+ return elem.innerText.replace( rReturn, '' );
+ } else {
+ // Traverse it's children
+ for ( elem = elem.firstChild; elem; elem = elem.nextSibling) {
+ ret += getText( elem );
+ }
+ }
+ } else if ( nodeType === 3 || nodeType === 4 ) {
+ return elem.nodeValue;
+ }
+ } else {
+
+ // If no nodeType, this is expected to be an array
+ for ( i = 0; (node = elem[i]); i++ ) {
+ // Do not traverse comment nodes
+ if ( node.nodeType !== 8 ) {
+ ret += getText( node );
+ }
+ }
+ }
+ return ret;
+};
+
+var Expr = Sizzle.selectors = {
+ order: [ "ID", "NAME", "TAG" ],
+
+ match: {
+ ID: /#((?:[\w\u00c0-\uFFFF\-]|\\.)+)/,
+ CLASS: /\.((?:[\w\u00c0-\uFFFF\-]|\\.)+)/,
+ NAME: /\[name=['"]*((?:[\w\u00c0-\uFFFF\-]|\\.)+)['"]*\]/,
+ ATTR: /\[\s*((?:[\w\u00c0-\uFFFF\-]|\\.)+)\s*(?:(\S?=)\s*(?:(['"])(.*?)\3|(#?(?:[\w\u00c0-\uFFFF\-]|\\.)*)|)|)\s*\]/,
+ TAG: /^((?:[\w\u00c0-\uFFFF\*\-]|\\.)+)/,
+ CHILD: /:(only|nth|last|first)-child(?:\(\s*(even|odd|(?:[+\-]?\d+|(?:[+\-]?\d*)?n\s*(?:[+\-]\s*\d+)?))\s*\))?/,
+ POS: /:(nth|eq|gt|lt|first|last|even|odd)(?:\((\d*)\))?(?=[^\-]|$)/,
+ PSEUDO: /:((?:[\w\u00c0-\uFFFF\-]|\\.)+)(?:\((['"]?)((?:\([^\)]+\)|[^\(\)]*)+)\2\))?/
+ },
+
+ leftMatch: {},
+
+ attrMap: {
+ "class": "className",
+ "for": "htmlFor"
+ },
+
+ attrHandle: {
+ href: function( elem ) {
+ return elem.getAttribute( "href" );
+ },
+ type: function( elem ) {
+ return elem.getAttribute( "type" );
+ }
+ },
+
+ relative: {
+ "+": function(checkSet, part){
+ var isPartStr = typeof part === "string",
+ isTag = isPartStr && !rNonWord.test( part ),
+ isPartStrNotTag = isPartStr && !isTag;
+
+ if ( isTag ) {
+ part = part.toLowerCase();
+ }
+
+ for ( var i = 0, l = checkSet.length, elem; i < l; i++ ) {
+ if ( (elem = checkSet[i]) ) {
+ while ( (elem = elem.previousSibling) && elem.nodeType !== 1 ) {}
+
+ checkSet[i] = isPartStrNotTag || elem && elem.nodeName.toLowerCase() === part ?
+ elem || false :
+ elem === part;
+ }
+ }
+
+ if ( isPartStrNotTag ) {
+ Sizzle.filter( part, checkSet, true );
+ }
+ },
+
+ ">": function( checkSet, part ) {
+ var elem,
+ isPartStr = typeof part === "string",
+ i = 0,
+ l = checkSet.length;
+
+ if ( isPartStr && !rNonWord.test( part ) ) {
+ part = part.toLowerCase();
+
+ for ( ; i < l; i++ ) {
+ elem = checkSet[i];
+
+ if ( elem ) {
+ var parent = elem.parentNode;
+ checkSet[i] = parent.nodeName.toLowerCase() === part ? parent : false;
+ }
+ }
+
+ } else {
+ for ( ; i < l; i++ ) {
+ elem = checkSet[i];
+
+ if ( elem ) {
+ checkSet[i] = isPartStr ?
+ elem.parentNode :
+ elem.parentNode === part;
+ }
+ }
+
+ if ( isPartStr ) {
+ Sizzle.filter( part, checkSet, true );
+ }
+ }
+ },
+
+ "": function(checkSet, part, isXML){
+ var nodeCheck,
+ doneName = done++,
+ checkFn = dirCheck;
+
+ if ( typeof part === "string" && !rNonWord.test( part ) ) {
+ part = part.toLowerCase();
+ nodeCheck = part;
+ checkFn = dirNodeCheck;
+ }
+
+ checkFn( "parentNode", part, doneName, checkSet, nodeCheck, isXML );
+ },
+
+ "~": function( checkSet, part, isXML ) {
+ var nodeCheck,
+ doneName = done++,
+ checkFn = dirCheck;
+
+ if ( typeof part === "string" && !rNonWord.test( part ) ) {
+ part = part.toLowerCase();
+ nodeCheck = part;
+ checkFn = dirNodeCheck;
+ }
+
+ checkFn( "previousSibling", part, doneName, checkSet, nodeCheck, isXML );
+ }
+ },
+
+ find: {
+ ID: function( match, context, isXML ) {
+ if ( typeof context.getElementById !== "undefined" && !isXML ) {
+ var m = context.getElementById(match[1]);
+ // Check parentNode to catch when Blackberry 4.6 returns
+ // nodes that are no longer in the document #6963
+ return m && m.parentNode ? [m] : [];
+ }
+ },
+
+ NAME: function( match, context ) {
+ if ( typeof context.getElementsByName !== "undefined" ) {
+ var ret = [],
+ results = context.getElementsByName( match[1] );
+
+ for ( var i = 0, l = results.length; i < l; i++ ) {
+ if ( results[i].getAttribute("name") === match[1] ) {
+ ret.push( results[i] );
+ }
+ }
+
+ return ret.length === 0 ? null : ret;
+ }
+ },
+
+ TAG: function( match, context ) {
+ if ( typeof context.getElementsByTagName !== "undefined" ) {
+ return context.getElementsByTagName( match[1] );
+ }
+ }
+ },
+ preFilter: {
+ CLASS: function( match, curLoop, inplace, result, not, isXML ) {
+ match = " " + match[1].replace( rBackslash, "" ) + " ";
+
+ if ( isXML ) {
+ return match;
+ }
+
+ for ( var i = 0, elem; (elem = curLoop[i]) != null; i++ ) {
+ if ( elem ) {
+ if ( not ^ (elem.className && (" " + elem.className + " ").replace(/[\t\n\r]/g, " ").indexOf(match) >= 0) ) {
+ if ( !inplace ) {
+ result.push( elem );
+ }
+
+ } else if ( inplace ) {
+ curLoop[i] = false;
+ }
+ }
+ }
+
+ return false;
+ },
+
+ ID: function( match ) {
+ return match[1].replace( rBackslash, "" );
+ },
+
+ TAG: function( match, curLoop ) {
+ return match[1].replace( rBackslash, "" ).toLowerCase();
+ },
+
+ CHILD: function( match ) {
+ if ( match[1] === "nth" ) {
+ if ( !match[2] ) {
+ Sizzle.error( match[0] );
+ }
+
+ match[2] = match[2].replace(/^\+|\s*/g, '');
+
+ // parse equations like 'even', 'odd', '5', '2n', '3n+2', '4n-1', '-n+6'
+ var test = /(-?)(\d*)(?:n([+\-]?\d*))?/.exec(
+ match[2] === "even" && "2n" || match[2] === "odd" && "2n+1" ||
+ !/\D/.test( match[2] ) && "0n+" + match[2] || match[2]);
+
+ // calculate the numbers (first)n+(last) including if they are negative
+ match[2] = (test[1] + (test[2] || 1)) - 0;
+ match[3] = test[3] - 0;
+ }
+ else if ( match[2] ) {
+ Sizzle.error( match[0] );
+ }
+
+ // TODO: Move to normal caching system
+ match[0] = done++;
+
+ return match;
+ },
+
+ ATTR: function( match, curLoop, inplace, result, not, isXML ) {
+ var name = match[1] = match[1].replace( rBackslash, "" );
+
+ if ( !isXML && Expr.attrMap[name] ) {
+ match[1] = Expr.attrMap[name];
+ }
+
+ // Handle if an un-quoted value was used
+ match[4] = ( match[4] || match[5] || "" ).replace( rBackslash, "" );
+
+ if ( match[2] === "~=" ) {
+ match[4] = " " + match[4] + " ";
+ }
+
+ return match;
+ },
+
+ PSEUDO: function( match, curLoop, inplace, result, not ) {
+ if ( match[1] === "not" ) {
+ // If we're dealing with a complex expression, or a simple one
+ if ( ( chunker.exec(match[3]) || "" ).length > 1 || /^\w/.test(match[3]) ) {
+ match[3] = Sizzle(match[3], null, null, curLoop);
+
+ } else {
+ var ret = Sizzle.filter(match[3], curLoop, inplace, true ^ not);
+
+ if ( !inplace ) {
+ result.push.apply( result, ret );
+ }
+
+ return false;
+ }
+
+ } else if ( Expr.match.POS.test( match[0] ) || Expr.match.CHILD.test( match[0] ) ) {
+ return true;
+ }
+
+ return match;
+ },
+
+ POS: function( match ) {
+ match.unshift( true );
+
+ return match;
+ }
+ },
+
+ filters: {
+ enabled: function( elem ) {
+ return elem.disabled === false && elem.type !== "hidden";
+ },
+
+ disabled: function( elem ) {
+ return elem.disabled === true;
+ },
+
+ checked: function( elem ) {
+ return elem.checked === true;
+ },
+
+ selected: function( elem ) {
+ // Accessing this property makes selected-by-default
+ // options in Safari work properly
+ if ( elem.parentNode ) {
+ elem.parentNode.selectedIndex;
+ }
+
+ return elem.selected === true;
+ },
+
+ parent: function( elem ) {
+ return !!elem.firstChild;
+ },
+
+ empty: function( elem ) {
+ return !elem.firstChild;
+ },
+
+ has: function( elem, i, match ) {
+ return !!Sizzle( match[3], elem ).length;
+ },
+
+ header: function( elem ) {
+ return (/h\d/i).test( elem.nodeName );
+ },
+
+ text: function( elem ) {
+ var attr = elem.getAttribute( "type" ), type = elem.type;
+ // IE6 and 7 will map elem.type to 'text' for new HTML5 types (search, etc)
+ // use getAttribute instead to test this case
+ return elem.nodeName.toLowerCase() === "input" && "text" === type && ( attr === type || attr === null );
+ },
+
+ radio: function( elem ) {
+ return elem.nodeName.toLowerCase() === "input" && "radio" === elem.type;
+ },
+
+ checkbox: function( elem ) {
+ return elem.nodeName.toLowerCase() === "input" && "checkbox" === elem.type;
+ },
+
+ file: function( elem ) {
+ return elem.nodeName.toLowerCase() === "input" && "file" === elem.type;
+ },
+
+ password: function( elem ) {
+ return elem.nodeName.toLowerCase() === "input" && "password" === elem.type;
+ },
+
+ submit: function( elem ) {
+ var name = elem.nodeName.toLowerCase();
+ return (name === "input" || name === "button") && "submit" === elem.type;
+ },
+
+ image: function( elem ) {
+ return elem.nodeName.toLowerCase() === "input" && "image" === elem.type;
+ },
+
+ reset: function( elem ) {
+ var name = elem.nodeName.toLowerCase();
+ return (name === "input" || name === "button") && "reset" === elem.type;
+ },
+
+ button: function( elem ) {
+ var name = elem.nodeName.toLowerCase();
+ return name === "input" && "button" === elem.type || name === "button";
+ },
+
+ input: function( elem ) {
+ return (/input|select|textarea|button/i).test( elem.nodeName );
+ },
+
+ focus: function( elem ) {
+ return elem === elem.ownerDocument.activeElement;
+ }
+ },
+ setFilters: {
+ first: function( elem, i ) {
+ return i === 0;
+ },
+
+ last: function( elem, i, match, array ) {
+ return i === array.length - 1;
+ },
+
+ even: function( elem, i ) {
+ return i % 2 === 0;
+ },
+
+ odd: function( elem, i ) {
+ return i % 2 === 1;
+ },
+
+ lt: function( elem, i, match ) {
+ return i < match[3] - 0;
+ },
+
+ gt: function( elem, i, match ) {
+ return i > match[3] - 0;
+ },
+
+ nth: function( elem, i, match ) {
+ return match[3] - 0 === i;
+ },
+
+ eq: function( elem, i, match ) {
+ return match[3] - 0 === i;
+ }
+ },
+ filter: {
+ PSEUDO: function( elem, match, i, array ) {
+ var name = match[1],
+ filter = Expr.filters[ name ];
+
+ if ( filter ) {
+ return filter( elem, i, match, array );
+
+ } else if ( name === "contains" ) {
+ return (elem.textContent || elem.innerText || getText([ elem ]) || "").indexOf(match[3]) >= 0;
+
+ } else if ( name === "not" ) {
+ var not = match[3];
+
+ for ( var j = 0, l = not.length; j < l; j++ ) {
+ if ( not[j] === elem ) {
+ return false;
+ }
+ }
+
+ return true;
+
+ } else {
+ Sizzle.error( name );
+ }
+ },
+
+ CHILD: function( elem, match ) {
+ var first, last,
+ doneName, parent, cache,
+ count, diff,
+ type = match[1],
+ node = elem;
+
+ switch ( type ) {
+ case "only":
+ case "first":
+ while ( (node = node.previousSibling) ) {
+ if ( node.nodeType === 1 ) {
+ return false;
+ }
+ }
+
+ if ( type === "first" ) {
+ return true;
+ }
+
+ node = elem;
+
+ case "last":
+ while ( (node = node.nextSibling) ) {
+ if ( node.nodeType === 1 ) {
+ return false;
+ }
+ }
+
+ return true;
+
+ case "nth":
+ first = match[2];
+ last = match[3];
+
+ if ( first === 1 && last === 0 ) {
+ return true;
+ }
+
+ doneName = match[0];
+ parent = elem.parentNode;
+
+ if ( parent && (parent[ expando ] !== doneName || !elem.nodeIndex) ) {
+ count = 0;
+
+ for ( node = parent.firstChild; node; node = node.nextSibling ) {
+ if ( node.nodeType === 1 ) {
+ node.nodeIndex = ++count;
+ }
+ }
+
+ parent[ expando ] = doneName;
+ }
+
+ diff = elem.nodeIndex - last;
+
+ if ( first === 0 ) {
+ return diff === 0;
+
+ } else {
+ return ( diff % first === 0 && diff / first >= 0 );
+ }
+ }
+ },
+
+ ID: function( elem, match ) {
+ return elem.nodeType === 1 && elem.getAttribute("id") === match;
+ },
+
+ TAG: function( elem, match ) {
+ return (match === "*" && elem.nodeType === 1) || !!elem.nodeName && elem.nodeName.toLowerCase() === match;
+ },
+
+ CLASS: function( elem, match ) {
+ return (" " + (elem.className || elem.getAttribute("class")) + " ")
+ .indexOf( match ) > -1;
+ },
+
+ ATTR: function( elem, match ) {
+ var name = match[1],
+ result = Sizzle.attr ?
+ Sizzle.attr( elem, name ) :
+ Expr.attrHandle[ name ] ?
+ Expr.attrHandle[ name ]( elem ) :
+ elem[ name ] != null ?
+ elem[ name ] :
+ elem.getAttribute( name ),
+ value = result + "",
+ type = match[2],
+ check = match[4];
+
+ return result == null ?
+ type === "!=" :
+ !type && Sizzle.attr ?
+ result != null :
+ type === "=" ?
+ value === check :
+ type === "*=" ?
+ value.indexOf(check) >= 0 :
+ type === "~=" ?
+ (" " + value + " ").indexOf(check) >= 0 :
+ !check ?
+ value && result !== false :
+ type === "!=" ?
+ value !== check :
+ type === "^=" ?
+ value.indexOf(check) === 0 :
+ type === "$=" ?
+ value.substr(value.length - check.length) === check :
+ type === "|=" ?
+ value === check || value.substr(0, check.length + 1) === check + "-" :
+ false;
+ },
+
+ POS: function( elem, match, i, array ) {
+ var name = match[2],
+ filter = Expr.setFilters[ name ];
+
+ if ( filter ) {
+ return filter( elem, i, match, array );
+ }
+ }
+ }
+};
+
+var origPOS = Expr.match.POS,
+ fescape = function(all, num){
+ return "\\" + (num - 0 + 1);
+ };
+
+for ( var type in Expr.match ) {
+ Expr.match[ type ] = new RegExp( Expr.match[ type ].source + (/(?![^\[]*\])(?![^\(]*\))/.source) );
+ Expr.leftMatch[ type ] = new RegExp( /(^(?:.|\r|\n)*?)/.source + Expr.match[ type ].source.replace(/\\(\d+)/g, fescape) );
+}
+
+var makeArray = function( array, results ) {
+ array = Array.prototype.slice.call( array, 0 );
+
+ if ( results ) {
+ results.push.apply( results, array );
+ return results;
+ }
+
+ return array;
+};
+
+// Perform a simple check to determine if the browser is capable of
+// converting a NodeList to an array using builtin methods.
+// Also verifies that the returned array holds DOM nodes
+// (which is not the case in the Blackberry browser)
+try {
+ Array.prototype.slice.call( document.documentElement.childNodes, 0 )[0].nodeType;
+
+// Provide a fallback method if it does not work
+} catch( e ) {
+ makeArray = function( array, results ) {
+ var i = 0,
+ ret = results || [];
+
+ if ( toString.call(array) === "[object Array]" ) {
+ Array.prototype.push.apply( ret, array );
+
+ } else {
+ if ( typeof array.length === "number" ) {
+ for ( var l = array.length; i < l; i++ ) {
+ ret.push( array[i] );
+ }
+
+ } else {
+ for ( ; array[i]; i++ ) {
+ ret.push( array[i] );
+ }
+ }
+ }
+
+ return ret;
+ };
+}
+
+var sortOrder, siblingCheck;
+
+if ( document.documentElement.compareDocumentPosition ) {
+ sortOrder = function( a, b ) {
+ if ( a === b ) {
+ hasDuplicate = true;
+ return 0;
+ }
+
+ if ( !a.compareDocumentPosition || !b.compareDocumentPosition ) {
+ return a.compareDocumentPosition ? -1 : 1;
+ }
+
+ return a.compareDocumentPosition(b) & 4 ? -1 : 1;
+ };
+
+} else {
+ sortOrder = function( a, b ) {
+ // The nodes are identical, we can exit early
+ if ( a === b ) {
+ hasDuplicate = true;
+ return 0;
+
+ // Fallback to using sourceIndex (in IE) if it's available on both nodes
+ } else if ( a.sourceIndex && b.sourceIndex ) {
+ return a.sourceIndex - b.sourceIndex;
+ }
+
+ var al, bl,
+ ap = [],
+ bp = [],
+ aup = a.parentNode,
+ bup = b.parentNode,
+ cur = aup;
+
+ // If the nodes are siblings (or identical) we can do a quick check
+ if ( aup === bup ) {
+ return siblingCheck( a, b );
+
+ // If no parents were found then the nodes are disconnected
+ } else if ( !aup ) {
+ return -1;
+
+ } else if ( !bup ) {
+ return 1;
+ }
+
+ // Otherwise they're somewhere else in the tree so we need
+ // to build up a full list of the parentNodes for comparison
+ while ( cur ) {
+ ap.unshift( cur );
+ cur = cur.parentNode;
+ }
+
+ cur = bup;
+
+ while ( cur ) {
+ bp.unshift( cur );
+ cur = cur.parentNode;
+ }
+
+ al = ap.length;
+ bl = bp.length;
+
+ // Start walking down the tree looking for a discrepancy
+ for ( var i = 0; i < al && i < bl; i++ ) {
+ if ( ap[i] !== bp[i] ) {
+ return siblingCheck( ap[i], bp[i] );
+ }
+ }
+
+ // We ended someplace up the tree so do a sibling check
+ return i === al ?
+ siblingCheck( a, bp[i], -1 ) :
+ siblingCheck( ap[i], b, 1 );
+ };
+
+ siblingCheck = function( a, b, ret ) {
+ if ( a === b ) {
+ return ret;
+ }
+
+ var cur = a.nextSibling;
+
+ while ( cur ) {
+ if ( cur === b ) {
+ return -1;
+ }
+
+ cur = cur.nextSibling;
+ }
+
+ return 1;
+ };
+}
+
+// Check to see if the browser returns elements by name when
+// querying by getElementById (and provide a workaround)
+(function(){
+ // We're going to inject a fake input element with a specified name
+ var form = document.createElement("div"),
+ id = "script" + (new Date()).getTime(),
+ root = document.documentElement;
+
+ form.innerHTML = "";
+
+ // Inject it into the root element, check its status, and remove it quickly
+ root.insertBefore( form, root.firstChild );
+
+ // The workaround has to do additional checks after a getElementById
+ // Which slows things down for other browsers (hence the branching)
+ if ( document.getElementById( id ) ) {
+ Expr.find.ID = function( match, context, isXML ) {
+ if ( typeof context.getElementById !== "undefined" && !isXML ) {
+ var m = context.getElementById(match[1]);
+
+ return m ?
+ m.id === match[1] || typeof m.getAttributeNode !== "undefined" && m.getAttributeNode("id").nodeValue === match[1] ?
+ [m] :
+ undefined :
+ [];
+ }
+ };
+
+ Expr.filter.ID = function( elem, match ) {
+ var node = typeof elem.getAttributeNode !== "undefined" && elem.getAttributeNode("id");
+
+ return elem.nodeType === 1 && node && node.nodeValue === match;
+ };
+ }
+
+ root.removeChild( form );
+
+ // release memory in IE
+ root = form = null;
+})();
+
+(function(){
+ // Check to see if the browser returns only elements
+ // when doing getElementsByTagName("*")
+
+ // Create a fake element
+ var div = document.createElement("div");
+ div.appendChild( document.createComment("") );
+
+ // Make sure no comments are found
+ if ( div.getElementsByTagName("*").length > 0 ) {
+ Expr.find.TAG = function( match, context ) {
+ var results = context.getElementsByTagName( match[1] );
+
+ // Filter out possible comments
+ if ( match[1] === "*" ) {
+ var tmp = [];
+
+ for ( var i = 0; results[i]; i++ ) {
+ if ( results[i].nodeType === 1 ) {
+ tmp.push( results[i] );
+ }
+ }
+
+ results = tmp;
+ }
+
+ return results;
+ };
+ }
+
+ // Check to see if an attribute returns normalized href attributes
+ div.innerHTML = "";
+
+ if ( div.firstChild && typeof div.firstChild.getAttribute !== "undefined" &&
+ div.firstChild.getAttribute("href") !== "#" ) {
+
+ Expr.attrHandle.href = function( elem ) {
+ return elem.getAttribute( "href", 2 );
+ };
+ }
+
+ // release memory in IE
+ div = null;
+})();
+
+if ( document.querySelectorAll ) {
+ (function(){
+ var oldSizzle = Sizzle,
+ div = document.createElement("div"),
+ id = "__sizzle__";
+
+ div.innerHTML = "";
+
+ // Safari can't handle uppercase or unicode characters when
+ // in quirks mode.
+ if ( div.querySelectorAll && div.querySelectorAll(".TEST").length === 0 ) {
+ return;
+ }
+
+ Sizzle = function( query, context, extra, seed ) {
+ context = context || document;
+
+ // Only use querySelectorAll on non-XML documents
+ // (ID selectors don't work in non-HTML documents)
+ if ( !seed && !Sizzle.isXML(context) ) {
+ // See if we find a selector to speed up
+ var match = /^(\w+$)|^\.([\w\-]+$)|^#([\w\-]+$)/.exec( query );
+
+ if ( match && (context.nodeType === 1 || context.nodeType === 9) ) {
+ // Speed-up: Sizzle("TAG")
+ if ( match[1] ) {
+ return makeArray( context.getElementsByTagName( query ), extra );
+
+ // Speed-up: Sizzle(".CLASS")
+ } else if ( match[2] && Expr.find.CLASS && context.getElementsByClassName ) {
+ return makeArray( context.getElementsByClassName( match[2] ), extra );
+ }
+ }
+
+ if ( context.nodeType === 9 ) {
+ // Speed-up: Sizzle("body")
+ // The body element only exists once, optimize finding it
+ if ( query === "body" && context.body ) {
+ return makeArray( [ context.body ], extra );
+
+ // Speed-up: Sizzle("#ID")
+ } else if ( match && match[3] ) {
+ var elem = context.getElementById( match[3] );
+
+ // Check parentNode to catch when Blackberry 4.6 returns
+ // nodes that are no longer in the document #6963
+ if ( elem && elem.parentNode ) {
+ // Handle the case where IE and Opera return items
+ // by name instead of ID
+ if ( elem.id === match[3] ) {
+ return makeArray( [ elem ], extra );
+ }
+
+ } else {
+ return makeArray( [], extra );
+ }
+ }
+
+ try {
+ return makeArray( context.querySelectorAll(query), extra );
+ } catch(qsaError) {}
+
+ // qSA works strangely on Element-rooted queries
+ // We can work around this by specifying an extra ID on the root
+ // and working up from there (Thanks to Andrew Dupont for the technique)
+ // IE 8 doesn't work on object elements
+ } else if ( context.nodeType === 1 && context.nodeName.toLowerCase() !== "object" ) {
+ var oldContext = context,
+ old = context.getAttribute( "id" ),
+ nid = old || id,
+ hasParent = context.parentNode,
+ relativeHierarchySelector = /^\s*[+~]/.test( query );
+
+ if ( !old ) {
+ context.setAttribute( "id", nid );
+ } else {
+ nid = nid.replace( /'/g, "\\$&" );
+ }
+ if ( relativeHierarchySelector && hasParent ) {
+ context = context.parentNode;
+ }
+
+ try {
+ if ( !relativeHierarchySelector || hasParent ) {
+ return makeArray( context.querySelectorAll( "[id='" + nid + "'] " + query ), extra );
+ }
+
+ } catch(pseudoError) {
+ } finally {
+ if ( !old ) {
+ oldContext.removeAttribute( "id" );
+ }
+ }
+ }
+ }
+
+ return oldSizzle(query, context, extra, seed);
+ };
+
+ for ( var prop in oldSizzle ) {
+ Sizzle[ prop ] = oldSizzle[ prop ];
+ }
+
+ // release memory in IE
+ div = null;
+ })();
+}
+
+(function(){
+ var html = document.documentElement,
+ matches = html.matchesSelector || html.mozMatchesSelector || html.webkitMatchesSelector || html.msMatchesSelector;
+
+ if ( matches ) {
+ // Check to see if it's possible to do matchesSelector
+ // on a disconnected node (IE 9 fails this)
+ var disconnectedMatch = !matches.call( document.createElement( "div" ), "div" ),
+ pseudoWorks = false;
+
+ try {
+ // This should fail with an exception
+ // Gecko does not error, returns false instead
+ matches.call( document.documentElement, "[test!='']:sizzle" );
+
+ } catch( pseudoError ) {
+ pseudoWorks = true;
+ }
+
+ Sizzle.matchesSelector = function( node, expr ) {
+ // Make sure that attribute selectors are quoted
+ expr = expr.replace(/\=\s*([^'"\]]*)\s*\]/g, "='$1']");
+
+ if ( !Sizzle.isXML( node ) ) {
+ try {
+ if ( pseudoWorks || !Expr.match.PSEUDO.test( expr ) && !/!=/.test( expr ) ) {
+ var ret = matches.call( node, expr );
+
+ // IE 9's matchesSelector returns false on disconnected nodes
+ if ( ret || !disconnectedMatch ||
+ // As well, disconnected nodes are said to be in a document
+ // fragment in IE 9, so check for that
+ node.document && node.document.nodeType !== 11 ) {
+ return ret;
+ }
+ }
+ } catch(e) {}
+ }
+
+ return Sizzle(expr, null, null, [node]).length > 0;
+ };
+ }
+})();
+
+(function(){
+ var div = document.createElement("div");
+
+ div.innerHTML = "";
+
+ // Opera can't find a second classname (in 9.6)
+ // Also, make sure that getElementsByClassName actually exists
+ if ( !div.getElementsByClassName || div.getElementsByClassName("e").length === 0 ) {
+ return;
+ }
+
+ // Safari caches class attributes, doesn't catch changes (in 3.2)
+ div.lastChild.className = "e";
+
+ if ( div.getElementsByClassName("e").length === 1 ) {
+ return;
+ }
+
+ Expr.order.splice(1, 0, "CLASS");
+ Expr.find.CLASS = function( match, context, isXML ) {
+ if ( typeof context.getElementsByClassName !== "undefined" && !isXML ) {
+ return context.getElementsByClassName(match[1]);
+ }
+ };
+
+ // release memory in IE
+ div = null;
+})();
+
+function dirNodeCheck( dir, cur, doneName, checkSet, nodeCheck, isXML ) {
+ for ( var i = 0, l = checkSet.length; i < l; i++ ) {
+ var elem = checkSet[i];
+
+ if ( elem ) {
+ var match = false;
+
+ elem = elem[dir];
+
+ while ( elem ) {
+ if ( elem[ expando ] === doneName ) {
+ match = checkSet[elem.sizset];
+ break;
+ }
+
+ if ( elem.nodeType === 1 && !isXML ){
+ elem[ expando ] = doneName;
+ elem.sizset = i;
+ }
+
+ if ( elem.nodeName.toLowerCase() === cur ) {
+ match = elem;
+ break;
+ }
+
+ elem = elem[dir];
+ }
+
+ checkSet[i] = match;
+ }
+ }
+}
+
+function dirCheck( dir, cur, doneName, checkSet, nodeCheck, isXML ) {
+ for ( var i = 0, l = checkSet.length; i < l; i++ ) {
+ var elem = checkSet[i];
+
+ if ( elem ) {
+ var match = false;
+
+ elem = elem[dir];
+
+ while ( elem ) {
+ if ( elem[ expando ] === doneName ) {
+ match = checkSet[elem.sizset];
+ break;
+ }
+
+ if ( elem.nodeType === 1 ) {
+ if ( !isXML ) {
+ elem[ expando ] = doneName;
+ elem.sizset = i;
+ }
+
+ if ( typeof cur !== "string" ) {
+ if ( elem === cur ) {
+ match = true;
+ break;
+ }
+
+ } else if ( Sizzle.filter( cur, [elem] ).length > 0 ) {
+ match = elem;
+ break;
+ }
+ }
+
+ elem = elem[dir];
+ }
+
+ checkSet[i] = match;
+ }
+ }
+}
+
+if ( document.documentElement.contains ) {
+ Sizzle.contains = function( a, b ) {
+ return a !== b && (a.contains ? a.contains(b) : true);
+ };
+
+} else if ( document.documentElement.compareDocumentPosition ) {
+ Sizzle.contains = function( a, b ) {
+ return !!(a.compareDocumentPosition(b) & 16);
+ };
+
+} else {
+ Sizzle.contains = function() {
+ return false;
+ };
+}
+
+Sizzle.isXML = function( elem ) {
+ // documentElement is verified for cases where it doesn't yet exist
+ // (such as loading iframes in IE - #4833)
+ var documentElement = (elem ? elem.ownerDocument || elem : 0).documentElement;
+
+ return documentElement ? documentElement.nodeName !== "HTML" : false;
+};
+
+var posProcess = function( selector, context, seed ) {
+ var match,
+ tmpSet = [],
+ later = "",
+ root = context.nodeType ? [context] : context;
+
+ // Position selectors must be done after the filter
+ // And so must :not(positional) so we move all PSEUDOs to the end
+ while ( (match = Expr.match.PSEUDO.exec( selector )) ) {
+ later += match[0];
+ selector = selector.replace( Expr.match.PSEUDO, "" );
+ }
+
+ selector = Expr.relative[selector] ? selector + "*" : selector;
+
+ for ( var i = 0, l = root.length; i < l; i++ ) {
+ Sizzle( selector, root[i], tmpSet, seed );
+ }
+
+ return Sizzle.filter( later, tmpSet );
+};
+
+// EXPOSE
+// Override sizzle attribute retrieval
+Sizzle.attr = jQuery.attr;
+Sizzle.selectors.attrMap = {};
+jQuery.find = Sizzle;
+jQuery.expr = Sizzle.selectors;
+jQuery.expr[":"] = jQuery.expr.filters;
+jQuery.unique = Sizzle.uniqueSort;
+jQuery.text = Sizzle.getText;
+jQuery.isXMLDoc = Sizzle.isXML;
+jQuery.contains = Sizzle.contains;
+
+
+})();
+
+
+var runtil = /Until$/,
+ rparentsprev = /^(?:parents|prevUntil|prevAll)/,
+ // Note: This RegExp should be improved, or likely pulled from Sizzle
+ rmultiselector = /,/,
+ isSimple = /^.[^:#\[\.,]*$/,
+ slice = Array.prototype.slice,
+ POS = jQuery.expr.match.POS,
+ // methods guaranteed to produce a unique set when starting from a unique set
+ guaranteedUnique = {
+ children: true,
+ contents: true,
+ next: true,
+ prev: true
+ };
+
+jQuery.fn.extend({
+ find: function( selector ) {
+ var self = this,
+ i, l;
+
+ if ( typeof selector !== "string" ) {
+ return jQuery( selector ).filter(function() {
+ for ( i = 0, l = self.length; i < l; i++ ) {
+ if ( jQuery.contains( self[ i ], this ) ) {
+ return true;
+ }
+ }
+ });
+ }
+
+ var ret = this.pushStack( "", "find", selector ),
+ length, n, r;
+
+ for ( i = 0, l = this.length; i < l; i++ ) {
+ length = ret.length;
+ jQuery.find( selector, this[i], ret );
+
+ if ( i > 0 ) {
+ // Make sure that the results are unique
+ for ( n = length; n < ret.length; n++ ) {
+ for ( r = 0; r < length; r++ ) {
+ if ( ret[r] === ret[n] ) {
+ ret.splice(n--, 1);
+ break;
+ }
+ }
+ }
+ }
+ }
+
+ return ret;
+ },
+
+ has: function( target ) {
+ var targets = jQuery( target );
+ return this.filter(function() {
+ for ( var i = 0, l = targets.length; i < l; i++ ) {
+ if ( jQuery.contains( this, targets[i] ) ) {
+ return true;
+ }
+ }
+ });
+ },
+
+ not: function( selector ) {
+ return this.pushStack( winnow(this, selector, false), "not", selector);
+ },
+
+ filter: function( selector ) {
+ return this.pushStack( winnow(this, selector, true), "filter", selector );
+ },
+
+ is: function( selector ) {
+ return !!selector && (
+ typeof selector === "string" ?
+ // If this is a positional selector, check membership in the returned set
+ // so $("p:first").is("p:last") won't return true for a doc with two "p".
+ POS.test( selector ) ?
+ jQuery( selector, this.context ).index( this[0] ) >= 0 :
+ jQuery.filter( selector, this ).length > 0 :
+ this.filter( selector ).length > 0 );
+ },
+
+ closest: function( selectors, context ) {
+ var ret = [], i, l, cur = this[0];
+
+ // Array (deprecated as of jQuery 1.7)
+ if ( jQuery.isArray( selectors ) ) {
+ var level = 1;
+
+ while ( cur && cur.ownerDocument && cur !== context ) {
+ for ( i = 0; i < selectors.length; i++ ) {
+
+ if ( jQuery( cur ).is( selectors[ i ] ) ) {
+ ret.push({ selector: selectors[ i ], elem: cur, level: level });
+ }
+ }
+
+ cur = cur.parentNode;
+ level++;
+ }
+
+ return ret;
+ }
+
+ // String
+ var pos = POS.test( selectors ) || typeof selectors !== "string" ?
+ jQuery( selectors, context || this.context ) :
+ 0;
+
+ for ( i = 0, l = this.length; i < l; i++ ) {
+ cur = this[i];
+
+ while ( cur ) {
+ if ( pos ? pos.index(cur) > -1 : jQuery.find.matchesSelector(cur, selectors) ) {
+ ret.push( cur );
+ break;
+
+ } else {
+ cur = cur.parentNode;
+ if ( !cur || !cur.ownerDocument || cur === context || cur.nodeType === 11 ) {
+ break;
+ }
+ }
+ }
+ }
+
+ ret = ret.length > 1 ? jQuery.unique( ret ) : ret;
+
+ return this.pushStack( ret, "closest", selectors );
+ },
+
+ // Determine the position of an element within
+ // the matched set of elements
+ index: function( elem ) {
+
+ // No argument, return index in parent
+ if ( !elem ) {
+ return ( this[0] && this[0].parentNode ) ? this.prevAll().length : -1;
+ }
+
+ // index in selector
+ if ( typeof elem === "string" ) {
+ return jQuery.inArray( this[0], jQuery( elem ) );
+ }
+
+ // Locate the position of the desired element
+ return jQuery.inArray(
+ // If it receives a jQuery object, the first element is used
+ elem.jquery ? elem[0] : elem, this );
+ },
+
+ add: function( selector, context ) {
+ var set = typeof selector === "string" ?
+ jQuery( selector, context ) :
+ jQuery.makeArray( selector && selector.nodeType ? [ selector ] : selector ),
+ all = jQuery.merge( this.get(), set );
+
+ return this.pushStack( isDisconnected( set[0] ) || isDisconnected( all[0] ) ?
+ all :
+ jQuery.unique( all ) );
+ },
+
+ andSelf: function() {
+ return this.add( this.prevObject );
+ }
+});
+
+// A painfully simple check to see if an element is disconnected
+// from a document (should be improved, where feasible).
+function isDisconnected( node ) {
+ return !node || !node.parentNode || node.parentNode.nodeType === 11;
+}
+
+jQuery.each({
+ parent: function( elem ) {
+ var parent = elem.parentNode;
+ return parent && parent.nodeType !== 11 ? parent : null;
+ },
+ parents: function( elem ) {
+ return jQuery.dir( elem, "parentNode" );
+ },
+ parentsUntil: function( elem, i, until ) {
+ return jQuery.dir( elem, "parentNode", until );
+ },
+ next: function( elem ) {
+ return jQuery.nth( elem, 2, "nextSibling" );
+ },
+ prev: function( elem ) {
+ return jQuery.nth( elem, 2, "previousSibling" );
+ },
+ nextAll: function( elem ) {
+ return jQuery.dir( elem, "nextSibling" );
+ },
+ prevAll: function( elem ) {
+ return jQuery.dir( elem, "previousSibling" );
+ },
+ nextUntil: function( elem, i, until ) {
+ return jQuery.dir( elem, "nextSibling", until );
+ },
+ prevUntil: function( elem, i, until ) {
+ return jQuery.dir( elem, "previousSibling", until );
+ },
+ siblings: function( elem ) {
+ return jQuery.sibling( elem.parentNode.firstChild, elem );
+ },
+ children: function( elem ) {
+ return jQuery.sibling( elem.firstChild );
+ },
+ contents: function( elem ) {
+ return jQuery.nodeName( elem, "iframe" ) ?
+ elem.contentDocument || elem.contentWindow.document :
+ jQuery.makeArray( elem.childNodes );
+ }
+}, function( name, fn ) {
+ jQuery.fn[ name ] = function( until, selector ) {
+ var ret = jQuery.map( this, fn, until );
+
+ if ( !runtil.test( name ) ) {
+ selector = until;
+ }
+
+ if ( selector && typeof selector === "string" ) {
+ ret = jQuery.filter( selector, ret );
+ }
+
+ ret = this.length > 1 && !guaranteedUnique[ name ] ? jQuery.unique( ret ) : ret;
+
+ if ( (this.length > 1 || rmultiselector.test( selector )) && rparentsprev.test( name ) ) {
+ ret = ret.reverse();
+ }
+
+ return this.pushStack( ret, name, slice.call( arguments ).join(",") );
+ };
+});
+
+jQuery.extend({
+ filter: function( expr, elems, not ) {
+ if ( not ) {
+ expr = ":not(" + expr + ")";
+ }
+
+ return elems.length === 1 ?
+ jQuery.find.matchesSelector(elems[0], expr) ? [ elems[0] ] : [] :
+ jQuery.find.matches(expr, elems);
+ },
+
+ dir: function( elem, dir, until ) {
+ var matched = [],
+ cur = elem[ dir ];
+
+ while ( cur && cur.nodeType !== 9 && (until === undefined || cur.nodeType !== 1 || !jQuery( cur ).is( until )) ) {
+ if ( cur.nodeType === 1 ) {
+ matched.push( cur );
+ }
+ cur = cur[dir];
+ }
+ return matched;
+ },
+
+ nth: function( cur, result, dir, elem ) {
+ result = result || 1;
+ var num = 0;
+
+ for ( ; cur; cur = cur[dir] ) {
+ if ( cur.nodeType === 1 && ++num === result ) {
+ break;
+ }
+ }
+
+ return cur;
+ },
+
+ sibling: function( n, elem ) {
+ var r = [];
+
+ for ( ; n; n = n.nextSibling ) {
+ if ( n.nodeType === 1 && n !== elem ) {
+ r.push( n );
+ }
+ }
+
+ return r;
+ }
+});
+
+// Implement the identical functionality for filter and not
+function winnow( elements, qualifier, keep ) {
+
+ // Can't pass null or undefined to indexOf in Firefox 4
+ // Set to 0 to skip string check
+ qualifier = qualifier || 0;
+
+ if ( jQuery.isFunction( qualifier ) ) {
+ return jQuery.grep(elements, function( elem, i ) {
+ var retVal = !!qualifier.call( elem, i, elem );
+ return retVal === keep;
+ });
+
+ } else if ( qualifier.nodeType ) {
+ return jQuery.grep(elements, function( elem, i ) {
+ return ( elem === qualifier ) === keep;
+ });
+
+ } else if ( typeof qualifier === "string" ) {
+ var filtered = jQuery.grep(elements, function( elem ) {
+ return elem.nodeType === 1;
+ });
+
+ if ( isSimple.test( qualifier ) ) {
+ return jQuery.filter(qualifier, filtered, !keep);
+ } else {
+ qualifier = jQuery.filter( qualifier, filtered );
+ }
+ }
+
+ return jQuery.grep(elements, function( elem, i ) {
+ return ( jQuery.inArray( elem, qualifier ) >= 0 ) === keep;
+ });
+}
+
+
+
+
+function createSafeFragment( document ) {
+ var list = nodeNames.split( "|" ),
+ safeFrag = document.createDocumentFragment();
+
+ if ( safeFrag.createElement ) {
+ while ( list.length ) {
+ safeFrag.createElement(
+ list.pop()
+ );
+ }
+ }
+ return safeFrag;
+}
+
+var nodeNames = "abbr|article|aside|audio|canvas|datalist|details|figcaption|figure|footer|" +
+ "header|hgroup|mark|meter|nav|output|progress|section|summary|time|video",
+ rinlinejQuery = / jQuery\d+="(?:\d+|null)"/g,
+ rleadingWhitespace = /^\s+/,
+ rxhtmlTag = /<(?!area|br|col|embed|hr|img|input|link|meta|param)(([\w:]+)[^>]*)\/>/ig,
+ rtagName = /<([\w:]+)/,
+ rtbody = /", "" ],
+ legend: [ 1, "" ],
+ thead: [ 1, "